Skip to main content

miden_protocol/block/nullifier_tree/
partial.rs

1use 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// PARTIAL NULLIFIER TREE
9// ================================================================================================
10
11/// The partial sparse merkle tree containing the nullifiers of consumed notes.
12///
13/// A nullifier can only ever be spent once and its value in the tree is the block number at which
14/// it was spent.
15///
16/// The tree guarantees that once a nullifier has been inserted into the tree, its block number does
17/// not change. Note that inserting the nullifier multiple times with the same block number is
18/// valid.
19#[derive(Debug, Clone, Default, PartialEq, Eq)]
20pub struct PartialNullifierTree(PartialSmt);
21
22impl PartialNullifierTree {
23    /// Creates a new partial nullifier tree with the provided root that does not track any
24    /// nullifiers.
25    pub fn new(root: Word) -> Self {
26        PartialNullifierTree(PartialSmt::new(root))
27    }
28
29    /// Returns a new [`PartialNullifierTree`] instantiated with the provided entries.
30    ///
31    /// # Errors
32    ///
33    /// Returns an error if:
34    /// - the merkle paths of the witnesses do not result in the same tree root.
35    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    /// Returns the root of the tree.
44    pub fn root(&self) -> Word {
45        self.0.root()
46    }
47
48    /// Adds the given nullifier witness to the partial tree and tracks it.
49    ///
50    /// Once a nullifier has been added to the tree, it can be marked as spent using
51    /// [`Self::mark_spent`].
52    ///
53    /// # Errors
54    ///
55    /// Returns an error if:
56    /// - after the witness' merkle path was added, the partial nullifier tree has a different root
57    ///   than before it was added.
58    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    /// Marks the given nullifier as spent at the given block number.
64    ///
65    /// # Errors
66    ///
67    /// Returns an error if:
68    /// - a nullifier was already spent.
69    /// - a nullifier is not tracked by this partial nullifier tree, that is, its
70    ///   [`NullifierWitness`] was not added to the tree previously.
71    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    /// Marks the given nullifiers as spent at the given block number.
90    ///
91    /// # Errors
92    ///
93    /// See [`Self::mark_spent`] for the possible error conditions.
94    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// TESTS
108// ================================================================================================
109
110#[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 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 = 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        // Insert a non-empty value so the nullifier tree's root changes.
137        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        // Attempt to insert nullifier 1 again at a different block number.
165        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        // Insert a new value into partial and full tree and assert the root is the same.
193        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}