miden_objects/block/
partial_nullifier_tree.rs

1use crate::Word;
2use crate::block::{BlockNumber, NullifierTree, NullifierWitness};
3use crate::crypto::merkle::PartialSmt;
4use crate::errors::NullifierTreeError;
5use crate::note::Nullifier;
6
7/// The partial sparse merkle tree containing the nullifiers of consumed notes.
8///
9/// A nullifier can only ever be spent once and its value in the tree is the block number at which
10/// it was spent.
11///
12/// The tree guarantees that once a nullifier has been inserted into the tree, its block number does
13/// not change. Note that inserting the nullifier multiple times with the same block number is
14/// valid.
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct PartialNullifierTree(PartialSmt);
17
18impl PartialNullifierTree {
19    /// Creates a new, empty partial nullifier tree.
20    pub fn new() -> Self {
21        PartialNullifierTree(PartialSmt::new())
22    }
23
24    /// Returns a new [`PartialNullifierTree`] instantiated with the provided entries.
25    ///
26    /// # Errors
27    ///
28    /// Returns an error if:
29    /// - the merkle paths of the witnesses do not result in the same tree root.
30    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    /// Adds the given nullifier witness to the partial tree and tracks it. Once a nullifier has
43    /// been added to the tree, it can be marked as spent using [`Self::mark_spent`].
44    ///
45    /// # Errors
46    ///
47    /// Returns an error if:
48    /// - after the witness' merkle path was added, the partial nullifier tree has a different root
49    ///   than before it was added.
50    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    /// Marks the given nullifiers as spent at the given block number.
56    ///
57    /// # Errors
58    ///
59    /// Returns an error if:
60    /// - a nullifier was already spent.
61    /// - a nullifier is not tracked by this partial nullifier tree, that is, its
62    ///   [`NullifierWitness`] was not added to the tree previously.
63    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    /// Returns the root of the tree.
76    pub fn root(&self) -> Word {
77        self.0.root()
78    }
79
80    /// Marks the given nullifier as spent at the given block number.
81    ///
82    /// # Errors
83    ///
84    /// See [`Self::mark_spent`] for the possible error conditions.
85    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 that using a stale nullifier witness together with a current one results in a different
119    /// tree root and thus an error.
120    #[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        // Insert a non-empty value so the nullifier tree's root changes.
135        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        // Attempt to insert nullifier 1 again at a different block number.
164        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        // Insert a new value into partial and full tree and assert the root is the same.
192        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}