zkcrypto / bellman

zk-SNARK library.
Other
988 stars 534 forks source link

Expected more bases from source error #104

Open MakisChristou opened 4 months ago

MakisChristou commented 4 months ago

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:

thread 'main' panicked at src/main.rs:217:70:
called `Result::unwrap()` on an `Err` value: IoError(Custom { kind: UnexpectedEof, error: "expected more bases from source" })
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Here's my main.rs code:

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"), &not_a, &not_b)?;
    Ok(Boolean::not(&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,
                &not_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(&params.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, &params, &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!");
}

and some auxiliary items

#[derive(Clone, Debug, PartialEq)]
pub enum NodeOrder {
    Right,
    Left,
}

#[derive(Clone)]
pub struct ProofListItem {
    pub hash: Vec<Boolean>,
    pub order: Option<NodeOrder>,
}

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?