miden_objects/block/
partial_nullifier_tree.rs1use crate::{
2 Digest,
3 block::{BlockNumber, NullifierTree, NullifierWitness},
4 crypto::merkle::PartialSmt,
5 errors::NullifierTreeError,
6 note::Nullifier,
7};
8
9#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct PartialNullifierTree(PartialSmt);
19
20impl PartialNullifierTree {
21 pub fn new() -> Self {
23 PartialNullifierTree(PartialSmt::new())
24 }
25
26 pub fn with_witnesses(
33 witnesses: impl IntoIterator<Item = NullifierWitness>,
34 ) -> Result<Self, NullifierTreeError> {
35 let mut tree = Self::new();
36
37 for witness in witnesses {
38 tree.track_nullifier(witness)?;
39 }
40
41 Ok(tree)
42 }
43
44 pub fn track_nullifier(&mut self, witness: NullifierWitness) -> Result<(), NullifierTreeError> {
53 let (path, leaf) = witness.into_proof().into_parts();
54 self.0.add_path(leaf, path).map_err(NullifierTreeError::TreeRootConflict)
55 }
56
57 pub fn mark_spent(
66 &mut self,
67 nullifiers: impl IntoIterator<Item = Nullifier>,
68 block_num: BlockNumber,
69 ) -> Result<(), NullifierTreeError> {
70 for nullifier in nullifiers {
71 self.mark_spent_single(nullifier, block_num)?;
72 }
73
74 Ok(())
75 }
76
77 pub fn root(&self) -> Digest {
79 self.0.root()
80 }
81
82 fn mark_spent_single(
88 &mut self,
89 nullifier: Nullifier,
90 block_num: BlockNumber,
91 ) -> Result<(), NullifierTreeError> {
92 let prev_nullifier_value = self
93 .0
94 .insert(nullifier.inner(), NullifierTree::block_num_to_leaf_value(block_num))
95 .map_err(|source| NullifierTreeError::UntrackedNullifier { nullifier, source })?;
96
97 if prev_nullifier_value != NullifierTree::UNSPENT_NULLIFIER {
98 Err(NullifierTreeError::NullifierAlreadySpent(nullifier))
99 } else {
100 Ok(())
101 }
102 }
103}
104
105impl Default for PartialNullifierTree {
106 fn default() -> Self {
107 Self::new()
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use assert_matches::assert_matches;
114 use miden_crypto::merkle::Smt;
115 use winter_rand_utils::rand_array;
116
117 use super::*;
118 use crate::{EMPTY_WORD, Word};
119
120 #[test]
123 fn partial_nullifier_tree_root_mismatch() {
124 let key0 = Digest::from(Word::from(rand_array()));
125 let key1 = Digest::from(Word::from(rand_array()));
126 let key2 = Digest::from(Word::from(rand_array()));
127
128 let value0 = EMPTY_WORD;
129 let value1 = Word::from(rand_array());
130 let value2 = EMPTY_WORD;
131
132 let kv_pairs = vec![(key0, value0)];
133
134 let mut full = Smt::with_entries(kv_pairs).unwrap();
135 let stale_proof0 = full.open(&key0);
136 full.insert(key1, value1);
138 full.insert(key2, value2);
139 let proof2 = full.open(&key2);
140
141 assert_ne!(stale_proof0.compute_root(), proof2.compute_root());
142
143 let mut partial = PartialNullifierTree::new();
144
145 partial.track_nullifier(NullifierWitness::new(stale_proof0)).unwrap();
146 let error = partial.track_nullifier(NullifierWitness::new(proof2)).unwrap_err();
147
148 assert_matches!(error, NullifierTreeError::TreeRootConflict(_));
149 }
150
151 #[test]
152 fn nullifier_already_spent() {
153 let nullifier1 = Nullifier::dummy(1);
154
155 let block1 = BlockNumber::from(1);
156 let block2 = BlockNumber::from(2);
157
158 let tree = NullifierTree::with_entries([(nullifier1, block1)]).unwrap();
159
160 let witness = tree.open(&nullifier1);
161
162 let mut partial_tree = PartialNullifierTree::new();
163 partial_tree.track_nullifier(witness).unwrap();
164
165 let err = partial_tree.mark_spent([nullifier1], block2).unwrap_err();
167
168 assert_matches!(err, NullifierTreeError::NullifierAlreadySpent(nullifier) if nullifier == nullifier1);
169 }
170
171 #[test]
172 fn full_and_partial_nullifier_tree_consistency() {
173 let nullifier1 = Nullifier::dummy(1);
174 let nullifier2 = Nullifier::dummy(2);
175 let nullifier3 = Nullifier::dummy(3);
176
177 let block1 = BlockNumber::from(1);
178 let block2 = BlockNumber::from(2);
179 let block3 = BlockNumber::from(3);
180
181 let mut tree =
182 NullifierTree::with_entries([(nullifier1, block1), (nullifier2, block2)]).unwrap();
183
184 let mut partial_tree = PartialNullifierTree::new();
185
186 for nullifier in [nullifier1, nullifier2, nullifier3] {
187 let witness = tree.open(&nullifier);
188 partial_tree.track_nullifier(witness).unwrap();
189 }
190
191 assert_eq!(tree.root(), partial_tree.root());
192
193 tree.mark_spent(nullifier3, block3).unwrap();
195 partial_tree.mark_spent([nullifier3], block3).unwrap();
196
197 assert_eq!(tree.root(), partial_tree.root());
198 }
199}