af_keys/
intent.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3#![expect(
4    clippy::too_long_first_doc_paragraph,
5    reason = "Mysten-inherited docs sometimes have long first paragraphs."
6)]
7
8use std::str::FromStr;
9
10use eyre::eyre;
11use fastcrypto::encoding::decode_bytes_hex;
12use serde::{Deserialize, Serialize};
13use serde_repr::{Deserialize_repr, Serialize_repr};
14
15/// The version here is to distinguish between signing different versions of the struct
16/// or enum. Serialized output between two different versions of the same struct/enum
17/// might accidentally (or maliciously on purpose) match.
18#[derive(Serialize_repr, Deserialize_repr, Copy, Clone, PartialEq, Eq, Debug, Hash)]
19#[repr(u8)]
20pub enum IntentVersion {
21    V0 = 0,
22}
23
24impl TryFrom<u8> for IntentVersion {
25    type Error = eyre::Report;
26    fn try_from(value: u8) -> Result<Self, Self::Error> {
27        bcs::from_bytes(&[value]).map_err(|_| eyre!("Invalid IntentVersion"))
28    }
29}
30
31/// This enums specifies the application ID. Two intents in two different applications
32/// (i.e., Narwhal, Sui, Ethereum etc) should never collide, so that even when a signing
33/// key is reused, nobody can take a signature designated for app_1 and present it as a
34/// valid signature for an (any) intent in app_2.
35#[derive(Serialize_repr, Deserialize_repr, Copy, Clone, PartialEq, Eq, Debug, Hash)]
36#[repr(u8)]
37pub enum AppId {
38    Sui = 0,
39    Narwhal = 1,
40    Consensus = 2,
41}
42
43impl TryFrom<u8> for AppId {
44    type Error = eyre::Report;
45    fn try_from(value: u8) -> Result<Self, Self::Error> {
46        bcs::from_bytes(&[value]).map_err(|_| eyre!("Invalid AppId"))
47    }
48}
49
50impl Default for AppId {
51    fn default() -> Self {
52        Self::Sui
53    }
54}
55
56/// This enums specifies the intent scope. Two intents for different scope should
57/// never collide, so no signature provided for one intent scope can be used for
58/// another, even when the serialized data itself may be the same.
59#[derive(Serialize_repr, Deserialize_repr, Copy, Clone, PartialEq, Eq, Debug, Hash)]
60#[repr(u8)]
61pub enum IntentScope {
62    TransactionData = 0,         // Used for a user signature on a transaction data.
63    TransactionEffects = 1,      // Used for an authority signature on transaction effects.
64    CheckpointSummary = 2,       // Used for an authority signature on a checkpoint summary.
65    PersonalMessage = 3,         // Used for a user signature on a personal message.
66    SenderSignedTransaction = 4, // Used for an authority signature on a user signed transaction.
67    ProofOfPossession = 5, // Used as a signature representing an authority's proof of possession of its authority protocol key.
68    HeaderDigest = 6,      // Used for narwhal authority signature on header digest.
69    BridgeEventUnused = 7, // for bridge purposes but it's currently not included in messages.
70    ConsensusBlock = 8,    // Used for consensus authority signature on block's digest
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 FromStr for Intent {
93    type Err = eyre::Report;
94    fn from_str(s: &str) -> Result<Self, Self::Err> {
95        let s: Vec<u8> = decode_bytes_hex(s).map_err(|_| eyre!("Invalid Intent"))?;
96        if s.len() != 3 {
97            return Err(eyre!("Invalid Intent"));
98        }
99        Ok(Self {
100            scope: s[0].try_into()?,
101            version: s[1].try_into()?,
102            app_id: s[2].try_into()?,
103        })
104    }
105}
106
107impl Intent {
108    pub const fn sui_app(scope: IntentScope) -> Self {
109        Self {
110            version: IntentVersion::V0,
111            scope,
112            app_id: AppId::Sui,
113        }
114    }
115
116    pub const fn sui_transaction() -> Self {
117        Self {
118            scope: IntentScope::TransactionData,
119            version: IntentVersion::V0,
120            app_id: AppId::Sui,
121        }
122    }
123
124    pub const fn personal_message() -> Self {
125        Self {
126            scope: IntentScope::PersonalMessage,
127            version: IntentVersion::V0,
128            app_id: AppId::Sui,
129        }
130    }
131
132    pub const fn narwhal_app(scope: IntentScope) -> Self {
133        Self {
134            scope,
135            version: IntentVersion::V0,
136            app_id: AppId::Narwhal,
137        }
138    }
139
140    pub const fn consensus_app(scope: IntentScope) -> Self {
141        Self {
142            scope,
143            version: IntentVersion::V0,
144            app_id: AppId::Consensus,
145        }
146    }
147}
148
149/// Intent Message is a wrapper around a message with its intent. The message can
150/// be any type that implements [trait Serialize]. *ALL* signatures in Sui must commits
151/// to the intent message, not the message itself. This guarantees any intent
152/// message signed in the system cannot collide with another since they are domain
153/// separated by intent.
154///
155/// The serialization of an IntentMessage is compact: it only appends three bytes
156/// to the message itself.
157#[derive(Debug, PartialEq, Eq, Serialize, Clone, Hash, Deserialize)]
158pub struct IntentMessage<T> {
159    pub intent: Intent,
160    pub value: T,
161}
162
163impl<T> IntentMessage<T> {
164    pub const fn new(intent: Intent, value: T) -> Self {
165        Self { intent, value }
166    }
167}