miden_protocol/block/nullifier_tree/
partial.rs1use super::{NullifierBlock, NullifierWitness};
2use crate::Word;
3use crate::block::BlockNumber;
4use crate::crypto::merkle::smt::PartialSmt;
5use crate::errors::NullifierTreeError;
6use crate::note::Nullifier;
7
8#[derive(Debug, Clone, Default, PartialEq, Eq)]
20pub struct PartialNullifierTree(PartialSmt);
21
22impl PartialNullifierTree {
23 pub fn new(root: Word) -> Self {
26 PartialNullifierTree(PartialSmt::new(root))
27 }
28
29 pub fn with_witnesses(
36 witnesses: impl IntoIterator<Item = NullifierWitness>,
37 ) -> Result<Self, NullifierTreeError> {
38 PartialSmt::from_proofs(witnesses.into_iter().map(NullifierWitness::into_proof))
39 .map_err(NullifierTreeError::TreeRootConflict)
40 .map(Self)
41 }
42
43 pub fn root(&self) -> Word {
45 self.0.root()
46 }
47
48 pub fn track_nullifier(&mut self, witness: NullifierWitness) -> Result<(), NullifierTreeError> {
59 let (path, leaf) = witness.into_proof().into_parts();
60 self.0.add_path(leaf, path).map_err(NullifierTreeError::TreeRootConflict)
61 }
62
63 pub fn mark_spent(
72 &mut self,
73 nullifier: Nullifier,
74 block_num: BlockNumber,
75 ) -> Result<(), NullifierTreeError> {
76 let prev_nullifier_value: NullifierBlock = self
77 .0
78 .insert(nullifier.as_word(), NullifierBlock::from(block_num).into())
79 .map_err(|source| NullifierTreeError::UntrackedNullifier { nullifier, source })?
80 .try_into()?;
81
82 if prev_nullifier_value.is_spent() {
83 Err(NullifierTreeError::NullifierAlreadySpent(nullifier))
84 } else {
85 Ok(())
86 }
87 }
88
89 pub fn mark_spent_all(
95 &mut self,
96 nullifiers: impl IntoIterator<Item = Nullifier>,
97 block_num: BlockNumber,
98 ) -> Result<(), NullifierTreeError> {
99 for nullifier in nullifiers {
100 self.mark_spent(nullifier, block_num)?;
101 }
102
103 Ok(())
104 }
105}
106
107#[cfg(test)]
111mod tests {
112 use assert_matches::assert_matches;
113 use miden_crypto::merkle::smt::Smt;
114 use winter_rand_utils::rand_value;
115
116 use super::*;
117 use crate::block::nullifier_tree::NullifierTree;
118 use crate::{EMPTY_WORD, Word};
119
120 #[test]
123 fn partial_nullifier_tree_root_mismatch() {
124 let key0 = rand_value::<Word>();
125 let key1 = rand_value::<Word>();
126 let key2 = rand_value::<Word>();
127
128 let value0 = EMPTY_WORD;
129 let value1 = rand_value::<Word>();
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).unwrap();
138 full.insert(key2, value2).unwrap();
139 let proof2 = full.open(&key2);
140
141 assert_ne!(stale_proof0.compute_root(), proof2.compute_root());
142
143 let mut partial =
144 PartialNullifierTree::with_witnesses([NullifierWitness::new(stale_proof0)]).unwrap();
145
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::with_witnesses([witness]).unwrap();
163
164 let err = partial_tree.mark_spent_all([nullifier1], block2).unwrap_err();
166
167 assert_matches!(err, NullifierTreeError::NullifierAlreadySpent(nullifier) if nullifier == nullifier1);
168 }
169
170 #[test]
171 fn full_and_partial_nullifier_tree_consistency() {
172 let nullifier1 = Nullifier::dummy(1);
173 let nullifier2 = Nullifier::dummy(2);
174 let nullifier3 = Nullifier::dummy(3);
175
176 let block1 = BlockNumber::from(1);
177 let block2 = BlockNumber::from(2);
178 let block3 = BlockNumber::from(3);
179
180 let mut tree =
181 NullifierTree::with_entries([(nullifier1, block1), (nullifier2, block2)]).unwrap();
182
183 let mut partial_tree = PartialNullifierTree::new(tree.root());
184
185 for nullifier in [nullifier1, nullifier2, nullifier3] {
186 let witness = tree.open(&nullifier);
187 partial_tree.track_nullifier(witness).unwrap();
188 }
189
190 assert_eq!(tree.root(), partial_tree.root());
191
192 tree.mark_spent(nullifier3, block3).unwrap();
194 partial_tree.mark_spent(nullifier3, block3).unwrap();
195
196 assert_eq!(tree.root(), partial_tree.root());
197 }
198}