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