miden_objects/block/
partial_nullifier_tree.rs

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