Hello! I am trying to implement a set membership proof circuit and I am having some trouble. I basically get the following error when generating the proof:
use bellman::{
gadgets::{
boolean::{AllocatedBit, Boolean},
multipack,
sha256::sha256,
},
groth16, Circuit, ConstraintSystem, SynthesisError,
};
use bls12_381::Bls12;
use ff::PrimeField;
use merkle_tree::{NodeOrder, ProofListItem};
use rand::rngs::OsRng;
use sha2::{Digest, Sha256};
use crate::merkle_tree::MerkleTree;
mod merkle_tree;
mod utils;
/// Our own SHA-256d gadget. Input and output are in little-endian bit order.
fn sha256d<Scalar: PrimeField, CS: ConstraintSystem<Scalar>>(
mut cs: CS,
data: &[Boolean],
) -> Result<Vec<Boolean>, SynthesisError> {
// Flip endianness of each input byte
let input: Vec<_> = data
.chunks(8)
.map(|c| c.iter().rev())
.flatten()
.cloned()
.collect();
let res = sha256(cs.namespace(|| "SHA-256(input)"), &input)?;
// Flip endianness of each output byte
Ok(res
.chunks(8)
.map(|c| c.iter().rev())
.flatten()
.cloned()
.collect())
}
fn merkle_proofd<Scalar: PrimeField, CS: ConstraintSystem<Scalar>>(
mut cs: CS,
mut proof_list: Vec<ProofListItem>,
hashed_file_contents: &[Boolean],
) -> Result<Vec<Boolean>, SynthesisError> {
if proof_list.len() < 2 {
return Ok(Vec::new());
}
let res = contains_hash(
cs.namespace(|| "SHA-256(res)"),
&mut proof_list,
&hashed_file_contents,
);
match res {
Ok(Boolean::Constant(false)) | Err(_) => {
return Ok(Vec::new());
}
_ => {}
}
while proof_list.len() != 1 {
let h1 = proof_list.pop().unwrap();
let h2 = proof_list.pop().unwrap();
match h2.order {
Some(NodeOrder::Left) => {
let combined = [&h2.hash[..], &h1.hash[..]].concat();
let hash = sha256d(cs.namespace(|| "SHA-256(left_leaf)"), &combined).unwrap();
proof_list.push(ProofListItem::new(hash, None));
}
Some(NodeOrder::Right) => {
let combined = [&h1.hash[..], &h2.hash[..]].concat();
let hash = sha256d(cs.namespace(|| "SHA-256(right_leaf)"), &combined).unwrap();
proof_list.push(ProofListItem::new(hash, None));
}
None => {
panic!("Should not be None");
}
}
}
Ok(proof_list.pop().unwrap().hash)
}
fn boolean_or<Scalar: PrimeField, CS: ConstraintSystem<Scalar>>(
mut cs: CS,
a: &Boolean,
b: &Boolean,
) -> Result<Boolean, SynthesisError> {
let not_a = Boolean::not(a);
let not_b = Boolean::not(b);
let not_a_and_not_b = Boolean::and(cs.namespace(|| "not A AND not B"), ¬_a, ¬_b)?;
Ok(Boolean::not(¬_a_and_not_b))
}
fn contains_hash<Scalar: PrimeField, CS: ConstraintSystem<Scalar>>(
mut cs: CS,
proof_list: &mut Vec<ProofListItem>,
hash: &[Boolean],
) -> Result<Boolean, SynthesisError> {
let mut found = Boolean::constant(false); // Initially, no match is found.
for (i, item) in proof_list.iter().enumerate() {
if item.hash.len() != hash.len() {
continue; // Skip if lengths differ, cannot be equal.
}
let mut is_equal = Boolean::constant(true); // Assume equal until proven otherwise.
for (j, (a, b)) in item.hash.iter().zip(hash).enumerate() {
let equal_bits =
Boolean::xor(cs.namespace(|| format!("bit {} in item {}", j, i)), a, b)?;
let not_equal_bits = Boolean::not(&equal_bits);
is_equal = Boolean::and(
cs.namespace(|| format!("aggregate equality for item {}", i)),
&is_equal,
¬_equal_bits,
)?;
}
// Aggregate the results with OR across all items using the custom OR function
found = boolean_or(
cs.namespace(|| format!("aggregate found status after item {}", i)),
&found,
&is_equal,
)?;
}
Ok(found)
}
struct MerkleProofCircuit {
proof_list: Option<Vec<ProofListItem>>,
hashed_file_contents: Option<Vec<u8>>,
}
impl<Scalar: PrimeField> Circuit<Scalar> for MerkleProofCircuit {
fn synthesize<CS: ConstraintSystem<Scalar>>(self, cs: &mut CS) -> Result<(), SynthesisError> {
let proof_list = self.proof_list.unwrap_or_default();
let bit_values = if let Some(hashed_file_contents) = self.hashed_file_contents {
hashed_file_contents
.into_iter()
.map(|byte| (0..8).map(move |i| (byte >> i) & 1u8 == 1u8))
.flatten()
.map(|b| Some(b))
.collect()
} else {
vec![None; 32 * 8]
};
assert_eq!(bit_values.len(), 32 * 8);
// Witness the bits of the preimage.
let hashed_file_content_bits = bit_values
.into_iter()
.enumerate()
// Allocate each bit.
.map(|(i, b)| AllocatedBit::alloc(cs.namespace(|| format!("preimage bit {}", i)), b))
// Convert the AllocatedBits into Booleans (required for the sha256 gadget).
.map(|b| b.map(Boolean::from))
.collect::<Result<Vec<_>, _>>()?;
// Now process proof_list, merkle_root, and file_contents in the merkle_proofd function
// Ensure merkle_proofd can handle dummy values correctly
let merkle_root = merkle_proofd(
cs.namespace(|| "merkle_root"),
proof_list,
&hashed_file_content_bits,
)?;
multipack::pack_into_inputs(cs.namespace(|| "pack hash"), &merkle_root)
}
}
fn main() {
let params = {
let c = MerkleProofCircuit {
proof_list: None,
hashed_file_contents: None,
};
groth16::generate_random_parameters::<Bls12, _, _>(c, &mut OsRng).unwrap()
};
let pvk = groth16::prepare_verifying_key(¶ms.vk);
// These are the factors we are proving knowledge of
let files = (1..=8)
.map(|i| {
let file_name = format!("file{}.txt", i);
let file_content = format!("File {} contents", i).into_bytes();
(file_name, file_content)
})
.collect();
let merkle_tree = MerkleTree::new(&files);
let merkle_root = merkle_tree.get_root_hash();
let merkle_proof = merkle_tree
.generate_merkle_proof("file1.txt", &files)
.unwrap();
let file_contents = files.get("file1.txt").unwrap().clone();
let hashed_file_contents: Vec<u8> = Sha256::digest(&file_contents).to_vec();
// Create an instance of our circuit (with the preimage as a witness).
let c = MerkleProofCircuit {
proof_list: Some(merkle_proof),
hashed_file_contents: Some(hashed_file_contents),
};
// Pack the hash as inputs for proof verification.
let hash_bits = multipack::bytes_to_bits_le(&merkle_root);
let inputs = multipack::compute_multipacking(&hash_bits);
let proof = groth16::create_random_proof(c, ¶ms, &mut OsRng).unwrap();
// assert!(groth16::verify_proof(&pvk, &proof, &[public_input]).is_ok());
println!("{:?}", groth16::verify_proof(&pvk, &proof, &inputs));
println!("Merkle Tree Proof verified!");
}
Can anyone guide me to how to accomplish this? I didn't find any documentation or anything like that just a sha256 example and tried to modify that. I have a very high level understanding of how ZKP works so if its required to dig deeper please let me know. The circuit is supposed to implement the computation of a merkle root from a set of leaves. I assume that root is a public output and the verifier can check their merkle root against it?