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