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_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
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 @@
|
|||||||
/*
|
/// N-arity tree of recursion with conditionals.
|
||||||
Tree recursion with conditionals.
|
///
|
||||||
|
/// p_root
|
||||||
|
/// ▲
|
||||||
p_7
|
/// │
|
||||||
▲
|
/// ┌────────┐
|
||||||
│
|
/// │ F │
|
||||||
┌─┴─┐
|
/// └────────┘
|
||||||
│ F │
|
/// ▲ ▲ ▲ ▲
|
||||||
└───┘
|
/// ┌─┘ │ │ └─┐
|
||||||
▲ ▲
|
/// ┌────┘ ┌─┘ └┐ └───┐
|
||||||
┌─┘ └─┐
|
/// │ │ ... │ │
|
||||||
┌───┘ └───┐
|
/// ┌────────┐┌┴┐┌─┐┌┴┐ ┌────────┐
|
||||||
│p_5 │p_6
|
/// │ F ││.││.││.│ │ F │
|
||||||
┌─┴─┐ ┌─┴─┐
|
/// └────────┘└─┘└─┘└─┘ └────────┘
|
||||||
│ F │ │ F │
|
/// ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲
|
||||||
└───┘ └───┘
|
/// ┌─┘ │ └┐ └─┐ ┌─┘┌┘ └┐ └┐
|
||||||
▲ ▲ ▲ ▲
|
/// │ │ │ │ │ │ │ │
|
||||||
┌─┘ └─┐ ┌─┘ └─┐
|
/// p_1 p_2 ... p_n p'_1 p'_2... p'_n
|
||||||
│ │ │ │
|
///
|
||||||
p_1 p_2 p_3 p_4
|
///
|
||||||
|
/// where each p_i is either
|
||||||
where each p_i is either
|
/// - signature verification
|
||||||
- signature verification
|
/// - recursive plonky2 proof (proof that verifies previous proof)
|
||||||
- recursive plonky2 proof (proof that verifies previous proof)
|
/// (generated by `RecursiveCircuit::prove_step` method)
|
||||||
(generated by `RecursiveCircuit::prove_step` method)
|
/// in other words, each p_i is checking:
|
||||||
|
/// `(signature proof OR recursive proof)`
|
||||||
and F verifies the two incoming p_i's, that is
|
///
|
||||||
- (signature proof OR recursive proof) AND (signature proof OR recursive proof)
|
/// Each node of the recursion tree, ie. each F, verifies the N incoming p_i's, that is
|
||||||
and produces a new proof.
|
/// `(signature proof OR recursive proof) AND ... 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
|
/// For example, if N is set to N=2, then we work with a binary recursion tree:
|
||||||
*/
|
/// p_root
|
||||||
|
/// ▲
|
||||||
use anyhow::Result;
|
/// │
|
||||||
|
/// ┌─┴─┐
|
||||||
|
/// │ 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};
|
use super::{sig_gadget::SignatureGadgetTargets, PlonkyProof, 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, 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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).
|
||||||
///
|
pub struct RecursiveCircuit<const N: usize> {
|
||||||
/// 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 {
|
|
||||||
msg_targ: MessageTarget,
|
msg_targ: MessageTarget,
|
||||||
L_sig_targets: SignatureGadgetTargets,
|
sigs_targ: Vec<SignatureGadgetTargets>,
|
||||||
R_sig_targets: SignatureGadgetTargets,
|
proofs_targ: Vec<ProofWithPublicInputsTarget<D>>,
|
||||||
// L_sig_verif_targ: BoolTarget,
|
// the next two are common for all the given proofs. It is the data for this circuit itself
|
||||||
L_proof_targ: ProofWithPublicInputsTarget<2>,
|
// (cyclic circuit).
|
||||||
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).
|
|
||||||
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>,
|
builder: &mut CircuitBuilder<F, D>,
|
||||||
verifier_data: VerifierCircuitData<F, C, 2>,
|
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 mut sigs_targ: Vec<SignatureGadgetTargets> = vec![];
|
||||||
let R_sig_targets = SignatureGadgetTargets::build(builder, &msg_targ)?;
|
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);
|
let mut proofs_targ: Vec<ProofWithPublicInputsTarget<D>> = vec![];
|
||||||
builder.conditionally_verify_cyclic_proof_or_dummy::<C>(
|
for i in 0..N {
|
||||||
L_sig_targets.selector_booltarg,
|
let proof_targ = builder.add_virtual_proof_with_pis(&common_data);
|
||||||
&L_proof_targ,
|
builder.conditionally_verify_cyclic_proof_or_dummy::<C>(
|
||||||
&common_data,
|
sigs_targ[i].selector_booltarg,
|
||||||
)?;
|
&proof_targ,
|
||||||
let R_proof_targ = builder.add_virtual_proof_with_pis(&common_data);
|
&common_data,
|
||||||
builder.conditionally_verify_cyclic_proof_or_dummy::<C>(
|
)?;
|
||||||
R_sig_targets.selector_booltarg,
|
proofs_targ.push(proof_targ);
|
||||||
&R_proof_targ,
|
}
|
||||||
&common_data,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
msg_targ,
|
msg_targ,
|
||||||
L_sig_targets,
|
sigs_targ,
|
||||||
R_sig_targets,
|
proofs_targ,
|
||||||
L_proof_targ,
|
|
||||||
R_proof_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
|
selectors: Vec<F>, // 1=proof, 0=sig
|
||||||
L_selector: F, // 1=proof, 0=sig
|
pks: &Vec<SchnorrPublicKey>,
|
||||||
L_pk: &SchnorrPublicKey,
|
sigs: &Vec<SchnorrSignature>,
|
||||||
L_sig: &SchnorrSignature,
|
recursive_proofs: &Vec<PlonkyProof>,
|
||||||
L_recursive_proof: &PlonkyProof,
|
|
||||||
// right side
|
|
||||||
R_selector: F, // 1=proof, 0=sig
|
|
||||||
R_pk: &SchnorrPublicKey,
|
|
||||||
R_sig: &SchnorrSignature,
|
|
||||||
R_recursive_proof: &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
|
for i in 0..N {
|
||||||
.fill_targets(pw, L_selector, L_pk, L_sig)?;
|
self.sigs_targ[i].fill_targets(pw, selectors[i], &pks[i], &sigs[i])?;
|
||||||
self.R_sig_targets
|
}
|
||||||
.fill_targets(pw, R_selector, R_pk, R_sig)?;
|
|
||||||
|
|
||||||
// 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());
|
RecursiveCircuit::<N>::prepare_public_inputs(self.verifier_data.clone(), msg.clone());
|
||||||
// left proof verification values
|
for i in 0..N {
|
||||||
pw.set_proof_with_pis_target(
|
pw.set_proof_with_pis_target(
|
||||||
&self.L_proof_targ,
|
&self.proofs_targ[i],
|
||||||
&ProofWithPublicInputs {
|
&ProofWithPublicInputs {
|
||||||
proof: L_recursive_proof.clone(),
|
proof: recursive_proofs[i].clone(),
|
||||||
public_inputs: public_inputs.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,
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
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
|
// proofs
|
||||||
let proof = builder.add_virtual_proof_with_pis(&data.common);
|
for _ in 0..N {
|
||||||
builder.verify_proof::<C>(&proof, &verifier_data, &data.common);
|
let proof = builder.add_virtual_proof_with_pis(&data.common);
|
||||||
// right proof
|
builder.verify_proof::<C>(&proof, &verifier_data, &data.common);
|
||||||
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<F, C, 2> {
|
|||||||
|
|
||||||
// proofs verify
|
// proofs verify
|
||||||
let verifier_data = builder.add_verifier_data_public_inputs();
|
let verifier_data = builder.add_verifier_data_public_inputs();
|
||||||
// left proof
|
// proofs
|
||||||
let proof_L = builder.add_virtual_proof_with_pis(&data.common);
|
for _ in 0..N {
|
||||||
builder.verify_proof::<C>(&proof_L, &verifier_data, &data.common);
|
let proof = builder.add_virtual_proof_with_pis(&data.common);
|
||||||
// right proof
|
builder.verify_proof::<C>(&proof, &verifier_data, &data.common);
|
||||||
let proof_R = builder.add_virtual_proof_with_pis(&data.common);
|
}
|
||||||
builder.verify_proof::<C>(&proof_R, &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>())
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
/// returns the full-recursive CircuitData
|
||||||
pub fn circuit_data(msg_len: usize) -> Result<CircuitData<F, C, 2>> {
|
pub fn circuit_data(msg_len: usize) -> Result<CircuitData<F, C, D>> {
|
||||||
let mut data = common_data_for_recursion(msg_len);
|
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
|
selectors: Vec<F>, // 1=proof, 0=sig
|
||||||
L_selector: F, // 1=proof, 0=sig
|
pks: &Vec<SchnorrPublicKey>,
|
||||||
pk_L: &SchnorrPublicKey,
|
sigs: &Vec<SchnorrSignature>,
|
||||||
sig_L: &SchnorrSignature,
|
recursive_proofs: &Vec<PlonkyProof>,
|
||||||
recursive_proof_L: &PlonkyProof,
|
|
||||||
// right side
|
|
||||||
R_selector: F, // 1=proof, 0=sig
|
|
||||||
pk_R: &SchnorrPublicKey,
|
|
||||||
sig_R: &SchnorrSignature,
|
|
||||||
recursive_proof_R: &PlonkyProof,
|
|
||||||
) -> Result<PlonkyProof> {
|
) -> Result<PlonkyProof> {
|
||||||
println!("prove_step:");
|
println!("prove_step:");
|
||||||
if L_selector.is_nonzero() {
|
for i in 0..N {
|
||||||
println!(" (L_selector==1), verify left proof");
|
if selectors[i].is_nonzero() {
|
||||||
} else {
|
println!(" (selectors[{}]==1), verify {}-th proof", i, i);
|
||||||
println!(" (L_selector==0), verify left signature");
|
} else {
|
||||||
}
|
println!(" (selectors[{}]==0), verify {}-th signature", i, i);
|
||||||
if R_selector.is_nonzero() {
|
}
|
||||||
println!(" (R_selector==1), verify right proof");
|
|
||||||
} else {
|
|
||||||
println!(" (R_selector==0), verify right signature");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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(
|
circuit.fill_targets(&mut pw, msg, selectors, pks, sigs, recursive_proofs)?;
|
||||||
&mut pw,
|
|
||||||
msg,
|
|
||||||
L_selector,
|
|
||||||
pk_L,
|
|
||||||
sig_L,
|
|
||||||
recursive_proof_L,
|
|
||||||
R_selector,
|
|
||||||
pk_R,
|
|
||||||
sig_R,
|
|
||||||
recursive_proof_R,
|
|
||||||
)?;
|
|
||||||
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) {
|
for j in (0..proofs_at_level_i.len()).into_iter().step_by(N) {
|
||||||
println!("\n------ recursion node i={}, j={}", i, j);
|
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
|
// In future tests we will try other cases (eg. some sigs and some proofs in a
|
||||||
// the moment we just do base_case: sig verify, other cases: proof verify.
|
// 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:
|
proofs_selectors,
|
||||||
proof_enabled,
|
&pks,
|
||||||
&pk_vec[j],
|
&sigs,
|
||||||
&sig_vec[j],
|
&proofs,
|
||||||
&proofs_at_level_i[j],
|
|
||||||
// right side
|
|
||||||
proof_enabled,
|
|
||||||
&pk_vec[j + 1],
|
|
||||||
&sig_vec[j + 1],
|
|
||||||
&proofs_at_level_i[j + 1],
|
|
||||||
)?;
|
)?;
|
||||||
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 =
|
let public_inputs = RecursiveCircuit::<N>::prepare_public_inputs(
|
||||||
RecursiveCircuit::prepare_public_inputs(verifier_data.clone(), msg.clone());
|
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
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user