arnaucube
arnaucube/plonky2-recursion-experimentPublic Self-hosted repository
Extend recursive tree to variable arity:

Now the number of `(signature proof OR recursive proof) AND ...`
is defined by a parameter `N`.

                   p_root
                   ▲
                   │
               ┌────────┐
               │   F    │
               └────────┘
                ▲ ▲  ▲ ▲
              ┌─┘ │  │ └─┐
         ┌────┘ ┌─┘  └┐  └───┐
         │      │ ... │      │
     ┌────────┐┌┴┐┌─┐┌┴┐ ┌────────┐
     │   F    ││.││.││.│ │   F    │
     └────────┘└─┘└─┘└─┘ └────────┘
     ▲ ▲  ▲  ▲            ▲ ▲  ▲  ▲
   ┌─┘ │  └┐ └─┐        ┌─┘┌┘  └┐ └┐
   │   │   │   │        │  │    │  │
  p_1 p_2 ... p_n     p'_1 p'_2... p'_n

where each p_i is either
   - signature verification
   - recursive plonky2 proof (proof that verifies previous proof)
           (generated by `RecursiveCircuit::prove_step` method)
in other words, each p_i is checking:
  `(signature proof OR recursive proof)`

So for example, by setting `N=2`, we have a binary-tree as we had
in the previous commit, where at each 'node' it is verifying
`(signature proof OR recursive proof) AND (signature proof OR recursive proof)`

By setting `N=3`, each node verifies
`(signature proof OR recursive proof) AND (signature proof OR recursive proof) AND (signature proof OR recursive proof)`
arnaucube git@arnaucube.com
Wed, 16 Oct 2024 17:44:10 +0200
commit

d08f082ca4bba91f7f4c56820d864d93f16d041f

parent

55a08ad575ac43d71629b4a784cb696ff26496fc

3 files changed, 338 insertions(+), 278 deletions(-)

jump to
M src/lib.rs → src/lib.rs

@@ -3,6 +3,7 @@ #![allow(non_snake_case)]

#![allow(non_upper_case_globals)] #![allow(non_camel_case_types)] +pub mod sig_gadget; pub mod tree_recursion; use plonky2::field::goldilocks_field::GoldilocksField;

@@ -11,4 +12,5 @@ use plonky2::plonk::proof::Proof;

pub type F = GoldilocksField; pub type C = PoseidonGoldilocksConfig; +pub const D: usize = 2; pub type PlonkyProof = Proof<F, PoseidonGoldilocksConfig, 2>;
A src/sig_gadget.rs

@@ -0,0 +1,98 @@

+use anyhow::Result; +use plonky2::iop::target::{BoolTarget, Target}; +use plonky2::iop::witness::{PartialWitness, WitnessWrite}; +use plonky2::plonk::circuit_builder::CircuitBuilder; + +use sch::schnorr::*; +use sch::schnorr_prover::*; + +use super::{C, D, F}; + +/// if s==0: returns x +/// if s==1: returns y +/// Warning: this method assumes all input values are ensured to be \in {0,1} +fn selector_gate(builder: &mut CircuitBuilder<F, D>, x: Target, y: Target, s: Target) -> Target { + // z = x + s(y-x) + let y_x = builder.sub(y, x); + // z = x+s(y-x) <==> mul_add(s, yx, x)=s*(y-x)+x + builder.mul_add(s, y_x, x) +} + +/// ensures b \in {0,1} +fn binary_check(builder: &mut CircuitBuilder<F, D>, b: Target) { + let zero = builder.zero(); + let one = builder.one(); + // b * (b-1) == 0 + let b_1 = builder.sub(b, one); + let r = builder.mul(b, b_1); + builder.connect(r, zero); +} + +/// The logic of this gadget verifies the given signature if `selector==0`. +/// We reuse this gadget for all the the signature verifications in the node of the recursion tree. +/// +/// Contains the methods to `build` (ie. create the targets, the logic of the circuit), and +/// `fill_targets` (ie. set the specific values to be used for the previously created targets). +pub struct SignatureGadgetTargets { + pub selector_targ: Target, + pub selector_booltarg: BoolTarget, + + pub pk_targ: SchnorrPublicKeyTarget, + pub sig_targ: SchnorrSignatureTarget, +} +impl SignatureGadgetTargets { + pub fn build( + mut builder: &mut CircuitBuilder<F, D>, + msg_targ: &MessageTarget, + ) -> Result<SignatureGadgetTargets> { + let selector_targ = builder.add_virtual_target(); + // ensure that selector_booltarg is \in {0,1} + binary_check(builder, selector_targ); + let selector_booltarg = BoolTarget::new_unsafe(selector_targ); + + // signature verification: + let sb: SchnorrBuilder = SchnorrBuilder {}; + let pk_targ = SchnorrPublicKeyTarget::new_virtual(&mut builder); + let sig_targ = SchnorrSignatureTarget::new_virtual(&mut builder); + let sig_verif_targ = sb.verify_sig::<C>(&mut builder, &sig_targ, &msg_targ, &pk_targ); + + // - if selector=0 + // verify_sig==1 && proof_enabled=0 + // - if selector=1 + // verify_sig==NaN && proof_enabled=1 (don't check the sig) + // + // if selector=0: check that sig_verif==1 + // if selector=1: check that one==1 + let one = builder.one(); + let expected = selector_gate( + builder, + sig_verif_targ.target, + one, + selector_booltarg.target, + ); + let one_2 = builder.one(); + builder.connect(expected, one_2); + + Ok(Self { + selector_targ, + selector_booltarg, + pk_targ, + sig_targ, + }) + } + pub fn fill_targets( + &mut self, + pw: &mut PartialWitness<F>, + selector: F, // 1=proof, 0=sig + pk: &SchnorrPublicKey, + sig: &SchnorrSignature, + ) -> Result<()> { + pw.set_target(self.selector_targ, selector)?; + + // set signature related values: + self.pk_targ.set_witness(pw, &pk).unwrap(); + self.sig_targ.set_witness(pw, &sig).unwrap(); + + Ok(()) + } +}
M src/tree_recursion.rs → src/tree_recursion.rs

@@ -1,43 +1,78 @@

-/* - Tree recursion with conditionals. - - - p_7 - ▲ - │ - ┌─┴─┐ - │ F │ - └───┘ - ▲ ▲ - ┌─┘ └─┐ - ┌───┘ └───┐ - │p_5 │p_6 - ┌─┴─┐ ┌─┴─┐ - │ F │ │ F │ - └───┘ └───┘ - ▲ ▲ ▲ ▲ - ┌─┘ └─┐ ┌─┘ └─┐ - │ │ │ │ - p_1 p_2 p_3 p_4 - - where each p_i is either - - signature verification - - recursive plonky2 proof (proof that verifies previous proof) - (generated by `RecursiveCircuit::prove_step` method) - - and F verifies the two incoming p_i's, that is - - (signature proof OR recursive proof) AND (signature proof OR recursive proof) - and produces a new proof. - - - To run the tests that checks this logic: - cargo test --release test_tree_recursion -- --nocapture -*/ - -use anyhow::Result; +/// N-arity tree of recursion with conditionals. +/// +/// p_root +/// ▲ +/// │ +/// ┌────────┐ +/// │ F │ +/// └────────┘ +/// ▲ ▲ ▲ ▲ +/// ┌─┘ │ │ └─┐ +/// ┌────┘ ┌─┘ └┐ └───┐ +/// │ │ ... │ │ +/// ┌────────┐┌┴┐┌─┐┌┴┐ ┌────────┐ +/// │ F ││.││.││.│ │ F │ +/// └────────┘└─┘└─┘└─┘ └────────┘ +/// ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ +/// ┌─┘ │ └┐ └─┐ ┌─┘┌┘ └┐ └┐ +/// │ │ │ │ │ │ │ │ +/// p_1 p_2 ... p_n p'_1 p'_2... p'_n +/// +/// +/// where each p_i is either +/// - signature verification +/// - recursive plonky2 proof (proof that verifies previous proof) +/// (generated by `RecursiveCircuit::prove_step` method) +/// in other words, each p_i is checking: +/// `(signature proof OR recursive proof)` +/// +/// Each node of the recursion tree, ie. each F, verifies the N incoming p_i's, that is +/// `(signature proof OR recursive proof) AND ... AND (signature proof OR recursive proof)` +/// and produces a new proof. +/// +/// +/// For example, if N is set to N=2, then we work with a binary recursion tree: +/// p_root +/// ▲ +/// │ +/// ┌─┴─┐ +/// │ F │ +/// └───┘ +/// ▲ ▲ +/// ┌─┘ └─┐ +/// ┌───┘ └───┐ +/// │p_5 │p_6 +/// ┌─┴─┐ ┌─┴─┐ +/// │ F │ │ F │ +/// └───┘ └───┘ +/// ▲ ▲ ▲ ▲ +/// ┌─┘ └─┐ ┌─┘ └─┐ +/// │ │ │ │ +/// p_1 p_2 p_3 p_4 +/// +/// So that each node (F box) is verifying 2 p_i's, ie: +/// `(signature proof OR recursive proof) AND (signature proof OR recursive proof)` +/// +/// +/// With N=3, each node will be verifying 3 p_i's. +/// `(signature proof OR recursive proof) AND (signature proof OR recursive proof) AND (signature proof OR recursive proof)` +/// +/// +/// +/// Also, notice that if we set N=1, it is directly a linear chain of recursive proofs ('tree' of +/// arity 1): +/// ┌─┐ ┌─┐ ┌─┐ ┌─┐ +/// ─────►│F├────►│F├────►│F├────►│F├────► +/// p_1 └─┘ p_2 └─┘ p_3 └─┘ p_4 └─┘ p_5 +/// +/// where each p_i is proving: `(signature proof OR recursive proof)`. +/// +/// +/// To run the tests that checks this logic: +/// cargo test --release test_tree_recursion -- --nocapture +use anyhow::{anyhow, Result}; use plonky2::field::types::Field; use plonky2::gates::noop::NoopGate; -use plonky2::iop::target::{BoolTarget, Target}; use plonky2::iop::witness::{PartialWitness, WitnessWrite}; use plonky2::plonk::circuit_builder::CircuitBuilder; use plonky2::plonk::circuit_data::{

@@ -49,121 +84,23 @@

use sch::schnorr::*; use sch::schnorr_prover::*; -use super::{PlonkyProof, C, F}; - -/// if s==0: returns x -/// if s==1: returns y -/// Warning: this method assumes all input values are ensured to be \in {0,1} -fn selector_gate(builder: &mut CircuitBuilder<F, 2>, x: Target, y: Target, s: Target) -> Target { - // z = x + s(y-x) - let y_x = builder.sub(y, x); - // z = x+s(y-x) <==> mul_add(s, yx, x)=s*(y-x)+x - builder.mul_add(s, y_x, x) -} - -/// ensures b \in {0,1} -fn binary_check(builder: &mut CircuitBuilder<F, 2>, b: Target) { - let zero = builder.zero(); - let one = builder.one(); - // b * (b-1) == 0 - let b_1 = builder.sub(b, one); - let r = builder.mul(b, b_1); - builder.connect(r, zero); -} +use super::{sig_gadget::SignatureGadgetTargets, PlonkyProof, C, D, F}; /// Contains the methods to `build` (ie. create the targets, the logic of the circuit), and /// `fill_targets` (ie. set the specific values to be used for the previously created targets). -/// -/// The logic of this gadget verifies the given signature if `selector==0`. -/// We reuse this gadget for both the signature verifications of the left & right signatures in the -/// node of the recursion tree. -pub struct SignatureGadgetTargets { - selector_targ: Target, - selector_booltarg: BoolTarget, - - pk_targ: SchnorrPublicKeyTarget, - sig_targ: SchnorrSignatureTarget, -} -impl SignatureGadgetTargets { - pub fn build( - mut builder: &mut CircuitBuilder<F, 2>, - msg_targ: &MessageTarget, - ) -> Result<SignatureGadgetTargets> { - let selector_targ = builder.add_virtual_target(); - // ensure that selector_booltarg is \in {0,1} - binary_check(builder, selector_targ); - let selector_booltarg = BoolTarget::new_unsafe(selector_targ); - - // signature verification: - let sb: SchnorrBuilder = SchnorrBuilder {}; - let pk_targ = SchnorrPublicKeyTarget::new_virtual(&mut builder); - let sig_targ = SchnorrSignatureTarget::new_virtual(&mut builder); - let sig_verif_targ = sb.verify_sig::<C>(&mut builder, &sig_targ, &msg_targ, &pk_targ); - - /* - - if selector=0 - verify_sig==1 && proof_enabled=0 - - if selector=1 - verify_sig==NaN && proof_enabled=1 (don't check the sig) - - to disable the verify_sig check, when selector=1: - x=verify_sig, y=always_1, s=selector (all values \in {0,1}) - z = x + s(y-x) - */ - // if selector=0: check that sig_verif==1 - // if selector=1: check that one==1 - let one = builder.one(); - let expected = selector_gate( - builder, - sig_verif_targ.target, - one, - selector_booltarg.target, - ); - let one_2 = builder.one(); - builder.connect(expected, one_2); - - Ok(Self { - selector_targ, - selector_booltarg, - pk_targ, - sig_targ, - }) - } - pub fn fill_targets( - &mut self, - pw: &mut PartialWitness<F>, - // left side - selector: F, // 1=proof, 0=sig - pk: &SchnorrPublicKey, - sig: &SchnorrSignature, - ) -> Result<()> { - pw.set_target(self.selector_targ, selector)?; - - // set signature related values: - self.pk_targ.set_witness(pw, &pk).unwrap(); - self.sig_targ.set_witness(pw, &sig).unwrap(); - - Ok(()) - } -} - -/// Contains the methods to `build` (ie. create the targets, the logic of the circuit), and -/// `fill_targets` (ie. set the specific values to be used for the previously created targets). -pub struct RecursiveCircuit { +pub struct RecursiveCircuit<const N: usize> { msg_targ: MessageTarget, - L_sig_targets: SignatureGadgetTargets, - R_sig_targets: SignatureGadgetTargets, - // L_sig_verif_targ: BoolTarget, - L_proof_targ: ProofWithPublicInputsTarget<2>, - R_proof_targ: ProofWithPublicInputsTarget<2>, - // the next two are common for both L&R proofs. It is the data for this circuit itself (cyclic circuit). + sigs_targ: Vec<SignatureGadgetTargets>, + proofs_targ: Vec<ProofWithPublicInputsTarget<D>>, + // the next two are common for all the given proofs. It is the data for this circuit itself + // (cyclic circuit). verifier_data_targ: VerifierCircuitTarget, - verifier_data: VerifierCircuitData<F, C, 2>, + verifier_data: VerifierCircuitData<F, C, D>, } -impl RecursiveCircuit { +impl<const N: usize> RecursiveCircuit<N> { pub fn prepare_public_inputs( - verifier_data: VerifierCircuitData<F, C, 2>, + verifier_data: VerifierCircuitData<F, C, D>, msg: Vec<F>, ) -> Vec<F> { [

@@ -180,11 +117,12 @@ .collect(),

] .concat() } + // notice that this method does not fill the targets, which is done in the method // `fill_recursive_circuit_targets` pub fn build( - builder: &mut CircuitBuilder<F, 2>, - verifier_data: VerifierCircuitData<F, C, 2>, + builder: &mut CircuitBuilder<F, D>, + verifier_data: VerifierCircuitData<F, C, D>, msg_len: usize, ) -> Result<Self> { let msg_targ = MessageTarget::new_with_size(builder, msg_len);

@@ -192,33 +130,32 @@ // set msg as public input

builder.register_public_inputs(&msg_targ.msg); // build the signature verification logic - let L_sig_targets = SignatureGadgetTargets::build(builder, &msg_targ)?; - let R_sig_targets = SignatureGadgetTargets::build(builder, &msg_targ)?; + let mut sigs_targ: Vec<SignatureGadgetTargets> = vec![]; + for _ in 0..N { + let sig_targets = SignatureGadgetTargets::build(builder, &msg_targ)?; + sigs_targ.push(sig_targets); + } // proof verification: let common_data = verifier_data.common.clone(); let verifier_data_targ = builder.add_verifier_data_public_inputs(); - let L_proof_targ = builder.add_virtual_proof_with_pis(&common_data); - builder.conditionally_verify_cyclic_proof_or_dummy::<C>( - L_sig_targets.selector_booltarg, - &L_proof_targ, - &common_data, - )?; - let R_proof_targ = builder.add_virtual_proof_with_pis(&common_data); - builder.conditionally_verify_cyclic_proof_or_dummy::<C>( - R_sig_targets.selector_booltarg, - &R_proof_targ, - &common_data, - )?; + let mut proofs_targ: Vec<ProofWithPublicInputsTarget<D>> = vec![]; + for i in 0..N { + let proof_targ = builder.add_virtual_proof_with_pis(&common_data); + builder.conditionally_verify_cyclic_proof_or_dummy::<C>( + sigs_targ[i].selector_booltarg, + &proof_targ, + &common_data, + )?; + proofs_targ.push(proof_targ); + } Ok(Self { msg_targ, - L_sig_targets, - R_sig_targets, - L_proof_targ, - R_proof_targ, + sigs_targ, + proofs_targ, verifier_data_targ, verifier_data, })

@@ -228,25 +165,18 @@ pub fn fill_targets(

&mut self, pw: &mut PartialWitness<F>, msg: &Vec<F>, - // left side - L_selector: F, // 1=proof, 0=sig - L_pk: &SchnorrPublicKey, - L_sig: &SchnorrSignature, - L_recursive_proof: &PlonkyProof, - // right side - R_selector: F, // 1=proof, 0=sig - R_pk: &SchnorrPublicKey, - R_sig: &SchnorrSignature, - R_recursive_proof: &PlonkyProof, + selectors: Vec<F>, // 1=proof, 0=sig + pks: &Vec<SchnorrPublicKey>, + sigs: &Vec<SchnorrSignature>, + recursive_proofs: &Vec<PlonkyProof>, ) -> Result<()> { - // set the msg value (used by both sig gadgets, left and right) + // set the msg value (used by all N sig gadgets) self.msg_targ.set_witness(pw, &msg).unwrap(); // set the signature related values - self.L_sig_targets - .fill_targets(pw, L_selector, L_pk, L_sig)?; - self.R_sig_targets - .fill_targets(pw, R_selector, R_pk, R_sig)?; + for i in 0..N { + self.sigs_targ[i].fill_targets(pw, selectors[i], &pks[i], &sigs[i])?; + } // set proof related values:

@@ -254,52 +184,45 @@ // recursive proofs verification

pw.set_verifier_data_target(&self.verifier_data_targ, &self.verifier_data.verifier_only)?; let public_inputs = - RecursiveCircuit::prepare_public_inputs(self.verifier_data.clone(), msg.clone()); - // left proof verification values - pw.set_proof_with_pis_target( - &self.L_proof_targ, - &ProofWithPublicInputs { - proof: L_recursive_proof.clone(), - public_inputs: public_inputs.clone(), - }, - )?; - // right proof verification values - pw.set_proof_with_pis_target( - &self.R_proof_targ, - &ProofWithPublicInputs { - proof: R_recursive_proof.clone(), - public_inputs, - }, - )?; + RecursiveCircuit::<N>::prepare_public_inputs(self.verifier_data.clone(), msg.clone()); + for i in 0..N { + pw.set_proof_with_pis_target( + &self.proofs_targ[i], + &ProofWithPublicInputs { + proof: recursive_proofs[i].clone(), + public_inputs: public_inputs.clone(), + }, + )?; + } Ok(()) } } #[derive(Debug, Clone)] -pub struct Recursion {} +pub struct Recursion<const N: usize> {} -pub fn common_data_for_recursion(msg_len: usize) -> CircuitData<F, C, 2> { +pub fn common_data_for_recursion<const N: usize>(msg_len: usize) -> Result<CircuitData<F, C, D>> { // 1st let config = CircuitConfig::standard_recursion_config(); - let builder = CircuitBuilder::<F, 2>::new(config); + let builder = CircuitBuilder::<F, D>::new(config); let data = builder.build::<C>(); // 2nd let config = CircuitConfig::standard_recursion_config(); - let mut builder = CircuitBuilder::<F, 2>::new(config.clone()); + let mut builder = CircuitBuilder::<F, D>::new(config.clone()); let verifier_data = builder.add_virtual_verifier_data(data.common.config.fri_config.cap_height); - // left proof - let proof = builder.add_virtual_proof_with_pis(&data.common); - builder.verify_proof::<C>(&proof, &verifier_data, &data.common); - // right proof - let proof = builder.add_virtual_proof_with_pis(&data.common); - builder.verify_proof::<C>(&proof, &verifier_data, &data.common); + // proofs + for _ in 0..N { + let proof = builder.add_virtual_proof_with_pis(&data.common); + builder.verify_proof::<C>(&proof, &verifier_data, &data.common); + } + // let n_gates = builder.num_gates(); let data = builder.build::<C>(); // 3rd let config = CircuitConfig::standard_recursion_config(); - let mut builder = CircuitBuilder::<F, 2>::new(config.clone()); + let mut builder = CircuitBuilder::<F, D>::new(config.clone()); let msg_targ = MessageTarget::new_with_size(&mut builder, msg_len); // sigs verify builder.register_public_inputs(&msg_targ.msg);

@@ -318,58 +241,70 @@ let _ = SignatureGadgetTargets::build(&mut builder, &msg_targ).unwrap();

// proofs verify let verifier_data = builder.add_verifier_data_public_inputs(); - // left proof - let proof_L = builder.add_virtual_proof_with_pis(&data.common); - builder.verify_proof::<C>(&proof_L, &verifier_data, &data.common); - // right proof - let proof_R = builder.add_virtual_proof_with_pis(&data.common); - builder.verify_proof::<C>(&proof_R, &verifier_data, &data.common); + // proofs + for _ in 0..N { + let proof = builder.add_virtual_proof_with_pis(&data.common); + builder.verify_proof::<C>(&proof, &verifier_data, &data.common); + } // pad min gates - while builder.num_gates() < 1 << 13 { + let n_gates = compute_num_gates::<N>()?; + while builder.num_gates() < n_gates { builder.add_gate(NoopGate, vec![]); } - builder.build::<C>() + dbg!(builder.num_gates()); + Ok(builder.build::<C>()) } -impl Recursion { +fn compute_num_gates<const N: usize>() -> Result<usize> { + // Note: the following numbers are WIP, obtained by trial-error by running different + // configurations in the tests. + let n_gates = match N { + 1 => 1 << 12, + 2 => 1 << 13, + 3..=5 => 1 << 14, + 6 => 1 << 15, + _ => 0, + }; + if n_gates == 0 { + return Err(anyhow!( + "arity of N={} not supported yet. Currently supported N from 1 to 6 (both included)", + N + )); + } + Ok(n_gates) +} + +impl<const N: usize> Recursion<N> { /// returns the full-recursive CircuitData - pub fn circuit_data(msg_len: usize) -> Result<CircuitData<F, C, 2>> { - let mut data = common_data_for_recursion(msg_len); + pub fn circuit_data(msg_len: usize) -> Result<CircuitData<F, C, D>> { + let mut data = common_data_for_recursion::<N>(msg_len)?; // build the actual RecursiveCircuit circuit data let config = CircuitConfig::standard_recursion_config(); let mut builder = CircuitBuilder::new(config); - let _ = RecursiveCircuit::build(&mut builder, data.verifier_data(), msg_len)?; + let _ = RecursiveCircuit::<N>::build(&mut builder, data.verifier_data(), msg_len)?; + dbg!(builder.num_gates()); data = builder.build::<C>(); Ok(data) } pub fn prove_step( - verifier_data: VerifierCircuitData<F, C, 2>, + verifier_data: VerifierCircuitData<F, C, D>, msg: &Vec<F>, - // left side - L_selector: F, // 1=proof, 0=sig - pk_L: &SchnorrPublicKey, - sig_L: &SchnorrSignature, - recursive_proof_L: &PlonkyProof, - // right side - R_selector: F, // 1=proof, 0=sig - pk_R: &SchnorrPublicKey, - sig_R: &SchnorrSignature, - recursive_proof_R: &PlonkyProof, + selectors: Vec<F>, // 1=proof, 0=sig + pks: &Vec<SchnorrPublicKey>, + sigs: &Vec<SchnorrSignature>, + recursive_proofs: &Vec<PlonkyProof>, ) -> Result<PlonkyProof> { println!("prove_step:"); - if L_selector.is_nonzero() { - println!(" (L_selector==1), verify left proof"); - } else { - println!(" (L_selector==0), verify left signature"); - } - if R_selector.is_nonzero() { - println!(" (R_selector==1), verify right proof"); - } else { - println!(" (R_selector==0), verify right signature"); + for i in 0..N { + if selectors[i].is_nonzero() { + println!(" (selectors[{}]==1), verify {}-th proof", i, i); + } else { + println!(" (selectors[{}]==0), verify {}-th signature", i, i); + } } let config = CircuitConfig::standard_recursion_config();

@@ -377,24 +312,14 @@ let mut builder = CircuitBuilder::new(config);

// assign the targets let start = Instant::now(); - let mut circuit = RecursiveCircuit::build(&mut builder, verifier_data.clone(), msg.len())?; + let mut circuit = + RecursiveCircuit::<N>::build(&mut builder, verifier_data.clone(), msg.len())?; println!("RecursiveCircuit::build(): {:?}", start.elapsed()); // fill the targets let mut pw = PartialWitness::new(); let start = Instant::now(); - circuit.fill_targets( - &mut pw, - msg, - L_selector, - pk_L, - sig_L, - recursive_proof_L, - R_selector, - pk_R, - sig_R, - recursive_proof_R, - )?; + circuit.fill_targets(&mut pw, msg, selectors, pks, sigs, recursive_proofs)?; println!("circuit.fill_targets(): {:?}", start.elapsed()); let start = Instant::now();

@@ -449,15 +374,35 @@ /// to run:

/// cargo test --release test_tree_recursion -- --nocapture #[test] fn test_tree_recursion() -> Result<()> { + // For testing: change the following `N` value to try different arities of the recursion tree: + test_tree_recursion_opt::<2>()?; // N=2 + + test_tree_recursion_opt::<3>()?; // N=3 + + Ok(()) + } + + fn test_tree_recursion_opt<const N: usize>() -> Result<()> { set_log(); + println!("\n--------------------------------------------------"); + println!("\n--------------------------------------------------"); + println!( + "\nrunning test:\n test_tree_recursion_opt with N={} (arity)", + N + ); + + let l: u32 = 2; // levels of the recursion (binary) tree + let k = (N as u32).pow(l) as usize; // number of leafs in the recursion tree, N^l + println!( + "Testing a {}-arity recursion tree, of {} levels, with {} leaves", + N, l, k + ); + let mut rng: rand::rngs::ThreadRng = rand::thread_rng(); let schnorr = SchnorrSigner::new(); const MSG_LEN: usize = 5; let msg: Vec<F> = schnorr.u64_into_goldilocks_vec(vec![1500, 1600, 2, 2, 2]); assert_eq!(msg.len(), MSG_LEN); - - let l: u32 = 2; // levels of the recursion (binary) tree - let k = 2_u32.pow(l); // number of leafs in the recursion tree // generate k key pairs let sk_vec: Vec<SchnorrSecretKey> =

@@ -470,11 +415,9 @@ .map(|&sk| schnorr.sign(&msg, &sk, &mut rng))

.collect(); // build the circuit_data & verifier_data for the recursive circuit - let circuit_data = Recursion::circuit_data(MSG_LEN)?; + let circuit_data = Recursion::<N>::circuit_data(MSG_LEN)?; let verifier_data = circuit_data.verifier_data(); - // let dummy_circuit = dummy_circuit::<F, C, 2>(&circuit_data.common); // WIP - // let dummy_proof_pis = dummy_proof(&dummy_circuit, HashMap::new())?; // WIP let dummy_proof_pis = cyclic_base_proof( &circuit_data.common, &verifier_data.verifier_only,

@@ -493,33 +436,49 @@ println!("\n=== recursion level i={}", i);

let mut next_level_proofs: Vec<PlonkyProof> = vec![]; // loop over the nodes of each recursion tree level - for j in (0..proofs_at_level_i.len()).into_iter().step_by(2) { - println!("\n------ recursion node i={}, j={}", i, j); + for j in (0..proofs_at_level_i.len()).into_iter().step_by(N) { + println!( + "\n------ recursion node: (level) i={}, (node in level) j={}", + i, j + ); // - if we're at the first level of the recursion tree: // proof_enabled=false=0, so that the circuit verifies the signature and not the proof. // - else: // proof_enabled=true=1, so that the circuit verifies the proof and not the signature. // - // In future tests we will try other cases (eg. left sig, right proof), but for - // the moment we just do base_case: sig verify, other cases: proof verify. + // In future tests we will try other cases (eg. some sigs and some proofs in a + // node), but for the moment we just do base_case: sig verify, other cases: proof + // verify. let proof_enabled = if i == 0 { F::ZERO } else { F::ONE }; + // prepare the inputs for the `Recursion::prove_step` call + let proofs_selectors = (0..N).into_iter().map(|_| proof_enabled.clone()).collect(); + let pks = (0..N) + .into_iter() + .enumerate() + .map(|(k, _)| pk_vec[j + k].clone()) + .collect(); + let sigs = (0..N) + .into_iter() + .enumerate() + .map(|(k, _)| sig_vec[j + k].clone()) + .collect(); + let proofs = (0..N) + .into_iter() + .enumerate() + .map(|(k, _)| proofs_at_level_i[j + k].clone()) + .collect(); + // do the recursive step let start = Instant::now(); - let new_proof = Recursion::prove_step( + let new_proof = Recursion::<N>::prove_step( verifier_data.clone(), &msg, - // left side: - proof_enabled, - &pk_vec[j], - &sig_vec[j], - &proofs_at_level_i[j], - // right side - proof_enabled, - &pk_vec[j + 1], - &sig_vec[j + 1], - &proofs_at_level_i[j + 1], + proofs_selectors, + &pks, + &sigs, + &proofs, )?; println!( "Recursion::prove_step (level: i={}, node: j={}) took: {:?}",

@@ -529,8 +488,10 @@ start.elapsed()

); // verify the recursive proof - let public_inputs = - RecursiveCircuit::prepare_public_inputs(verifier_data.clone(), msg.clone()); + let public_inputs = RecursiveCircuit::<N>::prepare_public_inputs( + verifier_data.clone(), + msg.clone(), + ); verifier_data.clone().verify(ProofWithPublicInputs { proof: new_proof.clone(), public_inputs: public_inputs.clone(),

@@ -546,7 +507,7 @@ let last_proof = proofs_at_level_i[0].clone();

// verify the last proof let public_inputs = - RecursiveCircuit::prepare_public_inputs(verifier_data.clone(), msg.clone()); + RecursiveCircuit::<N>::prepare_public_inputs(verifier_data.clone(), msg.clone()); verifier_data.clone().verify(ProofWithPublicInputs { proof: last_proof.clone(), public_inputs: public_inputs.clone(),

@@ -554,5 +515,4 @@ })?;

Ok(()) } - // WIP will add more tests with other sig/proof combinations }