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}
94
95impl Debug for ConsensusTransactionKey {
96    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
97        match self {
98            Self::Certificate(digest) => write!(f, "Certificate({digest:?})"),
99            Self::CheckpointSignature(name, seq) => {
100                write!(f, "CheckpointSignature({:?}, {:?})", name.concise(), seq)
101            }
102            Self::EndOfPublish(name) => write!(f, "EndOfPublish({:?})", name.concise()),
103            Self::CapabilityNotification(name, generation) => write!(
104                f,
105                "CapabilityNotification({:?}, {:?})",
106                name.concise(),
107                generation
108            ),
109            Self::NewJWKFetched(key) => {
110                let (authority, id, jwk) = &**key;
111                write!(
112                    f,
113                    "NewJWKFetched({:?}, {:?}, {:?})",
114                    authority.concise(),
115                    id,
116                    jwk
117                )
118            }
119            Self::RandomnessDkgMessage(name) => {
120                write!(f, "RandomnessDkgMessage({:?})", name.concise())
121            }
122            Self::RandomnessDkgConfirmation(name) => {
123                write!(f, "RandomnessDkgConfirmation({:?})", name.concise())
124            }
125        }
126    }
127}
128
129pub type SignedAuthorityCapabilitiesV1 = Envelope<AuthorityCapabilitiesV1, AuthoritySignature>;
130
131pub type VerifiedAuthorityCapabilitiesV1 =
132    VerifiedEnvelope<AuthorityCapabilitiesV1, AuthoritySignature>;
133
134#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
135pub struct AuthorityCapabilitiesDigest(Digest);
136
137impl AuthorityCapabilitiesDigest {
138    pub const fn new(digest: [u8; 32]) -> Self {
139        Self(Digest::new(digest))
140    }
141}
142
143impl Debug for AuthorityCapabilitiesDigest {
144    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
145        f.debug_tuple("AuthorityCapabilitiesDigest")
146            .field(&self.0)
147            .finish()
148    }
149}
150
151/// Used to advertise capabilities of each authority via consensus. This allows
152/// validators to negotiate the creation of the ChangeEpoch transaction.
153#[derive(Serialize, Deserialize, Clone, Hash)]
154pub struct AuthorityCapabilitiesV1 {
155    /// Originating authority - must match transaction source authority from
156    /// consensus or the signature of a non-committee active validator.
157    pub authority: AuthorityName,
158    /// Generation number set by sending authority. Used to determine which of
159    /// multiple AuthorityCapabilities messages from the same authority is
160    /// the most recent.
161    ///
162    /// (Currently, we just set this to the current time in milliseconds since
163    /// the epoch, but this should not be interpreted as a timestamp.)
164    pub generation: u64,
165
166    /// ProtocolVersions that the authority supports, including the hash of the
167    /// serialized ProtocolConfig of that authority per version.
168    pub supported_protocol_versions: SupportedProtocolVersionsWithHashes,
169
170    /// The ObjectRefs of all versions of system packages that the validator
171    /// possesses. Used to determine whether to do a framework/movestdlib
172    /// upgrade.
173    pub available_system_packages: Vec<ObjectRef>,
174}
175
176impl Message for AuthorityCapabilitiesV1 {
177    type DigestType = AuthorityCapabilitiesDigest;
178    const SCOPE: IntentScope = IntentScope::AuthorityCapabilities;
179
180    fn digest(&self) -> Self::DigestType {
181        // Ensure deterministic serialization for digest
182        let mut hasher = DefaultHash::new();
183        let serialized = bcs::to_bytes(&self).expect("BCS should not fail");
184        hasher.update(&serialized);
185        AuthorityCapabilitiesDigest::new(<[u8; 32]>::from(hasher.finalize()))
186    }
187}
188
189impl Debug for AuthorityCapabilitiesV1 {
190    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
191        f.debug_struct("AuthorityCapabilities")
192            .field("authority", &self.authority.concise())
193            .field("generation", &self.generation)
194            .field(
195                "supported_protocol_versions",
196                &self.supported_protocol_versions,
197            )
198            .field("available_system_packages", &self.available_system_packages)
199            .finish()
200    }
201}
202
203impl AuthorityCapabilitiesV1 {
204    pub fn new(
205        authority: AuthorityName,
206        chain: Chain,
207        supported_protocol_versions: SupportedProtocolVersions,
208        available_system_packages: Vec<ObjectRef>,
209    ) -> Self {
210        let generation = SystemTime::now()
211            .duration_since(UNIX_EPOCH)
212            .expect("IOTA did not exist prior to 1970")
213            .as_millis()
214            .try_into()
215            .expect("This build of iota is not supported in the year 500,000,000");
216        Self {
217            authority,
218            generation,
219            supported_protocol_versions:
220                SupportedProtocolVersionsWithHashes::from_supported_versions(
221                    supported_protocol_versions,
222                    chain,
223                ),
224            available_system_packages,
225        }
226    }
227}
228
229impl SignedAuthorityCapabilitiesV1 {
230    pub fn cache_digest(&self, epoch: u64) -> AuthorityCapabilitiesDigest {
231        // Create a tuple that includes both the capabilities data and the epoch
232        let data_with_epoch = (self.data(), epoch);
233
234        // Ensure deterministic serialization for digest
235        let mut hasher = DefaultHash::new();
236        let serialized = bcs::to_bytes(&data_with_epoch).expect("BCS should not fail");
237        hasher.update(&serialized);
238        AuthorityCapabilitiesDigest::new(<[u8; 32]>::from(hasher.finalize()))
239    }
240}
241
242#[derive(Serialize, Deserialize, Clone, Debug)]
243pub enum ConsensusTransactionKind {
244    CertifiedTransaction(Box<CertifiedTransaction>),
245    CheckpointSignature(Box<CheckpointSignatureMessage>),
246    EndOfPublish(AuthorityName),
247
248    CapabilityNotificationV1(AuthorityCapabilitiesV1),
249    SignedCapabilityNotificationV1(SignedAuthorityCapabilitiesV1),
250
251    NewJWKFetched(AuthorityName, JwkId, JWK),
252
253    // DKG is used to generate keys for use in the random beacon protocol.
254    // `RandomnessDkgMessage` is sent out at start-of-epoch to initiate the process.
255    // Contents are a serialized `fastcrypto_tbls::dkg::Message`.
256    RandomnessDkgMessage(AuthorityName, Vec<u8>),
257    // `RandomnessDkgConfirmation` is the second DKG message, sent as soon as a threshold amount
258    // of `RandomnessDkgMessages` have been received locally, to complete the key generation
259    // process. Contents are a serialized `fastcrypto_tbls::dkg::Confirmation`.
260    RandomnessDkgConfirmation(AuthorityName, Vec<u8>),
261}
262
263impl ConsensusTransactionKind {
264    pub fn is_dkg(&self) -> bool {
265        matches!(
266            self,
267            ConsensusTransactionKind::RandomnessDkgMessage(_, _)
268                | ConsensusTransactionKind::RandomnessDkgConfirmation(_, _)
269        )
270    }
271}
272
273#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
274pub enum VersionedDkgMessage {
275    V1(dkg_v1::Message<bls12381::G2Element, bls12381::G2Element>),
276}
277
278#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
279pub enum VersionedDkgConfirmation {
280    V1(dkg_v1::Confirmation<bls12381::G2Element>),
281}
282
283impl Debug for VersionedDkgMessage {
284    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
285        match self {
286            VersionedDkgMessage::V1(msg) => write!(
287                f,
288                "DKG V1 Message with sender={}, vss_pk.degree={}, encrypted_shares.len()={}",
289                msg.sender,
290                msg.vss_pk.degree(),
291                msg.encrypted_shares.len(),
292            ),
293        }
294    }
295}
296
297impl VersionedDkgMessage {
298    pub fn sender(&self) -> u16 {
299        match self {
300            VersionedDkgMessage::V1(msg) => msg.sender,
301        }
302    }
303
304    pub fn create(
305        dkg_version: u64,
306        party: Arc<dkg_v1::Party<bls12381::G2Element, bls12381::G2Element>>,
307    ) -> FastCryptoResult<VersionedDkgMessage> {
308        assert_eq!(dkg_version, 1, "BUG: invalid DKG version");
309        let msg = party.create_message(&mut rand::thread_rng())?;
310        Ok(VersionedDkgMessage::V1(msg))
311    }
312
313    pub fn unwrap_v1(self) -> dkg_v1::Message<bls12381::G2Element, bls12381::G2Element> {
314        match self {
315            VersionedDkgMessage::V1(msg) => msg,
316        }
317    }
318
319    pub fn is_valid_version(&self, dkg_version: u64) -> bool {
320        matches!((self, dkg_version), (VersionedDkgMessage::V1(_), 1))
321    }
322}
323
324impl VersionedDkgConfirmation {
325    pub fn sender(&self) -> u16 {
326        match self {
327            VersionedDkgConfirmation::V1(msg) => msg.sender,
328        }
329    }
330
331    pub fn num_of_complaints(&self) -> usize {
332        match self {
333            VersionedDkgConfirmation::V1(msg) => msg.complaints.len(),
334        }
335    }
336
337    pub fn unwrap_v1(&self) -> &dkg_v1::Confirmation<bls12381::G2Element> {
338        match self {
339            VersionedDkgConfirmation::V1(msg) => msg,
340        }
341    }
342
343    pub fn is_valid_version(&self, dkg_version: u64) -> bool {
344        matches!((self, dkg_version), (VersionedDkgConfirmation::V1(_), 1))
345    }
346}
347
348impl ConsensusTransaction {
349    pub fn new_certificate_message(
350        authority: &AuthorityName,
351        certificate: CertifiedTransaction,
352    ) -> Self {
353        let mut hasher = DefaultHasher::new();
354        let tx_digest = certificate.digest();
355        tx_digest.hash(&mut hasher);
356        authority.hash(&mut hasher);
357        let tracking_id = hasher.finish().to_le_bytes();
358        Self {
359            tracking_id,
360            kind: ConsensusTransactionKind::CertifiedTransaction(Box::new(certificate)),
361        }
362    }
363
364    pub fn new_checkpoint_signature_message(data: CheckpointSignatureMessage) -> Self {
365        let mut hasher = DefaultHasher::new();
366        data.summary.auth_sig().signature.hash(&mut hasher);
367        let tracking_id = hasher.finish().to_le_bytes();
368        Self {
369            tracking_id,
370            kind: ConsensusTransactionKind::CheckpointSignature(Box::new(data)),
371        }
372    }
373
374    pub fn new_end_of_publish(authority: AuthorityName) -> Self {
375        let mut hasher = DefaultHasher::new();
376        authority.hash(&mut hasher);
377        let tracking_id = hasher.finish().to_le_bytes();
378        Self {
379            tracking_id,
380            kind: ConsensusTransactionKind::EndOfPublish(authority),
381        }
382    }
383
384    pub fn new_capability_notification_v1(capabilities: AuthorityCapabilitiesV1) -> Self {
385        let mut hasher = DefaultHasher::new();
386        capabilities.hash(&mut hasher);
387        let tracking_id = hasher.finish().to_le_bytes();
388        Self {
389            tracking_id,
390            kind: ConsensusTransactionKind::CapabilityNotificationV1(capabilities),
391        }
392    }
393
394    pub fn new_signed_capability_notification_v1(
395        signed_capabilities: SignedAuthorityCapabilitiesV1,
396    ) -> Self {
397        let mut hasher = DefaultHasher::new();
398        signed_capabilities.data().hash(&mut hasher);
399        signed_capabilities.auth_sig().hash(&mut hasher);
400        let tracking_id = hasher.finish().to_le_bytes();
401        Self {
402            tracking_id,
403            kind: ConsensusTransactionKind::SignedCapabilityNotificationV1(signed_capabilities),
404        }
405    }
406
407    pub fn new_mysticeti_certificate(
408        round: u64,
409        offset: u64,
410        certificate: CertifiedTransaction,
411    ) -> Self {
412        let mut hasher = DefaultHasher::new();
413        let tx_digest = certificate.digest();
414        tx_digest.hash(&mut hasher);
415        round.hash(&mut hasher);
416        offset.hash(&mut hasher);
417        let tracking_id = hasher.finish().to_le_bytes();
418        Self {
419            tracking_id,
420            kind: ConsensusTransactionKind::CertifiedTransaction(Box::new(certificate)),
421        }
422    }
423
424    pub fn new_jwk_fetched(authority: AuthorityName, id: JwkId, jwk: JWK) -> Self {
425        let mut hasher = DefaultHasher::new();
426        id.hash(&mut hasher);
427        let tracking_id = hasher.finish().to_le_bytes();
428        Self {
429            tracking_id,
430            kind: ConsensusTransactionKind::NewJWKFetched(authority, id, jwk),
431        }
432    }
433
434    pub fn new_randomness_dkg_message(
435        authority: AuthorityName,
436        versioned_message: &VersionedDkgMessage,
437    ) -> Self {
438        let message =
439            bcs::to_bytes(versioned_message).expect("message serialization should not fail");
440        let mut hasher = DefaultHasher::new();
441        message.hash(&mut hasher);
442        let tracking_id = hasher.finish().to_le_bytes();
443        Self {
444            tracking_id,
445            kind: ConsensusTransactionKind::RandomnessDkgMessage(authority, message),
446        }
447    }
448    pub fn new_randomness_dkg_confirmation(
449        authority: AuthorityName,
450        versioned_confirmation: &VersionedDkgConfirmation,
451    ) -> Self {
452        let confirmation =
453            bcs::to_bytes(versioned_confirmation).expect("message serialization should not fail");
454        let mut hasher = DefaultHasher::new();
455        confirmation.hash(&mut hasher);
456        let tracking_id = hasher.finish().to_le_bytes();
457        Self {
458            tracking_id,
459            kind: ConsensusTransactionKind::RandomnessDkgConfirmation(authority, confirmation),
460        }
461    }
462
463    pub fn get_tracking_id(&self) -> u64 {
464        (&self.tracking_id[..])
465            .read_u64::<BigEndian>()
466            .unwrap_or_default()
467    }
468
469    pub fn key(&self) -> ConsensusTransactionKey {
470        match &self.kind {
471            ConsensusTransactionKind::CertifiedTransaction(cert) => {
472                ConsensusTransactionKey::Certificate(*cert.digest())
473            }
474            ConsensusTransactionKind::CheckpointSignature(data) => {
475                ConsensusTransactionKey::CheckpointSignature(
476                    data.summary.auth_sig().authority,
477                    data.summary.sequence_number,
478                )
479            }
480            ConsensusTransactionKind::EndOfPublish(authority) => {
481                ConsensusTransactionKey::EndOfPublish(*authority)
482            }
483            ConsensusTransactionKind::CapabilityNotificationV1(cap) => {
484                ConsensusTransactionKey::CapabilityNotification(cap.authority, cap.generation)
485            }
486            ConsensusTransactionKind::SignedCapabilityNotificationV1(signed_cap) => {
487                ConsensusTransactionKey::CapabilityNotification(
488                    signed_cap.authority,
489                    signed_cap.generation,
490                )
491            }
492
493            ConsensusTransactionKind::NewJWKFetched(authority, id, key) => {
494                ConsensusTransactionKey::NewJWKFetched(Box::new((
495                    *authority,
496                    id.clone(),
497                    key.clone(),
498                )))
499            }
500            ConsensusTransactionKind::RandomnessDkgMessage(authority, _) => {
501                ConsensusTransactionKey::RandomnessDkgMessage(*authority)
502            }
503            ConsensusTransactionKind::RandomnessDkgConfirmation(authority, _) => {
504                ConsensusTransactionKey::RandomnessDkgConfirmation(*authority)
505            }
506        }
507    }
508
509    pub fn is_user_certificate(&self) -> bool {
510        matches!(self.kind, ConsensusTransactionKind::CertifiedTransaction(_))
511    }
512
513    pub fn is_end_of_publish(&self) -> bool {
514        matches!(self.kind, ConsensusTransactionKind::EndOfPublish(_))
515    }
516}
517
518#[test]
519fn test_jwk_compatibility() {
520    // Ensure that the JWK and JwkId structs in fastcrypto do not change formats.
521    // If this test breaks DO NOT JUST UPDATE THE EXPECTED BYTES. Instead, add a
522    // local JWK or JwkId struct that mirrors the fastcrypto struct, use it in
523    // AuthenticatorStateUpdate, and add Into/From as necessary.
524    let jwk = JWK {
525        kty: "a".to_string(),
526        e: "b".to_string(),
527        n: "c".to_string(),
528        alg: "d".to_string(),
529    };
530
531    let expected_jwk_bytes = vec![1, 97, 1, 98, 1, 99, 1, 100];
532    let jwk_bcs = bcs::to_bytes(&jwk).unwrap();
533    assert_eq!(jwk_bcs, expected_jwk_bytes);
534
535    let id = JwkId {
536        iss: "abc".to_string(),
537        kid: "def".to_string(),
538    };
539
540    let expected_id_bytes = vec![3, 97, 98, 99, 3, 100, 101, 102];
541    let id_bcs = bcs::to_bytes(&id).unwrap();
542    assert_eq!(id_bcs, expected_id_bytes);
543}