miden_objects/block/
partial_nullifier_tree.rs1use crate::Word;
2use crate::block::{BlockNumber, NullifierTree, NullifierWitness};
3use crate::crypto::merkle::PartialSmt;
4use crate::errors::NullifierTreeError;
5use crate::note::Nullifier;
6
7#[derive(Debug, Clone, Default, PartialEq, Eq)]
16pub struct PartialNullifierTree(PartialSmt);
17
18impl PartialNullifierTree {
19 pub fn new(root: Word) -> Self {
22 PartialNullifierTree(PartialSmt::new(root))
23 }
24
25 pub fn with_witnesses(
32 witnesses: impl IntoIterator<Item = NullifierWitness>,
33 ) -> Result<Self, NullifierTreeError> {
34 PartialSmt::from_proofs(witnesses.into_iter().map(NullifierWitness::into_proof))
35 .map_err(NullifierTreeError::TreeRootConflict)
36 .map(Self)
37 }
38
39 pub fn track_nullifier(&mut self, witness: NullifierWitness) -> Result<(), NullifierTreeError> {
48 let (path, leaf) = witness.into_proof().into_parts();
49 self.0.add_path(leaf, path).map_err(NullifierTreeError::TreeRootConflict)
50 }
51
52 pub fn mark_spent(
61 &mut self,
62 nullifiers: impl IntoIterator<Item = Nullifier>,
63 block_num: BlockNumber,
64 ) -> Result<(), NullifierTreeError> {
65 for nullifier in nullifiers {
66 self.mark_spent_single(nullifier, block_num)?;
67 }
68
69 Ok(())
70 }
71
72 pub fn root(&self) -> Word {
74 self.0.root()
75 }
76
77 fn mark_spent_single(
83 &mut self,
84 nullifier: Nullifier,
85 block_num: BlockNumber,
86 ) -> Result<(), NullifierTreeError> {
87 let prev_nullifier_value = self
88 .0
89 .insert(nullifier.as_word(), NullifierTree::block_num_to_leaf_value(block_num))
90 .map_err(|source| NullifierTreeError::UntrackedNullifier { nullifier, source })?;
91
92 if prev_nullifier_value != NullifierTree::UNSPENT_NULLIFIER {
93 Err(NullifierTreeError::NullifierAlreadySpent(nullifier))
94 } else {
95 Ok(())
96 }
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use assert_matches::assert_matches;
103 use miden_crypto::merkle::Smt;
104 use winter_rand_utils::rand_value;
105
106 use super::*;
107 use crate::{EMPTY_WORD, Word};
108
109 #[test]
112 fn partial_nullifier_tree_root_mismatch() {
113 let key0 = rand_value::<Word>();
114 let key1 = rand_value::<Word>();
115 let key2 = rand_value::<Word>();
116
117 let value0 = EMPTY_WORD;
118 let value1 = rand_value::<Word>();
119 let value2 = EMPTY_WORD;
120
121 let kv_pairs = vec![(key0, value0)];
122
123 let mut full = Smt::with_entries(kv_pairs).unwrap();
124 let stale_proof0 = full.open(&key0);
125 full.insert(key1, value1).unwrap();
127 full.insert(key2, value2).unwrap();
128 let proof2 = full.open(&key2);
129
130 assert_ne!(stale_proof0.compute_root(), proof2.compute_root());
131
132 let mut partial =
133 PartialNullifierTree::with_witnesses([NullifierWitness::new(stale_proof0)]).unwrap();
134
135 let error = partial.track_nullifier(NullifierWitness::new(proof2)).unwrap_err();
136
137 assert_matches!(error, NullifierTreeError::TreeRootConflict(_));
138 }
139
140 #[test]
141 fn nullifier_already_spent() {
142 let nullifier1 = Nullifier::dummy(1);
143
144 let block1 = BlockNumber::from(1);
145 let block2 = BlockNumber::from(2);
146
147 let tree = NullifierTree::with_entries([(nullifier1, block1)]).unwrap();
148
149 let witness = tree.open(&nullifier1);
150
151 let mut partial_tree = PartialNullifierTree::with_witnesses([witness]).unwrap();
152
153 let err = partial_tree.mark_spent([nullifier1], block2).unwrap_err();
155
156 assert_matches!(err, NullifierTreeError::NullifierAlreadySpent(nullifier) if nullifier == nullifier1);
157 }
158
159 #[test]
160 fn full_and_partial_nullifier_tree_consistency() {
161 let nullifier1 = Nullifier::dummy(1);
162 let nullifier2 = Nullifier::dummy(2);
163 let nullifier3 = Nullifier::dummy(3);
164
165 let block1 = BlockNumber::from(1);
166 let block2 = BlockNumber::from(2);
167 let block3 = BlockNumber::from(3);
168
169 let mut tree =
170 NullifierTree::with_entries([(nullifier1, block1), (nullifier2, block2)]).unwrap();
171
172 let mut partial_tree = PartialNullifierTree::new(tree.root());
173
174 for nullifier in [nullifier1, nullifier2, nullifier3] {
175 let witness = tree.open(&nullifier);
176 partial_tree.track_nullifier(witness).unwrap();
177 }
178
179 assert_eq!(tree.root(), partial_tree.root());
180
181 tree.mark_spent(nullifier3, block3).unwrap();
183 partial_tree.mark_spent([nullifier3], block3).unwrap();
184
185 assert_eq!(tree.root(), partial_tree.root());
186 }
187}