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 iota_sdk_types::crypto::IntentScope;
18use once_cell::sync::OnceCell;
19use schemars::JsonSchema;
20use serde::{Deserialize, Serialize};
21
22use crate::{
23    base_types::{
24        AuthorityName, ConciseableName, ObjectID, ObjectRef, SequenceNumber, TransactionDigest,
25    },
26    crypto::{AuthoritySignature, DefaultHash, default_hash},
27    digests::{ConsensusCommitDigest, Digest, MisbehaviorReportDigest},
28    message_envelope::{Envelope, Message, VerifiedEnvelope},
29    messages_checkpoint::{CheckpointSequenceNumber, CheckpointSignatureMessage},
30    supported_protocol_versions::{
31        Chain, SupportedProtocolVersions, SupportedProtocolVersionsWithHashes,
32    },
33    transaction::CertifiedTransaction,
34};
35
36/// Non-decreasing timestamp produced by consensus in ms.
37pub type TimestampMs = u64;
38
39/// Uses an enum to allow for future expansion of the
40/// ConsensusDeterminedVersionAssignments.
41#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, JsonSchema)]
42pub enum ConsensusDeterminedVersionAssignments {
43    // Cancelled transaction version assignment.
44    CancelledTransactions(Vec<(TransactionDigest, Vec<(ObjectID, SequenceNumber)>)>),
45}
46
47#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
48pub struct ConsensusCommitPrologueV1 {
49    /// Epoch of the commit prologue transaction
50    pub epoch: u64,
51    /// Consensus round of the commit
52    pub round: u64,
53    /// The sub DAG index of the consensus commit. This field will be populated
54    /// if there are multiple consensus commits per round.
55    pub sub_dag_index: Option<u64>,
56    /// Unix timestamp from consensus
57    pub commit_timestamp_ms: TimestampMs,
58    /// Digest of consensus output
59    pub consensus_commit_digest: ConsensusCommitDigest,
60    /// Stores consensus handler determined shared object version assignments.
61    pub consensus_determined_version_assignments: ConsensusDeterminedVersionAssignments,
62}
63
64// In practice, JWKs are about 500 bytes of json each, plus a bit more for the
65// ID. 4096 should give us plenty of space for any imaginable JWK while
66// preventing DoSes.
67static MAX_TOTAL_JWK_SIZE: usize = 4096;
68
69pub fn check_total_jwk_size(id: &JwkId, jwk: &JWK) -> bool {
70    id.iss.len() + id.kid.len() + jwk.kty.len() + jwk.alg.len() + jwk.e.len() + jwk.n.len()
71        <= MAX_TOTAL_JWK_SIZE
72}
73
74#[derive(Serialize, Deserialize, Clone, Debug)]
75pub struct ConsensusTransaction {
76    /// Encodes an u64 unique tracking id to allow us trace a message between
77    /// IOTA and consensus. Use an byte array instead of u64 to ensure stable
78    /// serialization.
79    pub tracking_id: [u8; 8],
80    pub kind: ConsensusTransactionKind,
81}
82
83#[derive(Serialize, Deserialize, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)]
84pub enum ConsensusTransactionKey {
85    Certificate(TransactionDigest),
86    CheckpointSignature(AuthorityName, CheckpointSequenceNumber),
87    EndOfPublish(AuthorityName),
88    CapabilityNotification(AuthorityName, u64 /* generation */),
89    // Key must include both id and jwk, because honest validators could be given multiple jwks
90    // for the same id by malfunctioning providers.
91    NewJWKFetched(Box<(AuthorityName, JwkId, JWK)>),
92    RandomnessDkgMessage(AuthorityName),
93    RandomnessDkgConfirmation(AuthorityName),
94    MisbehaviorReport(MisbehaviorReportDigest),
95    // New entries should be added at the end to preserve serialization compatibility. DO NOT
96    // CHANGE THE ORDER OF EXISTING ENTRIES!
97}
98
99impl Debug for ConsensusTransactionKey {
100    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
101        match self {
102            Self::Certificate(digest) => write!(f, "Certificate({digest:?})"),
103            Self::CheckpointSignature(name, seq) => {
104                write!(f, "CheckpointSignature({:?}, {:?})", name.concise(), seq)
105            }
106            Self::EndOfPublish(name) => write!(f, "EndOfPublish({:?})", name.concise()),
107            Self::CapabilityNotification(name, generation) => write!(
108                f,
109                "CapabilityNotification({:?}, {:?})",
110                name.concise(),
111                generation
112            ),
113            Self::NewJWKFetched(key) => {
114                let (authority, id, jwk) = &**key;
115                write!(
116                    f,
117                    "NewJWKFetched({:?}, {:?}, {:?})",
118                    authority.concise(),
119                    id,
120                    jwk
121                )
122            }
123            Self::RandomnessDkgMessage(name) => {
124                write!(f, "RandomnessDkgMessage({:?})", name.concise())
125            }
126            Self::RandomnessDkgConfirmation(name) => {
127                write!(f, "RandomnessDkgConfirmation({:?})", name.concise())
128            }
129            Self::MisbehaviorReport(digest) => {
130                write!(f, "MisbehaviorReport({digest:?})")
131            }
132        }
133    }
134}
135
136pub type SignedAuthorityCapabilitiesV1 = Envelope<AuthorityCapabilitiesV1, AuthoritySignature>;
137
138pub type VerifiedAuthorityCapabilitiesV1 =
139    VerifiedEnvelope<AuthorityCapabilitiesV1, AuthoritySignature>;
140
141#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
142pub struct AuthorityCapabilitiesDigest(Digest);
143
144impl AuthorityCapabilitiesDigest {
145    pub const fn new(digest: [u8; 32]) -> Self {
146        Self(Digest::new(digest))
147    }
148}
149
150impl Debug for AuthorityCapabilitiesDigest {
151    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
152        f.debug_tuple("AuthorityCapabilitiesDigest")
153            .field(&self.0)
154            .finish()
155    }
156}
157
158/// Used to advertise capabilities of each authority via consensus. This allows
159/// validators to negotiate the creation of the ChangeEpoch transaction.
160#[derive(Serialize, Deserialize, Clone, Hash)]
161pub struct AuthorityCapabilitiesV1 {
162    /// Originating authority - must match transaction source authority from
163    /// consensus or the signature of a non-committee active validator.
164    pub authority: AuthorityName,
165    /// Generation number set by sending authority. Used to determine which of
166    /// multiple AuthorityCapabilities messages from the same authority is
167    /// the most recent.
168    ///
169    /// (Currently, we just set this to the current time in milliseconds since
170    /// the epoch, but this should not be interpreted as a timestamp.)
171    pub generation: u64,
172
173    /// ProtocolVersions that the authority supports, including the hash of the
174    /// serialized ProtocolConfig of that authority per version.
175    pub supported_protocol_versions: SupportedProtocolVersionsWithHashes,
176
177    /// The ObjectRefs of all versions of system packages that the validator
178    /// possesses. Used to determine whether to do a framework/movestdlib
179    /// upgrade.
180    pub available_system_packages: Vec<ObjectRef>,
181}
182
183impl Message for AuthorityCapabilitiesV1 {
184    type DigestType = AuthorityCapabilitiesDigest;
185    const SCOPE: IntentScope = IntentScope::AuthorityCapabilities;
186
187    fn digest(&self) -> Self::DigestType {
188        // Ensure deterministic serialization for digest
189        let mut hasher = DefaultHash::new();
190        let serialized = bcs::to_bytes(&self).expect("BCS should not fail");
191        hasher.update(&serialized);
192        AuthorityCapabilitiesDigest::new(<[u8; 32]>::from(hasher.finalize()))
193    }
194}
195
196impl Debug for AuthorityCapabilitiesV1 {
197    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
198        f.debug_struct("AuthorityCapabilities")
199            .field("authority", &self.authority.concise())
200            .field("generation", &self.generation)
201            .field(
202                "supported_protocol_versions",
203                &self.supported_protocol_versions,
204            )
205            .field("available_system_packages", &self.available_system_packages)
206            .finish()
207    }
208}
209
210impl AuthorityCapabilitiesV1 {
211    pub fn new(
212        authority: AuthorityName,
213        chain: Chain,
214        supported_protocol_versions: SupportedProtocolVersions,
215        available_system_packages: Vec<ObjectRef>,
216    ) -> Self {
217        let generation = SystemTime::now()
218            .duration_since(UNIX_EPOCH)
219            .expect("IOTA did not exist prior to 1970")
220            .as_millis()
221            .try_into()
222            .expect("This build of iota is not supported in the year 500,000,000");
223        Self {
224            authority,
225            generation,
226            supported_protocol_versions:
227                SupportedProtocolVersionsWithHashes::from_supported_versions(
228                    supported_protocol_versions,
229                    chain,
230                ),
231            available_system_packages,
232        }
233    }
234}
235
236impl SignedAuthorityCapabilitiesV1 {
237    pub fn cache_digest(&self, epoch: u64) -> AuthorityCapabilitiesDigest {
238        // Create a tuple that includes both the capabilities data and the epoch
239        let data_with_epoch = (self.data(), epoch);
240
241        // Ensure deterministic serialization for digest
242        let mut hasher = DefaultHash::new();
243        let serialized = bcs::to_bytes(&data_with_epoch).expect("BCS should not fail");
244        hasher.update(&serialized);
245        AuthorityCapabilitiesDigest::new(<[u8; 32]>::from(hasher.finalize()))
246    }
247}
248
249#[derive(Serialize, Deserialize, Clone, Debug)]
250pub enum ConsensusTransactionKind {
251    CertifiedTransaction(Box<CertifiedTransaction>),
252    CheckpointSignature(Box<CheckpointSignatureMessage>),
253    EndOfPublish(AuthorityName),
254
255    CapabilityNotificationV1(AuthorityCapabilitiesV1),
256    SignedCapabilityNotificationV1(SignedAuthorityCapabilitiesV1),
257
258    NewJWKFetched(AuthorityName, JwkId, JWK),
259
260    // DKG is used to generate keys for use in the random beacon protocol.
261    // `RandomnessDkgMessage` is sent out at start-of-epoch to initiate the process.
262    // Contents are a serialized `fastcrypto_tbls::dkg::Message`.
263    RandomnessDkgMessage(AuthorityName, Vec<u8>),
264    // `RandomnessDkgConfirmation` is the second DKG message, sent as soon as a threshold amount
265    // of `RandomnessDkgMessages` have been received locally, to complete the key generation
266    // process. Contents are a serialized `fastcrypto_tbls::dkg::Confirmation`.
267    RandomnessDkgConfirmation(AuthorityName, Vec<u8>),
268    MisbehaviorReport(
269        AuthorityName,
270        VersionedMisbehaviorReport,
271        u64, // checkpoint_seq
272    ),
273    // New entries should be added at the end to preserve serialization compatibility. DO NOT
274    // CHANGE THE ORDER OF EXISTING ENTRIES!
275}
276
277impl ConsensusTransactionKind {
278    pub fn is_dkg(&self) -> bool {
279        matches!(
280            self,
281            ConsensusTransactionKind::RandomnessDkgMessage(_, _)
282                | ConsensusTransactionKind::RandomnessDkgConfirmation(_, _)
283        )
284    }
285}
286
287#[derive(Debug, Clone, Serialize, Deserialize)]
288pub enum VersionedMisbehaviorReport {
289    V1(
290        MisbehaviorsV1<Vec<u64>>,
291        #[serde(skip)] OnceCell<MisbehaviorReportDigest>,
292    ),
293}
294
295impl VersionedMisbehaviorReport {
296    pub fn new_v1(misbehaviors: MisbehaviorsV1<Vec<u64>>) -> Self {
297        VersionedMisbehaviorReport::V1(misbehaviors, OnceCell::new())
298    }
299
300    pub fn verify(&self, committee_size: usize) -> bool {
301        match self {
302            VersionedMisbehaviorReport::V1(report, _) => report.verify(committee_size),
303        }
304    }
305    /// Returns an iterator over references to some of the fields in the report.
306    pub fn iterate_over_metrics(&self) -> std::vec::IntoIter<&Vec<u64>> {
307        match self {
308            VersionedMisbehaviorReport::V1(report, _) => report.iter(),
309        }
310    }
311    /// Returns the digest of the misbehavior report, caching it if it has not
312    /// been computed yet.
313    pub fn digest(&self) -> &MisbehaviorReportDigest {
314        match self {
315            VersionedMisbehaviorReport::V1(_, digest) => {
316                digest.get_or_init(|| MisbehaviorReportDigest::new(default_hash(self)))
317            }
318        }
319    }
320}
321
322// MisbehaviorsV1 contains lists of all metrics used in v1 of misbehavior
323// reports, with a value for each metric. The metrics (misbeheaviors) include,
324// faulty blocks, equivocation and missing proposal counts for each authority.
325// This first version does not include any type of proof.
326#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
327pub struct MisbehaviorsV1<T> {
328    pub faulty_blocks_provable: T,
329    pub faulty_blocks_unprovable: T,
330    pub missing_proposals: T,
331    pub equivocations: T,
332}
333
334impl MisbehaviorsV1<Vec<u64>> {
335    pub fn verify(&self, committee_size: usize) -> bool {
336        // This version of reports are valid as long as they contain the counts for all
337        // authorities. Future versions may contain proofs that need verification.
338        // However, since the validity of a proof is deeply coupled with the protocol
339        // version and the consensus mechanism being used, we cannot verify it here. In
340        // the future, reports should be unwrapped (or translated) to a type verifiable
341        // by the consensus crate, which means that the verification logic will probably
342        // move out of this crate.
343        if (self.faulty_blocks_provable.len() != committee_size)
344            | (self.faulty_blocks_unprovable.len() != committee_size)
345            | (self.equivocations.len() != committee_size)
346            | (self.missing_proposals.len() != committee_size)
347        {
348            return false;
349        }
350        true
351    }
352}
353impl<T> MisbehaviorsV1<T> {
354    pub fn iter(&self) -> std::vec::IntoIter<&T> {
355        vec![
356            &self.faulty_blocks_provable,
357            &self.faulty_blocks_unprovable,
358            &self.missing_proposals,
359            &self.equivocations,
360        ]
361        .into_iter()
362    }
363    // Returns an iterator over references to major misbehavior fields in the
364    // report. Major misbehaviors carry a higher penalty in the scoring system.
365    pub fn iter_major_misbehaviors(&self) -> std::vec::IntoIter<&T> {
366        vec![&self.equivocations].into_iter()
367    }
368    // Returns an iterator over references to minor misbehavior fields in the
369    // report. Minor misbehaviors carry a lower penalty in the scoring system.
370    pub fn iter_minor_misbehaviors(&self) -> std::vec::IntoIter<&T> {
371        vec![
372            &self.faulty_blocks_provable,
373            &self.faulty_blocks_unprovable,
374            &self.missing_proposals,
375        ]
376        .into_iter()
377    }
378}
379
380impl<T> FromIterator<T> for MisbehaviorsV1<T> {
381    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
382        let mut iterator = iter.into_iter();
383        Self {
384            faulty_blocks_provable: iterator.next().expect("Not enough elements in iterator"),
385            faulty_blocks_unprovable: iterator.next().expect("Not enough elements in iterator"),
386            missing_proposals: iterator.next().expect("Not enough elements in iterator"),
387            equivocations: iterator.next().expect("Not enough elements in iterator"),
388        }
389    }
390}
391
392#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
393pub enum VersionedDkgMessage {
394    V1(dkg_v1::Message<bls12381::G2Element, bls12381::G2Element>),
395}
396
397#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
398pub enum VersionedDkgConfirmation {
399    V1(dkg_v1::Confirmation<bls12381::G2Element>),
400}
401
402impl Debug for VersionedDkgMessage {
403    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
404        match self {
405            VersionedDkgMessage::V1(msg) => write!(
406                f,
407                "DKG V1 Message with sender={}, vss_pk.degree={}, encrypted_shares.len()={}",
408                msg.sender,
409                msg.vss_pk.degree(),
410                msg.encrypted_shares.len(),
411            ),
412        }
413    }
414}
415
416impl VersionedDkgMessage {
417    pub fn sender(&self) -> u16 {
418        match self {
419            VersionedDkgMessage::V1(msg) => msg.sender,
420        }
421    }
422
423    pub fn create(
424        dkg_version: u64,
425        party: Arc<dkg_v1::Party<bls12381::G2Element, bls12381::G2Element>>,
426    ) -> FastCryptoResult<VersionedDkgMessage> {
427        assert_eq!(dkg_version, 1, "BUG: invalid DKG version");
428        let msg = party.create_message(&mut rand::thread_rng())?;
429        Ok(VersionedDkgMessage::V1(msg))
430    }
431
432    pub fn unwrap_v1(self) -> dkg_v1::Message<bls12381::G2Element, bls12381::G2Element> {
433        match self {
434            VersionedDkgMessage::V1(msg) => msg,
435        }
436    }
437
438    pub fn is_valid_version(&self, dkg_version: u64) -> bool {
439        matches!((self, dkg_version), (VersionedDkgMessage::V1(_), 1))
440    }
441}
442
443impl VersionedDkgConfirmation {
444    pub fn sender(&self) -> u16 {
445        match self {
446            VersionedDkgConfirmation::V1(msg) => msg.sender,
447        }
448    }
449
450    pub fn num_of_complaints(&self) -> usize {
451        match self {
452            VersionedDkgConfirmation::V1(msg) => msg.complaints.len(),
453        }
454    }
455
456    pub fn unwrap_v1(&self) -> &dkg_v1::Confirmation<bls12381::G2Element> {
457        match self {
458            VersionedDkgConfirmation::V1(msg) => msg,
459        }
460    }
461
462    pub fn is_valid_version(&self, dkg_version: u64) -> bool {
463        matches!((self, dkg_version), (VersionedDkgConfirmation::V1(_), 1))
464    }
465}
466
467impl ConsensusTransaction {
468    pub fn new_certificate_message(
469        authority: &AuthorityName,
470        certificate: CertifiedTransaction,
471    ) -> Self {
472        let mut hasher = DefaultHasher::new();
473        let tx_digest = certificate.digest();
474        tx_digest.hash(&mut hasher);
475        authority.hash(&mut hasher);
476        let tracking_id = hasher.finish().to_le_bytes();
477        Self {
478            tracking_id,
479            kind: ConsensusTransactionKind::CertifiedTransaction(Box::new(certificate)),
480        }
481    }
482
483    pub fn new_checkpoint_signature_message(data: CheckpointSignatureMessage) -> Self {
484        let mut hasher = DefaultHasher::new();
485        data.summary.auth_sig().signature.hash(&mut hasher);
486        let tracking_id = hasher.finish().to_le_bytes();
487        Self {
488            tracking_id,
489            kind: ConsensusTransactionKind::CheckpointSignature(Box::new(data)),
490        }
491    }
492
493    pub fn new_end_of_publish(authority: AuthorityName) -> Self {
494        let mut hasher = DefaultHasher::new();
495        authority.hash(&mut hasher);
496        let tracking_id = hasher.finish().to_le_bytes();
497        Self {
498            tracking_id,
499            kind: ConsensusTransactionKind::EndOfPublish(authority),
500        }
501    }
502
503    pub fn new_capability_notification_v1(capabilities: AuthorityCapabilitiesV1) -> Self {
504        let mut hasher = DefaultHasher::new();
505        capabilities.hash(&mut hasher);
506        let tracking_id = hasher.finish().to_le_bytes();
507        Self {
508            tracking_id,
509            kind: ConsensusTransactionKind::CapabilityNotificationV1(capabilities),
510        }
511    }
512
513    pub fn new_signed_capability_notification_v1(
514        signed_capabilities: SignedAuthorityCapabilitiesV1,
515    ) -> Self {
516        let mut hasher = DefaultHasher::new();
517        signed_capabilities.data().hash(&mut hasher);
518        signed_capabilities.auth_sig().hash(&mut hasher);
519        let tracking_id = hasher.finish().to_le_bytes();
520        Self {
521            tracking_id,
522            kind: ConsensusTransactionKind::SignedCapabilityNotificationV1(signed_capabilities),
523        }
524    }
525
526    pub fn new_mysticeti_certificate(
527        round: u64,
528        offset: u64,
529        certificate: CertifiedTransaction,
530    ) -> Self {
531        let mut hasher = DefaultHasher::new();
532        let tx_digest = certificate.digest();
533        tx_digest.hash(&mut hasher);
534        round.hash(&mut hasher);
535        offset.hash(&mut hasher);
536        let tracking_id = hasher.finish().to_le_bytes();
537        Self {
538            tracking_id,
539            kind: ConsensusTransactionKind::CertifiedTransaction(Box::new(certificate)),
540        }
541    }
542
543    pub fn new_jwk_fetched(authority: AuthorityName, id: JwkId, jwk: JWK) -> Self {
544        let mut hasher = DefaultHasher::new();
545        id.hash(&mut hasher);
546        let tracking_id = hasher.finish().to_le_bytes();
547        Self {
548            tracking_id,
549            kind: ConsensusTransactionKind::NewJWKFetched(authority, id, jwk),
550        }
551    }
552
553    pub fn new_randomness_dkg_message(
554        authority: AuthorityName,
555        versioned_message: &VersionedDkgMessage,
556    ) -> Self {
557        let message =
558            bcs::to_bytes(versioned_message).expect("message serialization should not fail");
559        let mut hasher = DefaultHasher::new();
560        message.hash(&mut hasher);
561        let tracking_id = hasher.finish().to_le_bytes();
562        Self {
563            tracking_id,
564            kind: ConsensusTransactionKind::RandomnessDkgMessage(authority, message),
565        }
566    }
567    pub fn new_randomness_dkg_confirmation(
568        authority: AuthorityName,
569        versioned_confirmation: &VersionedDkgConfirmation,
570    ) -> Self {
571        let confirmation =
572            bcs::to_bytes(versioned_confirmation).expect("message serialization should not fail");
573        let mut hasher = DefaultHasher::new();
574        confirmation.hash(&mut hasher);
575        let tracking_id = hasher.finish().to_le_bytes();
576        Self {
577            tracking_id,
578            kind: ConsensusTransactionKind::RandomnessDkgConfirmation(authority, confirmation),
579        }
580    }
581
582    pub fn new_misbehavior_report(
583        authority: AuthorityName,
584        report: &VersionedMisbehaviorReport,
585        checkpoint_seq: u64,
586    ) -> Self {
587        let serialized_report =
588            bcs::to_bytes(report).expect("report serialization should not fail");
589        let mut hasher = DefaultHasher::new();
590        serialized_report.hash(&mut hasher);
591        let tracking_id = hasher.finish().to_le_bytes();
592        Self {
593            tracking_id,
594            kind: ConsensusTransactionKind::MisbehaviorReport(
595                authority,
596                report.clone(),
597                checkpoint_seq,
598            ),
599        }
600    }
601
602    pub fn get_tracking_id(&self) -> u64 {
603        (&self.tracking_id[..])
604            .read_u64::<BigEndian>()
605            .unwrap_or_default()
606    }
607
608    pub fn key(&self) -> ConsensusTransactionKey {
609        match &self.kind {
610            ConsensusTransactionKind::CertifiedTransaction(cert) => {
611                ConsensusTransactionKey::Certificate(*cert.digest())
612            }
613            ConsensusTransactionKind::CheckpointSignature(data) => {
614                ConsensusTransactionKey::CheckpointSignature(
615                    data.summary.auth_sig().authority,
616                    data.summary.sequence_number,
617                )
618            }
619            ConsensusTransactionKind::EndOfPublish(authority) => {
620                ConsensusTransactionKey::EndOfPublish(*authority)
621            }
622            ConsensusTransactionKind::CapabilityNotificationV1(cap) => {
623                ConsensusTransactionKey::CapabilityNotification(cap.authority, cap.generation)
624            }
625            ConsensusTransactionKind::SignedCapabilityNotificationV1(signed_cap) => {
626                ConsensusTransactionKey::CapabilityNotification(
627                    signed_cap.authority,
628                    signed_cap.generation,
629                )
630            }
631
632            ConsensusTransactionKind::NewJWKFetched(authority, id, key) => {
633                ConsensusTransactionKey::NewJWKFetched(Box::new((
634                    *authority,
635                    id.clone(),
636                    key.clone(),
637                )))
638            }
639            ConsensusTransactionKind::RandomnessDkgMessage(authority, _) => {
640                ConsensusTransactionKey::RandomnessDkgMessage(*authority)
641            }
642            ConsensusTransactionKind::RandomnessDkgConfirmation(authority, _) => {
643                ConsensusTransactionKey::RandomnessDkgConfirmation(*authority)
644            }
645            ConsensusTransactionKind::MisbehaviorReport(_, report, _) => {
646                ConsensusTransactionKey::MisbehaviorReport(*report.digest())
647            }
648        }
649    }
650
651    pub fn is_user_certificate(&self) -> bool {
652        matches!(self.kind, ConsensusTransactionKind::CertifiedTransaction(_))
653    }
654
655    pub fn is_end_of_publish(&self) -> bool {
656        matches!(self.kind, ConsensusTransactionKind::EndOfPublish(_))
657    }
658}
659
660#[test]
661fn test_jwk_compatibility() {
662    // Ensure that the JWK and JwkId structs in fastcrypto do not change formats.
663    // If this test breaks DO NOT JUST UPDATE THE EXPECTED BYTES. Instead, add a
664    // local JWK or JwkId struct that mirrors the fastcrypto struct, use it in
665    // AuthenticatorStateUpdate, and add Into/From as necessary.
666    let jwk = JWK {
667        kty: "a".to_string(),
668        e: "b".to_string(),
669        n: "c".to_string(),
670        alg: "d".to_string(),
671    };
672
673    let expected_jwk_bytes = vec![1, 97, 1, 98, 1, 99, 1, 100];
674    let jwk_bcs = bcs::to_bytes(&jwk).unwrap();
675    assert_eq!(jwk_bcs, expected_jwk_bytes);
676
677    let id = JwkId {
678        iss: "abc".to_string(),
679        kid: "def".to_string(),
680    };
681
682    let expected_id_bytes = vec![3, 97, 98, 99, 3, 100, 101, 102];
683    let id_bcs = bcs::to_bytes(&id).unwrap();
684    assert_eq!(id_bcs, expected_id_bytes);
685}