1use 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_sdk_types::crypto::IntentScope;
17use once_cell::sync::OnceCell;
18use serde::{Deserialize, Serialize};
19use tracing::warn;
20
21use crate::{
22 base_types::{AuthorityName, ConciseableName, ObjectRef, TransactionDigest},
23 crypto::{AuthoritySignature, DefaultHash, default_hash},
24 digests::{Digest, MisbehaviorReportDigest},
25 message_envelope::{Envelope, Message, VerifiedEnvelope},
26 messages_checkpoint::{CheckpointSequenceNumber, CheckpointSignatureMessage},
27 supported_protocol_versions::{
28 Chain, SupportedProtocolVersions, SupportedProtocolVersionsWithHashes,
29 },
30 transaction::{CertifiedTransaction, Transaction},
31};
32
33#[derive(Serialize, Deserialize, Clone, Debug)]
34pub struct ConsensusTransaction {
35 pub tracking_id: [u8; 8],
39 pub kind: ConsensusTransactionKind,
40}
41
42#[derive(Serialize, Deserialize, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)]
43pub enum ConsensusTransactionKey {
44 Certificate(TransactionDigest),
45 CheckpointSignature(AuthorityName, CheckpointSequenceNumber),
46 EndOfPublish(AuthorityName),
47 CapabilityNotification(AuthorityName, u64 ),
48 #[deprecated(note = "Authenticator state (JWK) is deprecated and was never enabled on IOTA")]
49 NewJWKFetchedDeprecated,
50 RandomnessDkgMessage(AuthorityName),
51 RandomnessDkgConfirmation(AuthorityName),
52 MisbehaviorReport(
53 AuthorityName,
54 MisbehaviorReportDigest,
55 CheckpointSequenceNumber,
56 ),
57 UserTransaction(TransactionDigest),
59 OverloadNotificationV1(AuthorityName, u64 ),
60 }
63
64impl Debug for ConsensusTransactionKey {
65 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
66 match self {
67 Self::Certificate(digest) => write!(f, "Certificate({digest})"),
68 Self::CheckpointSignature(name, seq) => {
69 write!(f, "CheckpointSignature({:?}, {:?})", name.concise(), seq)
70 }
71 Self::EndOfPublish(name) => write!(f, "EndOfPublish({:?})", name.concise()),
72 Self::CapabilityNotification(name, generation) => write!(
73 f,
74 "CapabilityNotification({:?}, {:?})",
75 name.concise(),
76 generation
77 ),
78 #[allow(deprecated)]
79 Self::NewJWKFetchedDeprecated => {
80 write!(
81 f,
82 "NewJWKFetched(deprecated: Authenticator state (JWK) is deprecated and was never enabled on IOTA)"
83 )
84 }
85 Self::RandomnessDkgMessage(name) => {
86 write!(f, "RandomnessDkgMessage({:?})", name.concise())
87 }
88 Self::RandomnessDkgConfirmation(name) => {
89 write!(f, "RandomnessDkgConfirmation({:?})", name.concise())
90 }
91 Self::MisbehaviorReport(name, digest, checkpoint_seq) => {
92 write!(
93 f,
94 "MisbehaviorReport({:?}, {:?}, {:?})",
95 name.concise(),
96 digest,
97 checkpoint_seq
98 )
99 }
100 Self::UserTransaction(digest) => write!(f, "UserTransaction({digest:?})"),
101 Self::OverloadNotificationV1(name, generation) => {
102 write!(
103 f,
104 "OverloadNotificationV1({:?}, gen={generation:?})",
105 name.concise()
106 )
107 }
108 }
109 }
110}
111
112pub type SignedAuthorityCapabilitiesV1 = Envelope<AuthorityCapabilitiesV1, AuthoritySignature>;
113
114pub type VerifiedAuthorityCapabilitiesV1 =
115 VerifiedEnvelope<AuthorityCapabilitiesV1, AuthoritySignature>;
116
117#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
118pub struct AuthorityCapabilitiesDigest(Digest);
119
120impl AuthorityCapabilitiesDigest {
121 pub const fn new(digest: [u8; 32]) -> Self {
122 Self(Digest::new(digest))
123 }
124}
125
126impl Debug for AuthorityCapabilitiesDigest {
127 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
128 f.debug_tuple("AuthorityCapabilitiesDigest")
129 .field(&self.0)
130 .finish()
131 }
132}
133
134#[derive(Serialize, Deserialize, Clone, Hash)]
137pub struct AuthorityCapabilitiesV1 {
138 pub authority: AuthorityName,
141 pub generation: u64,
148
149 pub supported_protocol_versions: SupportedProtocolVersionsWithHashes,
152
153 pub available_system_packages: Vec<ObjectRef>,
157}
158
159impl Message for AuthorityCapabilitiesV1 {
160 type DigestType = AuthorityCapabilitiesDigest;
161 const SCOPE: IntentScope = IntentScope::AuthorityCapabilities;
162
163 fn digest(&self) -> Self::DigestType {
164 let mut hasher = DefaultHash::new();
166 let serialized = bcs::to_bytes(&self).expect("BCS should not fail");
167 hasher.update(&serialized);
168 AuthorityCapabilitiesDigest::new(<[u8; 32]>::from(hasher.finalize()))
169 }
170}
171
172impl Debug for AuthorityCapabilitiesV1 {
173 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
174 f.debug_struct("AuthorityCapabilities")
175 .field("authority", &self.authority.concise())
176 .field("generation", &self.generation)
177 .field(
178 "supported_protocol_versions",
179 &self.supported_protocol_versions,
180 )
181 .field("available_system_packages", &self.available_system_packages)
182 .finish()
183 }
184}
185
186impl AuthorityCapabilitiesV1 {
187 pub fn new(
188 authority: AuthorityName,
189 chain: Chain,
190 supported_protocol_versions: SupportedProtocolVersions,
191 available_system_packages: Vec<ObjectRef>,
192 ) -> Self {
193 let generation = SystemTime::now()
194 .duration_since(UNIX_EPOCH)
195 .expect("IOTA did not exist prior to 1970")
196 .as_millis()
197 .try_into()
198 .expect("This build of iota is not supported in the year 500,000,000");
199 Self {
200 authority,
201 generation,
202 supported_protocol_versions:
203 SupportedProtocolVersionsWithHashes::from_supported_versions(
204 supported_protocol_versions,
205 chain,
206 ),
207 available_system_packages,
208 }
209 }
210}
211
212impl SignedAuthorityCapabilitiesV1 {
213 pub fn cache_digest(&self, epoch: u64) -> AuthorityCapabilitiesDigest {
214 let data_with_epoch = (self.data(), epoch);
216
217 let mut hasher = DefaultHash::new();
219 let serialized = bcs::to_bytes(&data_with_epoch).expect("BCS should not fail");
220 hasher.update(&serialized);
221 AuthorityCapabilitiesDigest::new(<[u8; 32]>::from(hasher.finalize()))
222 }
223}
224
225#[derive(Serialize, Deserialize, Clone, Debug)]
226pub enum ConsensusTransactionKind {
227 CertifiedTransaction(Box<CertifiedTransaction>),
228 CheckpointSignature(Box<CheckpointSignatureMessage>),
229 EndOfPublish(AuthorityName),
230
231 CapabilityNotificationV1(AuthorityCapabilitiesV1),
232 SignedCapabilityNotificationV1(SignedAuthorityCapabilitiesV1),
233
234 #[deprecated(note = "Authenticator state (JWK) is deprecated and was never enabled on IOTA")]
235 NewJWKFetchedDeprecated,
236
237 RandomnessDkgMessage(AuthorityName, Vec<u8>),
241 RandomnessDkgConfirmation(AuthorityName, Vec<u8>),
245 MisbehaviorReport(VersionedMisbehaviorReport),
246 UserTransactionV1(Box<Transaction>),
250 OverloadNotificationV1(
251 AuthorityName,
252 u64, u8, ),
255 }
258
259impl ConsensusTransactionKind {
260 pub fn is_dkg(&self) -> bool {
261 matches!(
262 self,
263 ConsensusTransactionKind::RandomnessDkgMessage(_, _)
264 | ConsensusTransactionKind::RandomnessDkgConfirmation(_, _)
265 )
266 }
267
268 pub fn is_user_transaction(&self) -> bool {
269 matches!(self, ConsensusTransactionKind::UserTransactionV1(_))
270 }
271}
272
273#[derive(Debug, Clone, Serialize, Deserialize)]
283pub struct VersionedMisbehaviorReport {
284 pub authority: AuthorityName,
287 pub payload: MisbehaviorObservations,
289 pub generation: u64,
293 #[serde(skip)]
294 digest: OnceCell<MisbehaviorReportDigest>,
295}
296
297#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
303pub enum MisbehaviorObservations {
304 V1(MisbehaviorObservationsV1),
305}
306
307impl VersionedMisbehaviorReport {
308 pub fn new_v1(
309 authority: AuthorityName,
310 generation: u64,
311 observations: MisbehaviorObservationsV1,
312 ) -> Self {
313 Self {
314 authority,
315 payload: MisbehaviorObservations::V1(observations),
316 generation,
317 digest: OnceCell::new(),
318 }
319 }
320
321 pub fn digest(&self) -> &MisbehaviorReportDigest {
324 self.digest
325 .get_or_init(|| MisbehaviorReportDigest::new(default_hash(self)))
326 }
327
328 pub fn summary(&self) -> u64 {
331 let summary = match &self.payload {
332 MisbehaviorObservations::V1(report) => [
333 &report.faulty_blocks_provable,
334 &report.faulty_blocks_unprovable,
335 &report.missing_proposals,
336 &report.equivocations,
337 ]
338 .into_iter()
339 .flatten()
340 .fold(0u64, |acc, metric| acc.saturating_add(*metric)),
341 };
342 if summary == u64::MAX {
343 warn!("MisbehaviorReport summary reached its maximum value.");
344 }
345 summary
346 }
347}
348
349#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
355pub struct MisbehaviorObservationsV1 {
356 pub faulty_blocks_provable: Vec<u64>,
357 pub faulty_blocks_unprovable: Vec<u64>,
358 pub missing_proposals: Vec<u64>,
359 pub equivocations: Vec<u64>,
360}
361
362impl MisbehaviorObservationsV1 {
363 pub fn verify(&self, committee_size: usize) -> bool {
364 if (self.faulty_blocks_provable.len() != committee_size)
372 || (self.faulty_blocks_unprovable.len() != committee_size)
373 || (self.equivocations.len() != committee_size)
374 || (self.missing_proposals.len() != committee_size)
375 {
376 return false;
377 }
378 true
379 }
380}
381
382#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
383pub enum VersionedDkgMessage {
384 V1(dkg_v1::Message<bls12381::G2Element, bls12381::G2Element>),
385}
386
387#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
388pub enum VersionedDkgConfirmation {
389 V1(dkg_v1::Confirmation<bls12381::G2Element>),
390}
391
392impl Debug for VersionedDkgMessage {
393 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
394 match self {
395 VersionedDkgMessage::V1(msg) => write!(
396 f,
397 "DKG V1 Message with sender={}, vss_pk.degree={}, encrypted_shares.len()={}",
398 msg.sender,
399 msg.vss_pk.degree(),
400 msg.encrypted_shares.len(),
401 ),
402 }
403 }
404}
405
406impl VersionedDkgMessage {
407 pub fn sender(&self) -> u16 {
408 match self {
409 VersionedDkgMessage::V1(msg) => msg.sender,
410 }
411 }
412
413 pub fn create(
414 dkg_version: u64,
415 party: Arc<dkg_v1::Party<bls12381::G2Element, bls12381::G2Element>>,
416 ) -> FastCryptoResult<VersionedDkgMessage> {
417 assert_eq!(dkg_version, 1, "BUG: invalid DKG version");
418 let msg = party.create_message(&mut rand::thread_rng())?;
419 Ok(VersionedDkgMessage::V1(msg))
420 }
421
422 pub fn unwrap_v1(self) -> dkg_v1::Message<bls12381::G2Element, bls12381::G2Element> {
423 match self {
424 VersionedDkgMessage::V1(msg) => msg,
425 }
426 }
427
428 pub fn is_valid_version(&self, dkg_version: u64) -> bool {
429 matches!((self, dkg_version), (VersionedDkgMessage::V1(_), 1))
430 }
431}
432
433impl VersionedDkgConfirmation {
434 pub fn sender(&self) -> u16 {
435 match self {
436 VersionedDkgConfirmation::V1(msg) => msg.sender,
437 }
438 }
439
440 pub fn num_of_complaints(&self) -> usize {
441 match self {
442 VersionedDkgConfirmation::V1(msg) => msg.complaints.len(),
443 }
444 }
445
446 pub fn unwrap_v1(&self) -> &dkg_v1::Confirmation<bls12381::G2Element> {
447 match self {
448 VersionedDkgConfirmation::V1(msg) => msg,
449 }
450 }
451
452 pub fn is_valid_version(&self, dkg_version: u64) -> bool {
453 matches!((self, dkg_version), (VersionedDkgConfirmation::V1(_), 1))
454 }
455}
456
457impl ConsensusTransaction {
458 pub fn new_certificate_message(
459 authority: &AuthorityName,
460 certificate: CertifiedTransaction,
461 ) -> Self {
462 let mut hasher = DefaultHasher::new();
463 let tx_digest = certificate.digest();
464 tx_digest.hash(&mut hasher);
465 authority.hash(&mut hasher);
466 let tracking_id = hasher.finish().to_le_bytes();
467 Self {
468 tracking_id,
469 kind: ConsensusTransactionKind::CertifiedTransaction(Box::new(certificate)),
470 }
471 }
472
473 pub fn new_checkpoint_signature_message(data: CheckpointSignatureMessage) -> Self {
474 let mut hasher = DefaultHasher::new();
475 data.summary.auth_sig().signature.hash(&mut hasher);
476 let tracking_id = hasher.finish().to_le_bytes();
477 Self {
478 tracking_id,
479 kind: ConsensusTransactionKind::CheckpointSignature(Box::new(data)),
480 }
481 }
482
483 pub fn new_end_of_publish(authority: AuthorityName) -> Self {
484 let mut hasher = DefaultHasher::new();
485 authority.hash(&mut hasher);
486 let tracking_id = hasher.finish().to_le_bytes();
487 Self {
488 tracking_id,
489 kind: ConsensusTransactionKind::EndOfPublish(authority),
490 }
491 }
492
493 pub fn new_capability_notification_v1(capabilities: AuthorityCapabilitiesV1) -> Self {
494 let mut hasher = DefaultHasher::new();
495 capabilities.hash(&mut hasher);
496 let tracking_id = hasher.finish().to_le_bytes();
497 Self {
498 tracking_id,
499 kind: ConsensusTransactionKind::CapabilityNotificationV1(capabilities),
500 }
501 }
502
503 pub fn new_signed_capability_notification_v1(
504 signed_capabilities: SignedAuthorityCapabilitiesV1,
505 ) -> Self {
506 let mut hasher = DefaultHasher::new();
507 signed_capabilities.data().hash(&mut hasher);
508 signed_capabilities.auth_sig().hash(&mut hasher);
509 let tracking_id = hasher.finish().to_le_bytes();
510 Self {
511 tracking_id,
512 kind: ConsensusTransactionKind::SignedCapabilityNotificationV1(signed_capabilities),
513 }
514 }
515
516 pub fn new_randomness_dkg_message(
517 authority: AuthorityName,
518 versioned_message: &VersionedDkgMessage,
519 ) -> Self {
520 let message =
521 bcs::to_bytes(versioned_message).expect("message serialization should not fail");
522 let mut hasher = DefaultHasher::new();
523 message.hash(&mut hasher);
524 let tracking_id = hasher.finish().to_le_bytes();
525 Self {
526 tracking_id,
527 kind: ConsensusTransactionKind::RandomnessDkgMessage(authority, message),
528 }
529 }
530 pub fn new_randomness_dkg_confirmation(
531 authority: AuthorityName,
532 versioned_confirmation: &VersionedDkgConfirmation,
533 ) -> Self {
534 let confirmation =
535 bcs::to_bytes(versioned_confirmation).expect("message serialization should not fail");
536 let mut hasher = DefaultHasher::new();
537 confirmation.hash(&mut hasher);
538 let tracking_id = hasher.finish().to_le_bytes();
539 Self {
540 tracking_id,
541 kind: ConsensusTransactionKind::RandomnessDkgConfirmation(authority, confirmation),
542 }
543 }
544
545 pub fn new_misbehavior_report(report: VersionedMisbehaviorReport) -> Self {
546 let serialized_report =
547 bcs::to_bytes(&report).expect("report serialization should not fail");
548 let mut hasher = DefaultHasher::new();
549 serialized_report.hash(&mut hasher);
550 let tracking_id = hasher.finish().to_le_bytes();
551 Self {
552 tracking_id,
553 kind: ConsensusTransactionKind::MisbehaviorReport(report),
554 }
555 }
556
557 pub fn new_user_transaction(transaction: Transaction) -> Self {
558 let mut hasher = DefaultHasher::new();
559 let tx_digest = transaction.digest();
560 tx_digest.hash(&mut hasher);
561 let tracking_id = hasher.finish().to_le_bytes();
562 Self {
563 tracking_id,
564 kind: ConsensusTransactionKind::UserTransactionV1(Box::new(transaction)),
565 }
566 }
567
568 pub fn new_overload_notification_v1(
569 authority: AuthorityName,
570 load_shedding_percentage: u8,
571 ) -> Self {
572 let generation: u64 = SystemTime::now()
579 .duration_since(UNIX_EPOCH)
580 .expect("IOTA did not exist prior to 1970")
581 .as_millis()
582 .try_into()
583 .expect("This build of iota is not supported in the year 500,000,000");
584 let mut hasher = DefaultHasher::new();
585 authority.hash(&mut hasher);
586 generation.hash(&mut hasher);
587 load_shedding_percentage.hash(&mut hasher);
588 let tracking_id = hasher.finish().to_le_bytes();
589 Self {
590 tracking_id,
591 kind: ConsensusTransactionKind::OverloadNotificationV1(
592 authority,
593 generation,
594 load_shedding_percentage,
595 ),
596 }
597 }
598
599 pub fn get_tracking_id(&self) -> u64 {
600 (&self.tracking_id[..])
601 .read_u64::<BigEndian>()
602 .unwrap_or_default()
603 }
604
605 pub fn key(&self) -> ConsensusTransactionKey {
606 match &self.kind {
607 ConsensusTransactionKind::CertifiedTransaction(cert) => {
608 ConsensusTransactionKey::Certificate(*cert.digest())
609 }
610 ConsensusTransactionKind::CheckpointSignature(data) => {
611 ConsensusTransactionKey::CheckpointSignature(
612 data.summary.auth_sig().authority,
613 data.summary.sequence_number,
614 )
615 }
616 ConsensusTransactionKind::EndOfPublish(authority) => {
617 ConsensusTransactionKey::EndOfPublish(*authority)
618 }
619 ConsensusTransactionKind::CapabilityNotificationV1(cap) => {
620 ConsensusTransactionKey::CapabilityNotification(cap.authority, cap.generation)
621 }
622 ConsensusTransactionKind::SignedCapabilityNotificationV1(signed_cap) => {
623 ConsensusTransactionKey::CapabilityNotification(
624 signed_cap.authority,
625 signed_cap.generation,
626 )
627 }
628
629 #[allow(deprecated)]
630 ConsensusTransactionKind::NewJWKFetchedDeprecated => {
631 ConsensusTransactionKey::NewJWKFetchedDeprecated
632 }
633 ConsensusTransactionKind::RandomnessDkgMessage(authority, _) => {
634 ConsensusTransactionKey::RandomnessDkgMessage(*authority)
635 }
636 ConsensusTransactionKind::RandomnessDkgConfirmation(authority, _) => {
637 ConsensusTransactionKey::RandomnessDkgConfirmation(*authority)
638 }
639 ConsensusTransactionKind::MisbehaviorReport(report) => {
640 ConsensusTransactionKey::MisbehaviorReport(
641 report.authority,
642 *report.digest(),
643 report.generation,
644 )
645 }
646 ConsensusTransactionKind::UserTransactionV1(tx) => {
647 ConsensusTransactionKey::UserTransaction(*tx.digest())
648 }
649 ConsensusTransactionKind::OverloadNotificationV1(authority, generation, _) => {
650 ConsensusTransactionKey::OverloadNotificationV1(*authority, *generation)
651 }
652 }
653 }
654
655 pub fn is_user_certificate(&self) -> bool {
656 matches!(self.kind, ConsensusTransactionKind::CertifiedTransaction(_))
657 }
658
659 pub fn is_end_of_publish(&self) -> bool {
660 matches!(self.kind, ConsensusTransactionKind::EndOfPublish(_))
661 }
662}
663
664#[cfg(test)]
665mod tests {
666 use super::*;
667
668 #[derive(Serialize)]
672 struct LegacyVersionedMisbehaviorReport<'a> {
673 payload: &'a MisbehaviorObservations,
674 }
675
676 fn sample_payload() -> MisbehaviorObservations {
677 MisbehaviorObservations::V1(MisbehaviorObservationsV1 {
678 faulty_blocks_provable: vec![1, 2, 3],
679 faulty_blocks_unprovable: vec![4, 5, 6],
680 missing_proposals: vec![7, 8, 9],
681 equivocations: vec![10, 11, 12],
682 })
683 }
684
685 #[test]
693 fn misbehavior_report_wire_format_unchanged() {
694 let authority = AuthorityName::default();
695 let generation: u64 = 42;
696 let payload = sample_payload();
697
698 let legacy_bytes = bcs::to_bytes(&(
699 authority,
700 LegacyVersionedMisbehaviorReport { payload: &payload },
701 generation,
702 ))
703 .unwrap();
704
705 let new = VersionedMisbehaviorReport {
706 authority,
707 payload,
708 generation,
709 digest: OnceCell::new(),
710 };
711 let new_bytes = bcs::to_bytes(&new).unwrap();
712
713 assert_eq!(
714 legacy_bytes, new_bytes,
715 "VersionedMisbehaviorReport wire format must not change — testnet is live"
716 );
717 }
718
719 #[test]
726 fn misbehavior_report_consensus_kind_wire_format_unchanged() {
727 let authority = AuthorityName::default();
728 let generation: u64 = 7;
729 let payload = sample_payload();
730
731 let new_kind = ConsensusTransactionKind::MisbehaviorReport(VersionedMisbehaviorReport {
732 authority,
733 payload: payload.clone(),
734 generation,
735 digest: OnceCell::new(),
736 });
737 let new_bytes = bcs::to_bytes(&new_kind).unwrap();
738
739 let mut legacy_bytes = vec![8u8];
742 legacy_bytes.extend(
743 bcs::to_bytes(&(
744 authority,
745 LegacyVersionedMisbehaviorReport { payload: &payload },
746 generation,
747 ))
748 .unwrap(),
749 );
750
751 assert_eq!(
752 legacy_bytes, new_bytes,
753 "ConsensusTransactionKind::MisbehaviorReport wire format must not change — testnet is live"
754 );
755 }
756}