Browse Source

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)`
main
arnaucube 6 months ago
parent
commit
d08f082ca4
3 changed files with 338 additions and 278 deletions
  1. +2
    -0
      src/lib.rs
  2. +98
    -0
      src/sig_gadget.rs
  3. +238
    -278
      src/tree_recursion.rs

+ 2
- 0
src/lib.rs

@ -3,6 +3,7 @@
#![allow(non_upper_case_globals)] #![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)] #![allow(non_camel_case_types)]
pub mod sig_gadget;
pub mod tree_recursion; pub mod tree_recursion;
use plonky2::field::goldilocks_field::GoldilocksField; use plonky2::field::goldilocks_field::GoldilocksField;
@ -11,4 +12,5 @@ use plonky2::plonk::proof::Proof;
pub type F = GoldilocksField; pub type F = GoldilocksField;
pub type C = PoseidonGoldilocksConfig; pub type C = PoseidonGoldilocksConfig;
pub const D: usize = 2;
pub type PlonkyProof = Proof<F, PoseidonGoldilocksConfig, 2>; pub type PlonkyProof = Proof<F, PoseidonGoldilocksConfig, 2>;

+ 98
- 0
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(())
}
}

+ 238
- 278
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::field::types::Field;
use plonky2::gates::noop::NoopGate; use plonky2::gates::noop::NoopGate;
use plonky2::iop::target::{BoolTarget, Target};
use plonky2::iop::witness::{PartialWitness, WitnessWrite}; use plonky2::iop::witness::{PartialWitness, WitnessWrite};
use plonky2::plonk::circuit_builder::CircuitBuilder; use plonky2::plonk::circuit_builder::CircuitBuilder;
use plonky2::plonk::circuit_data::{ use plonky2::plonk::circuit_data::{
@ -49,121 +84,23 @@ use std::time::Instant;
use sch::schnorr::*; use sch::schnorr::*;
use sch::schnorr_prover::*; 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 /// 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). /// `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, 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_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( pub fn prepare_public_inputs(
verifier_data: VerifierCircuitData<F, C, 2>,
verifier_data: VerifierCircuitData<F, C, D>,
msg: Vec<F>, msg: Vec<F>,
) -> Vec<F> { ) -> Vec<F> {
[ [
@ -180,11 +117,12 @@ impl RecursiveCircuit {
] ]
.concat() .concat()
} }
// notice that this method does not fill the targets, which is done in the method // notice that this method does not fill the targets, which is done in the method
// `fill_recursive_circuit_targets` // `fill_recursive_circuit_targets`
pub fn build( 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, msg_len: usize,
) -> Result<Self> { ) -> Result<Self> {
let msg_targ = MessageTarget::new_with_size(builder, msg_len); let msg_targ = MessageTarget::new_with_size(builder, msg_len);
@ -192,33 +130,32 @@ impl RecursiveCircuit {
builder.register_public_inputs(&msg_targ.msg); builder.register_public_inputs(&msg_targ.msg);
// build the signature verification logic // 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: // proof verification:
let common_data = verifier_data.common.clone(); let common_data = verifier_data.common.clone();
let verifier_data_targ = builder.add_verifier_data_public_inputs(); 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 { Ok(Self {
msg_targ, msg_targ,
L_sig_targets,
R_sig_targets,
L_proof_targ,
R_proof_targ,
sigs_targ,
proofs_targ,
verifier_data_targ, verifier_data_targ,
verifier_data, verifier_data,
}) })
@ -228,25 +165,18 @@ impl RecursiveCircuit {
&mut self, &mut self,
pw: &mut PartialWitness<F>, pw: &mut PartialWitness<F>,
msg: &Vec<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<()> { ) -> 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(); self.msg_targ.set_witness(pw, &msg).unwrap();
// set the signature related values // 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: // set proof related values:
@ -254,52 +184,45 @@ impl RecursiveCircuit {
pw.set_verifier_data_target(&self.verifier_data_targ, &self.verifier_data.verifier_only)?; pw.set_verifier_data_target(&self.verifier_data_targ, &self.verifier_data.verifier_only)?;
let public_inputs = 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(()) Ok(())
} }
} }
#[derive(Debug, Clone)] #[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 // 1st
let config = CircuitConfig::standard_recursion_config(); 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>(); let data = builder.build::<C>();
// 2nd // 2nd
let config = CircuitConfig::standard_recursion_config(); 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); 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>(); let data = builder.build::<C>();
// 3rd // 3rd
let config = CircuitConfig::standard_recursion_config(); 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); let msg_targ = MessageTarget::new_with_size(&mut builder, msg_len);
// sigs verify // sigs verify
builder.register_public_inputs(&msg_targ.msg); builder.register_public_inputs(&msg_targ.msg);
@ -318,58 +241,70 @@ pub fn common_data_for_recursion(msg_len: usize) -> CircuitData {
// proofs verify // proofs verify
let verifier_data = builder.add_verifier_data_public_inputs(); 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 // 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.add_gate(NoopGate, vec![]);
} }
builder.build::<C>()
dbg!(builder.num_gates());
Ok(builder.build::<C>())
}
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 Recursion {
impl<const N: usize> Recursion<N> {
/// returns the full-recursive CircuitData /// 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 // build the actual RecursiveCircuit circuit data
let config = CircuitConfig::standard_recursion_config(); let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::new(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>(); data = builder.build::<C>();
Ok(data) Ok(data)
} }
pub fn prove_step( pub fn prove_step(
verifier_data: VerifierCircuitData<F, C, 2>,
verifier_data: VerifierCircuitData<F, C, D>,
msg: &Vec<F>, 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> { ) -> Result<PlonkyProof> {
println!("prove_step:"); 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(); let config = CircuitConfig::standard_recursion_config();
@ -377,24 +312,14 @@ impl Recursion {
// assign the targets // assign the targets
let start = Instant::now(); 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()); println!("RecursiveCircuit::build(): {:?}", start.elapsed());
// fill the targets // fill the targets
let mut pw = PartialWitness::new(); let mut pw = PartialWitness::new();
let start = Instant::now(); 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()); println!("circuit.fill_targets(): {:?}", start.elapsed());
let start = Instant::now(); let start = Instant::now();
@ -449,16 +374,36 @@ mod tests {
/// cargo test --release test_tree_recursion -- --nocapture /// cargo test --release test_tree_recursion -- --nocapture
#[test] #[test]
fn test_tree_recursion() -> Result<()> { 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(); 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 mut rng: rand::rngs::ThreadRng = rand::thread_rng();
let schnorr = SchnorrSigner::new(); let schnorr = SchnorrSigner::new();
const MSG_LEN: usize = 5; const MSG_LEN: usize = 5;
let msg: Vec<F> = schnorr.u64_into_goldilocks_vec(vec![1500, 1600, 2, 2, 2]); let msg: Vec<F> = schnorr.u64_into_goldilocks_vec(vec![1500, 1600, 2, 2, 2]);
assert_eq!(msg.len(), MSG_LEN); 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 // generate k key pairs
let sk_vec: Vec<SchnorrSecretKey> = let sk_vec: Vec<SchnorrSecretKey> =
(0..k).map(|i| SchnorrSecretKey { sk: i as u64 }).collect(); (0..k).map(|i| SchnorrSecretKey { sk: i as u64 }).collect();
@ -470,11 +415,9 @@ mod tests {
.collect(); .collect();
// build the circuit_data & verifier_data for the recursive circuit // 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 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( let dummy_proof_pis = cyclic_base_proof(
&circuit_data.common, &circuit_data.common,
&verifier_data.verifier_only, &verifier_data.verifier_only,
@ -493,33 +436,49 @@ mod tests {
let mut next_level_proofs: Vec<PlonkyProof> = vec![]; let mut next_level_proofs: Vec<PlonkyProof> = vec![];
// loop over the nodes of each recursion tree level // 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: // - 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. // proof_enabled=false=0, so that the circuit verifies the signature and not the proof.
// - else: // - else:
// proof_enabled=true=1, so that the circuit verifies the proof and not the signature. // 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 }; 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 // do the recursive step
let start = Instant::now(); let start = Instant::now();
let new_proof = Recursion::prove_step(
let new_proof = Recursion::<N>::prove_step(
verifier_data.clone(), verifier_data.clone(),
&msg, &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!( println!(
"Recursion::prove_step (level: i={}, node: j={}) took: {:?}", "Recursion::prove_step (level: i={}, node: j={}) took: {:?}",
@ -529,8 +488,10 @@ mod tests {
); );
// verify the recursive proof // 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 { verifier_data.clone().verify(ProofWithPublicInputs {
proof: new_proof.clone(), proof: new_proof.clone(),
public_inputs: public_inputs.clone(), public_inputs: public_inputs.clone(),
@ -546,7 +507,7 @@ mod tests {
// verify the last proof // verify the last proof
let public_inputs = 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 { verifier_data.clone().verify(ProofWithPublicInputs {
proof: last_proof.clone(), proof: last_proof.clone(),
public_inputs: public_inputs.clone(), public_inputs: public_inputs.clone(),
@ -554,5 +515,4 @@ mod tests {
Ok(()) Ok(())
} }
// WIP will add more tests with other sig/proof combinations
} }

Loading…
Cancel
Save