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,
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 }
60
61impl Debug for ConsensusTransactionKey {
62 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
63 match self {
64 Self::Certificate(digest) => write!(f, "Certificate({digest})"),
65 Self::CheckpointSignature(name, seq) => {
66 write!(f, "CheckpointSignature({:?}, {:?})", name.concise(), seq)
67 }
68 Self::EndOfPublish(name) => write!(f, "EndOfPublish({:?})", name.concise()),
69 Self::CapabilityNotification(name, generation) => write!(
70 f,
71 "CapabilityNotification({:?}, {:?})",
72 name.concise(),
73 generation
74 ),
75 #[allow(deprecated)]
76 Self::NewJWKFetchedDeprecated => {
77 write!(
78 f,
79 "NewJWKFetched(deprecated: Authenticator state (JWK) is deprecated and was never enabled on IOTA)"
80 )
81 }
82 Self::RandomnessDkgMessage(name) => {
83 write!(f, "RandomnessDkgMessage({:?})", name.concise())
84 }
85 Self::RandomnessDkgConfirmation(name) => {
86 write!(f, "RandomnessDkgConfirmation({:?})", name.concise())
87 }
88 Self::MisbehaviorReport(name, digest, checkpoint_seq) => {
89 write!(
90 f,
91 "MisbehaviorReport({:?}, {:?}, {:?})",
92 name.concise(),
93 digest,
94 checkpoint_seq
95 )
96 }
97 }
98 }
99}
100
101pub type SignedAuthorityCapabilitiesV1 = Envelope<AuthorityCapabilitiesV1, AuthoritySignature>;
102
103pub type VerifiedAuthorityCapabilitiesV1 =
104 VerifiedEnvelope<AuthorityCapabilitiesV1, AuthoritySignature>;
105
106#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
107pub struct AuthorityCapabilitiesDigest(Digest);
108
109impl AuthorityCapabilitiesDigest {
110 pub const fn new(digest: [u8; 32]) -> Self {
111 Self(Digest::new(digest))
112 }
113}
114
115impl Debug for AuthorityCapabilitiesDigest {
116 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
117 f.debug_tuple("AuthorityCapabilitiesDigest")
118 .field(&self.0)
119 .finish()
120 }
121}
122
123#[derive(Serialize, Deserialize, Clone, Hash)]
126pub struct AuthorityCapabilitiesV1 {
127 pub authority: AuthorityName,
130 pub generation: u64,
137
138 pub supported_protocol_versions: SupportedProtocolVersionsWithHashes,
141
142 pub available_system_packages: Vec<ObjectRef>,
146}
147
148impl Message for AuthorityCapabilitiesV1 {
149 type DigestType = AuthorityCapabilitiesDigest;
150 const SCOPE: IntentScope = IntentScope::AuthorityCapabilities;
151
152 fn digest(&self) -> Self::DigestType {
153 let mut hasher = DefaultHash::new();
155 let serialized = bcs::to_bytes(&self).expect("BCS should not fail");
156 hasher.update(&serialized);
157 AuthorityCapabilitiesDigest::new(<[u8; 32]>::from(hasher.finalize()))
158 }
159}
160
161impl Debug for AuthorityCapabilitiesV1 {
162 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
163 f.debug_struct("AuthorityCapabilities")
164 .field("authority", &self.authority.concise())
165 .field("generation", &self.generation)
166 .field(
167 "supported_protocol_versions",
168 &self.supported_protocol_versions,
169 )
170 .field("available_system_packages", &self.available_system_packages)
171 .finish()
172 }
173}
174
175impl AuthorityCapabilitiesV1 {
176 pub fn new(
177 authority: AuthorityName,
178 chain: Chain,
179 supported_protocol_versions: SupportedProtocolVersions,
180 available_system_packages: Vec<ObjectRef>,
181 ) -> Self {
182 let generation = SystemTime::now()
183 .duration_since(UNIX_EPOCH)
184 .expect("IOTA did not exist prior to 1970")
185 .as_millis()
186 .try_into()
187 .expect("This build of iota is not supported in the year 500,000,000");
188 Self {
189 authority,
190 generation,
191 supported_protocol_versions:
192 SupportedProtocolVersionsWithHashes::from_supported_versions(
193 supported_protocol_versions,
194 chain,
195 ),
196 available_system_packages,
197 }
198 }
199}
200
201impl SignedAuthorityCapabilitiesV1 {
202 pub fn cache_digest(&self, epoch: u64) -> AuthorityCapabilitiesDigest {
203 let data_with_epoch = (self.data(), epoch);
205
206 let mut hasher = DefaultHash::new();
208 let serialized = bcs::to_bytes(&data_with_epoch).expect("BCS should not fail");
209 hasher.update(&serialized);
210 AuthorityCapabilitiesDigest::new(<[u8; 32]>::from(hasher.finalize()))
211 }
212}
213
214#[derive(Serialize, Deserialize, Clone, Debug)]
215pub enum ConsensusTransactionKind {
216 CertifiedTransaction(Box<CertifiedTransaction>),
217 CheckpointSignature(Box<CheckpointSignatureMessage>),
218 EndOfPublish(AuthorityName),
219
220 CapabilityNotificationV1(AuthorityCapabilitiesV1),
221 SignedCapabilityNotificationV1(SignedAuthorityCapabilitiesV1),
222
223 #[deprecated(note = "Authenticator state (JWK) is deprecated and was never enabled on IOTA")]
224 NewJWKFetchedDeprecated,
225
226 RandomnessDkgMessage(AuthorityName, Vec<u8>),
230 RandomnessDkgConfirmation(AuthorityName, Vec<u8>),
234 MisbehaviorReport(VersionedMisbehaviorReport),
235 }
238
239impl ConsensusTransactionKind {
240 pub fn is_dkg(&self) -> bool {
241 matches!(
242 self,
243 ConsensusTransactionKind::RandomnessDkgMessage(_, _)
244 | ConsensusTransactionKind::RandomnessDkgConfirmation(_, _)
245 )
246 }
247}
248
249#[derive(Debug, Clone, Serialize, Deserialize)]
259pub struct VersionedMisbehaviorReport {
260 pub authority: AuthorityName,
263 pub payload: MisbehaviorObservations,
265 pub generation: u64,
269 #[serde(skip)]
270 digest: OnceCell<MisbehaviorReportDigest>,
271}
272
273#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
279pub enum MisbehaviorObservations {
280 V1(MisbehaviorObservationsV1),
281}
282
283impl VersionedMisbehaviorReport {
284 pub fn new_v1(
285 authority: AuthorityName,
286 generation: u64,
287 observations: MisbehaviorObservationsV1,
288 ) -> Self {
289 Self {
290 authority,
291 payload: MisbehaviorObservations::V1(observations),
292 generation,
293 digest: OnceCell::new(),
294 }
295 }
296
297 pub fn digest(&self) -> &MisbehaviorReportDigest {
300 self.digest
301 .get_or_init(|| MisbehaviorReportDigest::new(default_hash(self)))
302 }
303
304 pub fn summary(&self) -> u64 {
307 let summary = match &self.payload {
308 MisbehaviorObservations::V1(report) => [
309 &report.faulty_blocks_provable,
310 &report.faulty_blocks_unprovable,
311 &report.missing_proposals,
312 &report.equivocations,
313 ]
314 .into_iter()
315 .flatten()
316 .fold(0u64, |acc, metric| acc.saturating_add(*metric)),
317 };
318 if summary == u64::MAX {
319 warn!("MisbehaviorReport summary reached its maximum value.");
320 }
321 summary
322 }
323}
324
325#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
331pub struct MisbehaviorObservationsV1 {
332 pub faulty_blocks_provable: Vec<u64>,
333 pub faulty_blocks_unprovable: Vec<u64>,
334 pub missing_proposals: Vec<u64>,
335 pub equivocations: Vec<u64>,
336}
337
338impl MisbehaviorObservationsV1 {
339 pub fn verify(&self, committee_size: usize) -> bool {
340 if (self.faulty_blocks_provable.len() != committee_size)
348 || (self.faulty_blocks_unprovable.len() != committee_size)
349 || (self.equivocations.len() != committee_size)
350 || (self.missing_proposals.len() != committee_size)
351 {
352 return false;
353 }
354 true
355 }
356}
357
358#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
359pub enum VersionedDkgMessage {
360 V1(dkg_v1::Message<bls12381::G2Element, bls12381::G2Element>),
361}
362
363#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
364pub enum VersionedDkgConfirmation {
365 V1(dkg_v1::Confirmation<bls12381::G2Element>),
366}
367
368impl Debug for VersionedDkgMessage {
369 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
370 match self {
371 VersionedDkgMessage::V1(msg) => write!(
372 f,
373 "DKG V1 Message with sender={}, vss_pk.degree={}, encrypted_shares.len()={}",
374 msg.sender,
375 msg.vss_pk.degree(),
376 msg.encrypted_shares.len(),
377 ),
378 }
379 }
380}
381
382impl VersionedDkgMessage {
383 pub fn sender(&self) -> u16 {
384 match self {
385 VersionedDkgMessage::V1(msg) => msg.sender,
386 }
387 }
388
389 pub fn create(
390 dkg_version: u64,
391 party: Arc<dkg_v1::Party<bls12381::G2Element, bls12381::G2Element>>,
392 ) -> FastCryptoResult<VersionedDkgMessage> {
393 assert_eq!(dkg_version, 1, "BUG: invalid DKG version");
394 let msg = party.create_message(&mut rand::thread_rng())?;
395 Ok(VersionedDkgMessage::V1(msg))
396 }
397
398 pub fn unwrap_v1(self) -> dkg_v1::Message<bls12381::G2Element, bls12381::G2Element> {
399 match self {
400 VersionedDkgMessage::V1(msg) => msg,
401 }
402 }
403
404 pub fn is_valid_version(&self, dkg_version: u64) -> bool {
405 matches!((self, dkg_version), (VersionedDkgMessage::V1(_), 1))
406 }
407}
408
409impl VersionedDkgConfirmation {
410 pub fn sender(&self) -> u16 {
411 match self {
412 VersionedDkgConfirmation::V1(msg) => msg.sender,
413 }
414 }
415
416 pub fn num_of_complaints(&self) -> usize {
417 match self {
418 VersionedDkgConfirmation::V1(msg) => msg.complaints.len(),
419 }
420 }
421
422 pub fn unwrap_v1(&self) -> &dkg_v1::Confirmation<bls12381::G2Element> {
423 match self {
424 VersionedDkgConfirmation::V1(msg) => msg,
425 }
426 }
427
428 pub fn is_valid_version(&self, dkg_version: u64) -> bool {
429 matches!((self, dkg_version), (VersionedDkgConfirmation::V1(_), 1))
430 }
431}
432
433impl ConsensusTransaction {
434 pub fn new_certificate_message(
435 authority: &AuthorityName,
436 certificate: CertifiedTransaction,
437 ) -> Self {
438 let mut hasher = DefaultHasher::new();
439 let tx_digest = certificate.digest();
440 tx_digest.hash(&mut hasher);
441 authority.hash(&mut hasher);
442 let tracking_id = hasher.finish().to_le_bytes();
443 Self {
444 tracking_id,
445 kind: ConsensusTransactionKind::CertifiedTransaction(Box::new(certificate)),
446 }
447 }
448
449 pub fn new_checkpoint_signature_message(data: CheckpointSignatureMessage) -> Self {
450 let mut hasher = DefaultHasher::new();
451 data.summary.auth_sig().signature.hash(&mut hasher);
452 let tracking_id = hasher.finish().to_le_bytes();
453 Self {
454 tracking_id,
455 kind: ConsensusTransactionKind::CheckpointSignature(Box::new(data)),
456 }
457 }
458
459 pub fn new_end_of_publish(authority: AuthorityName) -> Self {
460 let mut hasher = DefaultHasher::new();
461 authority.hash(&mut hasher);
462 let tracking_id = hasher.finish().to_le_bytes();
463 Self {
464 tracking_id,
465 kind: ConsensusTransactionKind::EndOfPublish(authority),
466 }
467 }
468
469 pub fn new_capability_notification_v1(capabilities: AuthorityCapabilitiesV1) -> Self {
470 let mut hasher = DefaultHasher::new();
471 capabilities.hash(&mut hasher);
472 let tracking_id = hasher.finish().to_le_bytes();
473 Self {
474 tracking_id,
475 kind: ConsensusTransactionKind::CapabilityNotificationV1(capabilities),
476 }
477 }
478
479 pub fn new_signed_capability_notification_v1(
480 signed_capabilities: SignedAuthorityCapabilitiesV1,
481 ) -> Self {
482 let mut hasher = DefaultHasher::new();
483 signed_capabilities.data().hash(&mut hasher);
484 signed_capabilities.auth_sig().hash(&mut hasher);
485 let tracking_id = hasher.finish().to_le_bytes();
486 Self {
487 tracking_id,
488 kind: ConsensusTransactionKind::SignedCapabilityNotificationV1(signed_capabilities),
489 }
490 }
491
492 pub fn new_randomness_dkg_message(
493 authority: AuthorityName,
494 versioned_message: &VersionedDkgMessage,
495 ) -> Self {
496 let message =
497 bcs::to_bytes(versioned_message).expect("message serialization should not fail");
498 let mut hasher = DefaultHasher::new();
499 message.hash(&mut hasher);
500 let tracking_id = hasher.finish().to_le_bytes();
501 Self {
502 tracking_id,
503 kind: ConsensusTransactionKind::RandomnessDkgMessage(authority, message),
504 }
505 }
506 pub fn new_randomness_dkg_confirmation(
507 authority: AuthorityName,
508 versioned_confirmation: &VersionedDkgConfirmation,
509 ) -> Self {
510 let confirmation =
511 bcs::to_bytes(versioned_confirmation).expect("message serialization should not fail");
512 let mut hasher = DefaultHasher::new();
513 confirmation.hash(&mut hasher);
514 let tracking_id = hasher.finish().to_le_bytes();
515 Self {
516 tracking_id,
517 kind: ConsensusTransactionKind::RandomnessDkgConfirmation(authority, confirmation),
518 }
519 }
520
521 pub fn new_misbehavior_report(report: VersionedMisbehaviorReport) -> Self {
522 let serialized_report =
523 bcs::to_bytes(&report).expect("report serialization should not fail");
524 let mut hasher = DefaultHasher::new();
525 serialized_report.hash(&mut hasher);
526 let tracking_id = hasher.finish().to_le_bytes();
527 Self {
528 tracking_id,
529 kind: ConsensusTransactionKind::MisbehaviorReport(report),
530 }
531 }
532
533 pub fn get_tracking_id(&self) -> u64 {
534 (&self.tracking_id[..])
535 .read_u64::<BigEndian>()
536 .unwrap_or_default()
537 }
538
539 pub fn key(&self) -> ConsensusTransactionKey {
540 match &self.kind {
541 ConsensusTransactionKind::CertifiedTransaction(cert) => {
542 ConsensusTransactionKey::Certificate(*cert.digest())
543 }
544 ConsensusTransactionKind::CheckpointSignature(data) => {
545 ConsensusTransactionKey::CheckpointSignature(
546 data.summary.auth_sig().authority,
547 data.summary.sequence_number,
548 )
549 }
550 ConsensusTransactionKind::EndOfPublish(authority) => {
551 ConsensusTransactionKey::EndOfPublish(*authority)
552 }
553 ConsensusTransactionKind::CapabilityNotificationV1(cap) => {
554 ConsensusTransactionKey::CapabilityNotification(cap.authority, cap.generation)
555 }
556 ConsensusTransactionKind::SignedCapabilityNotificationV1(signed_cap) => {
557 ConsensusTransactionKey::CapabilityNotification(
558 signed_cap.authority,
559 signed_cap.generation,
560 )
561 }
562
563 #[allow(deprecated)]
564 ConsensusTransactionKind::NewJWKFetchedDeprecated => {
565 ConsensusTransactionKey::NewJWKFetchedDeprecated
566 }
567 ConsensusTransactionKind::RandomnessDkgMessage(authority, _) => {
568 ConsensusTransactionKey::RandomnessDkgMessage(*authority)
569 }
570 ConsensusTransactionKind::RandomnessDkgConfirmation(authority, _) => {
571 ConsensusTransactionKey::RandomnessDkgConfirmation(*authority)
572 }
573 ConsensusTransactionKind::MisbehaviorReport(report) => {
574 ConsensusTransactionKey::MisbehaviorReport(
575 report.authority,
576 *report.digest(),
577 report.generation,
578 )
579 }
580 }
581 }
582
583 pub fn is_user_certificate(&self) -> bool {
584 matches!(self.kind, ConsensusTransactionKind::CertifiedTransaction(_))
585 }
586
587 pub fn is_end_of_publish(&self) -> bool {
588 matches!(self.kind, ConsensusTransactionKind::EndOfPublish(_))
589 }
590}
591
592#[cfg(test)]
593mod tests {
594 use super::*;
595
596 #[derive(Serialize)]
600 struct LegacyVersionedMisbehaviorReport<'a> {
601 payload: &'a MisbehaviorObservations,
602 }
603
604 fn sample_payload() -> MisbehaviorObservations {
605 MisbehaviorObservations::V1(MisbehaviorObservationsV1 {
606 faulty_blocks_provable: vec![1, 2, 3],
607 faulty_blocks_unprovable: vec![4, 5, 6],
608 missing_proposals: vec![7, 8, 9],
609 equivocations: vec![10, 11, 12],
610 })
611 }
612
613 #[test]
621 fn misbehavior_report_wire_format_unchanged() {
622 let authority = AuthorityName::default();
623 let generation: u64 = 42;
624 let payload = sample_payload();
625
626 let legacy_bytes = bcs::to_bytes(&(
627 authority,
628 LegacyVersionedMisbehaviorReport { payload: &payload },
629 generation,
630 ))
631 .unwrap();
632
633 let new = VersionedMisbehaviorReport {
634 authority,
635 payload,
636 generation,
637 digest: OnceCell::new(),
638 };
639 let new_bytes = bcs::to_bytes(&new).unwrap();
640
641 assert_eq!(
642 legacy_bytes, new_bytes,
643 "VersionedMisbehaviorReport wire format must not change — testnet is live"
644 );
645 }
646
647 #[test]
654 fn misbehavior_report_consensus_kind_wire_format_unchanged() {
655 let authority = AuthorityName::default();
656 let generation: u64 = 7;
657 let payload = sample_payload();
658
659 let new_kind = ConsensusTransactionKind::MisbehaviorReport(VersionedMisbehaviorReport {
660 authority,
661 payload: payload.clone(),
662 generation,
663 digest: OnceCell::new(),
664 });
665 let new_bytes = bcs::to_bytes(&new_kind).unwrap();
666
667 let mut legacy_bytes = vec![8u8];
670 legacy_bytes.extend(
671 bcs::to_bytes(&(
672 authority,
673 LegacyVersionedMisbehaviorReport { payload: &payload },
674 generation,
675 ))
676 .unwrap(),
677 );
678
679 assert_eq!(
680 legacy_bytes, new_bytes,
681 "ConsensusTransactionKind::MisbehaviorReport wire format must not change — testnet is live"
682 );
683 }
684}