iota_types/
messages_consensus.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{
6    collections::hash_map::DefaultHasher,
7    fmt::{Debug, Formatter},
8    hash::{Hash, Hasher},
9    sync::Arc,
10    time::{SystemTime, UNIX_EPOCH},
11};
12
13use byteorder::{BigEndian, ReadBytesExt};
14use fastcrypto::{error::FastCryptoResult, groups::bls12381, hash::HashFunction};
15use fastcrypto_tbls::dkg_v1;
16use fastcrypto_zkp::bn254::zk_login::{JWK, JwkId};
17use schemars::JsonSchema;
18use serde::{Deserialize, Serialize};
19use shared_crypto::intent::IntentScope;
20
21use crate::{
22    base_types::{
23        AuthorityName, ConciseableName, ObjectID, ObjectRef, SequenceNumber, TransactionDigest,
24    },
25    crypto::{AuthoritySignature, DefaultHash},
26    digests::{ConsensusCommitDigest, Digest},
27    message_envelope::{Envelope, Message, VerifiedEnvelope},
28    messages_checkpoint::{CheckpointSequenceNumber, CheckpointSignatureMessage},
29    supported_protocol_versions::{
30        Chain, SupportedProtocolVersions, SupportedProtocolVersionsWithHashes,
31    },
32    transaction::CertifiedTransaction,
33};
34
35/// Non-decreasing timestamp produced by consensus in ms.
36pub type TimestampMs = u64;
37
38/// Uses an enum to allow for future expansion of the
39/// ConsensusDeterminedVersionAssignments.
40#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, JsonSchema)]
41pub enum ConsensusDeterminedVersionAssignments {
42    // Cancelled transaction version assignment.
43    CancelledTransactions(Vec<(TransactionDigest, Vec<(ObjectID, SequenceNumber)>)>),
44}
45
46#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
47pub struct ConsensusCommitPrologueV1 {
48    /// Epoch of the commit prologue transaction
49    pub epoch: u64,
50    /// Consensus round of the commit
51    pub round: u64,
52    /// The sub DAG index of the consensus commit. This field will be populated
53    /// if there are multiple consensus commits per round.
54    pub sub_dag_index: Option<u64>,
55    /// Unix timestamp from consensus
56    pub commit_timestamp_ms: TimestampMs,
57    /// Digest of consensus output
58    pub consensus_commit_digest: ConsensusCommitDigest,
59    /// Stores consensus handler determined shared object version assignments.
60    pub consensus_determined_version_assignments: ConsensusDeterminedVersionAssignments,
61}
62
63// In practice, JWKs are about 500 bytes of json each, plus a bit more for the
64// ID. 4096 should give us plenty of space for any imaginable JWK while
65// preventing DoSes.
66static MAX_TOTAL_JWK_SIZE: usize = 4096;
67
68pub fn check_total_jwk_size(id: &JwkId, jwk: &JWK) -> bool {
69    id.iss.len() + id.kid.len() + jwk.kty.len() + jwk.alg.len() + jwk.e.len() + jwk.n.len()
70        <= MAX_TOTAL_JWK_SIZE
71}
72
73#[derive(Serialize, Deserialize, Clone, Debug)]
74pub struct ConsensusTransaction {
75    /// Encodes an u64 unique tracking id to allow us trace a message between
76    /// IOTA and consensus. Use an byte array instead of u64 to ensure stable
77    /// serialization.
78    pub tracking_id: [u8; 8],
79    pub kind: ConsensusTransactionKind,
80}
81
82#[derive(Serialize, Deserialize, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)]
83pub enum ConsensusTransactionKey {
84    Certificate(TransactionDigest),
85    CheckpointSignature(AuthorityName, CheckpointSequenceNumber),
86    EndOfPublish(AuthorityName),
87    CapabilityNotification(AuthorityName, u64 /* generation */),
88    // Key must include both id and jwk, because honest validators could be given multiple jwks
89    // for the same id by malfunctioning providers.
90    NewJWKFetched(Box<(AuthorityName, JwkId, JWK)>),
91    RandomnessDkgMessage(AuthorityName),
92    RandomnessDkgConfirmation(AuthorityName),
93    // New entries should be added at the end to preserve serialization compatibility. DO NOT
94    // CHANGE THE ORDER OF EXISTING ENTRIES!
95}
96
97impl Debug for ConsensusTransactionKey {
98    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
99        match self {
100            Self::Certificate(digest) => write!(f, "Certificate({digest:?})"),
101            Self::CheckpointSignature(name, seq) => {
102                write!(f, "CheckpointSignature({:?}, {:?})", name.concise(), seq)
103            }
104            Self::EndOfPublish(name) => write!(f, "EndOfPublish({:?})", name.concise()),
105            Self::CapabilityNotification(name, generation) => write!(
106                f,
107                "CapabilityNotification({:?}, {:?})",
108                name.concise(),
109                generation
110            ),
111            Self::NewJWKFetched(key) => {
112                let (authority, id, jwk) = &**key;
113                write!(
114                    f,
115                    "NewJWKFetched({:?}, {:?}, {:?})",
116                    authority.concise(),
117                    id,
118                    jwk
119                )
120            }
121            Self::RandomnessDkgMessage(name) => {
122                write!(f, "RandomnessDkgMessage({:?})", name.concise())
123            }
124            Self::RandomnessDkgConfirmation(name) => {
125                write!(f, "RandomnessDkgConfirmation({:?})", name.concise())
126            }
127        }
128    }
129}
130
131pub type SignedAuthorityCapabilitiesV1 = Envelope<AuthorityCapabilitiesV1, AuthoritySignature>;
132
133pub type VerifiedAuthorityCapabilitiesV1 =
134    VerifiedEnvelope<AuthorityCapabilitiesV1, AuthoritySignature>;
135
136#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
137pub struct AuthorityCapabilitiesDigest(Digest);
138
139impl AuthorityCapabilitiesDigest {
140    pub const fn new(digest: [u8; 32]) -> Self {
141        Self(Digest::new(digest))
142    }
143}
144
145impl Debug for AuthorityCapabilitiesDigest {
146    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
147        f.debug_tuple("AuthorityCapabilitiesDigest")
148            .field(&self.0)
149            .finish()
150    }
151}
152
153/// Used to advertise capabilities of each authority via consensus. This allows
154/// validators to negotiate the creation of the ChangeEpoch transaction.
155#[derive(Serialize, Deserialize, Clone, Hash)]
156pub struct AuthorityCapabilitiesV1 {
157    /// Originating authority - must match transaction source authority from
158    /// consensus or the signature of a non-committee active validator.
159    pub authority: AuthorityName,
160    /// Generation number set by sending authority. Used to determine which of
161    /// multiple AuthorityCapabilities messages from the same authority is
162    /// the most recent.
163    ///
164    /// (Currently, we just set this to the current time in milliseconds since
165    /// the epoch, but this should not be interpreted as a timestamp.)
166    pub generation: u64,
167
168    /// ProtocolVersions that the authority supports, including the hash of the
169    /// serialized ProtocolConfig of that authority per version.
170    pub supported_protocol_versions: SupportedProtocolVersionsWithHashes,
171
172    /// The ObjectRefs of all versions of system packages that the validator
173    /// possesses. Used to determine whether to do a framework/movestdlib
174    /// upgrade.
175    pub available_system_packages: Vec<ObjectRef>,
176}
177
178impl Message for AuthorityCapabilitiesV1 {
179    type DigestType = AuthorityCapabilitiesDigest;
180    const SCOPE: IntentScope = IntentScope::AuthorityCapabilities;
181
182    fn digest(&self) -> Self::DigestType {
183        // Ensure deterministic serialization for digest
184        let mut hasher = DefaultHash::new();
185        let serialized = bcs::to_bytes(&self).expect("BCS should not fail");
186        hasher.update(&serialized);
187        AuthorityCapabilitiesDigest::new(<[u8; 32]>::from(hasher.finalize()))
188    }
189}
190
191impl Debug for AuthorityCapabilitiesV1 {
192    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
193        f.debug_struct("AuthorityCapabilities")
194            .field("authority", &self.authority.concise())
195            .field("generation", &self.generation)
196            .field(
197                "supported_protocol_versions",
198                &self.supported_protocol_versions,
199            )
200            .field("available_system_packages", &self.available_system_packages)
201            .finish()
202    }
203}
204
205impl AuthorityCapabilitiesV1 {
206    pub fn new(
207        authority: AuthorityName,
208        chain: Chain,
209        supported_protocol_versions: SupportedProtocolVersions,
210        available_system_packages: Vec<ObjectRef>,
211    ) -> Self {
212        let generation = SystemTime::now()
213            .duration_since(UNIX_EPOCH)
214            .expect("IOTA did not exist prior to 1970")
215            .as_millis()
216            .try_into()
217            .expect("This build of iota is not supported in the year 500,000,000");
218        Self {
219            authority,
220            generation,
221            supported_protocol_versions:
222                SupportedProtocolVersionsWithHashes::from_supported_versions(
223                    supported_protocol_versions,
224                    chain,
225                ),
226            available_system_packages,
227        }
228    }
229}
230
231impl SignedAuthorityCapabilitiesV1 {
232    pub fn cache_digest(&self, epoch: u64) -> AuthorityCapabilitiesDigest {
233        // Create a tuple that includes both the capabilities data and the epoch
234        let data_with_epoch = (self.data(), epoch);
235
236        // Ensure deterministic serialization for digest
237        let mut hasher = DefaultHash::new();
238        let serialized = bcs::to_bytes(&data_with_epoch).expect("BCS should not fail");
239        hasher.update(&serialized);
240        AuthorityCapabilitiesDigest::new(<[u8; 32]>::from(hasher.finalize()))
241    }
242}
243
244#[derive(Serialize, Deserialize, Clone, Debug)]
245pub enum ConsensusTransactionKind {
246    CertifiedTransaction(Box<CertifiedTransaction>),
247    CheckpointSignature(Box<CheckpointSignatureMessage>),
248    EndOfPublish(AuthorityName),
249
250    CapabilityNotificationV1(AuthorityCapabilitiesV1),
251    SignedCapabilityNotificationV1(SignedAuthorityCapabilitiesV1),
252
253    NewJWKFetched(AuthorityName, JwkId, JWK),
254
255    // DKG is used to generate keys for use in the random beacon protocol.
256    // `RandomnessDkgMessage` is sent out at start-of-epoch to initiate the process.
257    // Contents are a serialized `fastcrypto_tbls::dkg::Message`.
258    RandomnessDkgMessage(AuthorityName, Vec<u8>),
259    // `RandomnessDkgConfirmation` is the second DKG message, sent as soon as a threshold amount
260    // of `RandomnessDkgMessages` have been received locally, to complete the key generation
261    // process. Contents are a serialized `fastcrypto_tbls::dkg::Confirmation`.
262    RandomnessDkgConfirmation(AuthorityName, Vec<u8>),
263    // New entries should be added at the end to preserve serialization compatibility. DO NOT
264    // CHANGE THE ORDER OF EXISTING ENTRIES!
265}
266
267impl ConsensusTransactionKind {
268    pub fn is_dkg(&self) -> bool {
269        matches!(
270            self,
271            ConsensusTransactionKind::RandomnessDkgMessage(_, _)
272                | ConsensusTransactionKind::RandomnessDkgConfirmation(_, _)
273        )
274    }
275}
276
277#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
278pub enum VersionedDkgMessage {
279    V1(dkg_v1::Message<bls12381::G2Element, bls12381::G2Element>),
280}
281
282#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
283pub enum VersionedDkgConfirmation {
284    V1(dkg_v1::Confirmation<bls12381::G2Element>),
285}
286
287impl Debug for VersionedDkgMessage {
288    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
289        match self {
290            VersionedDkgMessage::V1(msg) => write!(
291                f,
292                "DKG V1 Message with sender={}, vss_pk.degree={}, encrypted_shares.len()={}",
293                msg.sender,
294                msg.vss_pk.degree(),
295                msg.encrypted_shares.len(),
296            ),
297        }
298    }
299}
300
301impl VersionedDkgMessage {
302    pub fn sender(&self) -> u16 {
303        match self {
304            VersionedDkgMessage::V1(msg) => msg.sender,
305        }
306    }
307
308    pub fn create(
309        dkg_version: u64,
310        party: Arc<dkg_v1::Party<bls12381::G2Element, bls12381::G2Element>>,
311    ) -> FastCryptoResult<VersionedDkgMessage> {
312        assert_eq!(dkg_version, 1, "BUG: invalid DKG version");
313        let msg = party.create_message(&mut rand::thread_rng())?;
314        Ok(VersionedDkgMessage::V1(msg))
315    }
316
317    pub fn unwrap_v1(self) -> dkg_v1::Message<bls12381::G2Element, bls12381::G2Element> {
318        match self {
319            VersionedDkgMessage::V1(msg) => msg,
320        }
321    }
322
323    pub fn is_valid_version(&self, dkg_version: u64) -> bool {
324        matches!((self, dkg_version), (VersionedDkgMessage::V1(_), 1))
325    }
326}
327
328impl VersionedDkgConfirmation {
329    pub fn sender(&self) -> u16 {
330        match self {
331            VersionedDkgConfirmation::V1(msg) => msg.sender,
332        }
333    }
334
335    pub fn num_of_complaints(&self) -> usize {
336        match self {
337            VersionedDkgConfirmation::V1(msg) => msg.complaints.len(),
338        }
339    }
340
341    pub fn unwrap_v1(&self) -> &dkg_v1::Confirmation<bls12381::G2Element> {
342        match self {
343            VersionedDkgConfirmation::V1(msg) => msg,
344        }
345    }
346
347    pub fn is_valid_version(&self, dkg_version: u64) -> bool {
348        matches!((self, dkg_version), (VersionedDkgConfirmation::V1(_), 1))
349    }
350}
351
352impl ConsensusTransaction {
353    pub fn new_certificate_message(
354        authority: &AuthorityName,
355        certificate: CertifiedTransaction,
356    ) -> Self {
357        let mut hasher = DefaultHasher::new();
358        let tx_digest = certificate.digest();
359        tx_digest.hash(&mut hasher);
360        authority.hash(&mut hasher);
361        let tracking_id = hasher.finish().to_le_bytes();
362        Self {
363            tracking_id,
364            kind: ConsensusTransactionKind::CertifiedTransaction(Box::new(certificate)),
365        }
366    }
367
368    pub fn new_checkpoint_signature_message(data: CheckpointSignatureMessage) -> Self {
369        let mut hasher = DefaultHasher::new();
370        data.summary.auth_sig().signature.hash(&mut hasher);
371        let tracking_id = hasher.finish().to_le_bytes();
372        Self {
373            tracking_id,
374            kind: ConsensusTransactionKind::CheckpointSignature(Box::new(data)),
375        }
376    }
377
378    pub fn new_end_of_publish(authority: AuthorityName) -> Self {
379        let mut hasher = DefaultHasher::new();
380        authority.hash(&mut hasher);
381        let tracking_id = hasher.finish().to_le_bytes();
382        Self {
383            tracking_id,
384            kind: ConsensusTransactionKind::EndOfPublish(authority),
385        }
386    }
387
388    pub fn new_capability_notification_v1(capabilities: AuthorityCapabilitiesV1) -> Self {
389        let mut hasher = DefaultHasher::new();
390        capabilities.hash(&mut hasher);
391        let tracking_id = hasher.finish().to_le_bytes();
392        Self {
393            tracking_id,
394            kind: ConsensusTransactionKind::CapabilityNotificationV1(capabilities),
395        }
396    }
397
398    pub fn new_signed_capability_notification_v1(
399        signed_capabilities: SignedAuthorityCapabilitiesV1,
400    ) -> Self {
401        let mut hasher = DefaultHasher::new();
402        signed_capabilities.data().hash(&mut hasher);
403        signed_capabilities.auth_sig().hash(&mut hasher);
404        let tracking_id = hasher.finish().to_le_bytes();
405        Self {
406            tracking_id,
407            kind: ConsensusTransactionKind::SignedCapabilityNotificationV1(signed_capabilities),
408        }
409    }
410
411    pub fn new_mysticeti_certificate(
412        round: u64,
413        offset: u64,
414        certificate: CertifiedTransaction,
415    ) -> Self {
416        let mut hasher = DefaultHasher::new();
417        let tx_digest = certificate.digest();
418        tx_digest.hash(&mut hasher);
419        round.hash(&mut hasher);
420        offset.hash(&mut hasher);
421        let tracking_id = hasher.finish().to_le_bytes();
422        Self {
423            tracking_id,
424            kind: ConsensusTransactionKind::CertifiedTransaction(Box::new(certificate)),
425        }
426    }
427
428    pub fn new_jwk_fetched(authority: AuthorityName, id: JwkId, jwk: JWK) -> Self {
429        let mut hasher = DefaultHasher::new();
430        id.hash(&mut hasher);
431        let tracking_id = hasher.finish().to_le_bytes();
432        Self {
433            tracking_id,
434            kind: ConsensusTransactionKind::NewJWKFetched(authority, id, jwk),
435        }
436    }
437
438    pub fn new_randomness_dkg_message(
439        authority: AuthorityName,
440        versioned_message: &VersionedDkgMessage,
441    ) -> Self {
442        let message =
443            bcs::to_bytes(versioned_message).expect("message serialization should not fail");
444        let mut hasher = DefaultHasher::new();
445        message.hash(&mut hasher);
446        let tracking_id = hasher.finish().to_le_bytes();
447        Self {
448            tracking_id,
449            kind: ConsensusTransactionKind::RandomnessDkgMessage(authority, message),
450        }
451    }
452    pub fn new_randomness_dkg_confirmation(
453        authority: AuthorityName,
454        versioned_confirmation: &VersionedDkgConfirmation,
455    ) -> Self {
456        let confirmation =
457            bcs::to_bytes(versioned_confirmation).expect("message serialization should not fail");
458        let mut hasher = DefaultHasher::new();
459        confirmation.hash(&mut hasher);
460        let tracking_id = hasher.finish().to_le_bytes();
461        Self {
462            tracking_id,
463            kind: ConsensusTransactionKind::RandomnessDkgConfirmation(authority, confirmation),
464        }
465    }
466
467    pub fn get_tracking_id(&self) -> u64 {
468        (&self.tracking_id[..])
469            .read_u64::<BigEndian>()
470            .unwrap_or_default()
471    }
472
473    pub fn key(&self) -> ConsensusTransactionKey {
474        match &self.kind {
475            ConsensusTransactionKind::CertifiedTransaction(cert) => {
476                ConsensusTransactionKey::Certificate(*cert.digest())
477            }
478            ConsensusTransactionKind::CheckpointSignature(data) => {
479                ConsensusTransactionKey::CheckpointSignature(
480                    data.summary.auth_sig().authority,
481                    data.summary.sequence_number,
482                )
483            }
484            ConsensusTransactionKind::EndOfPublish(authority) => {
485                ConsensusTransactionKey::EndOfPublish(*authority)
486            }
487            ConsensusTransactionKind::CapabilityNotificationV1(cap) => {
488                ConsensusTransactionKey::CapabilityNotification(cap.authority, cap.generation)
489            }
490            ConsensusTransactionKind::SignedCapabilityNotificationV1(signed_cap) => {
491                ConsensusTransactionKey::CapabilityNotification(
492                    signed_cap.authority,
493                    signed_cap.generation,
494                )
495            }
496
497            ConsensusTransactionKind::NewJWKFetched(authority, id, key) => {
498                ConsensusTransactionKey::NewJWKFetched(Box::new((
499                    *authority,
500                    id.clone(),
501                    key.clone(),
502                )))
503            }
504            ConsensusTransactionKind::RandomnessDkgMessage(authority, _) => {
505                ConsensusTransactionKey::RandomnessDkgMessage(*authority)
506            }
507            ConsensusTransactionKind::RandomnessDkgConfirmation(authority, _) => {
508                ConsensusTransactionKey::RandomnessDkgConfirmation(*authority)
509            }
510        }
511    }
512
513    pub fn is_user_certificate(&self) -> bool {
514        matches!(self.kind, ConsensusTransactionKind::CertifiedTransaction(_))
515    }
516
517    pub fn is_end_of_publish(&self) -> bool {
518        matches!(self.kind, ConsensusTransactionKind::EndOfPublish(_))
519    }
520}
521
522#[test]
523fn test_jwk_compatibility() {
524    // Ensure that the JWK and JwkId structs in fastcrypto do not change formats.
525    // If this test breaks DO NOT JUST UPDATE THE EXPECTED BYTES. Instead, add a
526    // local JWK or JwkId struct that mirrors the fastcrypto struct, use it in
527    // AuthenticatorStateUpdate, and add Into/From as necessary.
528    let jwk = JWK {
529        kty: "a".to_string(),
530        e: "b".to_string(),
531        n: "c".to_string(),
532        alg: "d".to_string(),
533    };
534
535    let expected_jwk_bytes = vec![1, 97, 1, 98, 1, 99, 1, 100];
536    let jwk_bcs = bcs::to_bytes(&jwk).unwrap();
537    assert_eq!(jwk_bcs, expected_jwk_bytes);
538
539    let id = JwkId {
540        iss: "abc".to_string(),
541        kid: "def".to_string(),
542    };
543
544    let expected_id_bytes = vec![3, 97, 98, 99, 3, 100, 101, 102];
545    let id_bcs = bcs::to_bytes(&id).unwrap();
546    assert_eq!(id_bcs, expected_id_bytes);
547}