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, Default)]
36#[repr(u8)]
37pub enum AppId {
38    #[default]
39    Sui = 0,
40    Narwhal = 1,
41    Consensus = 2,
42}
43
44impl TryFrom<u8> for AppId {
45    type Error = eyre::Report;
46    fn try_from(value: u8) -> Result<Self, Self::Error> {
47        bcs::from_bytes(&[value]).map_err(|_| eyre!("Invalid AppId"))
48    }
49}
50
51/// This enums specifies the intent scope. Two intents for different scope should
52/// never collide, so no signature provided for one intent scope can be used for
53/// another, even when the serialized data itself may be the same.
54#[derive(Serialize_repr, Deserialize_repr, Copy, Clone, PartialEq, Eq, Debug, Hash)]
55#[repr(u8)]
56pub enum IntentScope {
57    TransactionData = 0,         // Used for a user signature on a transaction data.
58    TransactionEffects = 1,      // Used for an authority signature on transaction effects.
59    CheckpointSummary = 2,       // Used for an authority signature on a checkpoint summary.
60    PersonalMessage = 3,         // Used for a user signature on a personal message.
61    SenderSignedTransaction = 4, // Used for an authority signature on a user signed transaction.
62    ProofOfPossession = 5, // Used as a signature representing an authority's proof of possession of its authority protocol key.
63    HeaderDigest = 6,      // Used for narwhal authority signature on header digest.
64    BridgeEventUnused = 7, // for bridge purposes but it's currently not included in messages.
65    ConsensusBlock = 8,    // Used for consensus authority signature on block's digest
66}
67
68impl TryFrom<u8> for IntentScope {
69    type Error = eyre::Report;
70    fn try_from(value: u8) -> Result<Self, Self::Error> {
71        bcs::from_bytes(&[value]).map_err(|_| eyre!("Invalid IntentScope"))
72    }
73}
74
75/// An intent is a compact struct serves as the domain separator for a message that a signature commits to.
76/// 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).
77/// It is used to construct [struct IntentMessage] that what a signature commits to.
78///
79/// The serialization of an Intent is a 3-byte array where each field is represented by a byte.
80#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Hash)]
81pub struct Intent {
82    pub scope: IntentScope,
83    pub version: IntentVersion,
84    pub app_id: AppId,
85}
86
87impl FromStr for Intent {
88    type Err = eyre::Report;
89    fn from_str(s: &str) -> Result<Self, Self::Err> {
90        let s: Vec<u8> = decode_bytes_hex(s).map_err(|_| eyre!("Invalid Intent"))?;
91        if s.len() != 3 {
92            return Err(eyre!("Invalid Intent"));
93        }
94        Ok(Self {
95            scope: s[0].try_into()?,
96            version: s[1].try_into()?,
97            app_id: s[2].try_into()?,
98        })
99    }
100}
101
102impl Intent {
103    pub const fn sui_app(scope: IntentScope) -> Self {
104        Self {
105            version: IntentVersion::V0,
106            scope,
107            app_id: AppId::Sui,
108        }
109    }
110
111    pub const fn sui_transaction() -> Self {
112        Self {
113            scope: IntentScope::TransactionData,
114            version: IntentVersion::V0,
115            app_id: AppId::Sui,
116        }
117    }
118
119    pub const fn personal_message() -> Self {
120        Self {
121            scope: IntentScope::PersonalMessage,
122            version: IntentVersion::V0,
123            app_id: AppId::Sui,
124        }
125    }
126
127    pub const fn narwhal_app(scope: IntentScope) -> Self {
128        Self {
129            scope,
130            version: IntentVersion::V0,
131            app_id: AppId::Narwhal,
132        }
133    }
134
135    pub const fn consensus_app(scope: IntentScope) -> Self {
136        Self {
137            scope,
138            version: IntentVersion::V0,
139            app_id: AppId::Consensus,
140        }
141    }
142}
143
144/// Intent Message is a wrapper around a message with its intent. The message can
145/// be any type that implements [trait Serialize]. *ALL* signatures in Sui must commits
146/// to the intent message, not the message itself. This guarantees any intent
147/// message signed in the system cannot collide with another since they are domain
148/// separated by intent.
149///
150/// The serialization of an IntentMessage is compact: it only appends three bytes
151/// to the message itself.
152#[derive(Debug, PartialEq, Eq, Serialize, Clone, Hash, Deserialize)]
153pub struct IntentMessage<T> {
154    pub intent: Intent,
155    pub value: T,
156}
157
158impl<T> IntentMessage<T> {
159    pub const fn new(intent: Intent, value: T) -> Self {
160        Self { intent, value }
161    }
162}