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