From b4a4c7232834e4a0abdd5ffea286d6354650ce52 Mon Sep 17 00:00:00 2001 From: arnaucube Date: Wed, 21 May 2025 13:15:22 +0200 Subject: [PATCH] Add RecursiveCircuit (#204) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add RecursiveCircuit The RecursiveCircuit verifies N proofs of itself (N=arity), together with the logic defined at the InnerCircuit (in our case, used for the MainPodCircuit logic). The arity defines the maximum amount of proofs of itself that the RecursiveCircuit verifies. When arity>1, using the RecursiveCircuit has the shape of a tree of the same arity. π_root ▲ ┌───────┴────────┐ │RecursiveCircuit│ └─▲───▲───▲────▲─┘ ┌───────┘ ┌┘ └┐ └──────┐ │π''_1 │ ... │ π''_N│ ┌────────┴───────┐ ┌┴┐┌─┐┌┴┐ ┌───────┴────────┐ │RecursiveCircuit│ │.││.││.│ │RecursiveCircuit│ └──▲─────────▲───┘ └─┘└─┘└─┘ └──▲─────────▲───┘ │ │ │ │ π_1 ... π_N π'_1 ... π'_N where N: arity of the RecursiveCircuit π_i: plonky2 proof of the RecursiveCircuit * add different inner-circuits in the test, reusing the same verifier_data; polish recursion interfaces * add InnerCircuit::Params * rm non_base_node * WIP refactor RecursiveCircuit * wip. change approach on verifier_data of circuits used in recursivecircuit. arity=1 works * recursion works fine without registering verifierdatas as publicinputs * add hashing of verifier_data (out & in-circuit) methods with test * connect previous and current verifier_datas hashes * polish * add InnerCircuit's public_inputs to the RecursiveCircuit * extend recursive test * polish & review suggestions --- .github/workflows/typos.toml | 1 + Cargo.toml | 1 + src/backends/plonky2/mod.rs | 1 + src/backends/plonky2/recursion/circuit.rs | 829 ++++++++++++++++++++++ src/backends/plonky2/recursion/mod.rs | 2 + 5 files changed, 834 insertions(+) create mode 100644 src/backends/plonky2/recursion/circuit.rs create mode 100644 src/backends/plonky2/recursion/mod.rs diff --git a/.github/workflows/typos.toml b/.github/workflows/typos.toml index beca8e5..0c36a42 100644 --- a/.github/workflows/typos.toml +++ b/.github/workflows/typos.toml @@ -7,3 +7,4 @@ aks = "aks" # anchored keys nin = "nin" # not in kow = "kow" # key or wildcard KOW = "KOW" # Key Or Wildcard +datas = "datas" # plural (for 'verifier_datas', a vector of 'verifier_data') diff --git a/Cargo.toml b/Cargo.toml index 2991a37..ee8b7e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ serde = "1.0.219" serde_json = "1.0.140" base64 = "0.22.1" schemars = "0.8.22" +hashbrown = { version = "0.14.3", default-features = false, features = ["serde"] } # Uncomment for debugging with https://github.com/ed255/plonky2/ at branch `feat/debug`. The repo directory needs to be checked out next to the pod2 repo directory. # [patch."https://github.com/0xPolygonZero/plonky2"] diff --git a/src/backends/plonky2/mod.rs b/src/backends/plonky2/mod.rs index 23cda93..9d65f2f 100644 --- a/src/backends/plonky2/mod.rs +++ b/src/backends/plonky2/mod.rs @@ -4,6 +4,7 @@ mod error; pub mod mainpod; pub mod mock; pub mod primitives; +pub mod recursion; pub mod signedpod; pub use error::*; diff --git a/src/backends/plonky2/recursion/circuit.rs b/src/backends/plonky2/recursion/circuit.rs new file mode 100644 index 0000000..9719ed2 --- /dev/null +++ b/src/backends/plonky2/recursion/circuit.rs @@ -0,0 +1,829 @@ +/// This file contains the RecursiveCircuit, the circuit used for recursion. +/// +/// The RecursiveCircuit verifies N proofs (N=arity), together with the logic +/// defined at the InnerCircuit (in our case, used for the MainPodCircuit +/// logic). +/// +/// The arity defines the maximum amount of proofs that the RecursiveCircuit +/// verifies. When arity>1, using the RecursiveCircuit has the shape of a tree +/// of the same arity. +/// +use hashbrown::HashMap; +use plonky2::{ + self, + gates::noop::NoopGate, + hash::{ + hash_types::{HashOut, HashOutTarget}, + poseidon::PoseidonHash, + }, + iop::{ + target::{BoolTarget, Target}, + witness::{PartialWitness, WitnessWrite}, + }, + plonk::{ + circuit_builder::CircuitBuilder, + circuit_data::{ + CircuitConfig, CircuitData, CommonCircuitData, ProverCircuitData, VerifierCircuitData, + VerifierCircuitTarget, + }, + config::Hasher, + proof::{ProofWithPublicInputs, ProofWithPublicInputsTarget}, + }, + recursion::dummy_circuit::{dummy_circuit, dummy_proof as plonky2_dummy_proof}, +}; + +use crate::{ + backends::plonky2::{ + basetypes::{Proof, C, D}, + error::{Error, Result}, + }, + middleware::F, +}; + +/// InnerCircuit is the trait used to define the logic of the circuit that is +/// computed inside the RecursiveCircuit. +pub trait InnerCircuit: Clone { + type Input; + type Params; + + fn build( + builder: &mut CircuitBuilder, + params: &Self::Params, + selectors: Vec, + ) -> Result; + + /// assigns the values to the targets + fn set_targets(&self, pw: &mut PartialWitness, input: &Self::Input) -> Result<()>; +} + +#[derive(Clone, Debug)] +pub struct RecursiveParams { + /// determines the arity of the RecursiveCircuit + arity: usize, + common_data: CommonCircuitData, + dummy_proof_pi: ProofWithPublicInputs, + dummy_verifier_data: VerifierCircuitData, +} + +pub fn new_params( + arity: usize, + inner_params: &I::Params, +) -> Result { + let circuit_data = RecursiveCircuit::::circuit_data(arity, inner_params)?; + let common_data = circuit_data.common.clone(); + let verifier_data = circuit_data.verifier_data(); + let dummy_proof_pi = RecursiveCircuit::::dummy_proof(circuit_data)?; + Ok(RecursiveParams { + arity, + common_data, + dummy_proof_pi, + dummy_verifier_data: verifier_data, + }) +} + +/// RecursiveCircuit defines the circuit that verifies `arity` proofs. +pub struct RecursiveCircuit { + params: RecursiveParams, + prover: ProverCircuitData, + targets: RecursiveCircuitTarget, +} + +#[derive(Clone, Debug)] +pub struct RecursiveCircuitTarget { + selectors_targ: Vec, + innercircuit_targ: I, + proofs_targ: Vec>, + vds_hash: HashOutTarget, + verifier_datas_targ: Vec, +} + +impl RecursiveCircuit { + pub fn prove( + &mut self, + inner_inputs: I::Input, + proofs: Vec, + proofs_inp: Vec>, + prev_hashes: Vec>, + verifier_datas: Vec>, + ) -> Result<(Proof, HashOut)> { + let mut pw = PartialWitness::new(); + let vds_hash = self.set_targets( + &mut pw, + inner_inputs, // innercircuit_input + proofs, + proofs_inp, + prev_hashes, + verifier_datas, + )?; + let proof = self.prover.prove(pw)?; + Ok((proof.proof, vds_hash)) + } + + /// builds the targets and returns also a ProverCircuitData + pub fn build(params: &RecursiveParams, inner_params: &I::Params) -> Result { + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::new(config.clone()); + + let targets: RecursiveCircuitTarget = + Self::build_targets(&mut builder, params, inner_params)?; + + println!("RecursiveCircuit num_gates {}", builder.num_gates()); + + let prover: ProverCircuitData = builder.build_prover::(); + Ok(Self { + params: params.clone(), + prover, + targets, + }) + } + + /// builds the targets + fn build_targets( + builder: &mut CircuitBuilder, + params: &RecursiveParams, + inner_params: &I::Params, + ) -> Result> { + let selectors_targ: Vec = (0..params.arity) + .map(|_| builder.add_virtual_bool_target_safe()) + .collect(); + + // TODO: investigate + builder.add_gate( + // add a ConstantGate, because without this, when later generating the `dummy_circuit` + // (inside the `conditionally_verify_proof_or_dummy`), it fails due the + // `CommonCircuitData` of the generated circuit not matching the given + // `CommonCircuitData` to create it. Without this it fails because it misses a + // ConstantGate. + plonky2::gates::constant::ConstantGate::new(params.common_data.config.num_constants), + vec![], + ); + + // proof verification + let verifier_datas_targ: Vec = (0..params.arity) + .map(|_| builder.add_virtual_verifier_data(builder.config.fri_config.cap_height)) + .collect(); + let proofs_targ: Result>> = (0..params.arity) + .map(|i| { + let proof_targ = builder.add_virtual_proof_with_pis(¶ms.common_data); + builder.conditionally_verify_proof_or_dummy::( + selectors_targ[i], + &proof_targ, + &verifier_datas_targ[i], + ¶ms.common_data, + )?; + Ok(proof_targ) + }) + .collect(); + let proofs_targ = proofs_targ?; + + // hash the various verifier_data + let prev_verifier_datas_hashes: Vec = proofs_targ + .iter() + .map(|p| HashOutTarget::from_vec(p.public_inputs[..4].to_vec())) + .collect(); + let vds_hash = gadget_hash_verifier_datas( + builder, + params.arity, + prev_verifier_datas_hashes.clone(), + verifier_datas_targ.clone(), + ); + // set vds_hash as public input, which are registered before the + // InnerCircuit public inputs in case that there are + builder.register_public_inputs(&vds_hash.elements); + + // build the InnerCircuit logic. Notice that if the InnerCircuit + // registers any public inputs, they will be placed after the + // `vds_hash` in the public inputs array + let innercircuit_targ: I = I::build(builder, inner_params, selectors_targ.clone())?; + + Ok(RecursiveCircuitTarget { + selectors_targ, + innercircuit_targ, + proofs_targ, + vds_hash, + verifier_datas_targ, + }) + } + + fn set_targets( + &mut self, + pw: &mut PartialWitness, + innercircuit_input: I::Input, + recursive_proofs: Vec, + recursive_proofs_inp: Vec>, + prev_verifier_datas_hashes: Vec>, + verifier_datas: Vec>, + ) -> Result> { + let n = recursive_proofs.len(); + assert!(n <= self.params.arity); + assert_eq!(n, recursive_proofs_inp.len()); + assert_eq!(n, prev_verifier_datas_hashes.len()); + assert_eq!(n, verifier_datas.len()); + + // fill the missing proofs with dummy_proofs + let dummy_proofs: Vec = (n..self.params.arity) + .map(|_| self.params.dummy_proof_pi.proof.clone()) + .collect(); + let recursive_proofs: Vec = [recursive_proofs, dummy_proofs].concat(); + + // fill the missing prev_verifier_data_hashes with the 'zero' hash + let mut prev_verifier_datas_hashes = prev_verifier_datas_hashes.clone(); + prev_verifier_datas_hashes.resize(self.params.arity, HashOut::::ZERO); + + let mut recursive_proofs_inp = recursive_proofs_inp.clone(); + recursive_proofs_inp.resize( + self.params.arity, + // skip the first 4 elements, which contain the vds_hash + self.params.dummy_proof_pi.public_inputs[4..].to_vec(), + ); + + // fill the missing verifier_datas with dummy_verifier_datas + let dummy_verifier_datas: Vec> = (n..self.params.arity) + .map(|_| self.params.dummy_verifier_data.clone()) + .collect(); + let verifier_datas: Vec> = + [verifier_datas, dummy_verifier_datas].concat(); + + // set the first n selectors to true, and the rest to false + for i in 0..n { + pw.set_bool_target(self.targets.selectors_targ[i], true)?; + } + for i in n..self.params.arity { + pw.set_bool_target(self.targets.selectors_targ[i], false)?; + } + + // set the InnerCircuit related values + self.targets + .innercircuit_targ + .set_targets(pw, &innercircuit_input)?; + + #[allow(clippy::needless_range_loop)] + for i in 0..self.params.arity { + pw.set_verifier_data_target( + &self.targets.verifier_datas_targ[i], + &verifier_datas[i].verifier_only, + )?; + + // put together the public inputs with the verifier_data used to + // verify the current proof + let proof_i_public_inputs = Self::prepare_public_inputs( + prev_verifier_datas_hashes[i], + recursive_proofs_inp[i].clone(), + ); + + pw.set_proof_with_pis_target( + &self.targets.proofs_targ[i], + &ProofWithPublicInputs { + proof: recursive_proofs[i].clone(), + public_inputs: proof_i_public_inputs.clone(), + }, + )?; + } + + // vds_hash is returned since it will be used as public input to verify + // the proof of the current instance of the circuit + let vds_hash = hash_verifier_datas( + self.params.arity, + prev_verifier_datas_hashes.clone(), + verifier_datas.clone(), + ); + pw.set_hash_target(self.targets.vds_hash, vds_hash)?; + + Ok(vds_hash) + } + + /// returns the full-recursive CircuitData + pub fn circuit_data(arity: usize, inner_params: &I::Params) -> Result> { + let data: CircuitData = common_data_for_recursion::(arity, inner_params)?; + let common_data = data.common.clone(); + let verifier_data = data.verifier_data(); + let dummy_proof_pi = Self::dummy_proof(data)?; + let params = RecursiveParams { + arity, + common_data, + dummy_proof_pi, + dummy_verifier_data: verifier_data, + }; + + // build the actual RecursiveCircuit circuit data + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::new(config); + + let _ = Self::build_targets(&mut builder, ¶ms, inner_params)?; + let data = builder.build::(); + + Ok(data) + } + + fn dummy_proof(circuit_data: CircuitData) -> Result> { + let dummy_circuit_data = dummy_circuit(&circuit_data.common); + let dummy_proof_pis = plonky2_dummy_proof(&dummy_circuit_data, HashMap::new())?; + Ok(dummy_proof_pis) + } + + pub fn prepare_public_inputs( + prev_verifier_datas_hash: HashOut, + inner_public_inputs: Vec, + ) -> Vec { + [ + prev_verifier_datas_hash.elements.to_vec(), + inner_public_inputs, + ] + .concat() + } +} + +fn hash_verifier_datas( + arity: usize, + prev_hashes: Vec>, + verifier_datas: Vec>, +) -> HashOut { + // sanity check + assert_eq!(verifier_datas.len(), arity); + + let zero_hash = HashOut::::ZERO; + let mut prev_hashes = prev_hashes.clone(); + prev_hashes.resize(arity, zero_hash); + let prev_hashes: Vec = prev_hashes + .iter() + .flat_map(|h| h.elements.to_vec()) + .collect(); + + let hashes: Vec = verifier_datas + .iter() + .flat_map(|vd| vd.verifier_only.circuit_digest.elements) + .collect(); + + let inp: Vec = [prev_hashes, hashes].concat(); + + PoseidonHash::hash_no_pad(&inp) +} + +fn gadget_hash_verifier_datas( + builder: &mut CircuitBuilder, + arity: usize, + prev_hashes: Vec, + verifier_datas: Vec, +) -> HashOutTarget { + // sanity checks + assert_eq!(prev_hashes.len(), arity); + assert_eq!(verifier_datas.len(), arity); + + let prev_hashes: Vec = prev_hashes + .iter() + .flat_map(|h| h.elements.to_vec()) + .collect(); + + let hashes: Vec = verifier_datas + .iter() + .flat_map(|vd| vd.circuit_digest.elements) + .collect(); + + let inp: Vec = [prev_hashes, hashes].concat(); + + builder.hash_n_to_hash_no_pad::(inp) +} + +fn common_data_for_recursion( + arity: usize, + inner_params: &I::Params, +) -> Result> { + // 1st + let config = CircuitConfig::standard_recursion_config(); + let builder = CircuitBuilder::::new(config); + let data = builder.build::(); + + // 2nd + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::new(config.clone()); + for _ in 0..arity { + let verifier_data_i = + builder.add_virtual_verifier_data(builder.config.fri_config.cap_height); + + let proof = builder.add_virtual_proof_with_pis(&data.common); + builder.verify_proof::(&proof, &verifier_data_i, &data.common); + } + let data = builder.build::(); + + // 3rd + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::new(config.clone()); + + builder.add_gate( + plonky2::gates::constant::ConstantGate::new(config.num_constants), + vec![], + ); + + let verifier_datas_targ: Vec = (0..arity) + .map(|_| builder.add_virtual_verifier_data(builder.config.fri_config.cap_height)) + .collect(); + for vd_i in verifier_datas_targ.iter() { + let proof = builder.add_virtual_proof_with_pis(&data.common); + builder.verify_proof::(&proof, vd_i, &data.common); + } + + let prev_verifier_datas_hashes = builder.add_virtual_hashes(arity); + let vds_hash = gadget_hash_verifier_datas( + &mut builder, + arity, + prev_verifier_datas_hashes.clone(), + verifier_datas_targ.clone(), + ); + // set vds_hash as public input + builder.register_public_inputs(&vds_hash.elements); + + // set the targets for the InnerCircuit + let _ = I::build(&mut builder, inner_params, vec![])?; + + // pad min gates + let n_gates = compute_num_gates(arity)?; + while builder.num_gates() < n_gates { + builder.add_gate(NoopGate, vec![]); + } + Ok(builder.build::()) +} + +fn compute_num_gates(arity: usize) -> Result { + // Note: the following numbers are WIP, obtained by trial-error by running different + // configurations in the tests. + let n_gates = match arity { + 0..=1 => 1 << 12, + 2 => 1 << 13, + 3..=5 => 1 << 14, + 6 => 1 << 15, + _ => 0, + }; + if n_gates == 0 { + return Err(Error::custom(format!( + "arity={} not supported. Currently supported arity from 1 to 6 (both included)", + arity + ))); + } + Ok(n_gates) +} + +#[cfg(test)] +mod tests { + use std::time::Instant; + + use plonky2::{ + field::types::Field, + hash::{ + hash_types::{HashOut, HashOutTarget}, + poseidon::PoseidonHash, + }, + plonk::config::Hasher, + }; + + use super::*; + + // out-of-circuit input-output computation for Circuit1 + fn circuit1_io(inp: HashOut) -> HashOut { + let mut aux: F = inp.elements[0]; + let two = F::from_canonical_u64(2u64); + for _ in 0..5_000 { + aux = aux + two; + } + HashOut::::from_vec(vec![aux, F::ZERO, F::ZERO, F::ZERO]) + } + // out-of-circuit input-output computation for Circuit2 + fn circuit2_io(inp: HashOut) -> HashOut { + let mut out: HashOut = inp; + for _ in 0..100 { + out = PoseidonHash::hash_no_pad(&out.elements) + } + out + } + // out-of-circuit input-output computation for Circuit3 + fn circuit3_io(inp: HashOut) -> HashOut { + let mut out: HashOut = inp; + for _ in 0..2000 { + out = PoseidonHash::hash_no_pad(&out.elements) + } + out + } + + #[derive(Clone, Debug)] + pub struct Circuit1 { + input: HashOutTarget, + output: HashOutTarget, + } + impl InnerCircuit for Circuit1 { + type Input = (HashOut, HashOut); // (input, output) + type Params = (); + fn build( + builder: &mut CircuitBuilder, + _params: &Self::Params, + _selectors: Vec, + ) -> Result { + let input_targ = builder.add_virtual_hash(); + let mut aux: Target = input_targ.elements[0]; + let two = builder.constant(F::from_canonical_u64(2u64)); + for _ in 0..5_000 { + aux = builder.add(aux, two); + } + let zero = builder.zero(); + let output_targ = HashOutTarget::from_vec(vec![aux, zero, zero, zero]); + + builder.register_public_inputs(&output_targ.elements.to_vec()); + + Ok(Self { + input: input_targ, + output: output_targ, + }) + } + fn set_targets(&self, pw: &mut PartialWitness, input: &Self::Input) -> Result<()> { + pw.set_hash_target(self.input, input.0)?; + pw.set_hash_target(self.output, input.1)?; + Ok(()) + } + } + #[derive(Clone, Debug)] + pub struct Circuit2 { + input: HashOutTarget, + output: HashOutTarget, + } + impl InnerCircuit for Circuit2 { + type Input = (HashOut, HashOut); // (input, output) + type Params = (); + fn build( + builder: &mut CircuitBuilder, + _params: &Self::Params, + _selectors: Vec, + ) -> Result { + let input_targ = builder.add_virtual_hash(); + + let mut output_targ: HashOutTarget = input_targ.clone(); + for _ in 0..100 { + output_targ = builder + .hash_n_to_hash_no_pad::(output_targ.elements.clone().to_vec()); + } + + builder.register_public_inputs(&output_targ.elements.to_vec()); + + Ok(Self { + input: input_targ, + output: output_targ, + }) + } + fn set_targets(&self, pw: &mut PartialWitness, input: &Self::Input) -> Result<()> { + pw.set_hash_target(self.input, input.0)?; + pw.set_hash_target(self.output, input.1)?; + Ok(()) + } + } + #[derive(Clone, Debug)] + pub struct Circuit3 { + input: HashOutTarget, + output: HashOutTarget, + } + impl InnerCircuit for Circuit3 { + type Input = (HashOut, HashOut); // (input, output) + type Params = (); + fn build( + builder: &mut CircuitBuilder, + _params: &Self::Params, + _selectors: Vec, + ) -> Result { + let input_targ = builder.add_virtual_hash(); + + let mut output_targ: HashOutTarget = input_targ.clone(); + for _ in 0..2000 { + output_targ = builder + .hash_n_to_hash_no_pad::(output_targ.elements.clone().to_vec()); + } + + builder.register_public_inputs(&output_targ.elements.to_vec()); + + Ok(Self { + input: input_targ, + output: output_targ, + }) + } + fn set_targets(&self, pw: &mut PartialWitness, input: &Self::Input) -> Result<()> { + pw.set_hash_target(self.input, input.0)?; + pw.set_hash_target(self.output, input.1)?; + Ok(()) + } + } + + #[test] + fn test_circuit_i() -> Result<()> { + let inner_params = (); + let inp = HashOut::::ZERO; + + let inner_inputs = (inp, circuit1_io(inp)); + test_circuit_i_opt::(&inner_params, inner_inputs)?; + + let inner_inputs = (inp, circuit2_io(inp)); + test_circuit_i_opt::(&inner_params, inner_inputs)?; + + let inner_inputs = (inp, circuit3_io(inp)); + test_circuit_i_opt::(&inner_params, inner_inputs)?; + + Ok(()) + } + fn test_circuit_i_opt( + inner_params: &IC::Params, + inner_inputs: IC::Input, + ) -> Result<()> { + // circuit + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::new(config); + let mut pw = PartialWitness::::new(); + + let targets = IC::build(&mut builder, inner_params, vec![])?; + targets.set_targets(&mut pw, &inner_inputs)?; + + // generate & verify proof + let data = builder.build::(); + let proof = data.prove(pw)?; + data.verify(proof.clone())?; + + Ok(()) + } + + #[test] + fn test_hash_verifier_datas() -> Result<()> { + let arity: usize = 2; + let circuit_data = RecursiveCircuit::::circuit_data(1, &())?; + let verifier_data = circuit_data.verifier_data(); + + let h = hash_verifier_datas( + arity, + vec![], + vec![verifier_data.clone(), verifier_data.clone()], + ); + + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::new(config); + let mut pw = PartialWitness::::new(); + + // circuit logic + let vd_targ1 = builder.add_virtual_verifier_data(builder.config.fri_config.cap_height); + let vd_targ2 = builder.add_virtual_verifier_data(builder.config.fri_config.cap_height); + let expected_h = builder.add_virtual_hash(); + let prev_hashes_targ = builder.add_virtual_hashes(arity); + + let h_targ = gadget_hash_verifier_datas( + &mut builder, + arity, + prev_hashes_targ.clone(), + vec![vd_targ1.clone(), vd_targ2.clone()], + ); + builder.connect_hashes(expected_h, h_targ); + + // set targets + for ph_targ in prev_hashes_targ { + pw.set_hash_target(ph_targ, HashOut::::ZERO)?; + } + pw.set_hash_target(expected_h, h)?; + pw.set_verifier_data_target(&vd_targ1, &verifier_data.verifier_only)?; + pw.set_verifier_data_target(&vd_targ2, &verifier_data.verifier_only)?; + pw.set_hash_target(expected_h, h)?; + + // generate & verify proof + let data = builder.build::(); + let proof = data.prove(pw)?; + data.verify(proof.clone())?; + + Ok(()) + } + + // test that recurses with arity=2, with the following shape: + // proof_1d + // ▲ ▲ + // ┌─┘ └──┐ + // proof_1b proof2 + // ▲ ▲ ▲ ▲ + // ┌─┘ └─┐ ┌─┘ └─┐ + // proof_1a 0 proof3 proof_1c + // + #[test] + fn test_recursive_circuit() -> Result<()> { + let arity: usize = 2; + + type RC = RecursiveCircuit; + let inner_params = (); + let params: RecursiveParams = new_params::(arity, &inner_params)?; + + // build the circuit_data & verifier_data for the recursive circuit + let start = Instant::now(); + let circuit_data_1 = RC::::circuit_data(arity, &inner_params)?; + let verifier_data_1 = circuit_data_1.verifier_data(); + + let circuit_data_2 = RC::::circuit_data(arity, &inner_params)?; + let verifier_data_2 = circuit_data_2.verifier_data(); + + let circuit_data_3 = RC::::circuit_data(arity, &inner_params)?; + let verifier_data_3 = circuit_data_3.verifier_data(); + + println!( + "new_params & (c1, c2, c3).circuit_data generated {:?}", + start.elapsed() + ); + + let mut circuit1 = RC::::build(¶ms, &())?; + let mut circuit2 = RC::::build(¶ms, &())?; + let mut circuit3 = RC::::build(¶ms, &())?; + + println!("circuit1.prove"); + let inp = HashOut::::ZERO; + let inner_inputs = (inp, circuit1_io(inp)); + let (proof_1a, vds_hash_1a) = + circuit1.prove(inner_inputs, vec![], vec![], vec![], vec![])?; + let inner_publicinputs_1a = circuit1_io(inp).elements.to_vec(); + let public_inputs = + RC::::prepare_public_inputs(vds_hash_1a, inner_publicinputs_1a.clone()); + verifier_data_1.clone().verify(ProofWithPublicInputs { + proof: proof_1a.clone(), + public_inputs: public_inputs.clone(), + })?; + + println!( + "circuit1.prove (2nd iteration), verifies the proof of 1st iteration with circuit1" + ); + let inp = HashOut::::ZERO; + let inner_inputs = (inp, circuit1_io(inp)); + let (proof_1b, vds_hash_1b) = circuit1.prove( + inner_inputs, + vec![proof_1a.clone()], + vec![inner_publicinputs_1a], + vec![vds_hash_1a], + vec![verifier_data_1.clone()], + )?; + let inner_publicinputs_1b = circuit1_io(inp).elements.to_vec(); + let public_inputs = + RC::::prepare_public_inputs(vds_hash_1b, inner_publicinputs_1b.clone()); + verifier_data_1.clone().verify(ProofWithPublicInputs { + proof: proof_1b.clone(), + public_inputs: public_inputs.clone(), + })?; + + println!("circuit3.prove"); + let inp = HashOut::::ZERO; + let inner_inputs = (inp, circuit3_io(inp)); + let (proof_3, vds_hash_3) = circuit3.prove(inner_inputs, vec![], vec![], vec![], vec![])?; + let inner_publicinputs_3 = circuit3_io(inp).elements.to_vec(); + let public_inputs = + RC::::prepare_public_inputs(vds_hash_3, inner_publicinputs_3.clone()); + verifier_data_3.clone().verify(ProofWithPublicInputs { + proof: proof_3.clone(), + public_inputs: public_inputs.clone(), + })?; + + println!("circuit1.prove"); + let inp = HashOut::::ZERO; + let inner_inputs = (inp, circuit1_io(inp)); + let (proof_1c, vds_hash_1c) = + circuit1.prove(inner_inputs, vec![], vec![], vec![], vec![])?; + let inner_publicinputs_1c = circuit1_io(inp).elements.to_vec(); + let public_inputs = + RC::::prepare_public_inputs(vds_hash_1c, inner_publicinputs_1c.clone()); + verifier_data_1.clone().verify(ProofWithPublicInputs { + proof: proof_1c.clone(), + public_inputs: public_inputs.clone(), + })?; + + // generate a proof of Circuit2, which internally verifies the proof_3 & proof_1c + println!("circuit2.prove, which internally verifies the proof_3 & proof_1c"); + let inner_inputs = (inp, circuit2_io(inp)); + let (proof_2, vds_hash_2) = circuit2.prove( + inner_inputs, + vec![proof_3.clone(), proof_1c], + vec![inner_publicinputs_3.clone(), inner_publicinputs_1c.clone()], + vec![vds_hash_3, vds_hash_1c], + vec![verifier_data_3.clone(), verifier_data_1.clone()], + )?; + let inner_publicinputs_2 = circuit2_io(inp).elements.to_vec(); + let public_inputs = + RC::::prepare_public_inputs(vds_hash_2, inner_publicinputs_2.clone()); + verifier_data_2.clone().verify(ProofWithPublicInputs { + proof: proof_2.clone(), + public_inputs: public_inputs.clone(), + })?; + + // verify the last proof of circuit2, inside a new circuit1's proof + println!("proof_1d = c1.prove([proof_1b, proof_2], [verifier_data_1, verifier_data_2])"); + let inp = HashOut::::ZERO; + let inner_inputs = (inp, circuit1_io(inp)); + let (proof_1d, vds_hash_1d) = circuit1.prove( + inner_inputs, + // NOTE: if it makes external usage easier, we could group as a + // single input the: proof + inner_publicinputs + vds_hash, in a + // single object `ProofWithPublicInputs`. + vec![proof_1b, proof_2], + vec![inner_publicinputs_1b, inner_publicinputs_2], + vec![vds_hash_1b, vds_hash_2], + vec![verifier_data_1.clone(), verifier_data_2.clone()], + )?; + let inner_publicinputs = circuit1_io(inp).elements.to_vec(); + let public_inputs = RC::::prepare_public_inputs(vds_hash_1d, inner_publicinputs); + verifier_data_1.clone().verify(ProofWithPublicInputs { + proof: proof_1d.clone(), + public_inputs: public_inputs.clone(), + })?; + + Ok(()) + } +} diff --git a/src/backends/plonky2/recursion/mod.rs b/src/backends/plonky2/recursion/mod.rs new file mode 100644 index 0000000..f9452a5 --- /dev/null +++ b/src/backends/plonky2/recursion/mod.rs @@ -0,0 +1,2 @@ +pub mod circuit; +pub use circuit::{InnerCircuit, RecursiveCircuit, RecursiveParams};