Skip to main content

MembershipCircuit

Proves Merkle tree membership: "I know a leaf + path such that leaf is in the tree with the given root."

  • 2 public inputs: root, leaf_commitment
  • Private witnesses: leaf_preimage (Fr), siblings (Vec<Fr>), indices (Vec<bool>)
  • 2 constraints: commitment correctness + Merkle path verification

The tree has depth MERKLE_DEPTH = 20.

Struct​

#[derive(Clone)]
pub struct MembershipCircuit {
pub leaf_preimage: Option<Fr>,
pub siblings: Option<Vec<Fr>>,
pub indices: Option<Vec<bool>>,
}

Constraint Generation​

impl ConstraintSynthesizer<Fr> for MembershipCircuit {
fn generate_constraints(self, cs: ConstraintSystemRef<Fr>) -> Result<(), SynthesisError> {
// Public inputs: root, leaf_commitment
let root_pub = FpVar::new_input(cs.clone(), || {
// ... compute root natively from leaf + path
})?;
let leaf_cm_pub = FpVar::new_input(cs.clone(), || {
let leaf = self.leaf_preimage.ok_or(SynthesisError::AssignmentMissing)?;
Ok(r14_poseidon::poseidon_hash(&[leaf]))
})?;

// Witness
let leaf_var = FpVar::new_witness(cs.clone(), || { /* ... */ })?;
// 20 sibling + index_bit pairs
let mut path_vars: Vec<(FpVar<Fr>, Boolean<Fr>)> = Vec::with_capacity(MERKLE_DEPTH);
for i in 0..MERKLE_DEPTH { /* ... */ }

// Constraint 1: poseidon(leaf_preimage) == leaf_commitment
let computed_cm = poseidon_hash_var(cs.clone(), &[leaf_var])?;
computed_cm.enforce_equal(&leaf_cm_pub)?;

// Constraint 2: merkle path from leaf_commitment to root
verify_merkle_path(cs, &computed_cm, &path_vars, &root_pub)?;

Ok(())
}
}

Constraint 1 ensures the prover actually knows the preimage that hashes to leaf_commitment. Constraint 2 walks the 20-level path using verify_merkle_path, which at each level selects left/right ordering via is_right.select() and hashes with hash2_var, then enforces the final value equals the public root.

Public Inputs​

pub struct PublicInputs {
pub root: Fr,
pub leaf_commitment: Fr,
}

impl PublicInputs {
pub fn to_vec(&self) -> Vec<Fr> {
vec![self.root, self.leaf_commitment]
}
}

API​

setup(rng) -> (ProvingKey, VerifyingKey)​

Groth16 trusted setup with an empty circuit.

pub fn setup<R: RngCore + CryptoRng>(
rng: &mut R,
) -> (ProvingKey<Bls12_381>, VerifyingKey<Bls12_381>)

prove(pk, leaf_preimage, siblings, indices, rng) -> (Proof, PublicInputs)​

Computes leaf_commitment = poseidon(leaf_preimage) and walks the path natively to compute the root, then generates the proof.

pub fn prove<R: RngCore + CryptoRng>(
pk: &ProvingKey<Bls12_381>,
leaf_preimage: Fr,
siblings: Vec<Fr>,
indices: Vec<bool>,
rng: &mut R,
) -> (ark_groth16::Proof<Bls12_381>, PublicInputs)

verify_offchain(vk, proof, pi) -> bool​

Off-chain verification using PreparedVerifyingKey.

pub fn verify_offchain(
vk: &VerifyingKey<Bls12_381>,
proof: &ark_groth16::Proof<Bls12_381>,
pi: &PublicInputs,
) -> bool

constraint_count() -> usize​

Returns the R1CS constraint count. Expected range: 2000-10000.

pub fn constraint_count() -> usize