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