iota_types/attestation.rs
1// Copyright (c) 2026 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use iota_sdk_types::{ObjectReference, TransactionDigest};
5use serde::{Deserialize, Serialize};
6
7use crate::transaction::TransactionEnvelope;
8
9/// Index of a validator in the current epoch's consensus committee. Kept as a
10/// plain `u8` so `iota-types` does not depend on `starfish-config`, whose
11/// `AuthorityIndex(u8)` is BCS-identical; the value is untrusted until checked
12/// against the block author post-consensus.
13pub(super) type AuthorityIndex = u8;
14
15/// A pre-consensus claim produced by a trusted actor certifying that a specific
16/// transaction has been validated before entering consensus. The attestation is
17/// a separate artifact that travels alongside the transaction; the transaction
18/// and the user's signature are completely unchanged.
19///
20/// An enum so that other attestor kinds can be appended later; the variant
21/// order is protocol-significant.
22#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
23pub enum Attestation {
24 /// Produced by the block-proposing validator and authenticated by the
25 /// block signature, so it carries no signature of its own.
26 Authority {
27 payload: AttestationData,
28 /// Index of the attesting validator in the current epoch's committee
29 authority_index: AuthorityIndex,
30 },
31}
32
33/// The attested content carried by every [`Attestation`] variant.
34///
35/// Versioned to allow new fields to be introduced without breaking existing
36/// match arms.
37#[non_exhaustive]
38#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
39pub enum AttestationData {
40 V1 {
41 /// Expected computation units billed by the attestor's dry-run
42 /// (`computation_cost / gas_price`). Used by the sequencer to improve
43 /// shared-object scheduling before execution.
44 computation_units: u64,
45 /// Versions of the run-time-resolved objects the attestor read during
46 /// the dry-run whose version is NOT already pinned by the signed
47 /// `TransactionData`. This covers shared objects,
48 /// Move-authenticator account and function-ref field objects,
49 /// coin-deny-list references, and dynamic fields / child objects loaded
50 /// during execution.
51 object_versions: Vec<ObjectReference>,
52 },
53}
54
55/// A user transaction bundled with its attestation. This is the inner payload
56/// of `ConsensusTransactionKind::UserTransactionV1`.
57#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
58pub struct AttestedTransaction {
59 pub transaction: TransactionEnvelope,
60 /// `None` until validators attest transactions. Reserved now so attaching
61 /// an attestation later leaves the encoding of an unattested transaction
62 /// unchanged.
63 pub attestation: Option<Attestation>,
64}
65
66impl Attestation {
67 /// An attestation by the block-proposing validator at `authority_index`.
68 pub fn new_authority(payload: AttestationData, authority_index: AuthorityIndex) -> Self {
69 Self::Authority {
70 payload,
71 authority_index,
72 }
73 }
74
75 pub fn computation_units(&self) -> u64 {
76 let Attestation::Authority { payload, .. } = self;
77 let AttestationData::V1 {
78 computation_units, ..
79 } = payload;
80 *computation_units
81 }
82
83 pub fn object_versions(&self) -> &[ObjectReference] {
84 let Attestation::Authority { payload, .. } = self;
85 let AttestationData::V1 {
86 object_versions, ..
87 } = payload;
88 object_versions
89 }
90}
91
92impl AttestedTransaction {
93 pub fn new(transaction: TransactionEnvelope, attestation: Option<Attestation>) -> Self {
94 Self {
95 transaction,
96 attestation,
97 }
98 }
99
100 pub fn digest(&self) -> &TransactionDigest {
101 self.transaction.digest()
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use crate::base_types::random_object_ref;
109
110 fn make_attestation_data() -> AttestationData {
111 AttestationData::V1 {
112 computation_units: 1_000_000,
113 object_versions: vec![random_object_ref()],
114 }
115 }
116
117 #[test]
118 fn attestation_data_bcs_round_trip() {
119 let data = make_attestation_data();
120 let encoded = bcs::to_bytes(&data).unwrap();
121 let decoded: AttestationData = bcs::from_bytes(&encoded).unwrap();
122 assert_eq!(decoded, data);
123 }
124
125 #[test]
126 fn attestation_authority_bcs_round_trip() {
127 let attestation = Attestation::Authority {
128 payload: make_attestation_data(),
129 authority_index: 3,
130 };
131 let encoded = bcs::to_bytes(&attestation).unwrap();
132 let decoded: Attestation = bcs::from_bytes(&encoded).unwrap();
133 assert_eq!(decoded, attestation);
134 }
135}