Skip to main content

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}