shared_crypto/
intent.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use eyre::eyre;
5use fastcrypto::encoding::decode_bytes_hex;
6use serde::{Deserialize, Serialize};
7use serde_repr::Deserialize_repr;
8use serde_repr::Serialize_repr;
9use std::str::FromStr;
10
11pub const INTENT_PREFIX_LENGTH: usize = 3;
12
13/// The version here is to distinguish between signing different versions of the struct
14/// or enum. Serialized output between two different versions of the same struct/enum
15/// might accidentally (or maliciously on purpose) match.
16#[derive(Serialize_repr, Deserialize_repr, Copy, Clone, PartialEq, Eq, Debug, Hash)]
17#[repr(u8)]
18pub enum IntentVersion {
19    V0 = 0,
20}
21
22impl TryFrom<u8> for IntentVersion {
23    type Error = eyre::Report;
24    fn try_from(value: u8) -> Result<Self, Self::Error> {
25        bcs::from_bytes(&[value]).map_err(|_| eyre!("Invalid IntentVersion"))
26    }
27}
28
29/// This enums specifies the application ID. Two intents in two different applications
30/// (i.e., Narwhal, Sui, Ethereum etc) should never collide, so that even when a signing
31/// key is reused, nobody can take a signature designated for app_1 and present it as a
32/// valid signature for an (any) intent in app_2.
33#[derive(Serialize_repr, Deserialize_repr, Copy, Clone, PartialEq, Eq, Debug, Hash)]
34#[repr(u8)]
35pub enum AppId {
36    Sui = 0,
37    Narwhal = 1,
38    Consensus = 2,
39}
40
41// TODO(joyqvq): Use num_derive
42impl TryFrom<u8> for AppId {
43    type Error = eyre::Report;
44    fn try_from(value: u8) -> Result<Self, Self::Error> {
45        bcs::from_bytes(&[value]).map_err(|_| eyre!("Invalid AppId"))
46    }
47}
48
49impl Default for AppId {
50    fn default() -> Self {
51        Self::Sui
52    }
53}
54
55/// This enums specifies the intent scope. Two intents for different scope should
56/// never collide, so no signature provided for one intent scope can be used for
57/// another, even when the serialized data itself may be the same.
58#[derive(Serialize_repr, Deserialize_repr, Copy, Clone, PartialEq, Eq, Debug, Hash)]
59#[repr(u8)]
60pub enum IntentScope {
61    TransactionData = 0,         // Used for a user signature on a transaction data.
62    TransactionEffects = 1,      // Used for an authority signature on transaction effects.
63    CheckpointSummary = 2,       // Used for an authority signature on a checkpoint summary.
64    PersonalMessage = 3,         // Used for a user signature on a personal message.
65    SenderSignedTransaction = 4, // Used for an authority signature on a user signed transaction.
66    ProofOfPossession = 5, // Used as a signature representing an authority's proof of possession of its authority protocol key.
67    HeaderDigest = 6,      // Used for narwhal authority signature on header digest.
68    BridgeEventUnused = 7, // for bridge purposes but it's currently not included in messages.
69    ConsensusBlock = 8,    // Used for consensus authority signature on block's digest.
70    DiscoveryPeers = 9,    // Used for reporting peer addresses in discovery.
71}
72
73impl TryFrom<u8> for IntentScope {
74    type Error = eyre::Report;
75    fn try_from(value: u8) -> Result<Self, Self::Error> {
76        bcs::from_bytes(&[value]).map_err(|_| eyre!("Invalid IntentScope"))
77    }
78}
79
80/// An intent is a compact struct serves as the domain separator for a message that a signature commits to.
81/// It consists of three parts: [enum IntentScope] (what the type of the message is), [enum IntentVersion], [enum AppId] (what application that the signature refers to).
82/// It is used to construct [struct IntentMessage] that what a signature commits to.
83///
84/// The serialization of an Intent is a 3-byte array where each field is represented by a byte.
85#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Hash)]
86pub struct Intent {
87    pub scope: IntentScope,
88    pub version: IntentVersion,
89    pub app_id: AppId,
90}
91
92impl Intent {
93    pub fn to_bytes(&self) -> [u8; INTENT_PREFIX_LENGTH] {
94        [self.scope as u8, self.version as u8, self.app_id as u8]
95    }
96
97    pub fn from_bytes(bytes: &[u8]) -> Result<Self, eyre::Report> {
98        if bytes.len() != INTENT_PREFIX_LENGTH {
99            return Err(eyre!("Invalid Intent"));
100        }
101        Ok(Self {
102            scope: bytes[0].try_into()?,
103            version: bytes[1].try_into()?,
104            app_id: bytes[2].try_into()?,
105        })
106    }
107}
108
109impl FromStr for Intent {
110    type Err = eyre::Report;
111    fn from_str(s: &str) -> Result<Self, Self::Err> {
112        let bytes: Vec<u8> = decode_bytes_hex(s).map_err(|_| eyre!("Invalid Intent"))?;
113        Self::from_bytes(bytes.as_slice())
114    }
115}
116
117impl Intent {
118    pub fn sui_app(scope: IntentScope) -> Self {
119        Self {
120            version: IntentVersion::V0,
121            scope,
122            app_id: AppId::Sui,
123        }
124    }
125
126    pub fn sui_transaction() -> Self {
127        Self {
128            scope: IntentScope::TransactionData,
129            version: IntentVersion::V0,
130            app_id: AppId::Sui,
131        }
132    }
133
134    pub fn personal_message() -> Self {
135        Self {
136            scope: IntentScope::PersonalMessage,
137            version: IntentVersion::V0,
138            app_id: AppId::Sui,
139        }
140    }
141
142    pub fn narwhal_app(scope: IntentScope) -> Self {
143        Self {
144            scope,
145            version: IntentVersion::V0,
146            app_id: AppId::Narwhal,
147        }
148    }
149
150    pub fn consensus_app(scope: IntentScope) -> Self {
151        Self {
152            scope,
153            version: IntentVersion::V0,
154            app_id: AppId::Consensus,
155        }
156    }
157}
158
159/// Intent Message is a wrapper around a message with its intent. The message can
160/// be any type that implements [trait Serialize]. *ALL* signatures in Sui must commits
161/// to the intent message, not the message itself. This guarantees any intent
162/// message signed in the system cannot collide with another since they are domain
163/// separated by intent.
164///
165/// The serialization of an IntentMessage is compact: it only appends three bytes
166/// to the message itself.
167#[derive(Debug, PartialEq, Eq, Serialize, Clone, Hash, Deserialize)]
168pub struct IntentMessage<T> {
169    pub intent: Intent,
170    pub value: T,
171}
172
173impl<T> IntentMessage<T> {
174    pub fn new(intent: Intent, value: T) -> Self {
175        Self { intent, value }
176    }
177}
178
179/// A person message that wraps around a byte array.
180#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
181pub struct PersonalMessage {
182    pub message: Vec<u8>,
183}
184
185pub trait SecureIntent: Serialize + private::SealedIntent {}
186
187pub(crate) mod private {
188    use super::IntentMessage;
189
190    pub trait SealedIntent {}
191    impl<T> SealedIntent for IntentMessage<T> {}
192}
193
194/// A 1-byte domain separator for hashing Object ID in Sui. It is starting from 0xf0
195/// to ensure no hashing collision for any ObjectID vs SuiAddress which is derived
196/// as the hash of `flag || pubkey`. See `sui_types::crypto::SignatureScheme::flag()`.
197#[derive(Serialize_repr, Deserialize_repr, Copy, Clone, PartialEq, Eq, Debug, Hash)]
198#[repr(u8)]
199pub enum HashingIntentScope {
200    ChildObjectId = 0xf0,
201    RegularObjectId = 0xf1,
202}