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, Default, PartialEq, Eq)]
16pub struct PartialNullifierTree(PartialSmt);
17
18impl PartialNullifierTree {
19    /// Creates a new partial nullifier tree with the provided root that does not track any
20    /// nullifiers.
21    pub fn new(root: Word) -> Self {
22        PartialNullifierTree(PartialSmt::new(root))
23    }
24
25    /// Returns a new [`PartialNullifierTree`] instantiated with the provided entries.
26    ///
27    /// # Errors
28    ///
29    /// Returns an error if:
30    /// - the merkle paths of the witnesses do not result in the same tree root.
31    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    /// Adds the given nullifier witness to the partial tree and tracks it. Once a nullifier has
40    /// been added to the tree, it can be marked as spent using [`Self::mark_spent`].
41    ///
42    /// # Errors
43    ///
44    /// Returns an error if:
45    /// - after the witness' merkle path was added, the partial nullifier tree has a different root
46    ///   than before it was added.
47    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    /// Marks the given nullifiers as spent at the given block number.
53    ///
54    /// # Errors
55    ///
56    /// Returns an error if:
57    /// - a nullifier was already spent.
58    /// - a nullifier is not tracked by this partial nullifier tree, that is, its
59    ///   [`NullifierWitness`] was not added to the tree previously.
60    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    /// Returns the root of the tree.
73    pub fn root(&self) -> Word {
74        self.0.root()
75    }
76
77    /// Marks the given nullifier as spent at the given block number.
78    ///
79    /// # Errors
80    ///
81    /// See [`Self::mark_spent`] for the possible error conditions.
82    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 that using a stale nullifier witness together with a current one results in a different
110    /// tree root and thus an error.
111    #[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        // Insert a non-empty value so the nullifier tree's root changes.
126        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        // Attempt to insert nullifier 1 again at a different block number.
154        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        // Insert a new value into partial and full tree and assert the root is the same.
182        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}