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