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