From fc1c9491c62ce51edf4f0b4e2ab270b85d0150ab Mon Sep 17 00:00:00 2001 From: intx4 <65897068+intx4@users.noreply.github.com> Date: Mon, 25 Mar 2024 22:06:34 +0100 Subject: [PATCH] Implemented Batched Proof for multiple openings and Path Pruning (#130) * implemented multipath * reverted index logic in verify * fixed compile errors * unittests passing * changed signature of generate_multi_proof to sort indexes * cargo fmt * implemented unit test for multi_proof internals * renamed multi_proof specific test * cargo fmt * commnts in unittest * changed function signatures * cargo fmt * modified use of BtreeSet and HashMap from ark_std crate Co-authored-by: Pratyush Mishra * modified multipath to derivative(PartialEq...) Co-authored-by: Pratyush Mishra * keep leaves as iterator in multipath verify Co-authored-by: Marcin * auth_paths to peekable in multipath.verify Co-authored-by: Marcin * updated syntax for iterators in multipath.verify Co-authored-by: Marcin * updated syntax for iterators in multipath.verify Co-authored-by: Marcin * updated syntax for iterators in multipath.verify Co-authored-by: Marcin * shortend init for multipath * fixed build issues and implementing benches * implemented benches for proof and multiproof * removed extra loop in decompress * removed explicit decompress and merged decompression into multipath verification * removed multi_path.compress and merged compression step in generate_multi_proof * implemented prefix_decode_path * removed redundant code with new helper functions * added doc for get_leaf_sibling_hash and made it pub. Renamed compute_path to compute_auth_path * cargo fmt * changed multiproof.verify to use "insert_with" instead of "insert" (this solves a bug where hashes were computed no matter what). This makes the lut optimization effective * cargo fmt * Use iterators in prefix_encode_path * Allow function-specific sample size in merkle tree benches * removed redundant imports (nightly build tests) * cargo fmt * nightly build fix --------- Co-authored-by: Pratyush Mishra Co-authored-by: Marcin Co-authored-by: Cesar199999 --- crypto-primitives/benches/merkle_tree.rs | 154 ++++++++++- .../src/commitment/blake2s/constraints.rs | 1 - .../src/commitment/pedersen/constraints.rs | 1 - .../src/commitment/pedersen/mod.rs | 2 +- .../src/crh/bowe_hopwood/constraints.rs | 16 +- crypto-primitives/src/crh/bowe_hopwood/mod.rs | 2 +- .../src/crh/injective_map/constraints.rs | 4 +- .../src/crh/injective_map/mod.rs | 1 - .../src/crh/pedersen/constraints.rs | 9 +- crypto-primitives/src/crh/pedersen/mod.rs | 2 +- .../src/crh/poseidon/constraints.rs | 2 +- .../src/crh/sha256/constraints.rs | 12 +- crypto-primitives/src/crh/sha256/mod.rs | 2 +- .../src/encryption/elgamal/constraints.rs | 2 +- .../src/merkle_tree/constraints.rs | 1 - crypto-primitives/src/merkle_tree/mod.rs | 252 ++++++++++++++++-- .../src/merkle_tree/tests/mod.rs | 110 +++++++- .../src/prf/blake2s/constraints.rs | 2 +- crypto-primitives/src/prf/blake2s/mod.rs | 1 - crypto-primitives/src/prf/constraints.rs | 2 +- crypto-primitives/src/signature/mod.rs | 4 +- .../src/signature/schnorr/constraints.rs | 1 - .../src/signature/schnorr/mod.rs | 2 +- crypto-primitives/src/snark/constraints.rs | 22 +- crypto-primitives/src/sponge/absorb.rs | 4 +- .../src/sponge/constraints/absorb.rs | 3 +- .../src/sponge/constraints/mod.rs | 2 - crypto-primitives/src/sponge/mod.rs | 1 - .../src/sponge/poseidon/constraints.rs | 2 - .../src/sponge/poseidon/grain_lfsr.rs | 1 - crypto-primitives/src/sponge/poseidon/mod.rs | 2 - .../src/sponge/poseidon/traits.rs | 3 +- 32 files changed, 525 insertions(+), 100 deletions(-) diff --git a/crypto-primitives/benches/merkle_tree.rs b/crypto-primitives/benches/merkle_tree.rs index b32168e8..405cbbc5 100644 --- a/crypto-primitives/benches/merkle_tree.rs +++ b/crypto-primitives/benches/merkle_tree.rs @@ -12,6 +12,7 @@ mod bytes_mt_benches { use ark_std::{test_rng, UniformRand}; use criterion::Criterion; use std::borrow::Borrow; + use std::iter::zip; use crate::NUM_LEAVES; @@ -56,11 +57,160 @@ mod bytes_mt_benches { }); } + pub fn merkle_tree_generate_proof(c: &mut Criterion) { + let mut rng = test_rng(); + let leaves: Vec<_> = (0..NUM_LEAVES) + .map(|_| { + let rnd = BigInteger256::rand(&mut rng); + to_uncompressed_bytes!(rnd).unwrap() + }) + .collect(); + let leaf_crh_params = ::setup(&mut rng).unwrap(); + let two_to_one_params = ::setup(&mut rng) + .unwrap() + .clone(); + + let tree = Sha256MerkleTree::new( + &leaf_crh_params.clone(), + &two_to_one_params.clone(), + &leaves, + ) + .unwrap(); + c.bench_function("Merkle Tree Generate Proof (Leaves as [u8])", move |b| { + b.iter(|| { + for (i, _) in leaves.iter().enumerate() { + tree.generate_proof(i).unwrap(); + } + }) + }); + } + + pub fn merkle_tree_verify_proof(c: &mut Criterion) { + let mut rng = test_rng(); + let leaves: Vec<_> = (0..NUM_LEAVES) + .map(|_| { + let rnd = BigInteger256::rand(&mut rng); + to_uncompressed_bytes!(rnd).unwrap() + }) + .collect(); + let leaf_crh_params = ::setup(&mut rng).unwrap(); + let two_to_one_params = ::setup(&mut rng) + .unwrap() + .clone(); + + let tree = Sha256MerkleTree::new( + &leaf_crh_params.clone(), + &two_to_one_params.clone(), + &leaves, + ) + .unwrap(); + + let root = tree.root(); + + let proofs: Vec<_> = leaves + .iter() + .enumerate() + .map(|(i, _)| tree.generate_proof(i).unwrap()) + .collect(); + + c.bench_function("Merkle Tree Verify Proof (Leaves as [u8])", move |b| { + b.iter(|| { + for (proof, leaf) in zip(proofs.clone(), leaves.clone()) { + proof + .verify(&leaf_crh_params, &two_to_one_params, &root, leaf.as_slice()) + .unwrap(); + } + }) + }); + } + + pub fn merkle_tree_generate_multi_proof(c: &mut Criterion) { + let mut rng = test_rng(); + let leaves: Vec<_> = (0..NUM_LEAVES) + .map(|_| { + let rnd = BigInteger256::rand(&mut rng); + to_uncompressed_bytes!(rnd).unwrap() + }) + .collect(); + let leaf_crh_params = ::setup(&mut rng).unwrap(); + let two_to_one_params = ::setup(&mut rng) + .unwrap() + .clone(); + + let tree = Sha256MerkleTree::new( + &leaf_crh_params.clone(), + &two_to_one_params.clone(), + &leaves, + ) + .unwrap(); + c.bench_function( + "Merkle Tree Generate Multi Proof (Leaves as [u8])", + move |b| { + b.iter(|| { + tree.generate_multi_proof((0..leaves.len()).collect::>()) + .unwrap(); + }) + }, + ); + } + + pub fn merkle_tree_verify_multi_proof(c: &mut Criterion) { + let mut rng = test_rng(); + let leaves: Vec<_> = (0..NUM_LEAVES) + .map(|_| { + let rnd = BigInteger256::rand(&mut rng); + to_uncompressed_bytes!(rnd).unwrap() + }) + .collect(); + let leaf_crh_params = ::setup(&mut rng).unwrap(); + let two_to_one_params = ::setup(&mut rng) + .unwrap() + .clone(); + + let tree = Sha256MerkleTree::new( + &leaf_crh_params.clone(), + &two_to_one_params.clone(), + &leaves, + ) + .unwrap(); + + let root = tree.root(); + + let multi_proof = tree + .generate_multi_proof((0..leaves.len()).collect::>()) + .unwrap(); + + c.bench_function( + "Merkle Tree Verify Multi Proof (Leaves as [u8])", + move |b| { + b.iter(|| { + multi_proof.verify(&leaf_crh_params, &two_to_one_params, &root, leaves.clone()) + }) + }, + ); + } + criterion_group! { name = mt_create; - config = Criterion::default().sample_size(10); + config = Criterion::default().sample_size(100); targets = merkle_tree_create } + + criterion_group! { + name = mt_proof; + config = Criterion::default().sample_size(100); + targets = merkle_tree_generate_proof, merkle_tree_generate_multi_proof + } + + criterion_group! { + name = mt_verify; + config = Criterion::default().sample_size(10); + targets = merkle_tree_verify_proof, merkle_tree_verify_multi_proof + } } -criterion_main!(crate::bytes_mt_benches::mt_create,); +criterion_main!( + bytes_mt_benches::mt_create, + bytes_mt_benches::mt_proof, + bytes_mt_benches::mt_verify +); diff --git a/crypto-primitives/src/commitment/blake2s/constraints.rs b/crypto-primitives/src/commitment/blake2s/constraints.rs index f38b7378..07f28ca8 100644 --- a/crypto-primitives/src/commitment/blake2s/constraints.rs +++ b/crypto-primitives/src/commitment/blake2s/constraints.rs @@ -3,7 +3,6 @@ use ark_relations::r1cs::{Namespace, SynthesisError}; use crate::{ commitment::{blake2s, CommitmentGadget}, prf::blake2s::constraints::{evaluate_blake2s, OutputVar}, - Vec, }; use ark_ff::{Field, PrimeField}; use ark_r1cs_std::prelude::*; diff --git a/crypto-primitives/src/commitment/pedersen/constraints.rs b/crypto-primitives/src/commitment/pedersen/constraints.rs index f077e295..8386815d 100644 --- a/crypto-primitives/src/commitment/pedersen/constraints.rs +++ b/crypto-primitives/src/commitment/pedersen/constraints.rs @@ -1,7 +1,6 @@ use crate::{ commitment::pedersen::{Commitment, Parameters, Randomness}, crh::pedersen::Window, - Vec, }; use ark_ec::CurveGroup; use ark_ff::{ diff --git a/crypto-primitives/src/commitment/pedersen/mod.rs b/crypto-primitives/src/commitment/pedersen/mod.rs index 6782e9c6..cfcdab74 100644 --- a/crypto-primitives/src/commitment/pedersen/mod.rs +++ b/crypto-primitives/src/commitment/pedersen/mod.rs @@ -1,4 +1,4 @@ -use crate::{crh::CRHScheme, Error, Vec}; +use crate::{crh::CRHScheme, Error}; use ark_ec::CurveGroup; use ark_ff::{BitIteratorLE, Field, PrimeField, ToConstraintField}; use ark_serialize::CanonicalSerialize; diff --git a/crypto-primitives/src/crh/bowe_hopwood/constraints.rs b/crypto-primitives/src/crh/bowe_hopwood/constraints.rs index e1f6f487..e5eef789 100644 --- a/crypto-primitives/src/crh/bowe_hopwood/constraints.rs +++ b/crypto-primitives/src/crh/bowe_hopwood/constraints.rs @@ -2,22 +2,16 @@ use ark_ec::twisted_edwards::{Projective as TEProjective, TECurveConfig}; use ark_ec::CurveConfig; use core::{borrow::Borrow, iter, marker::PhantomData}; -use crate::{ - crh::{ - bowe_hopwood::{Parameters, CHUNK_SIZE}, - pedersen::{self, Window}, - CRHSchemeGadget, TwoToOneCRHSchemeGadget, - }, - Vec, +use crate::crh::{ + bowe_hopwood::{Parameters, CHUNK_SIZE}, + pedersen::{self, Window}, + CRHSchemeGadget, TwoToOneCRHSchemeGadget, }; use ark_ff::Field; -use ark_r1cs_std::{ - alloc::AllocVar, groups::curves::twisted_edwards::AffineVar, prelude::*, uint8::UInt8, -}; +use ark_r1cs_std::{groups::curves::twisted_edwards::AffineVar, prelude::*}; use ark_relations::r1cs::{Namespace, SynthesisError}; use crate::crh::bowe_hopwood::{TwoToOneCRH, CRH}; -use ark_r1cs_std::boolean::Boolean; type ConstraintF

= <

::BaseField as Field>::BasePrimeField; diff --git a/crypto-primitives/src/crh/bowe_hopwood/mod.rs b/crypto-primitives/src/crh/bowe_hopwood/mod.rs index 820727a0..fb2bf0ac 100644 --- a/crypto-primitives/src/crh/bowe_hopwood/mod.rs +++ b/crypto-primitives/src/crh/bowe_hopwood/mod.rs @@ -2,7 +2,7 @@ //! specific Twisted Edwards (TE) curves. See [Section 5.4.17 of the Zcash protocol specification](https://raw.githubusercontent.com/zcash/zips/master/protocol/protocol.pdf#concretepedersenhash) for a formal description of this hash function, specialized for the Jubjub curve. //! The implementation in this repository is generic across choice of TE curves. -use crate::{Error, Vec}; +use crate::Error; use ark_std::rand::Rng; use ark_std::{ fmt::{Debug, Formatter, Result as FmtResult}, diff --git a/crypto-primitives/src/crh/injective_map/constraints.rs b/crypto-primitives/src/crh/injective_map/constraints.rs index 8e9cd703..1a60c842 100644 --- a/crypto-primitives/src/crh/injective_map/constraints.rs +++ b/crypto-primitives/src/crh/injective_map/constraints.rs @@ -14,9 +14,7 @@ use ark_ec::{ }; use ark_ff::fields::{Field, PrimeField}; use ark_r1cs_std::{ - fields::fp::FpVar, - groups::{curves::twisted_edwards::AffineVar as TEVar, CurveVar}, - prelude::*, + fields::fp::FpVar, groups::curves::twisted_edwards::AffineVar as TEVar, prelude::*, }; use ark_relations::r1cs::SynthesisError; diff --git a/crypto-primitives/src/crh/injective_map/mod.rs b/crypto-primitives/src/crh/injective_map/mod.rs index fbd99fd1..4927852a 100644 --- a/crypto-primitives/src/crh/injective_map/mod.rs +++ b/crypto-primitives/src/crh/injective_map/mod.rs @@ -9,7 +9,6 @@ use ark_ec::{ }; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::borrow::Borrow; -use ark_std::vec::Vec; #[cfg(feature = "r1cs")] pub mod constraints; diff --git a/crypto-primitives/src/crh/pedersen/constraints.rs b/crypto-primitives/src/crh/pedersen/constraints.rs index fdf2340a..3f64e755 100644 --- a/crypto-primitives/src/crh/pedersen/constraints.rs +++ b/crypto-primitives/src/crh/pedersen/constraints.rs @@ -1,9 +1,6 @@ -use crate::{ - crh::{ - pedersen::{Parameters, Window}, - CRHSchemeGadget as CRHGadgetTrait, - }, - Vec, +use crate::crh::{ + pedersen::{Parameters, Window}, + CRHSchemeGadget as CRHGadgetTrait, }; use ark_ec::CurveGroup; use ark_ff::Field; diff --git a/crypto-primitives/src/crh/pedersen/mod.rs b/crypto-primitives/src/crh/pedersen/mod.rs index eec86e36..97850b86 100644 --- a/crypto-primitives/src/crh/pedersen/mod.rs +++ b/crypto-primitives/src/crh/pedersen/mod.rs @@ -1,4 +1,4 @@ -use crate::{Error, Vec}; +use crate::Error; use ark_std::rand::Rng; use ark_std::{ fmt::{Debug, Formatter, Result as FmtResult}, diff --git a/crypto-primitives/src/crh/poseidon/constraints.rs b/crypto-primitives/src/crh/poseidon/constraints.rs index 9684624c..a0ad5001 100644 --- a/crypto-primitives/src/crh/poseidon/constraints.rs +++ b/crypto-primitives/src/crh/poseidon/constraints.rs @@ -1,11 +1,11 @@ use crate::crh::poseidon::{TwoToOneCRH, CRH}; +use crate::crh::CRHScheme; use crate::crh::{ CRHSchemeGadget as CRHGadgetTrait, TwoToOneCRHSchemeGadget as TwoToOneCRHGadgetTrait, }; use crate::sponge::constraints::CryptographicSpongeVar; use crate::sponge::poseidon::constraints::PoseidonSpongeVar; use crate::sponge::poseidon::PoseidonConfig; -use crate::{crh::CRHScheme, Vec}; use crate::sponge::Absorb; use ark_ff::PrimeField; diff --git a/crypto-primitives/src/crh/sha256/constraints.rs b/crypto-primitives/src/crh/sha256/constraints.rs index c7080809..bf40c945 100644 --- a/crypto-primitives/src/crh/sha256/constraints.rs +++ b/crypto-primitives/src/crh/sha256/constraints.rs @@ -19,7 +19,6 @@ use ark_r1cs_std::{ R1CSVar, }; use ark_relations::r1cs::{ConstraintSystemRef, Namespace, SynthesisError}; -use ark_std::{vec, vec::Vec}; const STATE_LEN: usize = 8; @@ -383,17 +382,10 @@ where #[cfg(test)] mod test { use super::*; - use crate::crh::{ - sha256::{digest::Digest, Sha256}, - CRHScheme, CRHSchemeGadget, TwoToOneCRHScheme, TwoToOneCRHSchemeGadget, - }; + use crate::crh::{sha256::digest::Digest, CRHScheme, TwoToOneCRHScheme}; use ark_bls12_377::Fr; - use ark_r1cs_std::R1CSVar; - use ark_relations::{ - ns, - r1cs::{ConstraintSystem, Namespace}, - }; + use ark_relations::{ns, r1cs::ConstraintSystem}; use ark_std::rand::RngCore; const TEST_LENGTHS: &[usize] = &[ diff --git a/crypto-primitives/src/crh/sha256/mod.rs b/crypto-primitives/src/crh/sha256/mod.rs index 6010be32..8a2cb1d4 100644 --- a/crypto-primitives/src/crh/sha256/mod.rs +++ b/crypto-primitives/src/crh/sha256/mod.rs @@ -1,5 +1,5 @@ use crate::crh::{CRHScheme, TwoToOneCRHScheme}; -use crate::{Error, Vec}; +use crate::Error; use ark_std::rand::Rng; diff --git a/crypto-primitives/src/encryption/elgamal/constraints.rs b/crypto-primitives/src/encryption/elgamal/constraints.rs index 7527352a..290492d2 100644 --- a/crypto-primitives/src/encryption/elgamal/constraints.rs +++ b/crypto-primitives/src/encryption/elgamal/constraints.rs @@ -11,7 +11,7 @@ use ark_ff::{ Zero, }; use ark_serialize::CanonicalSerialize; -use ark_std::{borrow::Borrow, marker::PhantomData, vec::Vec}; +use ark_std::{borrow::Borrow, marker::PhantomData}; pub type ConstraintF = <::BaseField as Field>::BasePrimeField; diff --git a/crypto-primitives/src/merkle_tree/constraints.rs b/crypto-primitives/src/merkle_tree/constraints.rs index e243079c..4cb764a3 100644 --- a/crypto-primitives/src/merkle_tree/constraints.rs +++ b/crypto-primitives/src/merkle_tree/constraints.rs @@ -6,7 +6,6 @@ use ark_r1cs_std::prelude::*; use ark_relations::r1cs::{Namespace, SynthesisError}; use ark_std::borrow::Borrow; use ark_std::fmt::Debug; -use ark_std::vec::Vec; pub trait DigestVarConverter { type TargetType: Borrow; diff --git a/crypto-primitives/src/merkle_tree/mod.rs b/crypto-primitives/src/merkle_tree/mod.rs index 4a0a4a19..a5fd30b0 100644 --- a/crypto-primitives/src/merkle_tree/mod.rs +++ b/crypto-primitives/src/merkle_tree/mod.rs @@ -6,8 +6,8 @@ use crate::sponge::Absorb; use crate::{crh::CRHScheme, Error}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::borrow::Borrow; +use ark_std::collections::{BTreeSet, HashMap}; use ark_std::hash::Hash; -use ark_std::vec::Vec; #[cfg(test)] mod tests; @@ -110,6 +110,7 @@ pub type LeafParam

= <

::LeafHash as CRHScheme>::Parameters; /// Suppose we want to prove I, then `leaf_sibling_hash` is J, `auth_path` is `[C,D]` #[derive(Derivative, CanonicalSerialize, CanonicalDeserialize)] #[derivative( + PartialEq(bound = "P: Config"), Clone(bound = "P: Config"), Debug(bound = "P: Config"), Default(bound = "P: Config") @@ -183,6 +184,143 @@ impl Path

{ } } +/// Optimized data structure to store multiple nodes proofs. +/// For example: +/// ```tree_diagram +/// [A] +/// / \ +/// [B] C +/// / \ / \ +/// D [E] F H +/// / \ / \ .... +/// [I]J L M +/// ``` +/// Suppose we want to prove I and J, then: +/// `leaf_indexes` is: [2,3] (indexes in Merkle Tree leaves vector) +/// `leaf_siblings_hashes`: [J,I] +/// `auth_paths_prefix_lenghts`: [0,2] +/// `auth_paths_suffixes`: [ [C,D], []] +/// We can reconstruct the paths incrementally: +/// First, we reconstruct the first path. The prefix length is 0, hence we do not have any prefix encoding. +/// The path is thus [C,D]. +/// Once the first path is verified, we can reconstruct the second path. +/// The prefix length of 2 means that the path prefix will be `previous_path[:2] -> [C,D]`. +/// Since the Merkle Tree branch is the same, the authentication path is the same (which means in this case that there is no suffix). +/// The second path is hence `[C,D] + []` (i.e., plus the empty suffix). We can verify the second path as the first one. + +#[derive(Derivative, CanonicalSerialize, CanonicalDeserialize)] +#[derivative( + Clone(bound = "P: Config"), + Debug(bound = "P: Config"), + Default(bound = "P: Config") +)] +pub struct MultiPath { + /// For node i, stores the hash of node i's sibling + pub leaf_siblings_hashes: Vec, + /// For node i path, stores at index i the prefix length of the path, for Incremental encoding + pub auth_paths_prefix_lenghts: Vec, + /// For node i path, stores at index i the suffix of the path for Incremental Encoding (as vector of symbols to be resolved with self.lut). Order is from higher layer to lower layer (does not include root node). + pub auth_paths_suffixes: Vec>, + /// stores the leaf indexes of the nodes to prove + pub leaf_indexes: Vec, +} + +impl MultiPath

{ + /// Verify that leaves are at `self.leaf_indexes` of the merkle tree. + /// Note that the order of the leaves hashes should match the leaves respective indexes + /// * `leaf_size`: leaf size in number of bytes + /// + /// `verify` infers the tree height by setting `tree_height = self.auth_paths_suffixes[0].len() + 2` + pub fn verify + Clone>( + &self, + leaf_hash_params: &LeafParam

, + two_to_one_params: &TwoToOneParam

, + root_hash: &P::InnerDigest, + leaves: impl IntoIterator, + ) -> Result { + let tree_height = self.auth_paths_suffixes[0].len() + 2; + let mut leaves = leaves.into_iter(); + + // LookUp table to speedup computation avoid redundant hash computations + let mut hash_lut: HashMap = HashMap::new(); + + // init prev path for decoding + let mut prev_path: Vec<_> = self.auth_paths_suffixes[0].clone(); + + for i in 0..self.leaf_indexes.len() { + let leaf_index = self.leaf_indexes[i]; + let leaf = leaves.next().unwrap(); + let leaf_sibling_hash = &self.leaf_siblings_hashes[i]; + + // decode i-th auth path + let auth_path = prefix_decode_path( + &prev_path, + self.auth_paths_prefix_lenghts[i], + &self.auth_paths_suffixes[i], + ); + // update prev path for decoding next one + prev_path = auth_path.clone(); + + let claimed_leaf_hash = P::LeafHash::evaluate(&leaf_hash_params, leaf.clone())?; + let (left_child, right_child) = + select_left_right_child(leaf_index, &claimed_leaf_hash, &leaf_sibling_hash)?; + // check hash along the path from bottom to root + + // leaf layer to inner layer conversion + let left_child = P::LeafInnerDigestConverter::convert(left_child)?; + let right_child = P::LeafInnerDigestConverter::convert(right_child)?; + + // we will use `index` variable to track the position of path + let mut index = leaf_index; + let mut index_in_tree = convert_index_to_last_level(leaf_index, tree_height); + index >>= 1; + index_in_tree = parent(index_in_tree).unwrap(); + + let mut curr_path_node = hash_lut.entry(index_in_tree).or_insert_with(|| { + P::TwoToOneHash::evaluate(&two_to_one_params, left_child, right_child).unwrap() + }); + + // Check levels between leaf level and root + for level in (0..auth_path.len()).rev() { + // check if path node at this level is left or right + let (left, right) = + select_left_right_child(index, curr_path_node, &auth_path[level])?; + // update curr_path_node + index >>= 1; + index_in_tree = parent(index_in_tree).unwrap(); + curr_path_node = hash_lut.entry(index_in_tree).or_insert_with(|| { + P::TwoToOneHash::compress(&two_to_one_params, left, right).unwrap() + }); + } + + // check if final hash is root + if curr_path_node != root_hash { + return Ok(false); + } + } + Ok(true) + } + + /// The position of on_path node in `leaf_and_sibling_hash` and `non_leaf_and_sibling_hash_path`. + /// `position[i]` is 0 (false) iff `i`th on-path node from top to bottom is on the left. + /// + /// This function simply converts every index in `self.leaf_indexes` to boolean array in big endian form. + #[allow(unused)] // this function is actually used when r1cs feature is on + fn position_list(&'_ self) -> impl '_ + Iterator> { + let path_len = self.auth_paths_suffixes[0].len(); + + cfg_into_iter!(self.leaf_indexes.clone()) + .map(move |i| { + (0..path_len + 1) + .map(move |j| ((i >> j) & 1) != 0) + .rev() + .collect() + }) + .collect::>() + .into_iter() + } +} + /// `index` is the first `path.len()` bits of /// the position of tree. /// @@ -295,8 +433,8 @@ impl MerkleTree

{ // leaf in the whole tree (represented as a list in level order). We need to shift it // by `-upper_bound` to get the index in `leaf_nodes` list. - //similarly, we need to rescale i by start_index - //to get the index outside the slice and in the level-ordered list of nodes + // similarly, we need to rescale i by start_index + // to get the index outside the slice and in the level-ordered list of nodes let current_index = i + start_index; let left_leaf_index = left_child(current_index) - upper_bound; @@ -331,8 +469,8 @@ impl MerkleTree

{ // leaf in the whole tree (represented as a list in level order). We need to shift it // by `-upper_bound` to get the index in `leaf_nodes` list. - //similarly, we need to rescale i by start_index - //to get the index outside the slice and in the level-ordered list of nodes + // similarly, we need to rescale i by start_index + // to get the index outside the slice and in the level-ordered list of nodes let current_index = i + start_index; let left_leaf_index = left_child(current_index) - upper_bound; let right_leaf_index = right_child(current_index) - upper_bound; @@ -365,20 +503,24 @@ impl MerkleTree

{ self.height } - /// Returns the authentication path from leaf at `index` to root. - pub fn generate_proof(&self, index: usize) -> Result, crate::Error> { - // gather basic tree information - let tree_height = tree_height(self.leaf_nodes.len()); - - // Get Leaf hash, and leaf sibling hash, - let leaf_index_in_tree = convert_index_to_last_level(index, tree_height); - let leaf_sibling_hash = if index & 1 == 0 { + /// Given the `index` of a leaf, returns the digest of its leaf sibling + pub fn get_leaf_sibling_hash(&self, index: usize) -> P::LeafDigest { + if index & 1 == 0 { // leaf is left child self.leaf_nodes[index + 1].clone() } else { // leaf is right child self.leaf_nodes[index - 1].clone() - }; + } + } + + /// Returns the authentication path from leaf at `index` to root, as a Vec of digests + fn compute_auth_path(&self, index: usize) -> Vec { + // gather basic tree information + let tree_height = tree_height(self.leaf_nodes.len()); + + // Get Leaf hash, and leaf sibling hash, + let leaf_index_in_tree = convert_index_to_last_level(index, tree_height); // path.len() = `tree height - 2`, the two missing elements being the leaf sibling hash and the root let mut path = Vec::with_capacity(tree_height - 2); @@ -394,11 +536,60 @@ impl MerkleTree

{ // we want to make path from root to bottom path.reverse(); + path + } + /// Returns the authentication path from leaf at `index` to root. + pub fn generate_proof(&self, index: usize) -> Result, crate::Error> { + let path = self.compute_auth_path(index); Ok(Path { leaf_index: index, auth_path: path, - leaf_sibling_hash, + leaf_sibling_hash: self.get_leaf_sibling_hash(index), + }) + } + + /// Returns a MultiPath (multiple authentication paths in compressed form, with Front Incremental Encoding), + /// from every leaf to root. + /// Note that for compression efficiency, the indexes are internally sorted. + /// For sorted indexes, MultiPath contains: + /// `2*( (num_leaves.log2()-1).pow(2) - (num_leaves.log2()-2) )` + /// instead of + /// `num_leaves*(num_leaves.log2()-1)` + /// When verifying the proof, leaves hashes should be supplied in order, that is: + /// let ordered_leaves: Vec<_> = self.leaf_indexes.into_iter().map(|i| leaves[i]).collect(); + pub fn generate_multi_proof( + &self, + indexes: impl IntoIterator, + ) -> Result, crate::Error> { + // pruned and sorted for encoding efficiency + let indexes: BTreeSet = indexes.into_iter().collect(); + + //let auth_paths = Vec::with_capacity(indexes.len()); + let mut auth_paths_prefix_lenghts: Vec = Vec::with_capacity(indexes.len()); + let mut auth_paths_suffixes: Vec> = Vec::with_capacity(indexes.len()); + + let mut leaf_siblings_hashes = Vec::with_capacity(indexes.len()); + + let mut prev_path = Vec::new(); + + for index in &indexes { + leaf_siblings_hashes.push(self.get_leaf_sibling_hash(*index)); + + let path = self.compute_auth_path(*index); + + // incremental encoding + let (prefix_len, suffix) = prefix_encode_path(&prev_path, &path); + auth_paths_prefix_lenghts.push(prefix_len); + auth_paths_suffixes.push(suffix); + prev_path = path; + } + + Ok(MultiPath { + leaf_indexes: Vec::from_iter(indexes), + auth_paths_prefix_lenghts, + auth_paths_suffixes, + leaf_siblings_hashes, }) } @@ -562,3 +753,34 @@ fn parent(index: usize) -> Option { fn convert_index_to_last_level(index: usize, tree_height: usize) -> usize { index + (1 << (tree_height - 1)) - 1 } + +/// Encodes path with Incremental Encoding by comparing with prev_path +/// Returns the prefix length and the suffix to append during decoding +/// Example: +/// If `prev_path` is vec![C,D] and `path` is vec![C,E] (where C,D,E are hashes) +/// `prefix_encode_path` returns 1,vec![E] + +#[inline] +fn prefix_encode_path(prev_path: &Vec, path: &Vec) -> (usize, Vec) +where + T: Eq + Clone, +{ + let prefix_length = prev_path + .iter() + .zip(path.iter()) + .take_while(|(a, b)| a == b) + .count(); + + (prefix_length, path[prefix_length..].to_vec()) +} + +fn prefix_decode_path(prev_path: &Vec, prefix_len: usize, suffix: &Vec) -> Vec +where + T: Eq + Clone, +{ + if prefix_len == 0 { + suffix.clone() + } else { + vec![prev_path[0..prefix_len].to_vec(), suffix.clone()].concat() + } +} diff --git a/crypto-primitives/src/merkle_tree/tests/mod.rs b/crypto-primitives/src/merkle_tree/tests/mod.rs index a4968917..a2815f57 100644 --- a/crypto-primitives/src/merkle_tree/tests/mod.rs +++ b/crypto-primitives/src/merkle_tree/tests/mod.rs @@ -4,13 +4,11 @@ mod test_utils; mod bytes_mt_tests { - use crate::{ - crh::{pedersen, *}, - merkle_tree::*, - }; + use crate::{crh::*, merkle_tree::*}; use ark_ed_on_bls12_381::EdwardsProjective as JubJub; use ark_ff::BigInteger256; use ark_std::{test_rng, UniformRand}; + use std::iter::zip; #[derive(Clone)] pub(super) struct Window4x256; @@ -60,6 +58,15 @@ mod bytes_mt_tests { .unwrap()); } + // test the merkle tree multi-proof functionality + let mut multi_proof = tree + .generate_multi_proof((0..leaves.len()).collect::>()) + .unwrap(); + + assert!(multi_proof + .verify(&leaf_crh_params, &two_to_one_params, &root, leaves.clone()) + .unwrap()); + // test merkle tree update functionality for (i, v) in update_query { let v = crate::to_uncompressed_bytes!(v).unwrap(); @@ -75,6 +82,15 @@ mod bytes_mt_tests { .verify(&leaf_crh_params, &two_to_one_params, &root, leaf.as_slice()) .unwrap()); } + + // test the merkle tree multi-proof functionality again + multi_proof = tree + .generate_multi_proof((0..leaves.len()).collect::>()) + .unwrap(); + + assert!(multi_proof + .verify(&leaf_crh_params, &two_to_one_params, &root, leaves.clone()) + .unwrap()); } #[test] @@ -114,13 +130,64 @@ mod bytes_mt_tests { ], ); } + + #[test] + fn multi_proof_dissection_test() { + let mut rng = test_rng(); + + let mut leaves = Vec::new(); + for _ in 0..8u8 { + leaves.push(BigInteger256::rand(&mut rng)); + } + assert_eq!(leaves.len(), 8); + + let serialized_leaves: Vec<_> = leaves + .iter() + .map(|leaf| crate::to_uncompressed_bytes!(leaf).unwrap()) + .collect(); + + let leaf_crh_params = ::setup(&mut rng).unwrap(); + let two_to_one_params = ::setup(&mut rng).unwrap(); + + let tree = JubJubMerkleTree::new(&leaf_crh_params, &two_to_one_params, &serialized_leaves) + .unwrap(); + + let mut proofs = Vec::with_capacity(leaves.len()); + + for (i, _) in leaves.iter().enumerate() { + proofs.push(tree.generate_proof(i).unwrap()); + } + + let multi_proof = tree + .generate_multi_proof((0..leaves.len()).collect::>()) + .unwrap(); + + // test compression theretical prefix lengths for size 8 Tree: + // we should send 6 hashes instead of 2*8 = 16 + let theoretical_prefix_lengths = vec![0, 2, 1, 2, 0, 2, 1, 2]; + + for (comp_len, exp_len) in zip( + &multi_proof.auth_paths_prefix_lenghts, + &theoretical_prefix_lengths, + ) { + assert_eq!(comp_len, exp_len); + } + + // test that the compressed paths can expand to expected len + for (prefix_len, suffix) in zip( + &multi_proof.auth_paths_prefix_lenghts, + &multi_proof.auth_paths_suffixes, + ) { + assert_eq!(prefix_len + suffix.len(), proofs[0].auth_path.len()); + } + } } mod field_mt_tests { use crate::crh::poseidon; use crate::merkle_tree::tests::test_utils::poseidon_parameters; use crate::merkle_tree::{Config, IdentityDigestConverter, MerkleTree}; - use ark_std::{test_rng, vec::Vec, One, UniformRand}; + use ark_std::{test_rng, One, UniformRand}; type F = ark_ed_on_bls12_381::Fr; type H = poseidon::CRH; @@ -155,6 +222,15 @@ mod field_mt_tests { .unwrap()); } + // test the merkle tree multi-proof functionality + let mut multi_proof = tree + .generate_multi_proof((0..leaves.len()).collect::>()) + .unwrap(); + + assert!(multi_proof + .verify(&leaf_crh_params, &two_to_one_params, &root, leaves.clone()) + .unwrap()); + { // wrong root should lead to error but do not panic let wrong_root = root + F::one(); @@ -166,7 +242,21 @@ mod field_mt_tests { &wrong_root, leaves[0].as_slice() ) - .unwrap()) + .unwrap()); + + // test the merkle tree multi-proof functionality + let multi_proof = tree + .generate_multi_proof((0..leaves.len()).collect::>()) + .unwrap(); + + assert!(!multi_proof + .verify( + &leaf_crh_params, + &two_to_one_params, + &wrong_root, + leaves.clone() + ) + .unwrap()); } // test merkle tree update functionality @@ -185,6 +275,14 @@ mod field_mt_tests { .verify(&leaf_crh_params, &two_to_one_params, &root, leaf.as_slice()) .unwrap()); } + + multi_proof = tree + .generate_multi_proof((0..leaves.len()).collect::>()) + .unwrap(); + + assert!(multi_proof + .verify(&leaf_crh_params, &two_to_one_params, &root, leaves.clone()) + .unwrap()); } #[test] diff --git a/crypto-primitives/src/prf/blake2s/constraints.rs b/crypto-primitives/src/prf/blake2s/constraints.rs index cd52cf69..fdfd99d7 100644 --- a/crypto-primitives/src/prf/blake2s/constraints.rs +++ b/crypto-primitives/src/prf/blake2s/constraints.rs @@ -1,7 +1,7 @@ use ark_ff::PrimeField; use ark_relations::r1cs::{ConstraintSystemRef, Namespace, SynthesisError}; -use crate::{prf::PRFGadget, Vec}; +use crate::prf::PRFGadget; use ark_r1cs_std::prelude::*; use core::borrow::Borrow; diff --git a/crypto-primitives/src/prf/blake2s/mod.rs b/crypto-primitives/src/prf/blake2s/mod.rs index 7455e18a..b2696c19 100644 --- a/crypto-primitives/src/prf/blake2s/mod.rs +++ b/crypto-primitives/src/prf/blake2s/mod.rs @@ -1,4 +1,3 @@ -use crate::Vec; use blake2::{Blake2s256 as B2s, Blake2sMac}; use digest::Digest; diff --git a/crypto-primitives/src/prf/constraints.rs b/crypto-primitives/src/prf/constraints.rs index bbca88c7..1e09571d 100644 --- a/crypto-primitives/src/prf/constraints.rs +++ b/crypto-primitives/src/prf/constraints.rs @@ -1,7 +1,7 @@ use ark_ff::Field; use core::fmt::Debug; -use crate::{prf::PRF, Vec}; +use crate::prf::PRF; use ark_relations::r1cs::{Namespace, SynthesisError}; use ark_r1cs_std::prelude::*; diff --git a/crypto-primitives/src/signature/mod.rs b/crypto-primitives/src/signature/mod.rs index c7cbddca..1f7cf219 100644 --- a/crypto-primitives/src/signature/mod.rs +++ b/crypto-primitives/src/signature/mod.rs @@ -52,10 +52,10 @@ pub trait SignatureScheme { #[cfg(test)] mod test { - use crate::signature::{schnorr, *}; + use crate::signature::*; use ark_ec::AdditiveGroup; use ark_ed_on_bls12_381::EdwardsProjective as JubJub; - use ark_std::{test_rng, vec::Vec, UniformRand}; + use ark_std::{test_rng, UniformRand}; use blake2::Blake2s256 as Blake2s; fn sign_and_verify(message: &[u8]) { diff --git a/crypto-primitives/src/signature/schnorr/constraints.rs b/crypto-primitives/src/signature/schnorr/constraints.rs index 0f663825..7d19ecc1 100644 --- a/crypto-primitives/src/signature/schnorr/constraints.rs +++ b/crypto-primitives/src/signature/schnorr/constraints.rs @@ -1,4 +1,3 @@ -use crate::Vec; use ark_ec::CurveGroup; use ark_ff::Field; use ark_r1cs_std::prelude::*; diff --git a/crypto-primitives/src/signature/schnorr/mod.rs b/crypto-primitives/src/signature/schnorr/mod.rs index e0584c44..c8c3cf75 100644 --- a/crypto-primitives/src/signature/schnorr/mod.rs +++ b/crypto-primitives/src/signature/schnorr/mod.rs @@ -1,4 +1,4 @@ -use crate::{signature::SignatureScheme, Error, Vec}; +use crate::{signature::SignatureScheme, Error}; use ark_ec::{AffineRepr, CurveGroup}; use ark_ff::{ fields::{Field, PrimeField}, diff --git a/crypto-primitives/src/snark/constraints.rs b/crypto-primitives/src/snark/constraints.rs index 66419198..ba1ef909 100644 --- a/crypto-primitives/src/snark/constraints.rs +++ b/crypto-primitives/src/snark/constraints.rs @@ -1,15 +1,12 @@ use ark_ff::{BigInteger, PrimeField}; -use ark_r1cs_std::prelude::*; -use ark_r1cs_std::{ - fields::{ - emulated_fp::{ - params::{get_params, OptimizationType}, - AllocatedEmulatedFpVar, EmulatedFpVar, - }, - fp::{AllocatedFp, FpVar}, +use ark_r1cs_std::fields::{ + emulated_fp::{ + params::{get_params, OptimizationType}, + AllocatedEmulatedFpVar, EmulatedFpVar, }, - R1CSVar, + fp::{AllocatedFp, FpVar}, }; +use ark_r1cs_std::prelude::*; use ark_relations::r1cs::OptimizationGoal; use ark_relations::{ lc, ns, @@ -18,12 +15,7 @@ use ark_relations::{ }, }; use ark_snark::{CircuitSpecificSetupSNARK, UniversalSetupSNARK, SNARK}; -use ark_std::{ - borrow::Borrow, - fmt, - marker::PhantomData, - vec::{IntoIter, Vec}, -}; +use ark_std::{borrow::Borrow, fmt, marker::PhantomData, vec::IntoIter}; /// This implements constraints for SNARK verifiers. pub trait SNARKGadget> { diff --git a/crypto-primitives/src/sponge/absorb.rs b/crypto-primitives/src/sponge/absorb.rs index ed98bf1d..eb154515 100644 --- a/crypto-primitives/src/sponge/absorb.rs +++ b/crypto-primitives/src/sponge/absorb.rs @@ -7,8 +7,6 @@ use ark_ec::{ use ark_ff::models::{Fp, FpConfig}; use ark_ff::{BigInteger, Field, PrimeField, ToConstraintField}; use ark_serialize::CanonicalSerialize; -use ark_std::string::String; -use ark_std::vec::Vec; pub use ark_crypto_primitives_macros::*; @@ -389,7 +387,7 @@ mod tests { use crate::sponge::Absorb; use crate::sponge::{field_cast, CryptographicSponge}; use ark_ff::PrimeField; - use ark_std::{test_rng, vec::Vec, UniformRand}; + use ark_std::{test_rng, UniformRand}; #[test] fn test_cast() { diff --git a/crypto-primitives/src/sponge/constraints/absorb.rs b/crypto-primitives/src/sponge/constraints/absorb.rs index 58779a02..fa943cdf 100644 --- a/crypto-primitives/src/sponge/constraints/absorb.rs +++ b/crypto-primitives/src/sponge/constraints/absorb.rs @@ -13,8 +13,7 @@ use ark_r1cs_std::groups::curves::short_weierstrass::{ use ark_r1cs_std::groups::curves::twisted_edwards::AffineVar as TEAffineVar; use ark_r1cs_std::uint8::UInt8; use ark_relations::r1cs::SynthesisError; -use ark_std::vec; -use ark_std::vec::Vec; + /// An interface for objects that can be absorbed by a `CryptographicSpongeVar` whose constraint field /// is `CF`. pub trait AbsorbGadget { diff --git a/crypto-primitives/src/sponge/constraints/mod.rs b/crypto-primitives/src/sponge/constraints/mod.rs index 0d29efe9..889f733f 100644 --- a/crypto-primitives/src/sponge/constraints/mod.rs +++ b/crypto-primitives/src/sponge/constraints/mod.rs @@ -9,8 +9,6 @@ use ark_r1cs_std::uint8::UInt8; use ark_r1cs_std::R1CSVar; use ark_relations::lc; use ark_relations::r1cs::{ConstraintSystemRef, LinearCombination, SynthesisError}; -use ark_std::vec; -use ark_std::vec::Vec; mod absorb; pub use absorb::*; diff --git a/crypto-primitives/src/sponge/mod.rs b/crypto-primitives/src/sponge/mod.rs index bd8a1d92..e0c84744 100644 --- a/crypto-primitives/src/sponge/mod.rs +++ b/crypto-primitives/src/sponge/mod.rs @@ -1,6 +1,5 @@ use ark_ff::PrimeField; use ark_std::vec; -use ark_std::vec::Vec; /// Infrastructure for the constraints counterparts. #[cfg(feature = "r1cs")] diff --git a/crypto-primitives/src/sponge/poseidon/constraints.rs b/crypto-primitives/src/sponge/poseidon/constraints.rs index 8cbec6d2..276ed0e0 100644 --- a/crypto-primitives/src/sponge/poseidon/constraints.rs +++ b/crypto-primitives/src/sponge/poseidon/constraints.rs @@ -7,8 +7,6 @@ use ark_ff::PrimeField; use ark_r1cs_std::fields::fp::FpVar; use ark_r1cs_std::prelude::*; use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError}; -use ark_std::vec; -use ark_std::vec::Vec; #[derive(Clone)] /// the gadget for Poseidon sponge diff --git a/crypto-primitives/src/sponge/poseidon/grain_lfsr.rs b/crypto-primitives/src/sponge/poseidon/grain_lfsr.rs index 41b83c65..1cda4f71 100644 --- a/crypto-primitives/src/sponge/poseidon/grain_lfsr.rs +++ b/crypto-primitives/src/sponge/poseidon/grain_lfsr.rs @@ -1,7 +1,6 @@ #![allow(dead_code)] use ark_ff::{BigInteger, PrimeField}; -use ark_std::vec::Vec; pub struct PoseidonGrainLFSR { pub prime_num_bits: u64, diff --git a/crypto-primitives/src/sponge/poseidon/mod.rs b/crypto-primitives/src/sponge/poseidon/mod.rs index 69dd01ff..26349b0b 100644 --- a/crypto-primitives/src/sponge/poseidon/mod.rs +++ b/crypto-primitives/src/sponge/poseidon/mod.rs @@ -5,8 +5,6 @@ use crate::sponge::{ use ark_ff::{BigInteger, PrimeField}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::any::TypeId; -use ark_std::vec; -use ark_std::vec::Vec; /// constraints for Poseidon #[cfg(feature = "r1cs")] diff --git a/crypto-primitives/src/sponge/poseidon/traits.rs b/crypto-primitives/src/sponge/poseidon/traits.rs index 1e85214c..237f8732 100644 --- a/crypto-primitives/src/sponge/poseidon/traits.rs +++ b/crypto-primitives/src/sponge/poseidon/traits.rs @@ -1,7 +1,6 @@ use crate::sponge::poseidon::grain_lfsr::PoseidonGrainLFSR; use crate::sponge::poseidon::PoseidonConfig; -use ark_ff::{fields::models::*, FpConfig, PrimeField}; -use ark_std::{vec, vec::Vec}; +use ark_ff::{fields::models::*, PrimeField}; /// An entry in the default Poseidon parameters pub struct PoseidonDefaultConfigEntry {