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