Skip to main content

miden_protocol/block/account_tree/
witness.rs

1use alloc::string::ToString;
2
3use miden_crypto::merkle::smt::{SMT_DEPTH, SmtLeaf, SmtProof, SmtProofError};
4use miden_crypto::merkle::{InnerNodeInfo, SparseMerklePath};
5
6use crate::Word;
7use crate::account::AccountId;
8use crate::block::account_tree::AccountIdKey;
9use crate::errors::AccountTreeError;
10use crate::utils::serde::{
11    ByteReader,
12    ByteWriter,
13    Deserializable,
14    DeserializationError,
15    Serializable,
16};
17
18// ACCOUNT WITNESS
19// ================================================================================================
20
21/// A specialized version of an [`SmtProof`] for use in [`AccountTree`](super::AccountTree) and
22/// [`PartialAccountTree`](super::PartialAccountTree). It proves the inclusion of an account
23/// ID at a certain state (i.e. [`Account::to_commitment`](crate::account::Account::to_commitment))
24/// in the [`AccountTree`](super::AccountTree).
25///
26/// By construction the witness can only represent the equivalent of an [`SmtLeaf`] with zero or one
27/// entries, which guarantees that the account ID prefix it represents is unique in the tree.
28///
29/// # Guarantees
30///
31/// This type guarantees that:
32/// - its MerklePath is of depth [`SMT_DEPTH`].
33/// - converting this type into an [`SmtProof`] results in a leaf with zero or one entries, i.e. the
34///   account ID prefix is unique.
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct AccountWitness {
37    /// The account ID that this witness proves inclusion for.
38    id: AccountId,
39    /// The state commitment of the account ID.
40    commitment: Word,
41    /// The merkle path of the account witness.
42    path: SparseMerklePath,
43}
44
45impl AccountWitness {
46    /// Constructs a new [`AccountWitness`] from the provided parts.
47    ///
48    /// # Errors
49    ///
50    /// Returns an error if:
51    /// - the merkle path's depth is not [`SMT_DEPTH`].
52    pub fn new(
53        account_id: AccountId,
54        commitment: Word,
55        path: SparseMerklePath,
56    ) -> Result<Self, AccountTreeError> {
57        if path.depth() != SMT_DEPTH {
58            return Err(AccountTreeError::WitnessMerklePathDepthDoesNotMatchAccountTreeDepth(
59                path.depth() as usize,
60            ));
61        }
62
63        Ok(Self::new_unchecked(account_id, commitment, path))
64    }
65
66    /// Creates an [`AccountWitness`] from the provided proof and the account ID for which the proof
67    /// was requested.
68    ///
69    /// # Warning
70    ///
71    /// This should only be called on SMT proofs retrieved from (partial) account tree, because it
72    /// relies on the guarantees of those types.
73    ///
74    /// # Panics
75    ///
76    /// Panics if:
77    /// - the proof contains an entry whose key contains an invalid account ID.
78    /// - the merkle path in the proof does not have depth equal to [`SMT_DEPTH`].
79    /// - the proof contains an SmtLeaf::Multiple.
80    pub(super) fn from_smt_proof(requested_account_id: AccountId, proof: SmtProof) -> Self {
81        // Check which account ID this proof actually contains. We rely on the fact that the
82        // trees only contain zero or one entry per account ID prefix.
83        //
84        // If the requested account ID matches an existing ID's prefix but their suffixes do
85        // not match, then this witness is for the _existing ID_.
86        //
87        // Otherwise, if the ID matches the one in the leaf or if it's empty, the witness is
88        // for the requested ID.
89        let witness_id = match proof.leaf() {
90            SmtLeaf::Empty(_) => requested_account_id,
91            SmtLeaf::Single((key_in_leaf, _)) => {
92                // SAFETY: By construction, the tree only contains valid IDs.
93                AccountIdKey::try_from_word(*key_in_leaf)
94                    .expect("account tree should only contain valid IDs")
95            },
96            SmtLeaf::Multiple(_) => {
97                unreachable!("account tree should only contain zero or one entry per ID prefix")
98            },
99        };
100
101        let commitment = proof
102            .get(&AccountIdKey::from(witness_id).as_word())
103            .expect("we should have received a proof for the witness key");
104
105        // SAFETY: The proof is guaranteed to have depth SMT_DEPTH if it comes from one of
106        // the account trees.
107        debug_assert_eq!(proof.path().depth(), SMT_DEPTH);
108
109        AccountWitness::new_unchecked(witness_id, commitment, proof.into_parts().0)
110    }
111
112    /// Constructs a new [`AccountWitness`] from the provided parts.
113    ///
114    /// # Warning
115    ///
116    /// This does not validate any of the guarantees of this type.
117    pub(super) fn new_unchecked(
118        account_id: AccountId,
119        commitment: Word,
120        path: SparseMerklePath,
121    ) -> Self {
122        Self { id: account_id, commitment, path }
123    }
124
125    /// Returns the underlying [`AccountId`] that this witness proves inclusion for.
126    pub fn id(&self) -> AccountId {
127        self.id
128    }
129
130    /// Returns the state commitment of the account witness.
131    pub fn state_commitment(&self) -> Word {
132        self.commitment
133    }
134
135    /// Returns the [`SparseMerklePath`] of the account witness.
136    pub fn path(&self) -> &SparseMerklePath {
137        &self.path
138    }
139
140    /// Returns the [`SmtLeaf`] of the account witness.
141    pub fn leaf(&self) -> SmtLeaf {
142        if self.commitment == Word::empty() {
143            let leaf_idx = AccountIdKey::from(self.id).to_leaf_index();
144            SmtLeaf::new_empty(leaf_idx)
145        } else {
146            let key = AccountIdKey::from(self.id).as_word();
147            SmtLeaf::new_single(key, self.commitment)
148        }
149    }
150
151    /// Consumes self and returns the inner proof.
152    pub fn into_proof(self) -> SmtProof {
153        let leaf = self.leaf();
154        debug_assert_eq!(self.path.depth(), SMT_DEPTH);
155        SmtProof::new(self.path, leaf)
156            .expect("merkle path depth should be the SMT depth by construction")
157    }
158
159    /// Returns an iterator over every inner node of this witness' merkle path.
160    pub fn authenticated_nodes(&self) -> impl Iterator<Item = InnerNodeInfo> + '_ {
161        let leaf = self.leaf();
162        self.path()
163            .authenticated_nodes(leaf.index().value(), leaf.hash())
164            .expect("leaf index is u64 and should be less than 2^SMT_DEPTH")
165    }
166}
167
168impl From<AccountWitness> for SmtProof {
169    fn from(witness: AccountWitness) -> Self {
170        witness.into_proof()
171    }
172}
173
174// SERIALIZATION
175// ================================================================================================
176
177impl Serializable for AccountWitness {
178    fn write_into<W: ByteWriter>(&self, target: &mut W) {
179        self.id.write_into(target);
180        self.commitment.write_into(target);
181        self.path.write_into(target);
182    }
183}
184
185impl Deserializable for AccountWitness {
186    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
187        let id = AccountId::read_from(source)?;
188        let commitment = Word::read_from(source)?;
189        let path = SparseMerklePath::read_from(source)?;
190
191        if path.depth() != SMT_DEPTH {
192            return Err(DeserializationError::InvalidValue(
193                SmtProofError::InvalidMerklePathLength(path.depth() as usize).to_string(),
194            ));
195        }
196
197        Ok(Self { id, commitment, path })
198    }
199}