miden_objects/block/account_witness.rs
1use alloc::string::ToString;
2
3use miden_crypto::merkle::{
4 InnerNodeInfo,
5 LeafIndex,
6 MerklePath,
7 SMT_DEPTH,
8 SmtLeaf,
9 SmtProof,
10 SmtProofError,
11};
12
13use crate::account::AccountId;
14use crate::block::AccountTree;
15use crate::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable};
16use crate::{AccountTreeError, Word};
17
18// ACCOUNT WITNESS
19// ================================================================================================
20
21/// A specialized version of an [`SmtProof`] for use in [`AccountTree`] and
22/// [`PartialAccountTree`](crate::block::PartialAccountTree). It proves the inclusion of an account
23/// ID at a certain state (i.e. [`Account::commitment`](crate::account::Account::commitment)) in the
24/// [`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: MerklePath,
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 [`AccountTree::DEPTH`].
52 pub fn new(
53 account_id: AccountId,
54 commitment: Word,
55 path: MerklePath,
56 ) -> Result<Self, AccountTreeError> {
57 if path.len() != SMT_DEPTH as usize {
58 return Err(AccountTreeError::WitnessMerklePathDepthDoesNotMatchAccountTreeDepth(
59 path.len(),
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 merkle path in the proof does not have depth equal to [`AccountTree::DEPTH`].
78 /// - the proof contains an SmtLeaf::Multiple.
79 pub(super) fn from_smt_proof(requested_account_id: AccountId, proof: SmtProof) -> Self {
80 // Check which account ID this proof actually contains. We rely on the fact that the
81 // trees only contain zero or one entry per account ID prefix.
82 //
83 // If the requested account ID matches an existing ID's prefix but their suffixes do
84 // not match, then this witness is for the _existing ID_.
85 //
86 // Otherwise, if the ID matches the one in the leaf or if it's empty, the witness is
87 // for the requested ID.
88 let witness_id = match proof.leaf() {
89 SmtLeaf::Empty(_) => requested_account_id,
90 SmtLeaf::Single((key_in_leaf, _)) => {
91 // SAFETY: By construction, the tree only contains valid IDs.
92 AccountTree::smt_key_to_id(*key_in_leaf)
93 },
94 SmtLeaf::Multiple(_) => {
95 unreachable!("account tree should only contain zero or one entry per ID prefix")
96 },
97 };
98
99 let commitment = proof
100 .get(&AccountTree::id_to_smt_key(witness_id))
101 .expect("we should have received a proof for the witness key");
102
103 // SAFETY: The proof is guaranteed to have depth AccountTree::DEPTH if it comes from one of
104 // the account trees.
105 debug_assert_eq!(proof.path().depth(), AccountTree::DEPTH);
106
107 AccountWitness::new_unchecked(witness_id, commitment, proof.into_parts().0)
108 }
109
110 /// Constructs a new [`AccountWitness`] from the provided parts.
111 ///
112 /// # Warning
113 ///
114 /// This does not validate any of the guarantees of this type.
115 pub(super) fn new_unchecked(account_id: AccountId, commitment: Word, path: MerklePath) -> Self {
116 Self { id: account_id, commitment, path }
117 }
118
119 /// Returns the underlying [`AccountId`] that this witness proves inclusion for.
120 pub fn id(&self) -> AccountId {
121 self.id
122 }
123
124 /// Returns the state commitment of the account witness.
125 pub fn state_commitment(&self) -> Word {
126 self.commitment
127 }
128
129 /// Returns the [`MerklePath`] of the account witness.
130 pub fn path(&self) -> &MerklePath {
131 &self.path
132 }
133
134 /// Returns the [`SmtLeaf`] of the account witness.
135 pub fn leaf(&self) -> SmtLeaf {
136 if self.commitment == Word::empty() {
137 let leaf_idx = LeafIndex::from(AccountTree::id_to_smt_key(self.id));
138 SmtLeaf::new_empty(leaf_idx)
139 } else {
140 let key = AccountTree::id_to_smt_key(self.id);
141 SmtLeaf::new_single(key, self.commitment)
142 }
143 }
144
145 /// Consumes self and returns the inner proof.
146 pub fn into_proof(self) -> SmtProof {
147 let leaf = self.leaf();
148 SmtProof::new(self.path, leaf)
149 .expect("merkle path depth should be the SMT depth by construction")
150 }
151
152 /// Returns an iterator over every inner node of this witness' merkle path.
153 pub fn authenticated_nodes(&self) -> impl Iterator<Item = InnerNodeInfo> + '_ {
154 let leaf = self.leaf();
155 self.path()
156 .authenticated_nodes(leaf.index().value(), leaf.hash())
157 .expect("leaf index is u64 and should be less than 2^SMT_DEPTH")
158 }
159}
160
161impl From<AccountWitness> for SmtProof {
162 fn from(witness: AccountWitness) -> Self {
163 witness.into_proof()
164 }
165}
166
167// SERIALIZATION
168// ================================================================================================
169
170impl Serializable for AccountWitness {
171 fn write_into<W: ByteWriter>(&self, target: &mut W) {
172 self.id.write_into(target);
173 self.commitment.write_into(target);
174 self.path.write_into(target);
175 }
176}
177
178impl Deserializable for AccountWitness {
179 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
180 let id = AccountId::read_from(source)?;
181 let commitment = Word::read_from(source)?;
182 let path = MerklePath::read_from(source)?;
183
184 if path.len() != SMT_DEPTH as usize {
185 return Err(DeserializationError::InvalidValue(
186 SmtProofError::InvalidMerklePathLength(path.len()).to_string(),
187 ));
188 }
189
190 Ok(Self { id, commitment, path })
191 }
192}