diff --git a/merkle_tree/CHANGELOG.md b/merkle_tree/CHANGELOG.md index ccd9ce510..ef5e898fa 100644 --- a/merkle_tree/CHANGELOG.md +++ b/merkle_tree/CHANGELOG.md @@ -3,6 +3,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.2.0 (2024-10-21) + +- [#692](https://github.com/EspressoSystems/jellyfish/pull/692) Major refactor for ergonomics reason + - `MerkleProof` now doesn't contain leaf information. Proofs should be verified along with claimed + index and element information. + - Merkle proof verification proof APIs now takes `MerkleCommitment` instead of simply a root digest + value. It can now be called without instantiating an actual Merkle tree struct. + - Deprecate namespace Merkle tree for now because it's no longer in use. + ## 0.1.0 - Initial release. diff --git a/merkle_tree/Cargo.toml b/merkle_tree/Cargo.toml index e0caa0495..0f39ae5ad 100644 --- a/merkle_tree/Cargo.toml +++ b/merkle_tree/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "jf-merkle-tree" -version = "0.1.0" +version = "0.2.0" description = "Various Merkle tree implementations." authors = { workspace = true } edition = { workspace = true } diff --git a/merkle_tree/benches/merkle_path.rs b/merkle_tree/benches/merkle_path.rs index aab16ff3e..a89290747 100644 --- a/merkle_tree/benches/merkle_path.rs +++ b/merkle_tree/benches/merkle_path.rs @@ -10,7 +10,7 @@ extern crate criterion; use ark_ed_on_bls12_381::Fq as Fq381; use ark_std::rand::Rng; use criterion::Criterion; -use jf_merkle_tree::{prelude::RescueMerkleTree, MerkleCommitment, MerkleTreeScheme}; +use jf_merkle_tree::{prelude::RescueMerkleTree, MerkleTreeScheme}; use std::time::Duration; const BENCH_NAME: &str = "merkle_path_height_20"; @@ -25,12 +25,12 @@ fn twenty_hashes(c: &mut Criterion) { let leaf: Fq381 = rng.gen(); let mt = RescueMerkleTree::::from_elems(Some(20), [leaf, leaf]).unwrap(); - let root = mt.commitment().digest(); - let (_, proof) = mt.lookup(0).expect_ok().unwrap(); + let commitment = mt.commitment(); + let (val, proof) = mt.lookup(0).expect_ok().unwrap(); let num_inputs = 0; benchmark_group.bench_with_input(BENCH_NAME, &num_inputs, move |b, &_num_inputs| { - b.iter(|| RescueMerkleTree::::verify(&root, 0, &proof).unwrap()) + b.iter(|| RescueMerkleTree::::verify(&commitment, 0, val, &proof).unwrap()) }); benchmark_group.finish(); } diff --git a/merkle_tree/src/append_only.rs b/merkle_tree/src/append_only.rs index ca832b5a3..f3b1f8bc0 100644 --- a/merkle_tree/src/append_only.rs +++ b/merkle_tree/src/append_only.rs @@ -8,11 +8,10 @@ use super::{ internal::{ - build_tree_internal, MerkleNode, MerkleProof, MerkleTreeCommitment, MerkleTreeIntoIter, - MerkleTreeIter, + build_tree_internal, MerkleNode, MerkleTreeIntoIter, MerkleTreeIter, MerkleTreeProof, }, AppendableMerkleTreeScheme, DigestAlgorithm, Element, ForgetableMerkleTreeScheme, Index, - LookupResult, MerkleCommitment, MerkleTreeScheme, NodeValue, ToTraversalPath, + LookupResult, MerkleProof, MerkleTreeScheme, NodeValue, ToTraversalPath, }; use crate::{ errors::MerkleTreeError, impl_forgetable_merkle_tree_scheme, impl_merkle_tree_scheme, @@ -104,12 +103,10 @@ where } } -// TODO(Chengyu): extract a merkle frontier - #[cfg(test)] mod mt_tests { use crate::{ - internal::{MerkleNode, MerkleProof}, + internal::{MerkleNode, MerkleTreeProof}, prelude::{RescueMerkleTree, RescueSparseMerkleTree}, *, }; @@ -166,43 +163,36 @@ mod mt_tests { let mt = RescueMerkleTree::::from_elems(Some(2), [F::from(3u64), F::from(1u64)]).unwrap(); - let root = mt.commitment().digest(); + let commitment = mt.commitment(); let (elem, proof) = mt.lookup(0).expect_ok().unwrap(); assert_eq!(elem, &F::from(3u64)); - assert_eq!(proof.tree_height(), 3); - assert!(RescueMerkleTree::::verify(&root, 0u64, &proof) + assert_eq!(proof.height(), 2); + assert!( + RescueMerkleTree::::verify(&commitment, 0u64, elem, &proof) + .unwrap() + .is_ok() + ); + + // Wrong element value, should fail. + assert!( + RescueMerkleTree::::verify(&commitment, 0, F::from(14u64), &proof) + .unwrap() + .is_err() + ); + + // Wrong pos, should fail. + assert!(RescueMerkleTree::::verify(&commitment, 1, elem, &proof) .unwrap() - .is_ok()); + .is_err()); let mut bad_proof = proof.clone(); - if let MerkleNode::Leaf { - value: _, - pos: _, - elem, - } = &mut bad_proof.proof[0] - { - *elem = F::from(4u64); - } else { - unreachable!() - } + bad_proof.0[0][0] = F::one(); - let result = RescueMerkleTree::::verify(&root, 0, &bad_proof); - assert!(result.unwrap().is_err()); - - let mut forge_proof = MerkleProof::new(2, proof.proof); - if let MerkleNode::Leaf { - value: _, - pos, - elem, - } = &mut forge_proof.proof[0] - { - *pos = 2; - *elem = F::from(0u64); - } else { - unreachable!() - } - let result = RescueMerkleTree::::verify(&root, 0, &forge_proof); - assert!(result.unwrap().is_err()); + assert!( + RescueMerkleTree::::verify(&commitment, 0, elem, &bad_proof) + .unwrap() + .is_err() + ); } #[test] @@ -213,55 +203,67 @@ mod mt_tests { } fn test_mt_forget_remember_helper() { - let mut mt = - RescueMerkleTree::::from_elems(Some(2), [F::from(3u64), F::from(1u64)]).unwrap(); - let root = mt.commitment().digest(); - let (lookup_elem, lookup_proof) = mt.lookup(0).expect_ok().unwrap(); - let lookup_elem = *lookup_elem; - let (elem, proof) = mt.forget(0).expect_ok().unwrap(); + let mut mt = RescueMerkleTree::::from_elems( + Some(2), + [F::from(3u64), F::from(1u64), F::from(2u64), F::from(5u64)], + ) + .unwrap(); + let commitment = mt.commitment(); + let (&lookup_elem, mut lookup_proof) = mt.lookup(3).expect_ok().unwrap(); + let (elem, proof) = mt.forget(3).expect_ok().unwrap(); assert_eq!(lookup_elem, elem); assert_eq!(lookup_proof, proof); - assert_eq!(elem, F::from(3u64)); - assert_eq!(proof.tree_height(), 3); - assert!(RescueMerkleTree::::verify(&root, 0, &lookup_proof) + assert_eq!(elem, F::from(5u64)); + assert_eq!(proof.height(), 2); + assert!( + RescueMerkleTree::::verify(&commitment, 3, elem, &lookup_proof) + .unwrap() + .is_ok() + ); + assert!(RescueMerkleTree::::verify(&commitment, 3, elem, &proof) .unwrap() .is_ok()); - assert!(RescueMerkleTree::::verify(&root, 0, &proof) + + assert!(mt.forget(3).expect_ok().is_err()); + assert!(matches!(mt.lookup(3), LookupResult::NotInMemory)); + + // Wrong element + assert!(mt.remember(3, F::from(19u64), &proof).is_err()); + // Wrong pos + assert!(mt.remember(1, elem, &proof).is_err()); + // Wrong proof + lookup_proof.0[0][0] = F::one(); + assert!(mt.remember(3, elem, &lookup_proof).is_err()); + + assert!(mt.remember(3, elem, &proof).is_ok()); + assert!(mt.lookup(3).expect_ok().is_ok()); + + // test another index + let (&lookup_elem, mut lookup_proof) = mt.lookup(0).expect_ok().unwrap(); + let (elem, proof) = mt.forget(0).expect_ok().unwrap(); + assert_eq!(lookup_elem, elem); + assert_eq!(lookup_proof, proof); + assert_eq!(elem, F::from(3u64)); + assert_eq!(proof.height(), 2); + assert!( + RescueMerkleTree::::verify(&commitment, 0, elem, &lookup_proof) + .unwrap() + .is_ok() + ); + assert!(RescueMerkleTree::::verify(&commitment, 0, elem, &proof) .unwrap() .is_ok()); assert!(mt.forget(0).expect_ok().is_err()); assert!(matches!(mt.lookup(0), LookupResult::NotInMemory)); - let mut bad_proof = proof.clone(); - if let MerkleNode::Leaf { - value: _, - pos: _, - elem, - } = &mut bad_proof.proof[0] - { - *elem = F::from(4u64); - } else { - unreachable!() - } - - let result = mt.remember(0, elem, &bad_proof); - assert!(result.is_err()); - - let mut forge_proof = MerkleProof::new(2, proof.proof.clone()); - if let MerkleNode::Leaf { - value: _, - pos, - elem, - } = &mut forge_proof.proof[0] - { - *pos = 2; - *elem = F::from(0u64); - } else { - unreachable!() - } - let result = mt.remember(2, elem, &forge_proof); - assert!(result.is_err()); + // Wrong element + assert!(mt.remember(0, F::from(19u64), &proof).is_err()); + // Wrong pos + assert!(mt.remember(1, elem, &proof).is_err()); + // Wrong proof + lookup_proof.0[0][0] = F::one(); + assert!(mt.remember(0, elem, &lookup_proof).is_err()); assert!(mt.remember(0, elem, &proof).is_ok()); assert!(mt.lookup(0).expect_ok().is_ok()); @@ -277,8 +279,7 @@ mod mt_tests { fn test_mt_serde_helper() { let mt = RescueMerkleTree::::from_elems(Some(2), [F::from(3u64), F::from(1u64)]).unwrap(); - let proof = mt.lookup(0).expect_ok().unwrap().1; - let node = &proof.proof[0]; + let (_, proof) = mt.lookup(0).expect_ok().unwrap(); assert_eq!( mt, @@ -288,10 +289,6 @@ mod mt_tests { proof, bincode::deserialize(&bincode::serialize(&proof).unwrap()).unwrap() ); - assert_eq!( - *node, - bincode::deserialize(&bincode::serialize(node).unwrap()).unwrap() - ); } #[test] diff --git a/merkle_tree/src/gadgets/mod.rs b/merkle_tree/src/gadgets/mod.rs index 17dc74ee8..5bb3bd686 100644 --- a/merkle_tree/src/gadgets/mod.rs +++ b/merkle_tree/src/gadgets/mod.rs @@ -14,9 +14,10 @@ mod universal_merkle_tree; use ark_std::{string::ToString, vec::Vec}; use crate::{ - internal::{MerkleNode, MerklePath, MerkleProof}, + internal::{MerkleNode, MerkleTreeProof}, prelude::RescueMerkleTree, - Element, Index, MerkleTreeScheme, NodeValue, ToTraversalPath, UniversalMerkleTreeScheme, + Element, Index, MerkleProof, MerkleTreeScheme, NodeValue, ToTraversalPath, + UniversalMerkleTreeScheme, }; use jf_rescue::RescueParameter; type NodeVal = as MerkleTreeScheme>::NodeValue; @@ -30,35 +31,39 @@ use jf_rescue::gadgets::RescueNativeGadget; /// use ark_bls12_377::Fq; /// use jf_merkle_tree::gadgets::MerkleTreeGadget; /// use jf_relation::{Circuit, PlonkCircuit}; -/// use jf_merkle_tree::{prelude::RescueMerkleTree, AppendableMerkleTreeScheme, MerkleTreeScheme, MerkleCommitment}; +/// use jf_merkle_tree::{prelude::RescueMerkleTree, AppendableMerkleTreeScheme, MerkleTreeScheme}; /// /// let mut circuit = PlonkCircuit::::new_turbo_plonk(); /// // Create a 3-ary MT, instantiated with a Rescue-based hash, of height 1. /// let elements = vec![Fq::from(1_u64), Fq::from(2_u64), Fq::from(100_u64)]; /// let mt = RescueMerkleTree::::from_elems(Some(1), elements).unwrap(); -/// let expected_root = mt.commitment().digest(); +/// let commitment = mt.commitment(); /// // Get a proof for the element in position 2 -/// let (_, proof) = mt.lookup(2).expect_ok().unwrap(); +/// let (elem, proof) = mt.lookup(2).expect_ok().unwrap(); /// /// // Circuit computation with a MT -/// let elem_idx = circuit.create_variable(2_u64.into()).unwrap(); +/// let pos = 2_u64; +/// let elem_idx = circuit.create_variable(pos.into()).unwrap(); +/// let elem_var = circuit.create_variable(*elem).unwrap(); /// let proof_var = /// MerkleTreeGadget::>::create_membership_proof_variable( /// &mut circuit, +/// &pos, /// &proof /// ) /// .unwrap(); -/// let root_var = -/// MerkleTreeGadget::>::create_root_variable( +/// let commitment_var = +/// MerkleTreeGadget::>::create_commitment_variable( /// &mut circuit, -/// expected_root +/// &commitment /// ) /// .unwrap(); /// MerkleTreeGadget::>::enforce_membership_proof( /// &mut circuit, /// elem_idx, -/// proof_var, -/// root_var +/// elem_var, +/// &proof_var, +/// commitment_var /// ) /// .unwrap(); /// assert!(circuit.check_circuit_satisfiability(&[]).is_ok()); @@ -79,31 +84,38 @@ where /// Allocate a variable for the membership proof. fn create_membership_proof_variable( &mut self, + pos: &M::Index, membership_proof: &M::MembershipProof, ) -> Result; /// Allocate a variable for the merkle root. - fn create_root_variable(&mut self, root: M::NodeValue) -> Result; + fn create_commitment_variable( + &mut self, + commitment: &M::Commitment, + ) -> Result; /// Given variables representing: /// * an element index + /// * the element itself /// * its merkle proof - /// * root + /// * Merkle commitment /// * return `BoolVar` indicating the correctness of its membership proof. fn is_member( &mut self, elem_idx_var: Variable, - proof_var: Self::MembershipProofVar, - root_var: Variable, + elem_var: Variable, + proof_var: &Self::MembershipProofVar, + commitment_var: Variable, ) -> Result; /// Enforce correct `proof_var` for the `elem_idx_var` against - /// `expected_root_var`. + /// `commitment_var`. fn enforce_membership_proof( &mut self, elem_idx_var: Variable, - proof_var: Self::MembershipProofVar, - expected_root_var: Variable, + elem_var: Variable, + proof_var: &Self::MembershipProofVar, + commitment_var: Variable, ) -> Result<(), CircuitError>; } @@ -115,7 +127,7 @@ where /// use ark_bls12_377::Fq; /// use jf_merkle_tree::gadgets::{MerkleTreeGadget, UniversalMerkleTreeGadget}; /// use jf_relation::{Circuit, PlonkCircuit}; -/// use jf_merkle_tree::{MerkleTreeScheme, MerkleCommitment, UniversalMerkleTreeScheme, +/// use jf_merkle_tree::{MerkleTreeScheme, UniversalMerkleTreeScheme, /// prelude::RescueSparseMerkleTree}; /// use hashbrown::HashMap; /// use num_bigint::BigUint; @@ -128,30 +140,32 @@ where /// hashmap.insert(BigUint::from(2u64), Fq::from(2u64)); /// hashmap.insert(BigUint::from(1u64), Fq::from(3u64)); /// let mt = SparseMerkleTree::::from_kv_set(2, &hashmap).unwrap(); -/// let expected_root = mt.commitment().digest(); -/// // Get a proof for the element in position 2 -/// let proof = mt.universal_lookup(&BigUint::from(3u64)).expect_not_found().unwrap(); +/// let commitment = mt.commitment(); +/// // Get a proof for the element in position 3 +/// let pos = BigUint::from(3u64); +/// let proof = mt.universal_lookup(&pos).expect_not_found().unwrap(); /// /// // Circuit computation with a MT -/// let non_elem_idx_var = circuit.create_variable(BigUint::from(3u64).into()).unwrap(); +/// let non_elem_idx_var = circuit.create_variable(pos.clone().into()).unwrap(); /// /// let proof_var = /// UniversalMerkleTreeGadget::>::create_non_membership_proof_variable( /// &mut circuit, +/// &pos, /// &proof /// ) /// .unwrap(); -/// let root_var = -/// MerkleTreeGadget::>::create_root_variable( +/// let commitment_var = +/// MerkleTreeGadget::>::create_commitment_variable( /// &mut circuit, -/// expected_root +/// &commitment /// ) /// .unwrap(); /// UniversalMerkleTreeGadget::>::enforce_non_membership_proof( /// &mut circuit, /// non_elem_idx_var, -/// proof_var, -/// root_var +/// &proof_var, +/// commitment_var /// ) /// .unwrap(); /// assert!(circuit.check_circuit_satisfiability(&[]).is_ok()); @@ -169,24 +183,25 @@ where /// Allocate a variable for the membership proof. fn create_non_membership_proof_variable( &mut self, - membership_proof: &M::NonMembershipProof, + pos: &M::Index, + non_membership_proof: &M::NonMembershipProof, ) -> Result; /// checking non-membership proof fn is_non_member( &mut self, non_elem_idx_var: Variable, - proof_var: Self::NonMembershipProofVar, - root_var: Variable, + proof_var: &Self::NonMembershipProofVar, + commitment_var: Variable, ) -> Result; /// Enforce correct `proof_var` for the empty elem `empty_elem_idx_var` - /// against `expected_root_var`. + /// against `expected_commitment_var`. fn enforce_non_membership_proof( &mut self, non_elem_idx_var: Variable, - proof_var: Self::NonMembershipProofVar, - expected_root_var: Variable, + proof_var: &Self::NonMembershipProofVar, + expected_commitment_var: Variable, ) -> Result<(), CircuitError>; } @@ -232,24 +247,12 @@ pub struct Merkle3AryNodeVar { is_right_child: BoolVar, } -/// Circuit variable for a Merkle non-membership proof of a 3-ary Merkle tree. -/// Contains: -/// * a list of node variables in the path, -/// * a variable correseponsing to the position of the element. -#[derive(Debug, Clone)] -pub struct Merkle3AryNonMembershipProofVar { - node_vars: Vec, - pos_var: Variable, -} - /// Circuit variable for a Merkle proof of a 3-ary Merkle tree. /// Contains: /// * a list of node variables in the path, -/// * a variable correseponsing to the value of the element. #[derive(Debug, Clone)] -pub struct Merkle3AryMembershipProofVar { +pub struct Merkle3AryProofVar { node_vars: Vec, - elem_var: Variable, } /// Circuit counterpart to DigestAlgorithm pub trait DigestAlgorithmGadget @@ -285,86 +288,30 @@ impl DigestAlgorithmGadget for RescueDigestGadget { } } -/// Proof of membership -pub trait MembershipProof -where - E: Element, - I: Index, - T: NodeValue, -{ - /// Get the tree height - fn tree_height(&self) -> usize; - /// Get index of element - fn index(&self) -> &I; - /// Get the element - fn elem(&self) -> Option<&E>; - /// Get the merkle path to the element - fn merkle_path(&self) -> &MerklePath; -} - -impl MembershipProof for MerkleProof -where - E: Element, - I: Index, - T: NodeValue, -{ - fn tree_height(&self) -> usize { - self.tree_height() - } - fn index(&self) -> &I { - self.index() - } - fn elem(&self) -> Option<&E> { - self.elem() - } - fn merkle_path(&self) -> &MerklePath { - &self.proof - } -} - -impl MerkleTreeGadget for PlonkCircuit +impl MerkleTreeGadget for PlonkCircuit where - T: MerkleTreeScheme, - T::MembershipProof: MembershipProof, - T::NodeValue: PrimeField + RescueParameter, + T: MerkleTreeScheme, + F: PrimeField + RescueParameter, T::Index: ToTraversalPath<3>, { - type MembershipProofVar = Merkle3AryMembershipProofVar; + type MembershipProofVar = Merkle3AryProofVar; type DigestGadget = RescueDigestGadget; fn create_membership_proof_variable( &mut self, + pos: &::Index, merkle_proof: &::MembershipProof, - ) -> Result { - let path = merkle_proof - .index() - .to_traversal_path(merkle_proof.tree_height() - 1); - - let elem = match merkle_proof.elem() { - Some(elem) => elem, - None => { - return Err(CircuitError::InternalError( - "The proof doesn't contain a leaf element".to_string(), - )) - }, - }; - - let elem_var = self.create_variable(*elem)?; + ) -> Result { + let path = pos.to_traversal_path(merkle_proof.height()); let nodes = path .iter() - .zip(merkle_proof.merkle_path().iter().skip(1)) - .filter_map(|(branch, node)| match node { - MerkleNode::Branch { value: _, children } => Some((children, branch)), - _ => None, - }) - .map(|(children, branch)| { - let sib_branch1 = if branch == &0 { 1 } else { 0 }; - let sib_branch2 = if branch == &2 { 1 } else { 2 }; + .zip(merkle_proof.path_values()) + .map(|(branch, siblings)| { Ok(Merkle3AryNodeVar { - sibling1: self.create_variable(children[sib_branch1].value())?, - sibling2: self.create_variable(children[sib_branch2].value())?, + sibling1: self.create_variable(siblings[0])?, + sibling2: self.create_variable(siblings[1])?, is_left_child: self.create_boolean_variable(branch == &0)?, is_right_child: self.create_boolean_variable(branch == &2)?, }) @@ -381,31 +328,26 @@ where self.enforce_bool(left_plus_right)?; } - Ok(Merkle3AryMembershipProofVar { - node_vars: nodes, - elem_var, - }) + Ok(Merkle3AryProofVar { node_vars: nodes }) } - fn create_root_variable( + fn create_commitment_variable( &mut self, - root: NodeVal, + commitment: &::Commitment, ) -> Result { - self.create_variable(root) + self.create_variable(*commitment) } fn is_member( &mut self, elem_idx_var: Variable, - proof_var: Merkle3AryMembershipProofVar, - root_var: Variable, + elem_var: Variable, + proof_var: &Merkle3AryProofVar, + commitment_var: Variable, ) -> Result { - let computed_root_var = { - let proof_var = &proof_var; - + let computed_commitment_var = { // elem label = H(0, uid, elem) - let mut cur_label = - Self::DigestGadget::digest_leaf(self, elem_idx_var, proof_var.elem_var)?; + let mut cur_label = Self::DigestGadget::digest_leaf(self, elem_idx_var, elem_var)?; for cur_node in proof_var.node_vars.iter() { let input_labels = constrain_sibling_order( self, @@ -421,17 +363,23 @@ where } Ok(cur_label) }?; - self.is_equal(root_var, computed_root_var) + self.is_equal(commitment_var, computed_commitment_var) } fn enforce_membership_proof( &mut self, elem_idx_var: Variable, - proof_var: Merkle3AryMembershipProofVar, - expected_root_var: Variable, + elem_var: Variable, + proof_var: &Merkle3AryProofVar, + commitment_var: Variable, ) -> Result<(), CircuitError> { - let bool_val = - MerkleTreeGadget::::is_member(self, elem_idx_var, proof_var, expected_root_var)?; + let bool_val = MerkleTreeGadget::::is_member( + self, + elem_idx_var, + elem_var, + proof_var, + commitment_var, + )?; self.enforce_true(bool_val.into()) } } @@ -439,10 +387,10 @@ where #[cfg(test)] mod test { use crate::{ - gadgets::{constrain_sibling_order, Merkle3AryMembershipProofVar, MerkleTreeGadget}, + gadgets::{constrain_sibling_order, Merkle3AryProofVar, MerkleTreeGadget}, internal::MerkleNode, prelude::RescueMerkleTree, - MerkleCommitment, MerkleTreeScheme, + MerkleTreeScheme, }; use alloc::sync::Arc; use ark_bls12_377::Fq as Fq377; @@ -560,36 +508,40 @@ mod test { let mut circuit = PlonkCircuit::::new_turbo_plonk(); let mut elements = (1u64..=9u64).map(|x| F::from(x)).collect::>(); elements[uid as usize] = elem; - let mt = RescueMerkleTree::::from_elems(Some(2), elements).unwrap(); - let expected_root = mt.commitment().digest(); + let mt = RescueMerkleTree::::from_elems(Some(2), &elements).unwrap(); + let commitment = mt.commitment(); let (retrieved_elem, proof) = mt.lookup(uid).expect_ok().unwrap(); assert_eq!(retrieved_elem, &elem); // Happy path // Circuit computation with a MT let elem_idx_var: Variable = circuit.create_variable(uid.into()).unwrap(); - let proof_var: Merkle3AryMembershipProofVar = + let elem_var: Variable = circuit.create_variable(elements[uid as usize]).unwrap(); + let proof_var = MerkleTreeGadget::>::create_membership_proof_variable( &mut circuit, + &uid, &proof, ) .unwrap(); - let root_var = MerkleTreeGadget::>::create_root_variable( - &mut circuit, - expected_root, - ) - .unwrap(); + let commitment_var = + MerkleTreeGadget::>::create_commitment_variable( + &mut circuit, + &commitment, + ) + .unwrap(); MerkleTreeGadget::>::enforce_membership_proof( &mut circuit, elem_idx_var, - proof_var, - root_var, + elem_var, + &proof_var, + commitment_var, ) .unwrap(); assert!(circuit.check_circuit_satisfiability(&[]).is_ok()); - *circuit.witness_mut(root_var) = F::zero(); + *circuit.witness_mut(commitment_var) = F::zero(); assert!(circuit.check_circuit_satisfiability(&[]).is_err()); // Bad path: @@ -599,28 +551,28 @@ mod test { let elem_idx_var: Variable = circuit.create_variable(uid.into()).unwrap(); let mut bad_proof = proof.clone(); + bad_proof.0[1][0] = F::zero(); - if let MerkleNode::Branch { value: _, children } = &mut bad_proof.proof[1] { - let left_sib = if uid % 3 == 0 { 1 } else { 0 }; - children[left_sib] = Arc::new(MerkleNode::ForgettenSubtree { value: F::zero() }); - } - let path_vars: Merkle3AryMembershipProofVar = + let proof_var = MerkleTreeGadget::>::create_membership_proof_variable( &mut circuit, + &uid, &bad_proof, ) .unwrap(); - let root_var = MerkleTreeGadget::>::create_root_variable( - &mut circuit, - expected_root, - ) - .unwrap(); + let commitment_var = + MerkleTreeGadget::>::create_commitment_variable( + &mut circuit, + &commitment, + ) + .unwrap(); MerkleTreeGadget::>::enforce_membership_proof( &mut circuit, elem_idx_var, - path_vars, - root_var, + elem_var, + &proof_var, + commitment_var, ) .unwrap(); diff --git a/merkle_tree/src/gadgets/universal_merkle_tree.rs b/merkle_tree/src/gadgets/universal_merkle_tree.rs index d9da32e5f..41967cbf8 100644 --- a/merkle_tree/src/gadgets/universal_merkle_tree.rs +++ b/merkle_tree/src/gadgets/universal_merkle_tree.rs @@ -8,7 +8,9 @@ //! with a Rescue hash function. use crate::{ - internal::MerkleNode, prelude::RescueSparseMerkleTree, MerkleTreeScheme, ToTraversalPath, + internal::MerkleNode, + prelude::{MerkleTreeProof, RescueSparseMerkleTree}, + MerkleProof, MerkleTreeScheme, ToTraversalPath, }; use ark_std::vec::Vec; use jf_relation::{BoolVar, Circuit, CircuitError, PlonkCircuit, Variable}; @@ -16,8 +18,8 @@ use jf_rescue::RescueParameter; type SparseMerkleTree = RescueSparseMerkleTree; use super::{ - constrain_sibling_order, DigestAlgorithmGadget, Merkle3AryNodeVar, - Merkle3AryNonMembershipProofVar, UniversalMerkleTreeGadget, + constrain_sibling_order, DigestAlgorithmGadget, Merkle3AryNodeVar, Merkle3AryProofVar, + UniversalMerkleTreeGadget, }; use num_bigint::BigUint; @@ -25,20 +27,17 @@ impl UniversalMerkleTreeGadget> for PlonkCircuit where F: RescueParameter, { - type NonMembershipProofVar = Merkle3AryNonMembershipProofVar; + type NonMembershipProofVar = Merkle3AryProofVar; fn is_non_member( &mut self, non_elem_idx_var: Variable, - proof_var: Self::NonMembershipProofVar, - root_var: Variable, + proof_var: &Merkle3AryProofVar, + commitment_var: Variable, ) -> Result { - // constrain that the element's index is part of the proof - self.enforce_equal(proof_var.pos_var, non_elem_idx_var)?; - let computed_root_var = { - let path_vars = &proof_var; + let computed_commitment_var = { let mut cur_label = self.zero(); - for cur_node in path_vars.node_vars.iter() { + for cur_node in proof_var.node_vars.iter() { let input_labels = constrain_sibling_order( self, cur_label, @@ -47,45 +46,43 @@ where cur_node.is_left_child, cur_node.is_right_child, )?; - // check that the left child's label is non-zero - self.non_zero_gate(input_labels[0])?; + let is_zero_vars = [ + self.is_zero(input_labels[0])?, + self.is_zero(input_labels[1])?, + self.is_zero(input_labels[2])?, + ]; cur_label = Self::DigestGadget::digest(self, &input_labels)?; } Ok(cur_label) }?; - self.is_equal(computed_root_var, root_var) + self.is_equal(computed_commitment_var, commitment_var) } fn enforce_non_membership_proof( &mut self, non_elem_idx_var: Variable, - proof_var: Self::NonMembershipProofVar, - expected_root_var: Variable, + proof_var: &Merkle3AryProofVar, + expected_commitment_var: Variable, ) -> Result<(), CircuitError> { - let bool_val = self.is_non_member(non_elem_idx_var, proof_var, expected_root_var)?; + let bool_val = self.is_non_member(non_elem_idx_var, proof_var, expected_commitment_var)?; self.enforce_true(bool_val.into()) } fn create_non_membership_proof_variable( &mut self, - merkle_proof: & as MerkleTreeScheme>::MembershipProof, - ) -> Result { - let path = >::to_traversal_path( - &merkle_proof.pos, - merkle_proof.tree_height() - 1, - ); + pos: & as MerkleTreeScheme>::Index, + merkle_proof: &MerkleTreeProof, + ) -> Result { + let path = >::to_traversal_path(&pos, merkle_proof.height()); let nodes = path .iter() - .zip(merkle_proof.proof.iter().skip(1)) - .filter_map(|(branch, node)| match node { - MerkleNode::Branch { value: _, children } => Some((children, branch)), - _ => None, - }) - .map(|(children, branch)| { + .zip(merkle_proof.path_values().iter()) + .filter(|(_, v)| v.len() > 0) + .map(|(branch, siblings)| { Ok(Merkle3AryNodeVar { - sibling1: self.create_variable(children[0].value())?, - sibling2: self.create_variable(children[1].value())?, + sibling1: self.create_variable(siblings[0])?, + sibling2: self.create_variable(siblings[1])?, is_left_child: self.create_boolean_variable(branch == &0)?, is_right_child: self.create_boolean_variable(branch == &2)?, }) @@ -102,12 +99,7 @@ where self.enforce_bool(left_plus_right)?; } - let pos = self.create_variable(merkle_proof.pos.clone().into())?; - - Ok(Self::NonMembershipProofVar { - node_vars: nodes, - pos_var: pos, - }) + Ok(Merkle3AryProofVar { node_vars: nodes }) } } @@ -116,7 +108,7 @@ mod test { use crate::{ gadgets::{MerkleTreeGadget, UniversalMerkleTreeGadget}, prelude::RescueSparseMerkleTree, - MerkleCommitment, MerkleTreeScheme, UniversalMerkleTreeScheme, + MerkleTreeScheme, UniversalMerkleTreeScheme, }; use ark_bls12_377::Fq as Fq377; use ark_ed_on_bls12_377::Fq as FqEd377; @@ -153,62 +145,65 @@ mod test { hashmap.insert(BigUint::from(2u64), F::from(2u64)); hashmap.insert(BigUint::from(1u64), F::from(3u64)); let mt = SparseMerkleTree::::from_kv_set(2, &hashmap).unwrap(); - let expected_root = mt.commitment().digest(); + let commitment = mt.commitment(); // proof of non-membership let proof = mt.universal_lookup(&uid).expect_not_found().unwrap(); // Circuit computation with a MT - let non_elem_idx_var = circuit.create_variable(uid.into()).unwrap(); + let non_elem_idx_var = circuit.create_variable(uid.clone().into()).unwrap(); let proof_var = UniversalMerkleTreeGadget::>::create_non_membership_proof_variable( &mut circuit, + &uid.into(), &proof, ) .unwrap(); - let root_var = MerkleTreeGadget::>::create_root_variable( + let commitment_var = MerkleTreeGadget::>::create_commitment_variable( &mut circuit, - expected_root, + &commitment, ) .unwrap(); UniversalMerkleTreeGadget::>::enforce_non_membership_proof( &mut circuit, non_elem_idx_var, - proof_var, - root_var, + &proof_var, + commitment_var, ) .unwrap(); assert!(circuit.check_circuit_satisfiability(&[]).is_ok()); - *circuit.witness_mut(root_var) = F::zero(); + *circuit.witness_mut(commitment_var) = F::zero(); assert!(circuit.check_circuit_satisfiability(&[]).is_err()); // Bad path: // The circuit cannot be satisfied if we try to prove non-membership of an // existing element. let mut circuit = PlonkCircuit::::new_turbo_plonk(); - let elem_idx_var = circuit.create_variable(2u64.into()).unwrap(); + let pos = 2u64; + let elem_idx_var = circuit.create_variable(pos.into()).unwrap(); - let path_vars = + let proof_var = UniversalMerkleTreeGadget::>::create_non_membership_proof_variable( &mut circuit, + &pos.into(), &proof, ) .unwrap(); - let root_var = MerkleTreeGadget::>::create_root_variable( + let commitment_var = MerkleTreeGadget::>::create_commitment_variable( &mut circuit, - expected_root, + &commitment, ) .unwrap(); UniversalMerkleTreeGadget::>::enforce_non_membership_proof( &mut circuit, elem_idx_var, - path_vars, - root_var, + &proof_var, + commitment_var, ) .unwrap(); diff --git a/merkle_tree/src/hasher.rs b/merkle_tree/src/hasher.rs index cd92fc9d8..44b2bb1a3 100644 --- a/merkle_tree/src/hasher.rs +++ b/merkle_tree/src/hasher.rs @@ -8,7 +8,7 @@ //! //! ``` //! # use jf_merkle_tree::errors::MerkleTreeError; -//! use jf_merkle_tree::{hasher::HasherMerkleTree, AppendableMerkleTreeScheme, MerkleCommitment, MerkleTreeScheme}; +//! use jf_merkle_tree::{hasher::HasherMerkleTree, AppendableMerkleTreeScheme, MerkleTreeScheme}; //! use sha2::Sha256; //! //! # fn main() -> Result<(), MerkleTreeError> { @@ -17,10 +17,10 @@ //! // payload type is `usize`, hash function is `Sha256`. //! let mt = HasherMerkleTree::::from_elems(Some(2), &my_data)?; //! -//! let root = mt.commitment().digest(); +//! let commitment = mt.commitment(); //! let (val, proof) = mt.lookup(2).expect_ok()?; //! assert_eq!(val, &3); -//! assert!(HasherMerkleTree::::verify(root, 2, proof)?.is_ok()); +//! assert!(HasherMerkleTree::::verify(commitment, 2, val, proof)?.is_ok()); //! # Ok(()) //! # } //! ``` diff --git a/merkle_tree/src/internal.rs b/merkle_tree/src/internal.rs index d07f9e22f..47eb5bebd 100644 --- a/merkle_tree/src/internal.rs +++ b/merkle_tree/src/internal.rs @@ -4,10 +4,8 @@ // You should have received a copy of the MIT License // along with the Jellyfish library. If not, see . -use super::{ - DigestAlgorithm, Element, Index, LookupResult, MerkleCommitment, NodeValue, ToTraversalPath, -}; -use crate::{errors::MerkleTreeError, VerificationResult}; +use super::{DigestAlgorithm, Element, Index, LookupResult, NodeValue, ToTraversalPath}; +use crate::{errors::MerkleTreeError, prelude::MerkleTree, VerificationResult, FAIL, SUCCESS}; use alloc::sync::Arc; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::{borrow::Borrow, format, iter::Peekable, string::ToString, vec, vec::Vec}; @@ -46,7 +44,7 @@ pub enum MerkleNode { elem: E, }, /// The subtree is forgotten from the memory - ForgettenSubtree { + ForgottenSubtree { /// Merkle hash value of this forgotten subtree #[serde(with = "canonical")] value: T, @@ -70,113 +68,82 @@ where elem: _, } => *value, Self::Branch { value, children: _ } => *value, - Self::ForgettenSubtree { value } => *value, + Self::ForgottenSubtree { value } => *value, } } #[inline] pub(crate) fn is_forgotten(&self) -> bool { - matches!(self, Self::ForgettenSubtree { .. }) + matches!(self, Self::ForgottenSubtree { .. }) } } -/// A merkle path is a bottom-up list of nodes from leaf to the root. -pub type MerklePath = Vec>; - -/// A merkle commitment consists a root hash value, a tree height and number of -/// leaves +/// A (non)membership Merkle proof consists of all values of siblings of a +/// Merkle path. #[derive( - Eq, - PartialEq, - Clone, - Copy, - Debug, - Ord, - PartialOrd, - Hash, - CanonicalSerialize, - CanonicalDeserialize, + Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, CanonicalSerialize, CanonicalDeserialize, )] -#[tagged("MERKLE_COMM")] -pub struct MerkleTreeCommitment { - /// Root of a tree - digest: T, - /// Height of a tree - height: usize, - /// Number of leaves in the tree - num_leaves: u64, -} - -impl MerkleTreeCommitment { - pub fn new(digest: T, height: usize, num_leaves: u64) -> Self { - MerkleTreeCommitment { - digest, - height, - num_leaves, - } - } -} - -impl MerkleCommitment for MerkleTreeCommitment { - fn digest(&self) -> T { - self.digest - } +#[tagged("MERKLE_PROOF")] +pub struct MerkleTreeProof(pub Vec>); +impl super::MerkleProof for MerkleTreeProof { + /// Expected height of the Merkle tree. fn height(&self) -> usize { - self.height + self.0.len() } - fn size(&self) -> u64 { - self.num_leaves + /// Return all values of siblings of this Merkle path + fn path_values(&self) -> &[Vec] { + &self.0 } } -/// Merkle proof struct. -#[derive(Derivative, Debug, Clone, Serialize, Deserialize)] -#[derivative(Eq, Hash, PartialEq)] -#[serde(bound = "E: CanonicalSerialize + CanonicalDeserialize, - I: CanonicalSerialize + CanonicalDeserialize,")] -pub struct MerkleProof +/// Verify a merkle proof +/// * `commitment` - a merkle tree commitment +/// * `pos` - zero-based index of the leaf in the tree +/// * `element` - the leaf value, None if verifying a non-membership proof +/// * `proof` - a membership proof for `element` at given `pos` +/// * `returns` - Ok(true) if the proof is accepted, Ok(false) if not. Err() if +/// the proof is not well structured, E.g. not for this merkle tree. +pub(crate) fn verify_merkle_proof( + commitment: &T, + pos: &I, + element: Option<&E>, + proof: &[Vec], +) -> Result where E: Element, - I: Index, - T: NodeValue, -{ - /// Proof of inclusion for element at index `pos` - #[serde(with = "canonical")] - pub pos: I, - /// Nodes of proof path, from root to leaf - pub proof: MerklePath, -} - -impl MerkleProof -where - E: Element, - I: Index, + I: Index + ToTraversalPath, T: NodeValue, + H: DigestAlgorithm, { - /// Return the height of this proof. - pub fn tree_height(&self) -> usize { - self.proof.len() - } - - /// Form a `MerkleProof` from a given index and Merkle path. - pub fn new(pos: I, proof: MerklePath) -> Self { - MerkleProof { pos, proof } - } - - /// Return the index of this `MerkleProof`. - pub fn index(&self) -> &I { - &self.pos - } - - /// Return the element associated with this `MerkleProof`. None if it's a - /// non-membership proof. - pub fn elem(&self) -> Option<&E> { - match self.proof.first() { - Some(MerkleNode::Leaf { elem, .. }) => Some(elem), - _ => None, - } + let init = if let Some(elem) = element { + H::digest_leaf(pos, elem)? + } else { + T::default() + }; + let mut data = [T::default(); ARITY]; + let computed_root = pos + .to_traversal_path(proof.len()) + .iter() + .zip(proof.iter()) + .try_fold( + init, + |val, (branch, values)| -> Result { + if values.len() == 0 { + Ok(T::default()) + } else { + data[..*branch].copy_from_slice(&values[..*branch]); + data[*branch] = val; + data[*branch + 1..].copy_from_slice(&values[*branch..]); + H::digest(&data) + } + }, + )?; + if computed_root == *commitment { + Ok(SUCCESS) + } else { + Ok(FAIL) } } @@ -247,9 +214,9 @@ where .chunks(ARITY) .into_iter() .map(|chunk| { - let children = chunk + let children: Vec<_> = chunk .pad_using(ARITY, |_| Arc::new(MerkleNode::::Empty)) - .collect::>(); + .collect(); Ok(Arc::new(MerkleNode::::Branch { value: digest_branch::(&children)?, children, @@ -311,7 +278,7 @@ where .map(|(pos, elem)| { let pos = pos as u64; Ok(if pos < num_leaves - 1 { - Arc::new(MerkleNode::ForgettenSubtree { + Arc::new(MerkleNode::ForgottenSubtree { value: H::digest_leaf(&pos, elem.borrow())?, }) } else { @@ -331,7 +298,7 @@ where }) .collect::, MerkleTreeError>>()?; for i in 1..cur_nodes.len() - 1 { - cur_nodes[i] = Arc::new(MerkleNode::ForgettenSubtree { + cur_nodes[i] = Arc::new(MerkleNode::ForgottenSubtree { value: cur_nodes[i].value(), }) } @@ -351,7 +318,7 @@ where }) .collect::, MerkleTreeError>>()?; for i in 1..cur_nodes.len() - 1 { - cur_nodes[i] = Arc::new(MerkleNode::ForgettenSubtree { + cur_nodes[i] = Arc::new(MerkleNode::ForgottenSubtree { value: cur_nodes[i].value(), }) } @@ -390,44 +357,37 @@ where traversal_path: &[usize], ) -> ( Arc, - LookupResult, MerklePath>, + LookupResult, MerkleTreeProof>, ) { match self { MerkleNode::Empty => ( Arc::new(self.clone()), - LookupResult::NotFound(vec![MerkleNode::Empty; height + 1]), + LookupResult::NotFound(MerkleTreeProof(vec![])), ), MerkleNode::Branch { value, children } => { let mut children = children.clone(); let (new_child, result) = children[traversal_path[height - 1]] .forget_internal(height - 1, traversal_path); match result { - LookupResult::Ok(elem, mut proof) => { - proof.push(MerkleNode::Branch { - value: T::default(), - children: children + LookupResult::Ok(elem, mut membership_proof) => { + membership_proof.0.push( + children .iter() - .map(|child| { - if let MerkleNode::Empty = **child { - Arc::new(MerkleNode::Empty) - } else { - Arc::new(MerkleNode::ForgettenSubtree { - value: child.value(), - }) - } - }) + .enumerate() + .filter(|(id, _)| *id != traversal_path[height - 1]) + .map(|(_, child)| child.value()) .collect::>(), - }); + ); children[traversal_path[height - 1]] = new_child; if children.iter().all(|child| { matches!( **child, - MerkleNode::Empty | MerkleNode::ForgettenSubtree { .. } + MerkleNode::Empty | MerkleNode::ForgottenSubtree { .. } ) }) { ( - Arc::new(MerkleNode::ForgettenSubtree { value: *value }), - LookupResult::Ok(elem, proof), + Arc::new(MerkleNode::ForgottenSubtree { value: *value }), + LookupResult::Ok(elem, membership_proof), ) } else { ( @@ -435,7 +395,7 @@ where value: *value, children, }), - LookupResult::Ok(elem, proof), + LookupResult::Ok(elem, membership_proof), ) } }, @@ -443,21 +403,14 @@ where (Arc::new(self.clone()), LookupResult::NotInMemory) }, LookupResult::NotFound(mut non_membership_proof) => { - non_membership_proof.push(MerkleNode::Branch { - value: T::default(), - children: children + non_membership_proof.0.push( + children .iter() - .map(|child| { - if let MerkleNode::Empty = **child { - Arc::new(MerkleNode::Empty) - } else { - Arc::new(MerkleNode::ForgettenSubtree { - value: child.value(), - }) - } - }) + .enumerate() + .filter(|(id, _)| *id != traversal_path[height - 1]) + .map(|(_, child)| child.value()) .collect::>(), - }); + ); ( Arc::new(self.clone()), LookupResult::NotFound(non_membership_proof), @@ -465,87 +418,96 @@ where }, } }, - MerkleNode::Leaf { value, pos, elem } => { - let elem = elem.clone(); - let proof = vec![MerkleNode::::Leaf { - value: *value, - pos: pos.clone(), - elem: elem.clone(), - }]; - ( - Arc::new(MerkleNode::ForgettenSubtree { value: *value }), - LookupResult::Ok(elem, proof), - ) - }, + MerkleNode::Leaf { value, pos, elem } => ( + Arc::new(MerkleNode::ForgottenSubtree { value: *value }), + LookupResult::Ok(elem.clone(), MerkleTreeProof(vec![])), + ), _ => (Arc::new(self.clone()), LookupResult::NotInMemory), } } - /// Re-insert a forgotten leaf to the Merkle tree. We assume that the proof - /// is valid and already checked. + /// Re-insert a forgotten leaf to the Merkle tree. + /// It also fails if the Merkle proof is invalid. pub(crate) fn remember_internal( &self, height: usize, traversal_path: &[usize], - path_values: &[T], - proof: &[MerkleNode], + pos: &I, + element: Option<&E>, + proof: &[Vec], ) -> Result, MerkleTreeError> where H: DigestAlgorithm, { - if self.value() != path_values[height] { - return Err(MerkleTreeError::InconsistentStructureError(format!( - "Invalid proof. Hash differs at height {}: (expected: {:?}, received: {:?})", - height, - self.value(), - path_values[height] - ))); - } - - match (self, &proof[height]) { - (Self::ForgettenSubtree { value }, Self::Branch { children, .. }) => { - // Recurse into the appropriate sub-tree to remember the rest of the path. - let mut children = children.clone(); - children[traversal_path[height - 1]] = children[traversal_path[height - 1]] - .remember_internal::( - height - 1, - traversal_path, - path_values, - proof, - )?; - // Remember `*self`. - Ok(Arc::new(Self::Branch { - value: *value, - children, - })) - }, - (Self::ForgettenSubtree { .. }, node) => { - // Replace forgotten sub-tree with a hopefully-less-forgotten sub-tree from the - // proof. Safe because we already checked our hash value matches the proof. - Ok(Arc::new(node.clone())) + match self { + MerkleNode::Empty => Ok(Arc::new(self.clone())), + MerkleNode::Leaf { + value, + pos: leaf_pos, + elem, + } => { + if height != 0 { + // Reach a leaf before it should + Err(MerkleTreeError::InconsistentStructureError( + "Malformed Merkle tree or proof".to_string(), + )) + } else { + Ok(Arc::new(self.clone())) + } }, - (Self::Branch { value, children }, Self::Branch { .. }) => { - let mut children = children.clone(); - children[traversal_path[height - 1]] = children[traversal_path[height - 1]] - .remember_internal::( + MerkleNode::Branch { value, children } => { + if height == 0 { + // Reach a branch when there should be a leaf + Err(MerkleTreeError::InconsistentStructureError( + "Malformed merkle tree".to_string(), + )) + } else { + let branch = traversal_path[height - 1]; + let mut children = children.clone(); + children[branch] = children[branch].remember_internal::( height - 1, traversal_path, - path_values, + pos, + element, proof, )?; - Ok(Arc::new(Self::Branch { - value: *value, - children, - })) - }, - (Self::Leaf { .. }, Self::Leaf { .. }) | (Self::Empty, Self::Empty) => { - // This node is already a complete sub-tree, so there's nothing to remember. The - // proof matches, so just return success. - Ok(Arc::new(self.clone())) + Ok(Arc::new(MerkleNode::Branch { + value: *value, + children, + })) + } }, - (..) => Err(MerkleTreeError::InconsistentStructureError( - "Invalid proof".into(), - )), + MerkleNode::ForgottenSubtree { value } => Ok(Arc::new(if height == 0 { + if let Some(element) = element { + MerkleNode::Leaf { + value: H::digest_leaf(pos, element)?, + pos: pos.clone(), + elem: element.clone(), + } + } else { + MerkleNode::Empty + } + } else { + let branch = traversal_path[height - 1]; + let mut values = proof[height - 1].clone(); + values.insert(branch, *value); + let mut children = values + .iter() + .map(|&value| Arc::new(MerkleNode::ForgottenSubtree { value })) + .collect::>(); + children[branch] = children[branch].remember_internal::( + height - 1, + traversal_path, + pos, + element, + proof, + )?; + values[branch] = children[branch].value(); + MerkleNode::Branch { + value: H::digest(&values)?, + children, + } + })), } } @@ -557,50 +519,34 @@ where &self, height: usize, traversal_path: &[usize], - ) -> LookupResult<&E, MerklePath, MerklePath> { + ) -> LookupResult<&E, MerkleTreeProof, MerkleTreeProof> { match self { - MerkleNode::Empty => { - LookupResult::NotFound(vec![MerkleNode::::Empty; height + 1]) - }, + MerkleNode::Empty => LookupResult::NotFound(MerkleTreeProof(vec![vec![]; height])), MerkleNode::Branch { value: _, children } => { match children[traversal_path[height - 1]] .lookup_internal(height - 1, traversal_path) { - LookupResult::Ok(elem, mut proof) => { - proof.push(MerkleNode::Branch { - value: T::default(), - children: children + LookupResult::Ok(elem, mut membership_proof) => { + membership_proof.0.push( + children .iter() - .map(|child| { - if let MerkleNode::Empty = **child { - Arc::new(MerkleNode::Empty) - } else { - Arc::new(MerkleNode::ForgettenSubtree { - value: child.value(), - }) - } - }) + .enumerate() + .filter(|(id, _)| *id != traversal_path[height - 1]) + .map(|(_, child)| child.value()) .collect::>(), - }); - LookupResult::Ok(elem, proof) + ); + LookupResult::Ok(elem, membership_proof) }, LookupResult::NotInMemory => LookupResult::NotInMemory, LookupResult::NotFound(mut non_membership_proof) => { - non_membership_proof.push(MerkleNode::Branch { - value: T::default(), - children: children + non_membership_proof.0.push( + children .iter() - .map(|child| { - if let MerkleNode::Empty = **child { - Arc::new(MerkleNode::Empty) - } else { - Arc::new(MerkleNode::ForgettenSubtree { - value: child.value(), - }) - } - }) + .enumerate() + .filter(|(id, _)| *id != traversal_path[height - 1]) + .map(|(_, child)| child.value()) .collect::>(), - }); + ); LookupResult::NotFound(non_membership_proof) }, } @@ -609,7 +555,7 @@ where elem, value: _, pos: _, - } => LookupResult::Ok(elem, vec![self.clone()]), + } => LookupResult::Ok(elem, MerkleTreeProof(vec![])), _ => LookupResult::NotInMemory, } } @@ -662,7 +608,7 @@ where )?; let mut children = children.clone(); children[branch] = result.0; - if matches!(*children[branch], MerkleNode::ForgettenSubtree { .. }) { + if matches!(*children[branch], MerkleNode::ForgottenSubtree { .. }) { // If the branch containing the update was forgotten by // user, the update failed and nothing was changed, so we // can short-circuit without recomputing this node's value. @@ -714,9 +660,7 @@ where } } else { let branch = traversal_path[height - 1]; - let mut children = (0..ARITY) - .map(|_| Arc::new(Self::Empty)) - .collect::>(); + let mut children: Vec<_> = (0..ARITY).map(|_| Arc::new(Self::Empty)).collect(); // Inserting new leave here, shortcutting let result = children[branch].update_with_internal::( height - 1, @@ -740,7 +684,7 @@ where } } }, - MerkleNode::ForgettenSubtree { .. } => Err(MerkleTreeError::ForgottenLeaf), + MerkleNode::ForgottenSubtree { .. } => Err(MerkleTreeError::ForgottenLeaf), } } } @@ -812,7 +756,7 @@ where 0 }; let cap = ARITY; - let mut children = (0..cap).map(|_| Arc::new(Self::Empty)).collect::>(); + let mut children: Vec<_> = (0..cap).map(|_| Arc::new(Self::Empty)).collect(); while data.peek().is_some() && frontier < cap { let (new_child, increment) = children[frontier] .extend_internal::( @@ -837,7 +781,7 @@ where } }, MerkleNode::Leaf { .. } => Err(MerkleTreeError::ExistingLeaf), - MerkleNode::ForgettenSubtree { .. } => Err(MerkleTreeError::ForgottenLeaf), + MerkleNode::ForgottenSubtree { .. } => Err(MerkleTreeError::ForgottenLeaf), } } @@ -871,7 +815,7 @@ where while data.peek().is_some() && frontier < cap { if frontier > 0 && !children[frontier - 1].is_forgotten() { children[frontier - 1] = - Arc::new(MerkleNode::::ForgettenSubtree { + Arc::new(MerkleNode::::ForgottenSubtree { value: children[frontier - 1].value(), }); } @@ -911,11 +855,11 @@ where 0 }; let cap = ARITY; - let mut children = (0..cap).map(|_| Arc::new(Self::Empty)).collect::>(); + let mut children: Vec<_> = (0..cap).map(|_| Arc::new(Self::Empty)).collect(); while data.peek().is_some() && frontier < cap { if frontier > 0 && !children[frontier - 1].is_forgotten() { children[frontier - 1] = - Arc::new(MerkleNode::::ForgettenSubtree { + Arc::new(MerkleNode::::ForgottenSubtree { value: children[frontier - 1].value(), }); } @@ -942,98 +886,7 @@ where } }, MerkleNode::Leaf { .. } => Err(MerkleTreeError::ExistingLeaf), - MerkleNode::ForgettenSubtree { .. } => Err(MerkleTreeError::ForgottenLeaf), - } - } -} - -impl MerkleProof -where - E: Element, - I: Index + ToTraversalPath, - T: NodeValue, -{ - /// Verify a membership proof by comparing the computed root value to the - /// expected one. - pub(crate) fn verify_membership_proof( - &self, - expected_root: &T, - ) -> Result - where - H: DigestAlgorithm, - { - if let MerkleNode::::Leaf { - value: _, - pos, - elem, - } = &self.proof[0] - { - let init = H::digest_leaf(pos, elem)?; - let computed_root = self - .pos - .to_traversal_path(self.tree_height() - 1) - .iter() - .zip(self.proof.iter().skip(1)) - .try_fold(init, |val, (branch, node)| -> Result { - match node { - MerkleNode::Branch { value: _, children } => { - let mut data = - children.iter().map(|node| node.value()).collect::>(); - data[*branch] = val; - H::digest(&data) - }, - _ => Err(MerkleTreeError::InconsistentStructureError( - "Incompatible proof for this merkle tree".to_string(), - )), - } - })?; - if computed_root == *expected_root { - Ok(Ok(())) - } else { - Ok(Err(())) - } - } else { - Err(MerkleTreeError::InconsistentStructureError( - "Invalid proof type".to_string(), - )) - } - } - - /// Verify a non membership proof by comparing the computed root value - /// to the expected one. - pub(crate) fn verify_non_membership_proof( - &self, - expected_root: &T, - ) -> Result - where - H: DigestAlgorithm, - { - if let MerkleNode::::Empty = &self.proof[0] { - let init = T::default(); - let computed_root = self - .pos - .to_traversal_path(self.tree_height() - 1) - .iter() - .zip(self.proof.iter().skip(1)) - .try_fold(init, |val, (branch, node)| -> Result { - match node { - MerkleNode::Branch { value: _, children } => { - let mut data = - children.iter().map(|node| node.value()).collect::>(); - data[*branch] = val; - H::digest(&data) - }, - MerkleNode::Empty => Ok(init), - _ => Err(MerkleTreeError::InconsistentStructureError( - "Incompatible proof for this merkle tree".to_string(), - )), - } - })?; - Ok(computed_root == *expected_root) - } else { - Err(MerkleTreeError::InconsistentStructureError( - "Invalid proof type".to_string(), - )) + MerkleNode::ForgottenSubtree { .. } => Err(MerkleTreeError::ForgottenLeaf), } } } diff --git a/merkle_tree/src/lib.rs b/merkle_tree/src/lib.rs index 9dd699642..36e650779 100644 --- a/merkle_tree/src/lib.rs +++ b/merkle_tree/src/lib.rs @@ -25,7 +25,6 @@ pub mod gadgets; pub mod hasher; pub mod light_weight; pub mod macros; -pub mod namespaced_merkle_tree; pub mod universal_merkle_tree; pub(crate) mod internal; @@ -42,6 +41,10 @@ use serde::{Deserialize, Serialize}; /// Glorified bool type pub(crate) type VerificationResult = Result<(), ()>; +/// Glorified true +pub const SUCCESS: VerificationResult = Ok(()); +/// Glorified false +pub const FAIL: VerificationResult = Err(()); #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] /// The result of querying at an index in the tree @@ -97,17 +100,7 @@ impl Index for T {} /// An internal node value type in a Merkle tree. pub trait NodeValue: - Default - + Eq - + PartialEq - + Hash - + Ord - + PartialOrd - + Copy - + Clone - + Debug - + CanonicalSerialize - + CanonicalDeserialize + Default + Eq + PartialEq + Hash + Copy + Clone + Debug + CanonicalSerialize + CanonicalDeserialize { } impl NodeValue for T where @@ -115,8 +108,6 @@ impl NodeValue for T where + Eq + PartialEq + Hash - + Ord - + PartialOrd + Copy + Clone + Debug @@ -160,26 +151,22 @@ impl_to_traversal_path_biguint!(ark_bn254::Fq); impl_to_traversal_path_biguint!(ark_bls12_377::Fq); impl_to_traversal_path_biguint!(ark_bls12_381::Fq); -/// Trait for a succinct merkle tree commitment -pub trait MerkleCommitment: +/// Trait for a Merkle proof +pub trait MerkleProof: Eq + PartialEq + Hash - + Ord - + PartialOrd + Clone - + Copy + + CanonicalSerialize + + CanonicalDeserialize + Serialize + for<'a> Deserialize<'a> - + CanonicalDeserialize - + CanonicalSerialize { - /// Return a digest of the tree - fn digest(&self) -> T; - /// Return the height of the tree + /// Expected height of the Merkle tree. fn height(&self) -> usize; - /// Return the number of elements included in the accumulator/tree - fn size(&self) -> u64; + + /// Return all values of siblings of this Merkle path + fn path_values(&self) -> &[Vec]; } /// Basic functionalities for a merkle tree implementation. Abstracted as an @@ -193,11 +180,11 @@ pub trait MerkleTreeScheme: Sized { /// Internal and root node value type NodeValue: NodeValue; /// Merkle proof - type MembershipProof: Clone + Eq + Hash; + type MembershipProof: MerkleProof; /// Batch proof type BatchMembershipProof: Clone; /// Merkle tree commitment - type Commitment: MerkleCommitment; + type Commitment: NodeValue; /// Tree ARITY const ARITY: usize; @@ -223,15 +210,16 @@ pub trait MerkleTreeScheme: Sized { ) -> LookupResult<&Self::Element, Self::MembershipProof, ()>; /// Verify an element is a leaf of a Merkle tree given the proof - /// * `root` - a merkle tree root, usually obtained from - /// `Self::commitment().digest()` + /// * `commitment` - a merkle tree commitment /// * `pos` - zero-based index of the leaf in the tree - /// * `proof` - a merkle tree proof + /// * `element` - the leaf value + /// * `proof` - a membership proof for `element` at given `pos` /// * `returns` - Ok(true) if the proof is accepted, Ok(false) if not. Err() /// if the proof is not well structured, E.g. not for this merkle tree. fn verify( - root: impl Borrow, + commitment: impl Borrow, pos: impl Borrow, + element: impl Borrow, proof: impl Borrow, ) -> Result; @@ -342,10 +330,10 @@ pub trait UniversalMerkleTreeScheme: MerkleTreeScheme { /// * `returns` - Ok(true) if the proof is accepted, Ok(false) if not. Err() /// if the proof is not well structured, E.g. not for this merkle tree. fn non_membership_verify( - &self, + commitment: impl Borrow, pos: impl Borrow, proof: impl Borrow, - ) -> Result; + ) -> Result; // TODO(Chengyu): non-membership proof interfaces } @@ -372,7 +360,11 @@ pub trait ForgetableMerkleTreeScheme: MerkleTreeScheme { /// Rebuild a merkle tree from a commitment. /// Return a tree which is entirely forgotten. - fn from_commitment(commitment: impl Borrow) -> Self; + fn from_commitment( + commitment: impl Borrow, + height: usize, + num_leaves: u64, + ) -> Self; } /// Universal Merkle tree that allows forget/remember elements from the memory diff --git a/merkle_tree/src/light_weight.rs b/merkle_tree/src/light_weight.rs index e00d2f048..3dc8708d6 100644 --- a/merkle_tree/src/light_weight.rs +++ b/merkle_tree/src/light_weight.rs @@ -9,11 +9,11 @@ use super::{ internal::{ - build_light_weight_tree_internal, MerkleNode, MerkleProof, MerkleTreeCommitment, - MerkleTreeIntoIter, MerkleTreeIter, + build_light_weight_tree_internal, MerkleNode, MerkleTreeIntoIter, MerkleTreeIter, + MerkleTreeProof, }, AppendableMerkleTreeScheme, DigestAlgorithm, Element, ForgetableMerkleTreeScheme, Index, - LookupResult, MerkleCommitment, MerkleTreeScheme, NodeValue, ToTraversalPath, + LookupResult, MerkleProof, MerkleTreeScheme, NodeValue, ToTraversalPath, }; use crate::{ errors::MerkleTreeError, impl_forgetable_merkle_tree_scheme, impl_merkle_tree_scheme, @@ -110,7 +110,7 @@ where #[cfg(test)] mod mt_tests { use crate::{ - internal::{MerkleNode, MerkleProof}, + internal::MerkleNode, prelude::{RescueLightWeightMerkleTree, RescueMerkleTree}, *, }; @@ -175,54 +175,51 @@ mod mt_tests { let mut mt = RescueLightWeightMerkleTree::::from_elems(Some(2), [F::from(3u64), F::from(1u64)]) .unwrap(); - let mut mock_mt = + let mut full_mt = RescueMerkleTree::::from_elems(Some(2), [F::from(3u64), F::from(1u64)]).unwrap(); assert!(mt.lookup(0).expect_not_in_memory().is_ok()); assert!(mt.lookup(1).expect_ok().is_ok()); - assert!(mt.extend(&[F::from(3u64), F::from(1u64)]).is_ok()); - assert!(mock_mt.extend(&[F::from(3u64), F::from(1u64)]).is_ok()); + assert!(mt.extend(&[F::from(33u64), F::from(41u64)]).is_ok()); + assert!(full_mt.extend(&[F::from(33u64), F::from(41u64)]).is_ok()); assert!(mt.lookup(0).expect_not_in_memory().is_ok()); assert!(mt.lookup(1).expect_not_in_memory().is_ok()); assert!(mt.lookup(2).expect_not_in_memory().is_ok()); assert!(mt.lookup(3).expect_ok().is_ok()); - let (elem, proof) = mock_mt.lookup(0).expect_ok().unwrap(); + + // Should have the same commitment + assert_eq!(mt.commitment(), full_mt.commitment()); + + let commitment = mt.commitment(); + let (elem, proof) = full_mt.lookup(0).expect_ok().unwrap(); assert_eq!(elem, &F::from(3u64)); - assert_eq!(proof.tree_height(), 3); assert!( - RescueLightWeightMerkleTree::::verify(&mt.root.value(), 0, &proof) + RescueLightWeightMerkleTree::::verify(&commitment, 0, elem, &proof) .unwrap() .is_ok() ); + // Wrong element value, should fail. + assert!( + RescueLightWeightMerkleTree::::verify(&commitment, 0, F::from(14u64), &proof) + .unwrap() + .is_err() + ); + + // Wrong pos, should fail. + assert!( + RescueLightWeightMerkleTree::::verify(&commitment, 2, elem, &proof) + .unwrap() + .is_err() + ); + let mut bad_proof = proof.clone(); - if let MerkleNode::Leaf { - value: _, - pos: _, - elem, - } = &mut bad_proof.proof[0] - { - *elem = F::from(4u64); - } else { - unreachable!() - } + bad_proof.0[0][0] = F::one(); - let result = RescueLightWeightMerkleTree::::verify(&mt.root.value(), 0, &bad_proof); - assert!(result.unwrap().is_err()); - - let mut forge_proof = MerkleProof::new(2, proof.proof); - if let MerkleNode::Leaf { - value: _, - pos, - elem, - } = &mut forge_proof.proof[0] - { - *pos = 2; - *elem = F::from(0u64); - } else { - unreachable!() - } - let result = RescueLightWeightMerkleTree::::verify(&mt.root.value(), 2, &forge_proof); - assert!(result.unwrap().is_err()); + assert!( + RescueLightWeightMerkleTree::::verify(&commitment, 0, elem, &bad_proof) + .unwrap() + .is_err() + ); } #[test] @@ -236,8 +233,7 @@ mod mt_tests { let mt = RescueLightWeightMerkleTree::::from_elems(Some(2), [F::from(3u64), F::from(1u64)]) .unwrap(); - let proof = mt.lookup(1).expect_ok().unwrap().1; - let node = &proof.proof[0]; + let (_, proof) = mt.lookup(1).expect_ok().unwrap(); assert_eq!( mt, @@ -247,9 +243,5 @@ mod mt_tests { proof, bincode::deserialize(&bincode::serialize(&proof).unwrap()).unwrap() ); - assert_eq!( - *node, - bincode::deserialize(&bincode::serialize(node).unwrap()).unwrap() - ); } } diff --git a/merkle_tree/src/macros.rs b/merkle_tree/src/macros.rs index 9f372ca72..c167f26a0 100644 --- a/merkle_tree/src/macros.rs +++ b/merkle_tree/src/macros.rs @@ -40,10 +40,10 @@ macro_rules! impl_merkle_tree_scheme { type Element = E; type Index = I; type NodeValue = T; - type MembershipProof = MerkleProof; + type MembershipProof = MerkleTreeProof; // TODO(Chengyu): implement batch membership proof type BatchMembershipProof = (); - type Commitment = MerkleTreeCommitment; + type Commitment = T; const ARITY: usize = ARITY; @@ -60,7 +60,7 @@ macro_rules! impl_merkle_tree_scheme { } fn commitment(&self) -> Self::Commitment { - MerkleTreeCommitment::new(self.root.value(), self.height, self.num_leaves) + self.root.value() } fn lookup( @@ -71,7 +71,7 @@ macro_rules! impl_merkle_tree_scheme { let traversal_path = pos.to_traversal_path(self.height); match self.root.lookup_internal(self.height, &traversal_path) { LookupResult::Ok(value, proof) => { - LookupResult::Ok(&value, MerkleProof::new(pos.clone(), proof)) + LookupResult::Ok(&value, proof) }, LookupResult::NotInMemory => LookupResult::NotInMemory, LookupResult::NotFound(_) => LookupResult::NotFound(()), @@ -79,14 +79,12 @@ macro_rules! impl_merkle_tree_scheme { } fn verify( - root: impl Borrow, + commitment: impl Borrow, pos: impl Borrow, + element: impl Borrow, proof: impl Borrow, ) -> Result { - if *pos.borrow() != proof.borrow().pos { - return Ok(Err(())); // invalid proof for the given pos - } - proof.borrow().verify_membership_proof::(root.borrow()) + crate::internal::verify_merkle_proof::(commitment.borrow(), pos.borrow(), Some(element.borrow()), proof.borrow().path_values()) } fn iter(&self) -> MerkleTreeIter { @@ -140,14 +138,16 @@ macro_rules! impl_forgetable_merkle_tree_scheme { I: Index + ToTraversalPath, T: NodeValue, { - fn from_commitment(com: impl Borrow) -> Self { + fn from_commitment( + com: impl Borrow, + height: usize, + num_leaves: u64, + ) -> Self { let com = com.borrow(); $name { - root: Arc::new(MerkleNode::ForgettenSubtree { - value: com.digest(), - }), - height: com.height(), - num_leaves: com.size(), + root: Arc::new(MerkleNode::ForgottenSubtree { value: com.clone() }), + height, + num_leaves, _phantom: PhantomData, } } @@ -161,9 +161,7 @@ macro_rules! impl_forgetable_merkle_tree_scheme { let (new_root, result) = self.root.forget_internal(self.height, &traversal_path); self.root = new_root; match result { - LookupResult::Ok(elem, proof) => { - LookupResult::Ok(elem, MerkleProof::new(pos.clone(), proof)) - }, + LookupResult::Ok(elem, proof) => LookupResult::Ok(elem, proof), LookupResult::NotInMemory => LookupResult::NotInMemory, LookupResult::NotFound(_) => LookupResult::NotFound(()), } @@ -175,53 +173,23 @@ macro_rules! impl_forgetable_merkle_tree_scheme { element: impl Borrow, proof: impl Borrow, ) -> Result<(), MerkleTreeError> { + let pos = pos.borrow(); + let element = element.borrow(); let proof = proof.borrow(); - let traversal_path = pos.borrow().to_traversal_path(self.height); - if let MerkleNode::::Leaf { - value: _, - pos, - elem, - } = &proof.proof[0] - { - if !elem.eq(element.borrow()) { - return Err(MerkleTreeError::InconsistentStructureError( - "Element does not match the proof.".to_string(), - )); - } - let proof_leaf_value = H::digest_leaf(pos, elem)?; - let mut path_values = vec![proof_leaf_value]; - traversal_path.iter().zip(proof.proof.iter().skip(1)).fold( - Ok(proof_leaf_value), - |result, (branch, node)| -> Result { - match result { - Ok(val) => match node { - MerkleNode::Branch { value: _, children } => { - let mut data: Vec<_> = - children.iter().map(|node| node.value()).collect(); - data[*branch] = val; - let digest = H::digest(&data)?; - path_values.push(digest); - Ok(digest) - }, - _ => Err(MerkleTreeError::InconsistentStructureError( - "Incompatible proof for this merkle tree".to_string(), - )), - }, - Err(e) => Err(e), - } - }, - )?; + if Self::verify(&self.commitment(), pos, element, proof)?.is_err() { + Err(MerkleTreeError::InconsistentStructureError( + "Wrong proof".to_string(), + )) + } else { + let traversal_path = pos.to_traversal_path(self.height); self.root = self.root.remember_internal::( self.height, &traversal_path, - &path_values, - &proof.proof, + pos, + Some(element), + proof.path_values(), )?; Ok(()) - } else { - Err(MerkleTreeError::InconsistentStructureError( - "Invalid proof type".to_string(), - )) } } } diff --git a/merkle_tree/src/namespaced_merkle_tree/hash.rs b/merkle_tree/src/namespaced_merkle_tree/hash.rs deleted file mode 100644 index 8077a0f42..000000000 --- a/merkle_tree/src/namespaced_merkle_tree/hash.rs +++ /dev/null @@ -1,145 +0,0 @@ -use alloc::vec; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use ark_std::{string::ToString, vec::Vec}; -use core::{fmt::Debug, hash::Hash, marker::PhantomData}; -use digest::Digest; -use sha3::Sha3_256; - -use crate::{ - errors::MerkleTreeError, - prelude::{Sha3Digest, Sha3Node}, -}; - -use super::{BindNamespace, DigestAlgorithm, Element, Index, Namespace, Namespaced, NodeValue}; - -/// NamespacedHasher wraps a standard hash function (implementer of -/// DigestAlgorithm), turning it into a hash function that tags internal nodes -/// with namespace ranges. -#[derive(Debug, Clone, Hash, Eq, PartialEq)] -pub struct NamespacedHasher -where - H: DigestAlgorithm, - E: Element + Namespaced, - N: Namespace, - I: Index, - T: NodeValue, -{ - phantom1: PhantomData, - phantom2: PhantomData, - phantom3: PhantomData, - phantom4: PhantomData, - phantom5: PhantomData, -} - -#[derive( - CanonicalSerialize, - CanonicalDeserialize, - Hash, - Copy, - Clone, - Debug, - Default, - Ord, - Eq, - PartialEq, - PartialOrd, -)] -/// Represents a namespaced internal tree node -pub struct NamespacedHash -where - N: Namespace, - T: NodeValue, -{ - pub(crate) min_namespace: N, - pub(crate) max_namespace: N, - pub(crate) hash: T, -} - -impl NamespacedHash -where - N: Namespace, - T: NodeValue, -{ - /// Constructs a new NamespacedHash - pub fn new(min_namespace: N, max_namespace: N, hash: T) -> Self { - Self { - min_namespace, - max_namespace, - hash, - } - } -} - -impl DigestAlgorithm> for NamespacedHasher -where - E: Element + Namespaced, - I: Index, - N: Namespace, - T: NodeValue, - H: DigestAlgorithm + BindNamespace, -{ - // Assumes that data is sorted by namespace, will be enforced by "append" - fn digest(data: &[NamespacedHash]) -> Result, MerkleTreeError> { - if data.is_empty() { - return Ok(NamespacedHash::default()); - } - let first_node = data[0]; - let min_namespace = first_node.min_namespace; - let mut max_namespace = first_node.max_namespace; - let mut nodes = vec![H::generate_namespaced_commitment(first_node)]; - for node in &data[1..] { - if node == &NamespacedHash::default() { - continue; - } - // Ensure that namespaced nodes are sorted - if node.min_namespace < max_namespace { - return Err(MerkleTreeError::InconsistentStructureError( - "Namespace Merkle tree leaves are out of order".to_string(), - )); - } - max_namespace = node.max_namespace; - nodes.push(H::generate_namespaced_commitment(*node)); - } - - let inner_hash = H::digest(&nodes)?; - - Ok(NamespacedHash::new( - min_namespace, - max_namespace, - inner_hash, - )) - } - - fn digest_leaf(pos: &I, elem: &E) -> Result, MerkleTreeError> { - let namespace = elem.get_namespace(); - let hash = H::digest_leaf(pos, elem)?; - Ok(NamespacedHash::new(namespace, namespace, hash)) - } -} - -impl BindNamespace for Sha3Digest -where - E: Element + CanonicalSerialize, - I: Index, - N: Namespace, -{ - // TODO ensure the hashing of (min,max,hash) is collision resistant - fn generate_namespaced_commitment(namespaced_hash: NamespacedHash) -> Sha3Node { - let mut hasher = Sha3_256::new(); - let mut writer = Vec::new(); - namespaced_hash - .min_namespace - .serialize_compressed(&mut writer) - .unwrap(); - namespaced_hash - .max_namespace - .serialize_compressed(&mut writer) - .unwrap(); - namespaced_hash - .hash - .serialize_compressed(&mut writer) - .unwrap(); - hasher.update(&mut writer); - Sha3Node(hasher.finalize().into()) - } -} diff --git a/merkle_tree/src/namespaced_merkle_tree/mod.rs b/merkle_tree/src/namespaced_merkle_tree/mod.rs deleted file mode 100644 index ea86ed456..000000000 --- a/merkle_tree/src/namespaced_merkle_tree/mod.rs +++ /dev/null @@ -1,605 +0,0 @@ -// Copyright (c) 2022 Espresso Systems (espressosys.com) -// This file is part of the Jellyfish library. - -// You should have received a copy of the MIT License -// along with the Jellyfish library. If not, see . - -//! Implementation of a Namespaced Merkle Tree. - -use self::{ - hash::{NamespacedHash, NamespacedHasher}, - proof::{NaiveNamespaceProof, NamespaceProofType}, -}; -use super::{ - append_only::MerkleTree, - internal::{MerkleProof, MerkleTreeIter}, - AppendableMerkleTreeScheme, DigestAlgorithm, Element, Index, LookupResult, MerkleCommitment, - MerkleTreeScheme, NodeValue, -}; -use crate::{errors::MerkleTreeError, VerificationResult}; -use alloc::collections::{btree_map::Entry, BTreeMap}; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use ark_std::vec::Vec; -use core::{borrow::Borrow, fmt::Debug, hash::Hash, marker::PhantomData, ops::Range}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; - -mod hash; -mod proof; - -/// Namespaced Merkle Tree where leaves are sorted by a namespace identifier. -/// The data structure supports namespace inclusion proofs. -pub trait NamespacedMerkleTreeScheme: AppendableMerkleTreeScheme -where - Self::Element: Namespaced, -{ - /// Namespace proof type - type NamespaceProof: NamespaceProof; - /// Namespace type - type NamespaceId: Namespace; - - /// Returns the entire set of leaves corresponding to a given namespace and - /// a completeness proof. - fn get_namespace_proof(&self, namespace: Self::NamespaceId) -> Self::NamespaceProof; - - /// Verifies the completeness proof for a given set of leaves and a - /// namespace. - fn verify_namespace_proof( - &self, - proof: &Self::NamespaceProof, - namespace: Self::NamespaceId, - ) -> Result; -} - -/// Completeness proof for a namespace -pub trait NamespaceProof { - /// Namespace type - type Namespace: Namespace; - /// Namespaced leaf - type Leaf: Element + Namespaced; - /// Internal node value - type Node: NodeValue; - - /// Return the set of leaves associated with this Namespace proof - fn get_namespace_leaves(&self) -> Vec<&Self::Leaf>; - - /// Verify a namespace proof - fn verify( - &self, - root: &NamespacedHash, - namespace: Self::Namespace, - ) -> Result; -} - -/// Trait indicating that a leaf has a namespace. -pub trait Namespaced { - /// Namespace type - type Namespace: Namespace; - /// Returns the namespace of the leaf - fn get_namespace(&self) -> Self::Namespace; -} - -/// Trait indicating that a digest algorithm can commit to -/// a namespace range. -pub trait BindNamespace: DigestAlgorithm -where - E: Element, - N: Namespace, - T: NodeValue, - I: Index, -{ - /// Generate a commitment that binds a node to a namespace range - fn generate_namespaced_commitment(namespaced_hash: NamespacedHash) -> T; -} - -/// Trait indicating that a struct can act as an orderable namespace -pub trait Namespace: - Debug - + Clone - + CanonicalDeserialize - + CanonicalSerialize - + Default - + Copy - + Hash - + Ord - + Serialize - + DeserializeOwned -{ - /// Returns the minimum possible namespace - fn min() -> Self; - /// Returns the maximum possible namespace - fn max() -> Self; -} - -impl Namespace for u64 { - fn min() -> u64 { - u64::MIN - } - fn max() -> u64 { - u64::MAX - } -} - -type InnerTree = - MerkleTree, u64, ARITY, NamespacedHash>; - -type NamespaceRanges = BTreeMap>; - -#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)] -#[serde(bound = "E: CanonicalSerialize + CanonicalDeserialize, - T: CanonicalSerialize + CanonicalDeserialize")] -/// NMT -pub struct NMT -where - H: DigestAlgorithm + BindNamespace, - E: Element + Namespaced, - T: NodeValue, - N: Namespace, -{ - namespace_ranges: NamespaceRanges, - inner: InnerTree, -} - -impl NMT -where - H: DigestAlgorithm + BindNamespace, - E: Element + Namespaced, - T: NodeValue, - N: Namespace, -{ - /// Initializze an empty NMT - pub fn new(height: usize) -> Self { - let namespace_ranges: BTreeMap> = BTreeMap::new(); - let inner = InnerTree::::new(height); - NMT { - inner, - namespace_ranges, - } - } - - /// Construct an NMT from elements. - pub fn from_elems( - height: Option, - elems: impl IntoIterator>, - ) -> Result { - let mut namespace_ranges: BTreeMap> = BTreeMap::new(); - let leaves = - NMT::::update_namespace_metadata(&mut namespace_ranges, elems)?; - let inner = InnerTree::::from_elems(height, leaves)?; - Ok(NMT { - inner, - namespace_ranges, - }) - } -} - -impl MerkleTreeScheme for NMT -where - H: DigestAlgorithm + BindNamespace, - E: Element + Namespaced, - T: NodeValue, - N: Namespace, -{ - type Element = E; - type Index = u64; - type NodeValue = NamespacedHash; - type MembershipProof = as MerkleTreeScheme>::MembershipProof; - type BatchMembershipProof = - as MerkleTreeScheme>::BatchMembershipProof; - const ARITY: usize = as MerkleTreeScheme>::ARITY; - type Commitment = as MerkleTreeScheme>::Commitment; - - fn height(&self) -> usize { - self.inner.height() - } - - fn capacity(&self) -> num_bigint::BigUint { - self.inner.capacity() - } - - fn num_leaves(&self) -> u64 { - self.inner.num_leaves() - } - - fn commitment(&self) -> Self::Commitment { - self.inner.commitment() - } - - fn lookup( - &self, - pos: impl Borrow, - ) -> super::LookupResult<&Self::Element, Self::MembershipProof, ()> { - self.inner.lookup(pos) - } - - fn verify( - root: impl Borrow, - pos: impl Borrow, - proof: impl Borrow, - ) -> Result { - as MerkleTreeScheme>::verify(root, pos, proof) - } - - fn iter(&self) -> MerkleTreeIter { - self.inner.iter() - } -} - -impl AppendableMerkleTreeScheme for NMT -where - H: DigestAlgorithm + BindNamespace, - E: Element + Namespaced, - T: NodeValue, - N: Namespace, -{ - fn extend( - &mut self, - elems: impl IntoIterator>, - ) -> Result<(), MerkleTreeError> { - let leaves = - NMT::::update_namespace_metadata(&mut self.namespace_ranges, elems)?; - self.inner.extend(leaves) - } - - fn push( - &mut self, - elem: impl core::borrow::Borrow, - ) -> Result<(), MerkleTreeError> { - self.extend([elem]) - } -} - -impl NMT -where - H: DigestAlgorithm + BindNamespace, - E: Element + Namespaced, - T: NodeValue, - N: Namespace, -{ - // Helper function to lookup a proof that should be in the tree because of NMT - // invariants - fn lookup_proof(&self, idx: u64) -> MerkleProof, ARITY> { - if let LookupResult::Ok(_, proof) = self.inner.lookup(idx) { - proof - } else { - // The NMT is malformed, we cannot recover - panic!() - } - } - - /// Helper function to return an iterator over the leaves in the tree - pub fn leaves(&self) -> impl ExactSizeIterator + '_ { - (0..self.num_leaves() as usize).map(|idx| match self.inner.lookup(idx as u64) { - LookupResult::Ok(elem, _) => elem, - _ => panic!("NMT variant violated: every leaf in the tree should be occupied"), - }) - } - - // Helper function to keep namespace metadata in sync with new leaves, - // Returns cached leaf references so that the inner merkle tree can append them - fn update_namespace_metadata( - namespace_ranges: &mut NamespaceRanges, - elems: impl IntoIterator>, - ) -> Result>, MerkleTreeError> { - let (mut max_namespace, start_idx) = namespace_ranges - .iter() - .next_back() - .map(|(n, range)| (*n, range.end)) - .unwrap_or((::min(), 0)); - let mut leaves = Vec::new(); - for (idx, elem) in elems.into_iter().enumerate() { - let ns = elem.borrow().get_namespace(); - let idx = start_idx + idx as u64; - if ns < max_namespace { - return Err(MerkleTreeError::InconsistentStructureError( - "Namespace leaves must be pushed in sorted order".into(), - )); - } - match namespace_ranges.entry(ns) { - Entry::Occupied(entry) => { - entry.into_mut().end = idx + 1; - }, - Entry::Vacant(entry) => { - entry.insert(idx..idx + 1); - }, - } - max_namespace = ns; - leaves.push(elem); - } - Ok(leaves) - } -} - -impl NamespacedMerkleTreeScheme for NMT -where - H: DigestAlgorithm + BindNamespace + Clone, - E: Element + Namespaced, - T: NodeValue, - N: Namespace, -{ - type NamespaceId = N; - type NamespaceProof = NaiveNamespaceProof; - - fn get_namespace_proof(&self, namespace: Self::NamespaceId) -> Self::NamespaceProof { - let ns_range = self.namespace_ranges.get(&namespace); - let mut proofs = Vec::new(); - let mut left_boundary_proof = None; - let mut right_boundary_proof = None; - let proof_type; - let mut first_index = None; - if let Some(ns_range) = ns_range { - proof_type = NamespaceProofType::Presence; - for i in ns_range.clone() { - if first_index.is_none() { - first_index = Some(i); - } - proofs.push(self.lookup_proof(i)); - } - let left_index = first_index.unwrap_or(0); - let right_index = left_index + proofs.len() as u64; - if left_index > 0 { - left_boundary_proof = Some(self.lookup_proof(left_index - 1)); - } - if right_index < self.num_leaves() - 1 { - right_boundary_proof = Some(self.lookup_proof(right_index + 1)); - } - } else { - proof_type = NamespaceProofType::Absence; - // If there is a namespace in the tree greater than our target - // namespace at some index i, prove that the - // target namespace is empty by providing proofs of leaves at index i and - // i - 1 - if let Some((_, range)) = self.namespace_ranges.range(namespace..).next() { - let i = range.start; - // If i == 0, the target namespace is less than the tree's minimum namespace - if i > 0 { - left_boundary_proof = Some(self.lookup_proof(i - 1)); - right_boundary_proof = Some(self.lookup_proof(i)); - } - } - } - NaiveNamespaceProof { - proof_type, - proofs, - left_boundary_proof, - right_boundary_proof, - first_index: first_index.unwrap_or(0), - phantom: PhantomData, - } - } - - fn verify_namespace_proof( - &self, - proof: &Self::NamespaceProof, - namespace: Self::NamespaceId, - ) -> Result { - proof.verify(&self.commitment().digest(), namespace) - } -} - -#[cfg(test)] -mod nmt_tests { - - use super::*; - use crate::prelude::{Sha3Digest, Sha3Node}; - - type NamespaceId = u64; - type Hasher = NamespacedHasher; - - #[derive( - Default, - Eq, - PartialEq, - Hash, - Ord, - PartialOrd, - Copy, - Clone, - Debug, - CanonicalSerialize, - CanonicalDeserialize, - )] - struct Leaf { - namespace: NamespaceId, - } - - impl Leaf { - pub fn new(namespace: NamespaceId) -> Self { - Leaf { namespace } - } - } - - impl Namespaced for Leaf { - type Namespace = NamespaceId; - fn get_namespace(&self) -> NamespaceId { - self.namespace - } - } - - type TestNMT = NMT; - - #[test] - fn test_namespaced_hash() { - let num_leaves = 5; - let leaves: Vec = (0..num_leaves).map(Leaf::new).collect(); - - // Ensure that leaves are digested correctly - let mut hashes = leaves - .iter() - .enumerate() - .map(|(idx, leaf)| Hasher::digest_leaf(&(idx as u64), leaf)) - .collect::, MerkleTreeError>>() - .unwrap(); - assert_eq!((hashes[0].min_namespace, hashes[0].max_namespace), (0, 0)); - - // Ensure that sorted internal nodes are digested correctly - let hash = Hasher::digest(&hashes).unwrap(); - assert_eq!( - (hash.min_namespace, hash.max_namespace), - (0, num_leaves - 1) - ); - - // Ensure that digest errors when internal nodes are not sorted by namespace - hashes[0] = hashes[hashes.len() - 1]; - assert!(Hasher::digest(&hashes).is_err()); - } - - enum BuildType { - FromElems, - Push, - Extend, - } - - fn build_tree(leaves: &[Leaf], build_type: BuildType) -> TestNMT { - match build_type { - BuildType::FromElems => TestNMT::from_elems(Some(3), leaves).unwrap(), - BuildType::Extend => { - let mut nmt = TestNMT::new(3); - nmt.extend(leaves).unwrap(); - nmt - }, - BuildType::Push => { - let mut nmt = TestNMT::new(3); - for leaf in leaves { - nmt.push(leaf).unwrap(); - } - nmt - }, - } - } - - #[test] - fn test_nmt() { - test_nmt_with_build_type(BuildType::Extend); - test_nmt_with_build_type(BuildType::FromElems); - test_nmt_with_build_type(BuildType::Push); - } - - #[test] - fn test_tree_consistency() { - let namespaces = [1, 2, 2, 2, 4, 4, 4, 5]; - let leaves: Vec = namespaces.iter().map(|i| Leaf::new(*i)).collect(); - let tree1 = build_tree(&leaves, BuildType::Extend); - let tree2 = build_tree(&leaves, BuildType::FromElems); - let tree3 = build_tree(&leaves, BuildType::Push); - assert_eq!(tree1.commitment(), tree2.commitment()); - assert_eq!(tree1.namespace_ranges, tree2.namespace_ranges); - assert_eq!(tree1.commitment(), tree3.commitment()); - assert_eq!(tree1.namespace_ranges, tree3.namespace_ranges); - } - - fn test_nmt_with_build_type(build_type: BuildType) { - let namespaces = [1, 2, 2, 2, 4, 4, 4, 5]; - let leaves: Vec = namespaces.iter().map(|i| Leaf::new(*i)).collect(); - let first_ns = namespaces[0]; - let last_ns = namespaces[namespaces.len() - 1]; - let internal_ns = namespaces[1]; - let tree = build_tree(&leaves, build_type); - let left_proof = tree.get_namespace_proof(first_ns); - let right_proof = tree.get_namespace_proof(last_ns); - let mut internal_proof = tree.get_namespace_proof(internal_ns); - - // Check all of the leaves - let fetched_leaves = tree.leaves().cloned().collect::>(); - assert_eq!(fetched_leaves, leaves); - - // Check namespace proof on the left boundary - assert!(tree - .verify_namespace_proof(&left_proof, first_ns) - .unwrap() - .is_ok()); - - // Check namespace proof on the right boundary - assert!(tree - .verify_namespace_proof(&right_proof, last_ns) - .unwrap() - .is_ok()); - - // Check namespace proof for some internal namespace - assert!(tree - .verify_namespace_proof(&internal_proof, internal_ns) - .unwrap() - .is_ok()); - - // Assert that namespace proof fails for a different namespace - assert!(tree - .verify_namespace_proof(&left_proof, 2) - .unwrap() - .is_err()); - - // Sanity check that the leaves returned by the proof are correct - let internal_leaves: Vec = internal_proof - .get_namespace_leaves() - .into_iter() - .copied() - .collect(); - let raw_leaves_for_ns = &leaves[1..4]; - assert_eq!(raw_leaves_for_ns, internal_leaves); - - // Check that a namespace proof fails if one of the leaves is removed - internal_proof.proofs.remove(1); - assert!(tree - .verify_namespace_proof(&internal_proof, internal_ns) - .unwrap() - .is_err()); - - // Check the simple absence proof case when the namespace falls outside of the - // tree range (namespace > root.max_namespace) - let absence_proof = tree.get_namespace_proof(last_ns + 1); - let leaves: Vec = absence_proof - .get_namespace_leaves() - .into_iter() - .cloned() - .collect(); - assert!(tree - .verify_namespace_proof(&absence_proof, last_ns + 1) - .unwrap() - .is_ok()); - assert_eq!(leaves, []); - - // Check the simple absence proof case when the namespace falls outside of the - // tree range (namespace < root.min_namespace) - let absence_proof = tree.get_namespace_proof(first_ns - 1); - let leaves: Vec = absence_proof - .get_namespace_leaves() - .into_iter() - .cloned() - .collect(); - assert!(tree - .verify_namespace_proof(&absence_proof, first_ns - 1) - .unwrap() - .is_ok()); - assert_eq!(leaves, []); - - // Check absence proof case when the namespace falls inside of the tree range - let absence_proof = tree.get_namespace_proof(3); - let leaves: Vec = absence_proof - .get_namespace_leaves() - .into_iter() - .cloned() - .collect(); - assert!(tree - .verify_namespace_proof(&absence_proof, 3) - .unwrap() - .is_ok()); - assert_eq!(leaves, []); - - // Ensure that the absence proof fails when the boundaries are not provided - let mut malformed_proof = absence_proof.clone(); - malformed_proof.left_boundary_proof = None; - assert!(tree.verify_namespace_proof(&malformed_proof, 3).is_err()); - let mut malformed_proof = absence_proof.clone(); - malformed_proof.right_boundary_proof = None; - assert!(tree.verify_namespace_proof(&malformed_proof, 3).is_err()); - - // Ensure that the absence proof returns a verification error when one of the - // boundary proofs is incorrect - let mut malicious_proof = absence_proof.clone(); - malicious_proof - .right_boundary_proof - .clone_from(&malicious_proof.left_boundary_proof); - assert!(tree - .verify_namespace_proof(&malicious_proof, 3) - .unwrap() - .is_err()); - } -} diff --git a/merkle_tree/src/namespaced_merkle_tree/proof.rs b/merkle_tree/src/namespaced_merkle_tree/proof.rs deleted file mode 100644 index d728fe365..000000000 --- a/merkle_tree/src/namespaced_merkle_tree/proof.rs +++ /dev/null @@ -1,270 +0,0 @@ -// Copyright (c) 2022 Espresso Systems (espressosys.com) -// This file is part of the Jellyfish library. - -// You should have received a copy of the MIT License -// along with the Jellyfish library. If not, see . -//! Namespace proof - -use super::{ - hash::NamespacedHash, BindNamespace, Element, InnerTree, Namespace, NamespaceProof, Namespaced, -}; -use crate::{ - errors::MerkleTreeError, internal::MerkleProof, DigestAlgorithm, MerkleTreeScheme, NodeValue, - VerificationResult, -}; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use ark_std::{string::ToString, vec::Vec}; -use core::{fmt::Debug, marker::PhantomData}; -use itertools::Itertools; -use serde::{Deserialize, Serialize}; - -/// Indicates whether the namespace proof represents a populated set or an empty -/// set -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -pub(crate) enum NamespaceProofType { - Presence, - Absence, -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -#[serde(bound = "E: CanonicalSerialize + CanonicalDeserialize, - T: CanonicalSerialize + CanonicalDeserialize,")] -/// Namespace Proof -pub struct NaiveNamespaceProof -where - E: Element + Namespaced, - T: NodeValue, - H: DigestAlgorithm + BindNamespace, - N: Namespace, -{ - pub(crate) proof_type: NamespaceProofType, - // TODO(#140) Switch to a batch proof - pub(crate) proofs: Vec, ARITY>>, - pub(crate) left_boundary_proof: Option, ARITY>>, - pub(crate) right_boundary_proof: Option, ARITY>>, - pub(crate) first_index: u64, - pub(crate) phantom: PhantomData, -} -impl NamespaceProof for NaiveNamespaceProof -where - E: Element + Namespaced, - T: NodeValue, - H: DigestAlgorithm + BindNamespace, - N: Namespace, -{ - type Leaf = E; - type Node = T; - type Namespace = N; - - fn get_namespace_leaves(&self) -> Vec<&Self::Leaf> { - let num_leaves = match self.proof_type { - NamespaceProofType::Presence => self.proofs.len(), - NamespaceProofType::Absence => 0, - }; - self.proofs - .iter() - // This unwrap is safe assuming that the proof is valid - .map(|proof| proof.elem().unwrap()) - .take(num_leaves) - .collect_vec() - } - - fn verify( - &self, - root: &NamespacedHash, - namespace: N, - ) -> Result { - match self.proof_type { - NamespaceProofType::Presence => self.verify_presence_proof(root, namespace), - NamespaceProofType::Absence => self.verify_absence_proof(root, namespace), - } - } -} - -impl NaiveNamespaceProof -where - E: Element + Namespaced, - T: NodeValue, - H: DigestAlgorithm + BindNamespace, - N: Namespace, -{ - fn verify_left_namespace_boundary( - &self, - root: &NamespacedHash, - namespace: N, - ) -> Result { - if let Some(boundary_proof) = self.left_boundary_proof.as_ref() { - // If there is a leaf to the left of the namespace range, check that it is less - // than the target namespace - if boundary_proof - .elem() - .ok_or(MerkleTreeError::InconsistentStructureError( - "Boundary proof does not contain an element".into(), - ))? - .get_namespace() - >= namespace - || *boundary_proof.index() != self.first_index - 1 - { - return Ok(Err(())); - } - // Verify the boundary proof - if >::verify(root, boundary_proof.index(), boundary_proof)? - .is_err() - { - return Ok(Err(())); - } - } else { - // If there is no left boundary, ensure that target namespace is the tree's - // minimum namespace - if root.min_namespace != namespace { - return Ok(Err(())); - } - } - Ok(Ok(())) - } - - fn verify_right_namespace_boundary( - &self, - root: &NamespacedHash, - namespace: N, - ) -> Result { - if let Some(boundary_proof) = self.right_boundary_proof.as_ref() { - // If there is a leaf to the left of the namespace range, check that it is less - // than the target namespace - if boundary_proof - .elem() - .ok_or(MerkleTreeError::InconsistentStructureError( - "Boundary proof does not contain an element".to_string(), - ))? - .get_namespace() - <= namespace - || *boundary_proof.index() != self.first_index + self.proofs.len() as u64 - { - return Ok(Err(())); - } - // Verify the boundary proof - if >::verify(root, boundary_proof.index(), boundary_proof)? - .is_err() - { - return Ok(Err(())); - } - } else { - // If there is no left boundary, ensure that target namespace is the tree's - // minimum namespace - if root.max_namespace != namespace { - return Ok(Err(())); - } - } - Ok(Ok(())) - } - - fn verify_absence_proof( - &self, - root: &NamespacedHash, - namespace: N, - ) -> Result { - if namespace < root.min_namespace || namespace > root.max_namespace { - // Easy case where the namespace isn't covered by the range of the tree root - return Ok(Ok(())); - } else { - // Harder case: Find an element whose namespace is greater than our - // target and show that the namespace to the left is less than our - // target - let left_proof = &self.left_boundary_proof.as_ref().cloned().ok_or( - MerkleTreeError::InconsistentStructureError( - "Left Boundary proof must be present".into(), - ), - )?; - let right_proof = &self.right_boundary_proof.as_ref().cloned().ok_or( - MerkleTreeError::InconsistentStructureError( - "Right boundary proof must be present".into(), - ), - )?; - let left_index = left_proof.index(); - let left_ns = left_proof - .elem() - .ok_or(MerkleTreeError::InconsistentStructureError( - "The left boundary proof is missing an element".into(), - ))? - .get_namespace(); - let right_index = right_proof.index(); - let right_ns = right_proof - .elem() - .ok_or(MerkleTreeError::InconsistentStructureError( - "The left boundary proof is missing an element".into(), - ))? - .get_namespace(); - // Ensure that leaves are adjacent - if *right_index != left_index + 1 { - return Ok(Err(())); - } - // And that our target namespace is in between the leaves' - // namespaces - if namespace <= left_ns || namespace >= right_ns { - return Ok(Err(())); - } - // Verify the boundary proofs - if >::verify(root, left_proof.index(), left_proof)? - .is_err() - { - return Ok(Err(())); - } - if >::verify(root, right_proof.index(), right_proof)? - .is_err() - { - return Ok(Err(())); - } - } - - Ok(Ok(())) - } - - fn verify_presence_proof( - &self, - root: &NamespacedHash, - namespace: N, - ) -> Result { - let mut last_idx: Option = None; - for (idx, proof) in self.proofs.iter().enumerate() { - let leaf_index = self.first_index + idx as u64; - if >::verify(root, leaf_index, proof)?.is_err() { - return Ok(Err(())); - } - if proof - .elem() - .ok_or(MerkleTreeError::InconsistentStructureError( - "Missing namespace element".into(), - ))? - .get_namespace() - != namespace - { - return Ok(Err(())); - } - // Indices must be sequential, this checks that there are no gaps in the - // namespace - if let Some(prev_index) = last_idx { - if leaf_index != prev_index + 1 { - return Ok(Err(())); - } - last_idx = Some(leaf_index); - } - } - // Verify that the proof contains the left boundary of the namespace - if self - .verify_left_namespace_boundary(root, namespace) - .is_err() - { - return Ok(Err(())); - } - - // Verify that the proof contains the right boundary of the namespace - if self - .verify_right_namespace_boundary(root, namespace) - .is_err() - { - return Ok(Err(())); - } - - Ok(Ok(())) - } -} diff --git a/merkle_tree/src/prelude.rs b/merkle_tree/src/prelude.rs index 5925cf060..8a9c26e3a 100644 --- a/merkle_tree/src/prelude.rs +++ b/merkle_tree/src/prelude.rs @@ -9,11 +9,11 @@ pub use crate::{ append_only::MerkleTree, impl_to_traversal_path_biguint, impl_to_traversal_path_primitives, - internal::{MerkleNode, MerklePath, MerkleProof}, + internal::{MerkleNode, MerkleTreeProof}, universal_merkle_tree::UniversalMerkleTree, AppendableMerkleTreeScheme, DigestAlgorithm, Element, ForgetableMerkleTreeScheme, - ForgetableUniversalMerkleTreeScheme, Index, LookupResult, MerkleCommitment, MerkleTreeScheme, - NodeValue, ToTraversalPath, UniversalMerkleTreeScheme, + ForgetableUniversalMerkleTreeScheme, Index, LookupResult, MerkleTreeScheme, NodeValue, + ToTraversalPath, UniversalMerkleTreeScheme, }; use super::light_weight::LightWeightMerkleTree; diff --git a/merkle_tree/src/universal_merkle_tree.rs b/merkle_tree/src/universal_merkle_tree.rs index 939006bbe..d4ef37d03 100644 --- a/merkle_tree/src/universal_merkle_tree.rs +++ b/merkle_tree/src/universal_merkle_tree.rs @@ -6,9 +6,9 @@ //! Implementation of a typical Sparse Merkle Tree. use super::{ - internal::{MerkleNode, MerkleProof, MerkleTreeCommitment, MerkleTreeIntoIter, MerkleTreeIter}, + internal::{MerkleNode, MerkleTreeIntoIter, MerkleTreeIter, MerkleTreeProof}, DigestAlgorithm, Element, ForgetableMerkleTreeScheme, ForgetableUniversalMerkleTreeScheme, - Index, LookupResult, MerkleCommitment, MerkleTreeScheme, NodeValue, + Index, LookupResult, MerkleProof, MerkleTreeScheme, NodeValue, PersistentUniversalMerkleTreeScheme, ToTraversalPath, UniversalMerkleTreeScheme, }; use crate::{ @@ -70,7 +70,7 @@ where I: Index + ToTraversalPath, T: NodeValue, { - type NonMembershipProof = MerkleProof; + type NonMembershipProof = MerkleTreeProof; type BatchNonMembershipProof = (); fn update_with( @@ -92,23 +92,16 @@ where } fn non_membership_verify( - &self, + commitment: impl Borrow, pos: impl Borrow, proof: impl Borrow, - ) -> Result { - let pos = pos.borrow(); - let proof = proof.borrow(); - if self.height != proof.tree_height() - 1 { - return Err(MerkleTreeError::InconsistentStructureError( - "Incompatible membership proof for this merkle tree".to_string(), - )); - } - if *pos != proof.pos { - return Err(MerkleTreeError::InconsistentStructureError( - "Inconsistent proof index".to_string(), - )); - } - proof.verify_non_membership_proof::(&self.root.value()) + ) -> Result { + crate::internal::verify_merkle_proof::( + commitment.borrow(), + pos.borrow(), + None, + proof.borrow().path_values(), + ) } fn universal_lookup( @@ -117,15 +110,7 @@ where ) -> LookupResult<&Self::Element, Self::MembershipProof, Self::NonMembershipProof> { let pos = pos.borrow(); let traversal_path = pos.to_traversal_path(self.height); - match self.root.lookup_internal(self.height, &traversal_path) { - LookupResult::Ok(value, proof) => { - LookupResult::Ok(value, MerkleProof::new(pos.clone(), proof)) - }, - LookupResult::NotInMemory => LookupResult::NotInMemory, - LookupResult::NotFound(non_membership_proof) => { - LookupResult::NotFound(MerkleProof::new(pos.clone(), non_membership_proof)) - }, - } + self.root.lookup_internal(self.height, &traversal_path) } } @@ -176,11 +161,7 @@ where let traversal_path = pos.to_traversal_path(self.height); let (root, result) = self.root.forget_internal(self.height, &traversal_path); self.root = root; - match result { - LookupResult::Ok(elem, proof) => LookupResult::Ok(elem, MerkleProof::new(pos, proof)), - LookupResult::NotInMemory => LookupResult::NotInMemory, - LookupResult::NotFound(proof) => LookupResult::NotFound(MerkleProof::new(pos, proof)), - } + result } fn non_membership_remember( @@ -188,47 +169,22 @@ where pos: Self::Index, proof: impl Borrow, ) -> Result<(), MerkleTreeError> { + let pos = pos.borrow(); let proof = proof.borrow(); - let traversal_path = pos.to_traversal_path(self.height); - if matches!(&proof.proof[0], MerkleNode::Empty) { - let empty_value = T::default(); - let mut path_values = vec![empty_value]; - traversal_path - .iter() - .zip(proof.proof.iter().skip(1)) - .try_fold( - empty_value, - |val: T, (branch, node)| -> Result { - match node { - MerkleNode::Branch { value: _, children } => { - let mut data: Vec<_> = - children.iter().map(|node| node.value()).collect(); - data[*branch] = val; - let digest = H::digest(&data)?; - path_values.push(digest); - Ok(digest) - }, - MerkleNode::Empty => { - path_values.push(empty_value); - Ok(empty_value) - }, - _ => Err(MerkleTreeError::InconsistentStructureError( - "Incompatible proof for this merkle tree".to_string(), - )), - } - }, - )?; + if Self::non_membership_verify(&self.commitment(), pos, proof)?.is_err() { + Err(MerkleTreeError::InconsistentStructureError( + "Wrong proof".to_string(), + )) + } else { + let traversal_path = pos.to_traversal_path(self.height); self.root = self.root.remember_internal::( self.height, &traversal_path, - &path_values, - &proof.proof, + pos, + None, + proof.path_values(), )?; Ok(()) - } else { - Err(MerkleTreeError::InconsistentStructureError( - "Invalid proof type".to_string(), - )) } } } @@ -236,10 +192,10 @@ where #[cfg(test)] mod mt_tests { use crate::{ - internal::{MerkleNode, MerkleProof}, + internal::{MerkleNode, MerkleTreeProof}, prelude::{RescueHash, RescueSparseMerkleTree}, DigestAlgorithm, ForgetableMerkleTreeScheme, ForgetableUniversalMerkleTreeScheme, Index, - LookupResult, MerkleCommitment, MerkleTreeScheme, PersistentUniversalMerkleTreeScheme, + LookupResult, MerkleProof, MerkleTreeScheme, PersistentUniversalMerkleTreeScheme, ToTraversalPath, UniversalMerkleTreeScheme, }; use ark_bls12_377::Fr as Fr377; @@ -287,18 +243,27 @@ mod mt_tests { let mt = RescueSparseMerkleTree::::from_kv_set(10, &hashmap).unwrap(); assert_eq!(mt.num_leaves(), hashmap.len() as u64); + let commitment = mt.commitment(); + let mut proof = mt .universal_lookup(BigUint::from(3u64)) .expect_not_found() .unwrap(); - let verify_result = mt.non_membership_verify(BigUint::from(3u64), &proof); - assert!(verify_result.is_ok() && verify_result.unwrap()); - proof.pos = BigUint::from(1u64); - let verify_result = mt.non_membership_verify(BigUint::from(1u64), &proof); - assert!(verify_result.is_ok() && !verify_result.unwrap()); + let verify_result = RescueSparseMerkleTree::::non_membership_verify( + &commitment, + BigUint::from(3u64), + &proof, + ) + .unwrap(); + assert!(verify_result.is_ok()); - let verify_result = mt.non_membership_verify(BigUint::from(4u64), proof); + let verify_result = RescueSparseMerkleTree::::non_membership_verify( + &commitment, + BigUint::from(1u64), + &proof, + ) + .unwrap(); assert!(verify_result.is_err()); } @@ -323,13 +288,14 @@ mod mt_tests { for i in 0..2 { mt.update(F::from(i as u64), F::from(i as u64)).unwrap(); } + let commitment = mt.commitment(); for i in 0..2 { let (val, proof) = mt.universal_lookup(F::from(i as u64)).expect_ok().unwrap(); assert_eq!(val, &F::from(i as u64)); - assert_eq!(proof.elem().unwrap(), val); assert!(RescueSparseMerkleTree::::verify( - &mt.root.value(), + &commitment, F::from(i as u64), + val, &proof ) .unwrap() @@ -343,12 +309,12 @@ mod mt_tests { .unwrap(); } assert_eq!(mt.num_leaves(), 10); + let commitment = mt.commitment(); // test lookup at index 7 let (val, proof) = mt.universal_lookup(F::from(7u64)).expect_ok().unwrap(); assert_eq!(val, &F::from(7u64)); - assert_eq!(proof.elem().unwrap(), val); assert!( - RescueSparseMerkleTree::::verify(&mt.root.value(), F::from(7u64), &proof) + RescueSparseMerkleTree::::verify(&commitment, F::from(7u64), val, &proof) .unwrap() .is_ok() ); @@ -378,7 +344,7 @@ mod mt_tests { ], ) .unwrap(); - let root = mt.commitment().digest(); + let commitment = mt.commitment(); // Look up and forget an element that is in the tree. let (lookup_elem, lookup_mem_proof) = mt @@ -390,17 +356,19 @@ mod mt_tests { assert_eq!(lookup_elem, elem); assert_eq!(lookup_mem_proof, mem_proof); assert_eq!(elem, 1u64.into()); - assert_eq!(mem_proof.tree_height(), 11); + assert_eq!(mem_proof.height(), 10); assert!(RescueSparseMerkleTree::::verify( - &root, + &commitment, BigUint::from(0u64), + &elem, &lookup_mem_proof ) .unwrap() .is_ok()); assert!(RescueSparseMerkleTree::::verify( - &root, + &commitment, BigUint::from(0u64), + &elem, &mem_proof ) .unwrap() @@ -422,11 +390,14 @@ mod mt_tests { .expect_ok() .unwrap(); assert_eq!(elem, &3u64.into()); - assert!( - RescueSparseMerkleTree::::verify(&root, BigUint::from(2u64), &proof) - .unwrap() - .is_ok() - ); + assert!(RescueSparseMerkleTree::::verify( + &commitment, + BigUint::from(2u64), + elem, + &proof + ) + .unwrap() + .is_ok()); // Look up and forget an empty sub-tree. let lookup_non_mem_proof = match mt.universal_lookup(BigUint::from(1u64)) { @@ -438,13 +409,21 @@ mod mt_tests { res => panic!("expected NotFound, got {:?}", res), }; assert_eq!(lookup_non_mem_proof, non_mem_proof); - assert_eq!(non_mem_proof.tree_height(), 11); - assert!(mt - .non_membership_verify(BigUint::from(1u64), &lookup_non_mem_proof) - .unwrap()); - assert!(mt - .non_membership_verify(BigUint::from(1u64), &non_mem_proof) - .unwrap()); + assert_eq!(non_mem_proof.height(), 10); + assert!(RescueSparseMerkleTree::::non_membership_verify( + &commitment, + BigUint::from(1u64), + &lookup_non_mem_proof + ) + .unwrap() + .is_ok()); + assert!(RescueSparseMerkleTree::::non_membership_verify( + &commitment, + BigUint::from(1u64), + &non_mem_proof + ) + .unwrap() + .is_ok()); // Forgetting an empty sub-tree will never actually cause any new entries to be // forgotten, since empty sub-trees are _already_ treated as if they @@ -452,9 +431,13 @@ mod mt_tests { // though we "forgot" it, the empty sub-tree is still in memory. match mt.universal_lookup(BigUint::from(1u64)) { LookupResult::NotFound(proof) => { - assert!(mt - .non_membership_verify(BigUint::from(1u64), &proof) - .unwrap()); + assert!(RescueSparseMerkleTree::::non_membership_verify( + &commitment, + BigUint::from(1u64), + &proof + ) + .unwrap() + .is_ok()); }, res => { panic!("expected NotFound, got {:?}", res); @@ -467,11 +450,14 @@ mod mt_tests { .expect_ok() .unwrap(); assert_eq!(elem, &3u64.into()); - assert!( - RescueSparseMerkleTree::::verify(&root, BigUint::from(2u64), &proof) - .unwrap() - .is_ok() - ); + assert!(RescueSparseMerkleTree::::verify( + &commitment, + BigUint::from(2u64), + elem, + &proof + ) + .unwrap() + .is_ok()); // Now if we forget the last entry, which is the only thing keeping the root // branch in memory, every entry will be forgotten. @@ -492,39 +478,22 @@ mod mt_tests { )); // Remember should fail if the proof is invalid. + mt.remember(BigUint::from(0u64), F::from(2u64), &mem_proof) + .unwrap_err(); + mt.remember(BigUint::from(1u64), F::from(1u64), &mem_proof) + .unwrap_err(); let mut bad_mem_proof = mem_proof.clone(); - if let MerkleNode::Leaf { elem, .. } = &mut bad_mem_proof.proof[0] { - *elem = F::from(4u64); - } else { - panic!("expected membership proof to end in a Leaf"); - } + bad_mem_proof.0[0][0] = F::one(); mt.remember(BigUint::from(0u64), F::from(1u64), &bad_mem_proof) .unwrap_err(); + mt.non_membership_remember(0u64.into(), &non_mem_proof) + .unwrap_err(); let mut bad_non_mem_proof = non_mem_proof.clone(); - bad_non_mem_proof.proof[0] = MerkleNode::Leaf { - pos: 1u64.into(), - elem: Default::default(), - value: Default::default(), - }; + bad_non_mem_proof.0[0][0] = F::one(); mt.non_membership_remember(1u64.into(), &bad_non_mem_proof) .unwrap_err(); - let mut forge_mem_proof = MerkleProof::new(1u64.into(), mem_proof.proof.clone()); - if let MerkleNode::Leaf { pos, elem, .. } = &mut forge_mem_proof.proof[0] { - *pos = 1u64.into(); - *elem = F::from(0u64); - } else { - panic!("expected membership proof to end in a Leaf"); - } - mt.remember(BigUint::from(1u64), F::from(0u64), &forge_mem_proof) - .unwrap_err(); - - let forge_non_mem_proof = non_mem_proof.clone(); - assert!(matches!(forge_non_mem_proof.proof[0], MerkleNode::Empty)); - mt.non_membership_remember(0u64.into(), &forge_non_mem_proof) - .unwrap_err(); - // Remember an occupied and an empty sub-tree. mt.remember(BigUint::from(0u64), F::from(1u64), &mem_proof) .unwrap(); @@ -537,17 +506,24 @@ mod mt_tests { .expect_ok() .unwrap(); assert_eq!(elem, &1u64.into()); - assert!( - RescueSparseMerkleTree::::verify(&root, BigUint::from(0u64), &proof) - .unwrap() - .is_ok() - ); + assert!(RescueSparseMerkleTree::::verify( + &commitment, + BigUint::from(0u64), + elem, + &proof + ) + .unwrap() + .is_ok()); match mt.universal_lookup(BigUint::from(1u64)) { LookupResult::NotFound(proof) => { - assert!(mt - .non_membership_verify(BigUint::from(1u64), &proof) - .unwrap()); + assert!(RescueSparseMerkleTree::::non_membership_verify( + &commitment, + BigUint::from(1u64), + &proof + ) + .unwrap() + .is_ok()); }, res => { panic!("expected NotFound, got {:?}", res); @@ -610,8 +586,8 @@ mod mt_tests { hashmap.insert(F::from(1u64), F::from(2u64)); hashmap.insert(F::from(10u64), F::from(3u64)); let mt = RescueSparseMerkleTree::::from_kv_set(3, &hashmap).unwrap(); - let mem_proof = mt.lookup(F::from(10u64)).expect_ok().unwrap().1; - let node = &mem_proof.proof[0]; + let (_, mem_proof) = mt.lookup(F::from(10u64)).expect_ok().unwrap(); + // let node = (F::from(10u64), elem.clone()); let non_mem_proof = match mt.universal_lookup(F::from(9u64)) { LookupResult::NotFound(proof) => proof, res => panic!("expected NotFound, got {:?}", res), @@ -629,9 +605,5 @@ mod mt_tests { non_mem_proof, bincode::deserialize(&bincode::serialize(&non_mem_proof).unwrap()).unwrap() ); - assert_eq!( - *node, - bincode::deserialize(&bincode::serialize(node).unwrap()).unwrap() - ); } } diff --git a/merkle_tree/tests/merkle_tree_hasher.rs b/merkle_tree/tests/merkle_tree_hasher.rs index 3831e56d1..f586ecda1 100644 --- a/merkle_tree/tests/merkle_tree_hasher.rs +++ b/merkle_tree/tests/merkle_tree_hasher.rs @@ -1,6 +1,4 @@ -use jf_merkle_tree::{ - errors::MerkleTreeError, hasher::HasherMerkleTree, MerkleCommitment, MerkleTreeScheme, -}; +use jf_merkle_tree::{errors::MerkleTreeError, hasher::HasherMerkleTree, MerkleTreeScheme}; use sha2::Sha256; #[test] @@ -10,9 +8,9 @@ fn doctest_example() -> Result<(), MerkleTreeError> { // payload type is `usize`, hash function is `Sha256`. let mt = HasherMerkleTree::::from_elems(Some(2), my_data)?; - let root = mt.commitment().digest(); + let commitment = mt.commitment(); let (val, proof) = mt.lookup(2).expect_ok()?; assert_eq!(val, &3); - assert!(HasherMerkleTree::::verify(root, 2, proof)?.is_ok()); + assert!(HasherMerkleTree::::verify(commitment, 2, val, proof)?.is_ok()); Ok(()) }