use algebra::Field; use r1cs_core::{ConstraintSystem, SynthesisError}; use r1cs_std::prelude::*; use r1cs_std::boolean::AllocatedBit; use crate::{mht::HashMembershipProof, CommitmentScheme, FixedLengthCRH}; use crate::{commitment::CommitmentGadget, crh::FixedLengthCRHGadget}; use crate::ledger::{CommPath, Digest, LedgerDigest, LedgerWitness}; use std::{borrow::Borrow, marker::PhantomData}; pub trait LCWGadget, ConstraintF: Field> { type ParametersGadget: AllocGadget; type CommitmentGadget: AllocGadget; type DigestGadget: AllocGadget; type WitnessGadget: AllocGadget; fn check_witness_gadget>( cs: CS, parameters: &Self::ParametersGadget, ledger_state_digest: &Self::DigestGadget, commitment: &Self::CommitmentGadget, witness: &Self::WitnessGadget, ) -> Result<(), SynthesisError> { Self::conditionally_check_witness_gadget( cs, parameters, ledger_state_digest, commitment, witness, &Boolean::Constant(true), ) } fn conditionally_check_witness_gadget>( cs: CS, parameters: &Self::ParametersGadget, ledger_state_digest: &Self::DigestGadget, commitment: &Self::CommitmentGadget, witness: &Self::WitnessGadget, should_enforce: &Boolean, ) -> Result<(), SynthesisError>; } pub struct IdealLedgerGadget { #[doc(hidden)] _comm_scheme: PhantomData, #[doc(hidden)] _hash: PhantomData, #[doc(hidden)] _comm_gadget: PhantomData, #[doc(hidden)] _hash_gadget: PhantomData, } pub struct CommitmentWitness< H: FixedLengthCRH, C: CommitmentScheme, HGadget: FixedLengthCRHGadget, ConstraintF: Field, > { path: Vec<(HGadget::OutputGadget, HGadget::OutputGadget)>, _crh: PhantomData, _comm: PhantomData, _engine: PhantomData, } pub struct DigestGadget, ConstraintF: Field> { digest: HGadget::OutputGadget, #[doc(hidden)] _crh: PhantomData, #[doc(hidden)] _engine: PhantomData, } impl LCWGadget, CommPath, ConstraintF> for IdealLedgerGadget where C: CommitmentScheme, C::Output: Eq, ConstraintF: Field, H: FixedLengthCRH, CGadget: CommitmentGadget, HGadget: FixedLengthCRHGadget, { type ParametersGadget = >::ParametersGadget; type DigestGadget = DigestGadget; type CommitmentGadget = >::OutputGadget; type WitnessGadget = CommitmentWitness; /// Given a `leaf` and `path`, check that the `path` is a valid /// authentication path for the `leaf` in a Merkle tree. /// Note: It is assumed that the root is contained in the `path`. fn conditionally_check_witness_gadget>( mut cs: CS, parameters: &Self::ParametersGadget, root_hash: &Self::DigestGadget, commitment: &Self::CommitmentGadget, witness: &Self::WitnessGadget, should_enforce: &Boolean, ) -> Result<(), SynthesisError> { assert_eq!( witness.path.len(), (HashMembershipProof::::MAX_HEIGHT - 1) as usize ); // Check that the hash of the given leaf matches the leaf hash in the membership // proof. let commitment_bits = commitment.to_bytes(&mut cs.ns(|| "commitment_to_bytes"))?; let commitment_hash = HGadget::check_evaluation_gadget( cs.ns(|| "check_evaluation_gadget"), parameters, &commitment_bits, )?; // Check if leaf is one of the bottom-most siblings. let leaf_is_left = AllocatedBit::alloc(&mut cs.ns(|| "leaf_is_left"), || { Ok(commitment_hash == witness.path[0].0) })? .into(); ::conditional_enforce_equal_or( &mut cs.ns(|| "check_leaf_is_left"), &leaf_is_left, &commitment_hash, &witness.path[0].0, &witness.path[0].1, should_enforce, )?; // Check levels between leaf level and root. let mut previous_hash = commitment_hash; for (i, &(ref left_hash, ref right_hash)) in witness.path.iter().enumerate() { // Check if the previous_hash matches the correct current hash. let previous_is_left = AllocatedBit::alloc(&mut cs.ns(|| format!("previous_is_left_{}", i)), || { Ok(&previous_hash == left_hash) })? .into(); ::conditional_enforce_equal_or( &mut cs.ns(|| format!("check_equals_which_{}", i)), &previous_is_left, &previous_hash, left_hash, right_hash, should_enforce, )?; previous_hash = hash_inner_node_gadget::( &mut cs.ns(|| format!("hash_inner_node_{}", i)), parameters, left_hash, right_hash, )?; } root_hash.digest.conditional_enforce_equal( &mut cs.ns(|| "root_is_last"), &previous_hash, should_enforce, ) } } pub(crate) fn hash_inner_node_gadget( mut cs: CS, parameters: &HG::ParametersGadget, left_child: &HG::OutputGadget, right_child: &HG::OutputGadget, ) -> Result where ConstraintF: Field, CS: ConstraintSystem, H: FixedLengthCRH, HG: FixedLengthCRHGadget, { let left_bytes = left_child.to_bytes(&mut cs.ns(|| "left_to_bytes"))?; let right_bytes = right_child.to_bytes(&mut cs.ns(|| "right_to_bytes"))?; let mut bytes = left_bytes; bytes.extend_from_slice(&right_bytes); HG::check_evaluation_gadget(cs, parameters, &bytes) } impl AllocGadget, ConstraintF> for DigestGadget where H: FixedLengthCRH, HGadget: FixedLengthCRHGadget, ConstraintF: Field, { fn alloc>( mut cs: CS, value_gen: F, ) -> Result where F: FnOnce() -> Result, T: Borrow>, { let digest = HGadget::OutputGadget::alloc(&mut cs.ns(|| "digest"), || { Ok(value_gen()?.borrow().0.clone()) })?; Ok(DigestGadget { digest, _crh: PhantomData, _engine: PhantomData, }) } fn alloc_input>( mut cs: CS, value_gen: F, ) -> Result where F: FnOnce() -> Result, T: Borrow>, { let digest = HGadget::OutputGadget::alloc_input(&mut cs.ns(|| "input_digest"), || { Ok(value_gen()?.borrow().0.clone()) })?; Ok(DigestGadget { digest, _crh: PhantomData, _engine: PhantomData, }) } } impl AllocGadget, ConstraintF> for CommitmentWitness where H: FixedLengthCRH, C: CommitmentScheme, HGadget: FixedLengthCRHGadget, ConstraintF: Field, { fn alloc>( mut cs: CS, value_gen: F, ) -> Result where F: FnOnce() -> Result, T: Borrow>, { let mut path = Vec::new(); for (i, &(ref l, ref r)) in value_gen()?.borrow().0.path.iter().enumerate() { let l_hash = HGadget::OutputGadget::alloc(&mut cs.ns(|| format!("l_child_{}", i)), || { Ok(l.clone()) })?; let r_hash = HGadget::OutputGadget::alloc(&mut cs.ns(|| format!("r_child_{}", i)), || { Ok(r.clone()) })?; path.push((l_hash, r_hash)); } Ok(CommitmentWitness { path, _crh: PhantomData, _comm: PhantomData, _engine: PhantomData, }) } fn alloc_input>( mut cs: CS, value_gen: F, ) -> Result where F: FnOnce() -> Result, T: Borrow>, { let mut path = Vec::new(); for (i, &(ref l, ref r)) in value_gen()?.borrow().0.path.iter().enumerate() { let l_hash = HGadget::OutputGadget::alloc_input( &mut cs.ns(|| format!("l_child_{}", i)), || Ok(l.clone()), )?; let r_hash = HGadget::OutputGadget::alloc_input( &mut cs.ns(|| format!("r_child_{}", i)), || Ok(r.clone()), )?; path.push((l_hash, r_hash)); } Ok(CommitmentWitness { path, _crh: PhantomData, _comm: PhantomData, _engine: PhantomData, }) } } #[cfg(test)] mod test { use std::rc::Rc; use crate::crypto_primitives::{ commitment::{ pedersen::{PedersenCommitment, PedersenRandomness}, CommitmentScheme, }, crh::{ pedersen::{PedersenCRH, PedersenWindow}, FixedLengthCRH, }, mht::*, }; use algebra::{ curves::jubjub::JubJubAffine as JubJub, fields::jubjub::fr::Fr, fields::jubjub::fq::Fq, Group }; use rand::SeedableRng; use algebra::UniformRand; use rand_xorshift::XorShiftRng; use r1cs_core::ConstraintSystem; use super::*; use crate::gadgets::{ commitment::pedersen::PedersenCommitmentGadget, crh::{pedersen::PedersenCRHGadget, FixedLengthCRHGadget}, }; use r1cs_std::{ groups::curves::twisted_edwards::jubjub::JubJubGadget, test_constraint_system::TestConstraintSystem, }; use crate::ledger::{CommPath, Digest}; #[derive(Clone)] pub(super) struct Window4x256; impl PedersenWindow for Window4x256 { const WINDOW_SIZE: usize = 4; const NUM_WINDOWS: usize = 256; } type H = PedersenCRH; type HG = PedersenCRHGadget; type C = PedersenCommitment; type CG = PedersenCommitmentGadget; type JubJubMHT = MerkleHashTree::Output>; type LG = IdealLedgerGadget; type DG = DigestGadget; type LCWG = CommitmentWitness; fn generate_merkle_tree(leaves: &[::Output]) -> () { let mut rng = XorShiftRng::seed_from_u64(1231275789u64); let crh_parameters = Rc::new(H::setup(&mut rng).unwrap()); let tree = JubJubMHT::new(crh_parameters.clone(), &leaves).unwrap(); let root = tree.root(); let mut satisfied = true; for (i, leaf) in leaves.iter().enumerate() { let mut cs = TestConstraintSystem::::new(); let proof = tree.generate_proof(i, &leaf).unwrap(); assert!(proof.verify(&crh_parameters, &root, &leaf).unwrap()); let digest = DG::alloc(&mut cs.ns(|| format!("new_digest_{}", i)), || { Ok(Digest(root)) }) .unwrap(); let constraints_from_digest = cs.num_constraints(); println!("constraints from digest: {}", constraints_from_digest); let crh_parameters = >::ParametersGadget::alloc( &mut cs.ns(|| format!("new_parameters_{}", i)), || Ok(crh_parameters.clone()), ) .unwrap(); let constraints_from_parameters = cs.num_constraints() - constraints_from_digest; println!( "constraints from parameters: {}", constraints_from_parameters ); let comm = >::OutputGadget::alloc( &mut cs.ns(|| format!("new_comm_{}", i)), || { let leaf: JubJub = *leaf; Ok(leaf) }, ) .unwrap(); let constraints_from_comm = cs.num_constraints() - constraints_from_parameters - constraints_from_digest; println!("constraints from comm: {}", constraints_from_comm); let cw = LCWG::alloc(&mut cs.ns(|| format!("new_witness_{}", i)), || { Ok(CommPath(proof)) }) .unwrap(); let constraints_from_path = cs.num_constraints() - constraints_from_parameters - constraints_from_digest - constraints_from_comm; println!("constraints from path: {}", constraints_from_path); LG::check_witness_gadget( &mut cs.ns(|| format!("new_witness_check_{}", i)), &crh_parameters, &digest, &comm, &cw, ) .unwrap(); if !cs.is_satisfied() { satisfied = false; println!( "Unsatisfied constraint: {}", cs.which_is_unsatisfied().unwrap() ); } let setup_constraints = constraints_from_comm + constraints_from_digest + constraints_from_parameters + constraints_from_path; println!( "number of constraints: {}", cs.num_constraints() - setup_constraints ); } assert!(satisfied); } #[test] fn mht_gadget_test() { let mut leaves = Vec::new(); let mut rng = XorShiftRng::seed_from_u64(1231275789u64); let comm_parameters = C::setup(&mut rng).unwrap(); for i in 0..4u8 { let r = PedersenRandomness(Fr::rand(&mut rng)); let input = [i, i, i, i, i, i, i, i]; leaves.push(C::commit(&comm_parameters, &input, &r).unwrap()); } generate_merkle_tree(&leaves); // let mut leaves = Vec::new(); // for i in 0..100u8 { // leaves.push([i, i, i, i, i, i, i, i]); // } // generate_merkle_tree(&leaves); } fn bad_merkle_tree_verify(leaves: &[::Output]) -> () { let mut rng = XorShiftRng::seed_from_u64(1231275789u64); let crh_parameters = Rc::new(H::setup(&mut rng).unwrap()); let tree = JubJubMHT::new(crh_parameters.clone(), &leaves).unwrap(); let root = tree.root(); for (i, leaf) in leaves.iter().enumerate() { let mut cs = TestConstraintSystem::::new(); let proof = tree.generate_proof(i, &leaf).unwrap(); assert!(proof.verify(&crh_parameters, &root, &leaf).unwrap()); let digest = DG::alloc(&mut cs.ns(|| format!("new_digest_{}", i)), || { Ok(Digest(JubJub::zero())) }) .unwrap(); let crh_parameters = >::ParametersGadget::alloc( &mut cs.ns(|| format!("new_parameters_{}", i)), || Ok(crh_parameters.clone()), ) .unwrap(); let comm = >::OutputGadget::alloc( &mut cs.ns(|| format!("new_comm_{}", i)), || { let leaf = *leaf; Ok(leaf) }, ) .unwrap(); let cw = LCWG::alloc(&mut cs.ns(|| format!("new_witness_{}", i)), || { Ok(CommPath(proof)) }) .unwrap(); LG::check_witness_gadget( &mut cs.ns(|| format!("new_witness_check_{}", i)), &crh_parameters, &digest, &comm, &cw, ) .unwrap(); if !cs.is_satisfied() { println!( "Unsatisfied constraints: {}", cs.which_is_unsatisfied().unwrap() ); } assert!(cs.is_satisfied()); } } #[should_panic] #[test] fn bad_root_test() { let mut leaves = Vec::new(); let mut rng = XorShiftRng::seed_from_u64(1231275789u64); let comm_parameters = C::setup(&mut rng).unwrap(); for i in 0..4u8 { let r = PedersenRandomness(Fr::rand(&mut rng)); let input = [i, i, i, i, i, i, i, i]; leaves.push(C::commit(&comm_parameters, &input, &r).unwrap()); } bad_merkle_tree_verify(&leaves); } }