radix_transactions/model/v2/message_v2.rs
1use super::*;
2use crate::internal_prelude::*;
3
4/// Transaction messages as per REP-70.
5/// The only difference from V1 is that the AES key has been increased
6/// up to 256 bits for the encrypted messages.
7#[derive(Debug, Clone, Eq, PartialEq, Default, ManifestSbor, ScryptoDescribe)]
8pub enum MessageV2 {
9 #[default]
10 None,
11 Plaintext(PlaintextMessageV1),
12 Encrypted(EncryptedMessageV2),
13}
14
15impl TransactionPartialPrepare for MessageV2 {
16 type Prepared = PreparedMessageV2;
17}
18
19//============================================================================
20// ENCRYPTED MESSAGE
21//============================================================================
22
23/// A `PlaintextMessageV1` encrypted with "MultiPartyECIES" for a number of decryptors (public keys).
24///
25/// First, a `PlaintextMessageV1` should be created, and encoded as `manifest_sbor_encode(plaintext_message)`
26/// to get the plaintext message payload bytes.
27///
28/// The plaintext message payload bytes are encrypted via (256-bit) AES-GCM with an ephemeral symmetric key.
29///
30/// The (256-bit) AES-GCM symmetric key is encrypted separately for each decryptor public key via (256-bit) AES-KeyWrap.
31/// AES-KeyWrap uses a key derived via a KDF (Key Derivation Function) using a shared secret.
32/// For each decryptor public key, we create a shared curve point `G` via static Diffie-Helman between the
33/// decryptor public key, and a per-transaction ephemeral public key for that curve type.
34/// We then use that shared secret with a key derivation function to create the (256-bit) KEK (Key Encrypting Key):
35/// `KEK = HKDF(hash: Blake2b, secret: x co-ord of G, salt: [], length: 256 bits)`.
36///
37/// Note:
38/// - For ECDH, the secret we use is the `x` coordinate of the shared public point, unhashed. This ECDH output is
39/// known as ASN1 X9.63 variant of ECDH. Be careful - libsecp256k1 uses another non-standard variant.
40#[derive(Debug, Clone, PartialEq, Eq, ManifestSbor, ScryptoDescribe)]
41pub struct EncryptedMessageV2 {
42 pub encrypted: AesGcmPayload,
43 // Note we use a collection here rather than a struct to be forward-compatible to adding more curve types.
44 // The engine should validate each DecryptorsByCurve matches the CurveType.
45 pub decryptors_by_curve: IndexMap<CurveType, DecryptorsByCurveV2>,
46}
47
48#[derive(Debug, Clone, PartialEq, Eq, ManifestSbor, ScryptoDescribe)]
49pub enum DecryptorsByCurveV2 {
50 Ed25519 {
51 dh_ephemeral_public_key: Ed25519PublicKey,
52 decryptors: IndexMap<PublicKeyFingerprint, AesWrapped256BitKey>,
53 },
54 Secp256k1 {
55 dh_ephemeral_public_key: Secp256k1PublicKey,
56 decryptors: IndexMap<PublicKeyFingerprint, AesWrapped256BitKey>,
57 },
58}
59
60impl DecryptorsByCurveV2 {
61 pub fn curve_type(&self) -> CurveType {
62 match self {
63 Self::Ed25519 { .. } => CurveType::Ed25519,
64 Self::Secp256k1 { .. } => CurveType::Secp256k1,
65 }
66 }
67
68 pub fn number_of_decryptors(&self) -> usize {
69 match self {
70 Self::Ed25519 { decryptors, .. } => decryptors.len(),
71 Self::Secp256k1 { decryptors, .. } => decryptors.len(),
72 }
73 }
74}
75
76/// The wrapped key bytes from applying 256-bit AES-KeyWrap from RFC-3394
77/// to the 256-bit message ephemeral public key, with the secret KEK provided by
78/// static Diffie-Helman between the decryptor public key, and the `dh_ephemeral_public_key`
79/// for that curve type.
80///
81/// This must be serialized as per https://www.ietf.org/rfc/rfc3394.txt as `IV || Cipher` where:
82/// * IV: First 8 bytes
83/// * Cipher: The wrapped 256 bit key, encoded as four 64 bit blocks
84#[derive(Debug, Clone, Eq, PartialEq, ManifestSbor, ScryptoDescribe)]
85#[sbor(transparent)]
86pub struct AesWrapped256BitKey(pub [u8; Self::LENGTH]);
87
88impl AesWrapped256BitKey {
89 /// 8 bytes IV, and then 32 bytes of the encoded key
90 pub const LENGTH: usize = 40;
91}
92
93//============================================================================
94// PREPARATION
95//============================================================================
96
97pub type PreparedMessageV2 = SummarizedRawValueBody<MessageV2>;
98
99// TODO: Add tests with a canonical implementation of message encryption/decryption,
100// and corresponding test vectors for other implementers.