use algebra_core::Field;
use r1cs_core::{Namespace, SynthesisError};
use r1cs_std::prelude::*;
use crate::{
crh::{FixedLengthCRH, FixedLengthCRHGadget},
merkle_tree::*,
};
use core::borrow::Borrow;
pub struct PathVar
where
P: Config,
HGadget: FixedLengthCRHGadget,
ConstraintF: Field,
{
path: Vec<(HGadget::OutputVar, HGadget::OutputVar)>,
}
impl PathVar
where
P: Config,
ConstraintF: Field,
CRHGadget: FixedLengthCRHGadget,
>::Value: PartialEq,
{
#[tracing::instrument(target = "r1cs", skip(self, parameters, root, leaf))]
pub fn check_membership(
&self,
parameters: &CRHGadget::ParametersVar,
root: &CRHGadget::OutputVar,
leaf: impl ToBytesGadget,
) -> Result, SynthesisError> {
assert_eq!(self.path.len(), P::HEIGHT - 1);
// Check that the hash of the given leaf matches the leaf hash in the membership
// proof.
let leaf_bits = leaf.to_bytes()?;
let leaf_hash = CRHGadget::evaluate(parameters, &leaf_bits)?;
let cs = leaf_hash.cs().or(root.cs());
// Check if leaf is one of the bottom-most siblings.
let leaf_is_left = Boolean::new_witness(r1cs_core::ns!(cs, "leaf_is_left"), || {
Ok(leaf_hash.value()?.eq(&self.path[0].0.value()?))
})?;
let mut result =
leaf_hash.is_eq(&leaf_is_left.select(&self.path[0].0, &self.path[0].1)?)?;
// Check levels between leaf level and root.
let mut previous_hash = leaf_hash;
for &(ref left_hash, ref right_hash) in &self.path {
// Check if the previous_hash matches the correct current hash.
let previous_is_left =
Boolean::new_witness(r1cs_core::ns!(cs, "previous_is_left"), || {
Ok(previous_hash.value()?.eq(&left_hash.value()?))
})?;
let ns = r1cs_core::ns!(cs, "enforcing that inner hash is correct");
let equality_cmp = previous_is_left.select(left_hash, right_hash)?;
result = result.and(&previous_hash.is_eq(&equality_cmp)?)?;
drop(ns);
previous_hash =
hash_inner_node::(parameters, left_hash, right_hash)?;
}
result.and(&root.is_eq(&previous_hash)?)
}
}
pub(crate) fn hash_inner_node(
parameters: &HG::ParametersVar,
left_child: &HG::OutputVar,
right_child: &HG::OutputVar,
) -> Result
where
ConstraintF: Field,
H: FixedLengthCRH,
HG: FixedLengthCRHGadget,
{
let left_bytes = left_child.to_bytes()?;
let right_bytes = right_child.to_bytes()?;
let mut bytes = left_bytes;
bytes.extend_from_slice(&right_bytes);
HG::evaluate(parameters, &bytes)
}
impl AllocVar, ConstraintF> for PathVar
where
P: Config,
HGadget: FixedLengthCRHGadget,
ConstraintF: Field,
{
fn new_variable>>(
cs: impl Into>,
f: impl FnOnce() -> Result,
mode: AllocationMode,
) -> Result {
let ns = cs.into();
let cs = ns.cs();
f().and_then(|val| {
let mut path = Vec::new();
for &(ref l, ref r) in val.borrow().path.iter() {
let l_hash = HGadget::OutputVar::new_variable(
r1cs_core::ns!(cs, "l_child"),
|| Ok(l),
mode,
)?;
let r_hash = HGadget::OutputVar::new_variable(
r1cs_core::ns!(cs, "r_child"),
|| Ok(r),
mode,
)?;
path.push((l_hash, r_hash));
}
Ok(PathVar { path })
})
}
}
#[cfg(test)]
mod test {
use crate::{
crh::{
pedersen::{self, constraints::CRHGadget},
FixedLengthCRH, FixedLengthCRHGadget,
},
merkle_tree::*,
};
use algebra::ed_on_bls12_381::{EdwardsProjective as JubJub, Fq};
use r1cs_core::ConstraintSystem;
use rand::SeedableRng;
use rand_xorshift::XorShiftRng;
use super::*;
use r1cs_std::ed_on_bls12_381::EdwardsVar;
#[derive(Clone)]
pub(super) struct Window4x256;
impl pedersen::Window for Window4x256 {
const WINDOW_SIZE: usize = 4;
const NUM_WINDOWS: usize = 256;
}
type H = pedersen::CRH;
type HG = CRHGadget;
struct JubJubMerkleTreeParams;
impl Config for JubJubMerkleTreeParams {
const HEIGHT: usize = 32;
type H = H;
}
type JubJubMerkleTree = MerkleTree;
fn generate_merkle_tree(leaves: &[[u8; 30]], use_bad_root: bool) -> () {
let mut rng = XorShiftRng::seed_from_u64(9174123u64);
let crh_parameters = H::setup(&mut rng).unwrap();
let tree = JubJubMerkleTree::new(crh_parameters.clone(), leaves).unwrap();
let root = tree.root();
let cs = ConstraintSystem::::new_ref();
for (i, leaf) in leaves.iter().enumerate() {
let proof = tree.generate_proof(i, &leaf).unwrap();
assert!(proof.verify(&crh_parameters, &root, &leaf).unwrap());
// Allocate Merkle Tree Root
let root = >::OutputVar::new_witness(
r1cs_core::ns!(cs, "new_digest"),
|| {
if use_bad_root {
Ok(::Output::default())
} else {
Ok(root)
}
},
)
.unwrap();
let constraints_from_digest = cs.num_constraints();
println!("constraints from digest: {}", constraints_from_digest);
// Allocate Parameters for CRH
let crh_parameters = >::ParametersVar::new_constant(
r1cs_core::ns!(cs, "new_parameter"),
&crh_parameters,
)
.unwrap();
let constraints_from_parameters = cs.num_constraints() - constraints_from_digest;
println!(
"constraints from parameters: {}",
constraints_from_parameters
);
// Allocate Leaf
let leaf_g = UInt8::constant_vec(leaf);
let constraints_from_leaf =
cs.num_constraints() - constraints_from_parameters - constraints_from_digest;
println!("constraints from leaf: {}", constraints_from_leaf);
// Allocate Merkle Tree Path
let cw =
PathVar::<_, HG, _>::new_witness(r1cs_core::ns!(cs, "new_witness"), || Ok(&proof))
.unwrap();
for (i, (l, r)) in cw.path.iter().enumerate() {
assert_eq!(l.value().unwrap(), proof.path[i].0);
assert_eq!(r.value().unwrap(), proof.path[i].1);
}
let constraints_from_path = cs.num_constraints()
- constraints_from_parameters
- constraints_from_digest
- constraints_from_leaf;
println!("constraints from path: {}", constraints_from_path);
let leaf_g: &[_] = leaf_g.as_slice();
cw.check_membership(&crh_parameters, &root, &leaf_g)
.unwrap()
.enforce_equal(&Boolean::TRUE)
.unwrap();
let setup_constraints = constraints_from_leaf
+ constraints_from_digest
+ constraints_from_parameters
+ constraints_from_path;
println!(
"number of constraints: {}",
cs.num_constraints() - setup_constraints
);
}
assert!(cs.is_satisfied().unwrap());
}
#[test]
fn good_root_test() {
let mut leaves = Vec::new();
for i in 0..4u8 {
let input = [i; 30];
leaves.push(input);
}
generate_merkle_tree(&leaves, false);
}
#[should_panic]
#[test]
fn bad_root_test() {
let mut leaves = Vec::new();
for i in 0..4u8 {
let input = [i; 30];
leaves.push(input);
}
generate_merkle_tree(&leaves, true);
}
}