use crate::Word;
use crate::block::{BlockNumber, NullifierTree, NullifierWitness};
use crate::crypto::merkle::PartialSmt;
use crate::errors::NullifierTreeError;
use crate::note::Nullifier;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct PartialNullifierTree(PartialSmt);
impl PartialNullifierTree {
pub fn new(root: Word) -> Self {
PartialNullifierTree(PartialSmt::new(root))
}
pub fn with_witnesses(
witnesses: impl IntoIterator<Item = NullifierWitness>,
) -> Result<Self, NullifierTreeError> {
PartialSmt::from_proofs(witnesses.into_iter().map(NullifierWitness::into_proof))
.map_err(NullifierTreeError::TreeRootConflict)
.map(Self)
}
pub fn track_nullifier(&mut self, witness: NullifierWitness) -> Result<(), NullifierTreeError> {
let (path, leaf) = witness.into_proof().into_parts();
self.0.add_path(leaf, path).map_err(NullifierTreeError::TreeRootConflict)
}
pub fn mark_spent(
&mut self,
nullifiers: impl IntoIterator<Item = Nullifier>,
block_num: BlockNumber,
) -> Result<(), NullifierTreeError> {
for nullifier in nullifiers {
self.mark_spent_single(nullifier, block_num)?;
}
Ok(())
}
pub fn root(&self) -> Word {
self.0.root()
}
fn mark_spent_single(
&mut self,
nullifier: Nullifier,
block_num: BlockNumber,
) -> Result<(), NullifierTreeError> {
let prev_nullifier_value = self
.0
.insert(nullifier.as_word(), NullifierTree::block_num_to_leaf_value(block_num))
.map_err(|source| NullifierTreeError::UntrackedNullifier { nullifier, source })?;
if prev_nullifier_value != NullifierTree::UNSPENT_NULLIFIER {
Err(NullifierTreeError::NullifierAlreadySpent(nullifier))
} else {
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use assert_matches::assert_matches;
use miden_crypto::merkle::Smt;
use winter_rand_utils::rand_value;
use super::*;
use crate::{EMPTY_WORD, Word};
#[test]
fn partial_nullifier_tree_root_mismatch() {
let key0 = rand_value::<Word>();
let key1 = rand_value::<Word>();
let key2 = rand_value::<Word>();
let value0 = EMPTY_WORD;
let value1 = rand_value::<Word>();
let value2 = EMPTY_WORD;
let kv_pairs = vec![(key0, value0)];
let mut full = Smt::with_entries(kv_pairs).unwrap();
let stale_proof0 = full.open(&key0);
full.insert(key1, value1).unwrap();
full.insert(key2, value2).unwrap();
let proof2 = full.open(&key2);
assert_ne!(stale_proof0.compute_root(), proof2.compute_root());
let mut partial =
PartialNullifierTree::with_witnesses([NullifierWitness::new(stale_proof0)]).unwrap();
let error = partial.track_nullifier(NullifierWitness::new(proof2)).unwrap_err();
assert_matches!(error, NullifierTreeError::TreeRootConflict(_));
}
#[test]
fn nullifier_already_spent() {
let nullifier1 = Nullifier::dummy(1);
let block1 = BlockNumber::from(1);
let block2 = BlockNumber::from(2);
let tree = NullifierTree::with_entries([(nullifier1, block1)]).unwrap();
let witness = tree.open(&nullifier1);
let mut partial_tree = PartialNullifierTree::with_witnesses([witness]).unwrap();
let err = partial_tree.mark_spent([nullifier1], block2).unwrap_err();
assert_matches!(err, NullifierTreeError::NullifierAlreadySpent(nullifier) if nullifier == nullifier1);
}
#[test]
fn full_and_partial_nullifier_tree_consistency() {
let nullifier1 = Nullifier::dummy(1);
let nullifier2 = Nullifier::dummy(2);
let nullifier3 = Nullifier::dummy(3);
let block1 = BlockNumber::from(1);
let block2 = BlockNumber::from(2);
let block3 = BlockNumber::from(3);
let mut tree =
NullifierTree::with_entries([(nullifier1, block1), (nullifier2, block2)]).unwrap();
let mut partial_tree = PartialNullifierTree::new(tree.root());
for nullifier in [nullifier1, nullifier2, nullifier3] {
let witness = tree.open(&nullifier);
partial_tree.track_nullifier(witness).unwrap();
}
assert_eq!(tree.root(), partial_tree.root());
tree.mark_spent(nullifier3, block3).unwrap();
partial_tree.mark_spent([nullifier3], block3).unwrap();
assert_eq!(tree.root(), partial_tree.root());
}
}