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 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
// Copyright (c) Mysten Labs, Inc.
// Modifications Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0
use std::str::FromStr;
use eyre::eyre;
use fastcrypto::encoding::decode_bytes_hex;
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
pub const INTENT_PREFIX_LENGTH: usize = 3;
/// 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., Iota, 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 {
Iota = 0,
Consensus = 1,
}
// TODO(joyqvq): Use num_derive
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::Iota
}
}
/// 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 key. */
BridgeEventUnused = 6, // for bridge purposes but it's currently not included in messages.
ConsensusBlock = 7, // 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 Intent {
pub fn to_bytes(&self) -> [u8; INTENT_PREFIX_LENGTH] {
[self.scope as u8, self.version as u8, self.app_id as u8]
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, eyre::Report> {
if bytes.len() != INTENT_PREFIX_LENGTH {
return Err(eyre!("Invalid Intent"));
}
Ok(Self {
scope: bytes[0].try_into()?,
version: bytes[1].try_into()?,
app_id: bytes[2].try_into()?,
})
}
}
impl FromStr for Intent {
type Err = eyre::Report;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let bytes: Vec<u8> = decode_bytes_hex(s).map_err(|_| eyre!("Invalid Intent"))?;
Self::from_bytes(bytes.as_slice())
}
}
impl Intent {
pub fn iota_app(scope: IntentScope) -> Self {
Self {
version: IntentVersion::V0,
scope,
app_id: AppId::Iota,
}
}
pub fn iota_transaction() -> Self {
Self {
scope: IntentScope::TransactionData,
version: IntentVersion::V0,
app_id: AppId::Iota,
}
}
pub fn personal_message() -> Self {
Self {
scope: IntentScope::PersonalMessage,
version: IntentVersion::V0,
app_id: AppId::Iota,
}
}
pub 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 Iota
/// 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 fn new(intent: Intent, value: T) -> Self {
Self { intent, value }
}
}
/// A person message that wraps around a byte array.
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct PersonalMessage {
pub message: Vec<u8>,
}
pub trait SecureIntent: Serialize + private::SealedIntent {}
pub(crate) mod private {
use super::IntentMessage;
pub trait SealedIntent {}
impl<T> SealedIntent for IntentMessage<T> {}
}
/// A 1-byte domain separator for hashing Object ID in Iota. It is starting from
/// 0xf0 to ensure no hashing collision for any ObjectID vs IotaAddress which is
/// derived as the hash of `flag || pubkey`. See
/// `iota_types::crypto::SignatureScheme::flag()`.
#[derive(Serialize_repr, Deserialize_repr, Copy, Clone, PartialEq, Eq, Debug, Hash)]
#[repr(u8)]
pub enum HashingIntentScope {
ChildObjectId = 0xf0,
RegularObjectId = 0xf1,
}