mirror of
https://github.com/arnaucube/plonky2-recursion-experiment.git
synced 2026-01-19 20:31:34 +01:00
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)`
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
#![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>;
|
||||
|
||||
98
src/sig_gadget.rs
Normal file
98
src/sig_gadget.rs
Normal file
@@ -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(())
|
||||
}
|
||||
}
|
||||
@@ -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 std::time::Instant;
|
||||
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 @@ impl RecursiveCircuit {
|
||||
]
|
||||
.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 @@ impl RecursiveCircuit {
|
||||
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 @@ impl RecursiveCircuit {
|
||||
&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 @@ impl RecursiveCircuit {
|
||||
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 @@ pub fn common_data_for_recursion(msg_len: usize) -> CircuitData<F, C, 2> {
|
||||
|
||||
// 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 @@ impl Recursion {
|
||||
|
||||
// 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,16 +374,36 @@ mod tests {
|
||||
/// 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> =
|
||||
(0..k).map(|i| SchnorrSecretKey { sk: i as u64 }).collect();
|
||||
@@ -470,11 +415,9 @@ mod tests {
|
||||
.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 @@ mod tests {
|
||||
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 @@ mod tests {
|
||||
);
|
||||
|
||||
// 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 @@ mod tests {
|
||||
|
||||
// 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 @@ mod tests {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
// WIP will add more tests with other sig/proof combinations
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user