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    UserTransaction(Box<CertifiedTransaction>),
193    CheckpointSignature(Box<CheckpointSignatureMessage>),
194    EndOfPublish(AuthorityName),
195
196    CapabilityNotificationV1(AuthorityCapabilitiesV1),
197
198    NewJWKFetched(AuthorityName, JwkId, JWK),
199    // DKG is used to generate keys for use in the random beacon protocol.
200    // `RandomnessDkgMessage` is sent out at start-of-epoch to initiate the process.
201    // Contents are a serialized `fastcrypto_tbls::dkg::Message`.
202    RandomnessDkgMessage(AuthorityName, Vec<u8>),
203    // `RandomnessDkgConfirmation` is the second DKG message, sent as soon as a threshold amount
204    // of `RandomnessDkgMessages` have been received locally, to complete the key generation
205    // process. Contents are a serialized `fastcrypto_tbls::dkg::Confirmation`.
206    RandomnessDkgConfirmation(AuthorityName, Vec<u8>),
207}
208
209impl ConsensusTransactionKind {
210    pub fn is_dkg(&self) -> bool {
211        matches!(
212            self,
213            ConsensusTransactionKind::RandomnessDkgMessage(_, _)
214                | ConsensusTransactionKind::RandomnessDkgConfirmation(_, _)
215        )
216    }
217}
218
219#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
220pub enum VersionedDkgMessage {
221    V1(dkg_v1::Message<bls12381::G2Element, bls12381::G2Element>),
222}
223
224#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
225pub enum VersionedDkgConfirmation {
226    V1(dkg::Confirmation<bls12381::G2Element>),
227}
228
229impl Debug for VersionedDkgMessage {
230    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
231        match self {
232            VersionedDkgMessage::V1(msg) => write!(
233                f,
234                "DKG V1 Message with sender={}, vss_pk.degree={}, encrypted_shares.len()={}",
235                msg.sender,
236                msg.vss_pk.degree(),
237                msg.encrypted_shares.len(),
238            ),
239        }
240    }
241}
242
243impl VersionedDkgMessage {
244    pub fn sender(&self) -> u16 {
245        match self {
246            VersionedDkgMessage::V1(msg) => msg.sender,
247        }
248    }
249
250    pub fn create(
251        dkg_version: u64,
252        party: Arc<dkg::Party<bls12381::G2Element, bls12381::G2Element>>,
253    ) -> FastCryptoResult<VersionedDkgMessage> {
254        assert_eq!(dkg_version, 1, "BUG: invalid DKG version");
255        let msg = party.create_message_v1(&mut rand::thread_rng())?;
256        Ok(VersionedDkgMessage::V1(msg))
257    }
258
259    pub fn unwrap_v1(self) -> dkg_v1::Message<bls12381::G2Element, bls12381::G2Element> {
260        match self {
261            VersionedDkgMessage::V1(msg) => msg,
262        }
263    }
264
265    pub fn is_valid_version(&self, dkg_version: u64) -> bool {
266        matches!((self, dkg_version), (VersionedDkgMessage::V1(_), 1))
267    }
268}
269
270impl VersionedDkgConfirmation {
271    pub fn sender(&self) -> u16 {
272        match self {
273            VersionedDkgConfirmation::V1(msg) => msg.sender,
274        }
275    }
276
277    pub fn num_of_complaints(&self) -> usize {
278        match self {
279            VersionedDkgConfirmation::V1(msg) => msg.complaints.len(),
280        }
281    }
282
283    pub fn unwrap_v1(&self) -> &dkg::Confirmation<bls12381::G2Element> {
284        match self {
285            VersionedDkgConfirmation::V1(msg) => msg,
286        }
287    }
288
289    pub fn is_valid_version(&self, dkg_version: u64) -> bool {
290        matches!((self, dkg_version), (VersionedDkgConfirmation::V1(_), 1))
291    }
292}
293
294impl ConsensusTransaction {
295    pub fn new_certificate_message(
296        authority: &AuthorityName,
297        certificate: CertifiedTransaction,
298    ) -> Self {
299        let mut hasher = DefaultHasher::new();
300        let tx_digest = certificate.digest();
301        tx_digest.hash(&mut hasher);
302        authority.hash(&mut hasher);
303        let tracking_id = hasher.finish().to_le_bytes();
304        Self {
305            tracking_id,
306            kind: ConsensusTransactionKind::UserTransaction(Box::new(certificate)),
307        }
308    }
309
310    pub fn new_checkpoint_signature_message(data: CheckpointSignatureMessage) -> Self {
311        let mut hasher = DefaultHasher::new();
312        data.summary.auth_sig().signature.hash(&mut hasher);
313        let tracking_id = hasher.finish().to_le_bytes();
314        Self {
315            tracking_id,
316            kind: ConsensusTransactionKind::CheckpointSignature(Box::new(data)),
317        }
318    }
319
320    pub fn new_end_of_publish(authority: AuthorityName) -> Self {
321        let mut hasher = DefaultHasher::new();
322        authority.hash(&mut hasher);
323        let tracking_id = hasher.finish().to_le_bytes();
324        Self {
325            tracking_id,
326            kind: ConsensusTransactionKind::EndOfPublish(authority),
327        }
328    }
329
330    pub fn new_capability_notification_v1(capabilities: AuthorityCapabilitiesV1) -> Self {
331        let mut hasher = DefaultHasher::new();
332        capabilities.hash(&mut hasher);
333        let tracking_id = hasher.finish().to_le_bytes();
334        Self {
335            tracking_id,
336            kind: ConsensusTransactionKind::CapabilityNotificationV1(capabilities),
337        }
338    }
339
340    pub fn new_mysticeti_certificate(
341        round: u64,
342        offset: u64,
343        certificate: CertifiedTransaction,
344    ) -> Self {
345        let mut hasher = DefaultHasher::new();
346        let tx_digest = certificate.digest();
347        tx_digest.hash(&mut hasher);
348        round.hash(&mut hasher);
349        offset.hash(&mut hasher);
350        let tracking_id = hasher.finish().to_le_bytes();
351        Self {
352            tracking_id,
353            kind: ConsensusTransactionKind::UserTransaction(Box::new(certificate)),
354        }
355    }
356
357    pub fn new_jwk_fetched(authority: AuthorityName, id: JwkId, jwk: JWK) -> Self {
358        let mut hasher = DefaultHasher::new();
359        id.hash(&mut hasher);
360        let tracking_id = hasher.finish().to_le_bytes();
361        Self {
362            tracking_id,
363            kind: ConsensusTransactionKind::NewJWKFetched(authority, id, jwk),
364        }
365    }
366
367    pub fn new_randomness_dkg_message(
368        authority: AuthorityName,
369        versioned_message: &VersionedDkgMessage,
370    ) -> Self {
371        let message =
372            bcs::to_bytes(versioned_message).expect("message serialization should not fail");
373        let mut hasher = DefaultHasher::new();
374        message.hash(&mut hasher);
375        let tracking_id = hasher.finish().to_le_bytes();
376        Self {
377            tracking_id,
378            kind: ConsensusTransactionKind::RandomnessDkgMessage(authority, message),
379        }
380    }
381    pub fn new_randomness_dkg_confirmation(
382        authority: AuthorityName,
383        versioned_confirmation: &VersionedDkgConfirmation,
384    ) -> Self {
385        let confirmation =
386            bcs::to_bytes(versioned_confirmation).expect("message serialization should not fail");
387        let mut hasher = DefaultHasher::new();
388        confirmation.hash(&mut hasher);
389        let tracking_id = hasher.finish().to_le_bytes();
390        Self {
391            tracking_id,
392            kind: ConsensusTransactionKind::RandomnessDkgConfirmation(authority, confirmation),
393        }
394    }
395
396    pub fn get_tracking_id(&self) -> u64 {
397        (&self.tracking_id[..])
398            .read_u64::<BigEndian>()
399            .unwrap_or_default()
400    }
401
402    pub fn key(&self) -> ConsensusTransactionKey {
403        match &self.kind {
404            ConsensusTransactionKind::UserTransaction(cert) => {
405                ConsensusTransactionKey::Certificate(*cert.digest())
406            }
407            ConsensusTransactionKind::CheckpointSignature(data) => {
408                ConsensusTransactionKey::CheckpointSignature(
409                    data.summary.auth_sig().authority,
410                    data.summary.sequence_number,
411                )
412            }
413            ConsensusTransactionKind::EndOfPublish(authority) => {
414                ConsensusTransactionKey::EndOfPublish(*authority)
415            }
416            ConsensusTransactionKind::CapabilityNotificationV1(cap) => {
417                ConsensusTransactionKey::CapabilityNotification(cap.authority, cap.generation)
418            }
419            ConsensusTransactionKind::NewJWKFetched(authority, id, key) => {
420                ConsensusTransactionKey::NewJWKFetched(Box::new((
421                    *authority,
422                    id.clone(),
423                    key.clone(),
424                )))
425            }
426            ConsensusTransactionKind::RandomnessDkgMessage(authority, _) => {
427                ConsensusTransactionKey::RandomnessDkgMessage(*authority)
428            }
429            ConsensusTransactionKind::RandomnessDkgConfirmation(authority, _) => {
430                ConsensusTransactionKey::RandomnessDkgConfirmation(*authority)
431            }
432        }
433    }
434
435    pub fn is_user_certificate(&self) -> bool {
436        matches!(self.kind, ConsensusTransactionKind::UserTransaction(_))
437    }
438
439    pub fn is_end_of_publish(&self) -> bool {
440        matches!(self.kind, ConsensusTransactionKind::EndOfPublish(_))
441    }
442}
443
444#[test]
445fn test_jwk_compatibility() {
446    // Ensure that the JWK and JwkId structs in fastcrypto do not change formats.
447    // If this test breaks DO NOT JUST UPDATE THE EXPECTED BYTES. Instead, add a
448    // local JWK or JwkId struct that mirrors the fastcrypto struct, use it in
449    // AuthenticatorStateUpdate, and add Into/From as necessary.
450    let jwk = JWK {
451        kty: "a".to_string(),
452        e: "b".to_string(),
453        n: "c".to_string(),
454        alg: "d".to_string(),
455    };
456
457    let expected_jwk_bytes = vec![1, 97, 1, 98, 1, 99, 1, 100];
458    let jwk_bcs = bcs::to_bytes(&jwk).unwrap();
459    assert_eq!(jwk_bcs, expected_jwk_bytes);
460
461    let id = JwkId {
462        iss: "abc".to_string(),
463        kid: "def".to_string(),
464    };
465
466    let expected_id_bytes = vec![3, 97, 98, 99, 3, 100, 101, 102];
467    let id_bcs = bcs::to_bytes(&id).unwrap();
468    assert_eq!(id_bcs, expected_id_bytes);
469}