polymerdao/plonky2-circom

How can I verify a proof which may have more than 2 steps inside its query_round_proofs

AndrewLeeCHCH opened this issue ยท 12 comments

After running the e2e test successfully, I try to verify plonky2-sha256 proof through Circom way. Then, I add the following code to the plonky2-sha256 project. It compiles successfully and faces fatal error since the steps of query round proof is more than 2.
Is there a way to reduce the proof's query round step to pass this assertion?

let proof = data.prove(pw).unwrap(); /// sha256 proof
let conf = generate_verifier_config(&proof)?; /// Generate verifier config for sha256 proof, it failed
let (circom_constants, circom_gates) = generate_circom_verifier(&conf, &data.common, &data.verifier_only)?;

Fatal error position

assert_eq!(proof.opening_proof.query_round_proofs[0].steps.len(), 2);

Did you use standard_recursion_config in Plonky2 circuit? It should use only 2 steps.
You can always recursively prove your proof with this config.

Hi Saideng,

Thank you for the response. Unfortunately, I did use the standard recursion config. To avoid anything being messed up with my code change, I did a quick test with the plonky2-sha256 sample. I added one line for logging. It turns out the length of steps is 4.

Here is code snippet.

let mut hasher = Sha256::new();
    hasher.update(msg);
    let hash = hasher.finalize();
    // println!("Hash: {:#04X}", hash);

    let msg_bits = array_to_bits(msg);
    let len = msg.len() * 8;
    println!("block count: {}", (len + 65 + 511) / 512);
    const D: usize = 2;
    type C = PoseidonGoldilocksConfig;
    type F = <C as GenericConfig<D>>::F;
    let mut builder = CircuitBuilder::<F, D>::new(CircuitConfig::standard_recursion_config());
    let targets = make_circuits(&mut builder, len as u64);
    let mut pw = PartialWitness::new();

    for i in 0..len {
        pw.set_bool_target(targets.message[i], msg_bits[i]);
    }

    let expected_res = array_to_bits(hash.as_slice());
    for i in 0..expected_res.len() {
        if expected_res[i] {
            builder.assert_one(targets.digest[i].target);
        } else {
            builder.assert_zero(targets.digest[i].target);
        }
    }

    println!(
        "Constructing inner proof with {} gates",
        builder.num_gates()
    );
    let data = builder.build::<C>();
    let timing = TimingTree::new("prove", Level::Debug);
    let proof = data.prove(pw).unwrap();
    println!("proof steps length {}", proof.proof.opening_proof.query_round_proofs[0].steps.len()); // Print steps length
    timing.print();

    let timing = TimingTree::new("verify", Level::Debug);
    let res = data.verify(proof);
    timing.print();

    res
}

Here is the console log.

block count: 45
Constructing inner proof with 261980 gates
[INFO  plonky2::plonk::circuit_builder] Degree before blinding & padding: 262019
[INFO  plonky2::plonk::circuit_builder] Degree after blinding & padding: 262144
[DEBUG plonky2::plonk::circuit_builder] Building circuit took 43.592686s
proof steps length 4
[DEBUG plonky2::util::timing] 66.4551s to prove
[DEBUG plonky2::util::timing] 0.0172s to verify

I suggest you recursively prove it in a new proof. like this one: https://github.com/polymerdao/plonky2-ed25519/blob/main/src/main.rs#L71

It works as expected, thank you

After I replaced e2e testing constants.circom, gates.circom, conf.json and proof.json with my own generation files, plonky2.circom failed to compile. Do I miss any needed modifications for circom compilation?

****COMPILING CIRCUIT****
error[T3001]: Out of bounds exception
    โ”Œโ”€ "/home/user/zk-benchmark/plonky2-circom/circom/circuits/poseidon.circom":305:5
    โ”‚
305 โ”‚     cPoseidon[0].capacity[0] <== capacity[0];
    โ”‚     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ found here
    โ”‚
    = call trace:
      ->VerifyPlonky2Proof
       ->HashNoPad_GL

previous errors were found

Do you have any public inputs in your proof?

I don't think so

Here is the full sample code

pub fn prove_sha256(msg: &[u8]) -> Result<()> {
    let mut hasher = Sha256::new();
    hasher.update(msg);
    let hash = hasher.finalize();
    // println!("Hash: {:#04X}", hash);

    let msg_bits = array_to_bits(msg);
    let len = msg.len() * 8;
    println!("block count: {}", (len + 65 + 511) / 512);
    const D: usize = 2;
    type C = PoseidonGoldilocksConfig;
    type F = <C as GenericConfig<D>>::F;
    let config = CircuitConfig::standard_recursion_config();
    // config.fri_config.rate_bits = 3;
    println!("{:?}", config);

    let mut builder = CircuitBuilder::<F, D>::new(config);
    let mut pw = PartialWitness::new();
    let expected_res = array_to_bits(hash.as_slice());
    let mut targets = vec![];


    for _ in 0..2 {
        let curr = targets.len();
        targets.push(make_circuits(&mut builder, len as u64));

        for i in 0..len {
            pw.set_bool_target(targets[curr].message[i], msg_bits[i]);
        }

        for i in 0..expected_res.len() {
            if expected_res[i] {
                builder.assert_one(targets[curr].digest[i].target);
            } else {
                builder.assert_zero(targets[curr].digest[i].target);
            }
        }
    }

    println!(
        "Constructing inner proof with {} gates",
        builder.num_gates()
    );
    let data = builder.build::<C>();
    let timing = TimingTree::new("prove", Level::Debug);
    let sha256proof = data.prove(pw).unwrap();
    timing.print();
    println!("proof steps length {}", sha256proof.proof.opening_proof.query_round_proofs[0].steps.len());

    let config = CircuitConfig::standard_recursion_config();
    let proof = recursive_proof::<F, C, C, D>(&(sha256proof, data.verifier_only, data.common), None, &config, None)?;

    println!("middle proof steps {:?}", proof.0.proof.opening_proof.query_round_proofs[0].steps.len());

    let conf = generate_verifier_config(&proof.0)?;
    let (circom_constants, circom_gates) = generate_circom_verifier(&conf, &proof.2, &proof.1)?;

    let mut circom_file = File::create("./circom/circuits/constants.circom")?;
    circom_file.write_all(circom_constants.as_bytes())?;
    circom_file = File::create("./circom/circuits/gates.circom")?;
    circom_file.write_all(circom_gates.as_bytes())?;

    let proof_json = generate_proof_base64(&proof.0, &conf)?;

    if !Path::new("./circom/test/data").is_dir() {
        std::fs::create_dir("./circom/test/data")?;
    }

    let mut proof_file = File::create("./circom/test/data/proof.json")?;
    proof_file.write_all(proof_json.as_bytes())?;

    let mut conf_file = File::create("./circom/test/data/conf.json")?;
    conf_file.write_all(serde_json::to_string(&conf)?.as_ref())?;

    // let timing = TimingTree::new("verify", Level::Debug);
    // let res = data.verify(proof);
    // timing.print();

    // res
    Ok(())
}

Currently the circuits require some public inputs. Try to add some public inputs in your code.

builder.register_public_inputs()

Thanks a lot. I missed some information inside the recursive proving logic. Right now, it works fine. One more thing to confirm is whether I can use some lower ptau to generate circuit's corresponding zkey.

I haven't done this step. I think it needs 2**25 powers.

I haven't done this step. I think it needs 2**25 powers.

Exactly, 2**25 is the minimum requirement for this circuit