Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expected more bases from source error #104

Open
MakisChristou opened this issue Apr 22, 2024 · 0 comments
Open

Expected more bases from source error #104

MakisChristou opened this issue Apr 22, 2024 · 0 comments

Comments

@MakisChristou
Copy link

MakisChristou commented Apr 22, 2024

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant