refactor aggregation circuits and add error handling

This commit is contained in:
M Alghazwi 2025-01-14 10:54:43 +01:00
parent d63a309e02
commit c8a8ec0f5e
No known key found for this signature in database
GPG Key ID: 646E567CAD7DB607
25 changed files with 1517 additions and 1212 deletions

View File

@ -2,7 +2,6 @@
// consistent with the one in codex:
// https://github.com/codex-storage/codex-storage-proofs-circuits/blob/master/circuit/codex/merkle.circom
// use anyhow::Result;
use plonky2::{
field::{extension::Extendable, types::Field},
hash::hash_types::{HashOutTarget, RichField, NUM_HASH_OUT_ELTS},
@ -15,7 +14,6 @@ use std::marker::PhantomData;
use plonky2::plonk::config::AlgebraicHasher;
use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2;
use crate::circuits::keyed_compress::key_compress_circuit;
// use crate::circuits::params::HF;
use crate::circuits::utils::{add_assign_hash_out_target, mul_hash_out_target};
use crate::Result;
use crate::error::CircuitError;

View File

@ -131,7 +131,7 @@ impl<
/// samples and registers the public input
pub fn sample_slot_circuit_with_public_input(
&self,
builder: &mut CircuitBuilder::<F, D>,
builder: &mut CircuitBuilder<F, D>,
) -> Result<SampleTargets> {
let targets = self.sample_slot_circuit(builder)?;
let mut pub_targets = vec![];
@ -246,7 +246,7 @@ impl<
let ctr_target = builder.constant(F::from_canonical_u64((i+1) as u64));
let mut ctr = builder.add_virtual_hash();
for i in 0..ctr.elements.len() {
if(i==0){
if i==0 {
ctr.elements[i] = ctr_target;
}else{
ctr.elements[i] = zero.clone();
@ -321,7 +321,7 @@ impl<
}
/// calculate the cell index = H( entropy | slotRoot | counter ) `mod` nCells
pub fn calculate_cell_index_bits(&self, builder: &mut CircuitBuilder::<F, D>, entropy: &HashOutTarget, slot_root: &HashOutTarget, ctr: &HashOutTarget, mask_bits: Vec<BoolTarget>) -> Result<Vec<BoolTarget>> {
pub fn calculate_cell_index_bits(&self, builder: &mut CircuitBuilder<F, D>, entropy: &HashOutTarget, slot_root: &HashOutTarget, ctr: &HashOutTarget, mask_bits: Vec<BoolTarget>) -> Result<Vec<BoolTarget>> {
let mut hash_inputs:Vec<Target>= Vec::new();
hash_inputs.extend_from_slice(&entropy.elements);
hash_inputs.extend_from_slice(&slot_root.elements);

View File

@ -16,7 +16,7 @@ pub fn ceiling_log2<
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
>(
builder: &mut CircuitBuilder::<F, D>,
builder: &mut CircuitBuilder<F, D>,
inp: Target,
n: usize,
)-> (Vec<BoolTarget>, Vec<BoolTarget>){
@ -29,7 +29,7 @@ pub fn ceiling_log2<
aux[n] = BoolTarget::new_unsafe(one.clone());
let mut mask: Vec<BoolTarget> = vec![BoolTarget::new_unsafe(zero.clone()); n + 1];
for i in (0..n).rev(){
let diff = (builder.sub(one.clone(), last_bits[i].target));
let diff = builder.sub(one.clone(), last_bits[i].target);
let aux_i = builder.mul( aux[i+1].target, diff);
aux[i] = BoolTarget::new_unsafe(aux_i);
mask[i] = BoolTarget::new_unsafe(builder.sub(one.clone(), aux[i].target));
@ -102,7 +102,7 @@ pub fn add_assign_hash_out_target<
const D: usize,
>(builder: &mut CircuitBuilder<F, D>, mut_hot: &mut HashOutTarget, hot: &HashOutTarget) {
for i in 0..NUM_HASH_OUT_ELTS {
mut_hot.elements[i] = (builder.add(mut_hot.elements[i], hot.elements[i]));
mut_hot.elements[i] = builder.add(mut_hot.elements[i], hot.elements[i]);
}
}
@ -125,4 +125,13 @@ pub fn select_hash<
HashOutTarget {
elements: core::array::from_fn(|i| builder.select(b, h0.elements[i], h1.elements[i])),
}
}
}
/// Converts a Vec<T> into a fixed-size array [T; N], returning an error if the lengths don't match.
pub fn vec_to_array<const N: usize, T>(vec: Vec<T>) -> Result<[T; N]> {
vec.try_into().map_err(|v: Vec<T>| CircuitError::ArrayLengthMismatchError(format!(
"Expected exactly {} elements, got {}",
N,
v.len()
)))
}

View File

@ -15,12 +15,6 @@ pub enum CircuitError {
#[error("Path bits and max depth mismatch: path bits length {0}, max depth {1}")]
PathBitsMaxDepthMismatch(usize, usize),
#[error("Sibling hash at depth {0} has invalid length: expected {1}, found {2}")]
SiblingHashInvalidLength(usize, usize, usize),
#[error("Invalid path bits: expected {0}, found {1}")]
InvalidPathBits(usize, usize),
#[error("Insufficient input elements for chunk; expected {0}, found {1}")]
InsufficientInputs (usize, usize),
@ -44,4 +38,34 @@ pub enum CircuitError {
#[error("Failed to assign HashTarget {0}: {1}")]
HashTargetAssignmentError(String, String),
#[error("Failed to assign ProofTarget {0}: {1}")]
ProofTargetAssignmentError(String, String),
#[error("Failed to assign VerifierDataTarget {0}")]
VerifierDataTargetAssignmentError(String),
#[error("Array Length Mismatch Error {0}")]
ArrayLengthMismatchError(String),
#[error("Proof Verification Failed {0}")]
InvalidProofError(String),
#[error("Proof Generation Failed {0}")]
ProofGenerationError(String),
#[error("Error in Recursion Tree: {0}")]
RecursionTreeError(String),
#[error("Dummy Proof Generation Error: {0}")]
DummyProofGenerationError(String),
#[error("Conditional Verification Error: {0}")]
ConditionalVerificationError(String),
#[error("Recursive Proof VerifierData Check Failed: {0}")]
RecursiveProofVerifierDataCheckError(String),
#[error("Expected Option {0} to contain value")]
OptionError(String),
}

View File

@ -1,6 +1,5 @@
pub mod circuits;
// pub mod merkle_tree;
// pub mod recursion;
pub mod recursion;
pub mod error;
pub type Result<T> = core::result::Result<T, error::CircuitError>;

View File

@ -1,22 +1,27 @@
use plonky2::hash::hash_types::RichField;
use plonky2::iop::target::Target;
use plonky2::iop::witness::PartialWitness;
use plonky2::plonk::circuit_builder::CircuitBuilder;
use plonky2::plonk::circuit_data::CommonCircuitData;
use plonky2_field::extension::Extendable;
use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2;
use crate::Result;
use crate::params::{F, D};
/// InnerCircuit is the trait used to define the logic of the circuit and assign witnesses
/// to that circuit instance.
pub trait InnerCircuit<
// TODO: make it generic for F and D ?
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
> {
type Targets;
type Input:Clone;
/// build the circuit logic and return targets to be assigned later
/// based on register_pi, registers the public input or not.
fn build(
&self,
builder: &mut CircuitBuilder<F, D>,
register_pi: bool
) -> Result<Self::Targets>;
/// assign the actual witness values for the current instance of the circuit.
@ -33,8 +38,7 @@ pub trait InnerCircuit<
targets: &Self::Targets,
) -> Vec<Target>;
/// from the set of the targets, return only the targets which are public
/// TODO: this can probably be replaced with enum for Public/Private targets
/// get the common data for the inner-circuit
fn get_common_data(
&self
) -> Result<(CommonCircuitData<F, D>)>;

View File

@ -1,44 +1,60 @@
use std::marker::PhantomData;
use plonky2::hash::hash_types::RichField;
use plonky2::iop::target::Target;
use plonky2::iop::witness::PartialWitness;
use plonky2::plonk::circuit_builder::CircuitBuilder;
use plonky2::plonk::circuit_data::{CircuitConfig, CommonCircuitData};
use plonky2::plonk::config::{AlgebraicHasher, GenericConfig};
use plonky2_field::extension::Extendable;
use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2;
use crate::circuits::params::CircuitParams;
use crate::circuits::sample_cells::{SampleCircuit, SampleCircuitInput, SampleTargets};
use crate::params::{D, F, C};
use crate::recursion::circuits::inner_circuit::InnerCircuit;
use crate::Result;
/// recursion Inner circuit for the sampling circuit
#[derive(Clone, Debug)]
pub struct SamplingRecursion {
pub sampling_circ: SampleCircuit<F,D>,
pub struct SamplingRecursion<
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
H: AlgebraicHasher<F>,
C: GenericConfig<D, F = F>,
> {
pub sampling_circ: SampleCircuit<F,D,H>,
phantom_data: PhantomData<C>,
}
impl SamplingRecursion {
pub fn new(circ_params: CircuitParams) -> Self{
let sampling_circ = SampleCircuit::new(circ_params);
impl<
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
H: AlgebraicHasher<F>,
C: GenericConfig<D, F = F>,
> SamplingRecursion<F, D, H, C> {
pub fn new(circ_params:CircuitParams) -> Self {
Self{
sampling_circ,
}
}
}
impl Default for SamplingRecursion {
fn default() -> Self {
Self{
sampling_circ: SampleCircuit::new(CircuitParams::default())
sampling_circ: SampleCircuit::new(circ_params),
phantom_data: PhantomData::default(),
}
}
}
impl InnerCircuit for SamplingRecursion{
impl<
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
H: AlgebraicHasher<F>,
C: GenericConfig<D, F = F>,
> InnerCircuit<F, D> for SamplingRecursion<F, D, H, C> {
type Targets = SampleTargets;
type Input = SampleCircuitInput<F, D>;
/// build the circuit
fn build(&self, builder: &mut CircuitBuilder<F, D>) -> Result<Self::Targets> {
self.sampling_circ.sample_slot_circuit(builder)
fn build(&self, builder: &mut CircuitBuilder<F, D>, register_pi: bool) -> Result<Self::Targets> {
if register_pi{
self.sampling_circ.sample_slot_circuit_with_public_input(builder)
}else {
self.sampling_circ.sample_slot_circuit(builder)
}
}
fn assign_targets(&self, pw: &mut PartialWitness<F>, targets: &Self::Targets, input: &Self::Input) -> Result<()> {
@ -58,6 +74,7 @@ impl InnerCircuit for SamplingRecursion{
/// return the common circuit data for the sampling circuit
/// uses the `standard_recursion_config`
/// TODO: make it generic for any config
fn get_common_data(&self) -> Result<(CommonCircuitData<F, D>)> {
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);

View File

@ -3,8 +3,8 @@
// into another cyclic circle.
use hashbrown::HashMap;
use plonky2::hash::hash_types::{HashOut, HashOutTarget, RichField};
use plonky2::iop::target::{BoolTarget, Target};
use plonky2::hash::hash_types::{HashOutTarget, RichField};
use plonky2::iop::target::{BoolTarget};
use plonky2::iop::witness::{PartialWitness, WitnessWrite};
use plonky2::plonk::circuit_builder::CircuitBuilder;
use plonky2::plonk::circuit_data::{CircuitConfig, CircuitData, CommonCircuitData, VerifierCircuitTarget};
@ -12,31 +12,37 @@ use plonky2::plonk::config::{AlgebraicHasher, GenericConfig};
use plonky2::plonk::proof::{ProofWithPublicInputs, ProofWithPublicInputsTarget};
use plonky2::recursion::dummy_circuit::cyclic_base_proof;
use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2;
use crate::params::{F,D,C,Plonky2Proof,H};
use crate::recursion::circuits::inner_circuit::InnerCircuit;
use plonky2::gates::noop::NoopGate;
use plonky2::recursion::cyclic_recursion::check_cyclic_proof_verifier_data;
use plonky2_field::extension::Extendable;
use crate::circuits::utils::select_hash;
use crate::error::CircuitError;
use crate::Result;
/// cyclic circuit struct
/// contains necessary data
/// note: only keeps track of latest proof not all proofs.
pub struct CyclicCircuit<
I: InnerCircuit,
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
I: InnerCircuit<F, D>,
C: GenericConfig<D, F = F>,
>{
pub layer: usize,
pub circ: I,
pub cyclic_target: Option<CyclicCircuitTargets<I>>,
pub cyclic_circuit_data: Option<CircuitData<F, C, D>>,
pub common_data: Option<CommonCircuitData<F, D>>,
pub cyclic_target: CyclicCircuitTargets<F, D, I>,
pub cyclic_circuit_data: CircuitData<F, C, D>,
pub common_data: CommonCircuitData<F, D>,
pub latest_proof: Option<ProofWithPublicInputs<F, C, D>>,
}
/// targets need to be assigned for the cyclic circuit
#[derive(Clone)]
pub struct CyclicCircuitTargets<
I: InnerCircuit,
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
I: InnerCircuit<F, D>,
>{
pub inner_targets: I::Targets,
pub condition: BoolTarget,
@ -45,40 +51,32 @@ pub struct CyclicCircuitTargets<
}
impl<
I: InnerCircuit,
> CyclicCircuit<I> {
/// create a new cyclic circuit
pub fn new(circ: I) -> Self{
Self{
layer: 0,
circ,
cyclic_target: None,
cyclic_circuit_data: None,
common_data: None,
latest_proof: None,
}
}
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
I: InnerCircuit<F, D>,
C: GenericConfig<D, F = F> + 'static,
> CyclicCircuit<F, D, I, C> where
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
{
/// builds the cyclic recursion circuit using any inner circuit I
/// returns the circuit data
pub fn build_circuit(
&mut self,
) -> Result<()>{
// if the circuit data is already build then no need to rebuild
if self.cyclic_circuit_data.is_some(){
return Ok(());
}
/// return the circuit data
pub fn build_circuit<
H: AlgebraicHasher<F>,
>(
// &mut self,
inner_circuit: I
) -> Result<(Self)>{
// builder with standard recursion config
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
//build the inner circuit
let inner_t = self.circ.build(& mut builder)?;
let inner_t = inner_circuit.build(& mut builder, false)?;
// common data for recursion
let mut common_data = common_data_for_cyclic_recursion();
let mut common_data = Self::common_data_for_cyclic_recursion();
// the hash of the public input
let pub_input_hash = builder.add_virtual_hash_public_input();
// verifier data for inner proofs
@ -93,9 +91,9 @@ impl<
let inner_cyclic_proof_with_pis = builder.add_virtual_proof_with_pis(&common_data);
// get the hash of the pub input
let inner_cyclic_pis = &inner_cyclic_proof_with_pis.public_inputs;
let inner_pub_input_hash = HashOutTarget::try_from(&inner_cyclic_pis[0..4]).unwrap();
let inner_pub_input_hash = HashOutTarget::from_vec(inner_cyclic_pis[0..4].to_vec());
// now hash the current public input
let outer_pis = I::get_pub_input_targets(&inner_t)?;
let outer_pis = I::get_pub_input_targets(&inner_t);
let outer_pi_hash = builder.hash_n_to_hash_no_pad::<H>(outer_pis);
let zero_hash = HashOutTarget::from_vec([builder.zero(); 4].to_vec());
// if leaf pad with zeros
@ -108,30 +106,34 @@ impl<
// connect this up one to `pub_input_hash`
builder.connect_hashes(pub_input_hash,outer_pi_hash);
// connect entropy?
// verify proof in-circuit
builder.conditionally_verify_cyclic_proof_or_dummy::<C>(
condition,
&inner_cyclic_proof_with_pis,
&common_data,
)?;
).map_err(|e| CircuitError::ConditionalVerificationError(e.to_string()))?;
// build the cyclic circuit
let cyclic_circuit_data = builder.build::<C>();
// assign targets
let cyc_t = CyclicCircuitTargets::<I>{
let cyc_t = CyclicCircuitTargets::<F,D,I>{
inner_targets: inner_t,
condition,
inner_cyclic_proof_with_pis,
verifier_data: verifier_data_target
};
// assign the data
self.cyclic_circuit_data = Some(cyclic_circuit_data);
self.common_data = Some(common_data);
self.cyclic_target = Some(cyc_t);
Ok(())
Ok(
Self{
layer: 0,
circ: inner_circuit,
cyclic_target: cyc_t,
cyclic_circuit_data,
common_data,
latest_proof: None,
}
)
}
/// generates a proof with only one recursion layer
@ -141,21 +143,20 @@ impl<
circ_input: &I::Input,
) -> Result<ProofWithPublicInputs<F, C, D>>{
if self.cyclic_circuit_data.is_none(){
panic!("circuit data not found") // TODO: replace with err
}
let circ_data = self.cyclic_circuit_data.as_ref().unwrap();
let cyc_targets = self.cyclic_target.as_ref().unwrap();
let common_data = self.common_data.as_ref().unwrap();
let circ_data = &self.cyclic_circuit_data;
let cyc_targets = &self.cyclic_target;
let common_data = &self.common_data;
// assign targets
let mut pw = PartialWitness::new();
self.circ.assign_targets(&mut pw,&cyc_targets.inner_targets,&circ_input)?;
// if leaf add dummy proof
if(self.layer == 0) {
pw.set_bool_target(cyc_targets.condition, false)?;
if self.layer == 0 {
pw.set_bool_target(cyc_targets.condition, false)
.map_err(|e|
CircuitError::BoolTargetAssignmentError("condition".to_string(),e.to_string()),
)?;
pw.set_proof_with_pis_target::<C, D>(
&cyc_targets.inner_cyclic_proof_with_pis,
&cyclic_base_proof(
@ -163,22 +164,40 @@ impl<
&circ_data.verifier_only,
HashMap::new(),
),
).map_err(|e|
CircuitError::ProofTargetAssignmentError("cyclic proof".to_string(),e.to_string()),
)?;
}else{ // else add last proof
pw.set_bool_target(cyc_targets.condition, true)?;
let last_proof = self.latest_proof.as_ref().unwrap();
pw.set_proof_with_pis_target(&cyc_targets.inner_cyclic_proof_with_pis, last_proof)?;
pw.set_bool_target(cyc_targets.condition, true)
.map_err(|e|
CircuitError::BoolTargetAssignmentError("condition".to_string(),e.to_string()),
)?;
let last_proof = self.latest_proof
.as_ref()
.ok_or_else(|| CircuitError::OptionError("cyclic proof".to_string()))?
.clone();
pw.set_proof_with_pis_target(&cyc_targets.inner_cyclic_proof_with_pis, &last_proof)
.map_err(|e|
CircuitError::ProofTargetAssignmentError("cyclic proof".to_string(),e.to_string()),
)?;
}
// assign verifier data
pw.set_verifier_data_target(&cyc_targets.verifier_data, &circ_data.verifier_only)?;
pw.set_verifier_data_target(&cyc_targets.verifier_data, &circ_data.verifier_only)
.map_err(|e| CircuitError::VerifierDataTargetAssignmentError(e.to_string()))?;
// prove
let proof = circ_data.prove(pw)?;
let proof = circ_data.prove(pw).map_err(
|e| CircuitError::InvalidProofError(e.to_string())
)?;
// check that the correct verifier data is consistent
check_cyclic_proof_verifier_data(
&proof,
&circ_data.verifier_only,
&circ_data.common,
).map_err(
|e| CircuitError::RecursiveProofVerifierDataCheckError(e.to_string())
)?;
self.latest_proof = Some(proof.clone());
@ -188,65 +207,76 @@ impl<
/// prove n recursive layers
/// the function takes
/// - n: the number of layers and
/// - circ_input: vector of n inputs
pub fn prove_n_layers(
&mut self,
n: usize,
circ_input: Vec<I::Input>,
) -> Result<ProofWithPublicInputs<F, C, D>>{
// asserts that n equals the number of input
assert_eq!(n, circ_input.len()); // TODO: replace with err
for i in 0..n {
for i in 0..circ_input.len() {
self.prove_one_layer(&circ_input[i])?;
}
Ok(self.latest_proof.clone().unwrap())
let latest_proofs = self.latest_proof.clone().ok_or(CircuitError::OptionError("proof not found".to_string()))?;
Ok(latest_proofs)
}
/// verifies the latest proof generated
pub fn verify_latest_proof(
&mut self,
) -> Result<()>{
if(self.cyclic_circuit_data.is_none() || self.latest_proof.is_none()){
panic!("no circuit data or proof found"); // TODO: replace with err
}
let circ_data = self.cyclic_circuit_data.as_ref().unwrap();
let proof = self.latest_proof.clone().unwrap();
circ_data.verify(proof)?;
let proof = self.latest_proof
.as_ref()
.ok_or_else(|| CircuitError::OptionError("cyclic proof".to_string()))?
.clone();
// check that the correct verifier data is consistent
//TODO: test if it works with only one layer proof
check_cyclic_proof_verifier_data(
&proof,
&self.cyclic_circuit_data.verifier_only,
&self.cyclic_circuit_data.common,
).map_err(
|e| CircuitError::RecursiveProofVerifierDataCheckError(e.to_string())
)?;
self.cyclic_circuit_data.verify(proof).map_err(
|e| CircuitError::InvalidProofError(e.to_string())
)?;
Ok(())
}
/// Generates `CommonCircuitData` usable for recursion.
pub fn common_data_for_cyclic_recursion() -> CommonCircuitData<F, D>
{
// layer 1
let config = CircuitConfig::standard_recursion_config();
let builder = CircuitBuilder::<F, D>::new(config);
let data = builder.build::<C>();
// layer 2
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
let proof = builder.add_virtual_proof_with_pis(&data.common);
let verifier_data =
builder.add_virtual_verifier_data(data.common.config.fri_config.cap_height);
builder.verify_proof::<C>(&proof, &verifier_data, &data.common);
let data = builder.build::<C>();
// layer 3
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
let proof = builder.add_virtual_proof_with_pis(&data.common);
let verifier_data =
builder.add_virtual_verifier_data(data.common.config.fri_config.cap_height);
builder.verify_proof::<C>(&proof, &verifier_data, &data.common);
// pad with noop gates
while builder.num_gates() < 1 << 12 {
builder.add_gate(NoopGate, vec![]);
}
builder.build::<C>().common
}
}
/// Generates `CommonCircuitData` usable for recursion.
pub fn common_data_for_cyclic_recursion() -> CommonCircuitData<F, D>
{
// layer 1
let config = CircuitConfig::standard_recursion_config();
let builder = CircuitBuilder::<F, D>::new(config);
let data = builder.build::<C>();
// layer 2
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
let proof = builder.add_virtual_proof_with_pis(&data.common);
let verifier_data =
builder.add_virtual_verifier_data(data.common.config.fri_config.cap_height);
builder.verify_proof::<C>(&proof, &verifier_data, &data.common);
let data = builder.build::<C>();
// layer 3
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
let proof = builder.add_virtual_proof_with_pis(&data.common);
let verifier_data =
builder.add_virtual_verifier_data(data.common.config.fri_config.cap_height);
builder.verify_proof::<C>(&proof, &verifier_data, &data.common);
// pad with noop gates
while builder.num_gates() < 1 << 12 {
builder.add_gate(NoopGate, vec![]);
}
builder.build::<C>().common
}

View File

@ -3,4 +3,3 @@ pub mod circuits;
pub mod simple;
pub mod tree1;
pub mod tree2;
pub mod params;

View File

@ -1,15 +0,0 @@
use std::marker::PhantomData;
use plonky2::hash::poseidon::PoseidonHash;
use plonky2::plonk::config::PoseidonGoldilocksConfig;
use plonky2::plonk::proof::ProofWithPublicInputs;
use plonky2_field::goldilocks_field::GoldilocksField;
use plonky2_poseidon2::config::Poseidon2GoldilocksConfig;
// recursion param
// TODO: make it more generic or use global params
pub type F = GoldilocksField;
pub const D: usize = 2;
pub type C = PoseidonGoldilocksConfig;
pub type H = PoseidonHash;
pub type Plonky2Proof = ProofWithPublicInputs<F, C, D>;

View File

@ -1,2 +1,3 @@
pub mod simple_recursion;
pub mod simple_recursion_hashed_pi;
pub mod simple_tree_recursion;

View File

@ -1,106 +1,37 @@
// this file is mainly draft implementation and experimentation of multiple simple approaches
// the simple aggregation approach is verifying N proofs in-circuit and generating one final proof
use plonky2::hash::hash_types::{HashOut, HashOutTarget};
use std::marker::PhantomData;
use plonky2::hash::hash_types::{HashOut, HashOutTarget, RichField};
use plonky2::iop::witness::{PartialWitness, WitnessWrite};
use plonky2::plonk::circuit_builder::CircuitBuilder;
use plonky2::plonk::circuit_data::{VerifierCircuitData, VerifierCircuitTarget};
use plonky2::plonk::config::GenericConfig;
use plonky2::plonk::config::{AlgebraicHasher, GenericConfig};
use plonky2::plonk::proof::{ProofWithPublicInputs, ProofWithPublicInputsTarget};
use plonky2_field::extension::Extendable;
use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2;
use crate::error::CircuitError;
use crate::recursion::circuits::inner_circuit::InnerCircuit;
use crate::params::{C, D, F, Plonky2Proof};
use crate::Result;
/// aggregate sampling proofs
/// This function takes:
/// - N number of proofs (it has to be sampling proofs here)
/// - verifier_data of the sampling circuit
/// - circuit builder
/// - partial witness
///
/// The function doesn't return anything but sets the targets in the builder and assigns the witness
pub fn aggregate_sampling_proofs<
>(
proofs_with_pi: &Vec<Plonky2Proof>,
verifier_data: &VerifierCircuitData<F, C, D>,
builder: &mut CircuitBuilder::<F, D>,
pw: &mut PartialWitness<F>,
)-> Result<()>{
// the proof virtual targets
let mut proof_targets = vec![];
let mut inner_entropy_targets = vec![];
let num_pub_input = proofs_with_pi[0].public_inputs.len(); // assuming num of public input is the same for all proofs
for i in 0..proofs_with_pi.len() {
let vir_proof = builder.add_virtual_proof_with_pis(&verifier_data.common);
// register the inner public input as public input
// only register the slot index and dataset root, entropy later
// assuming public input are ordered:
// [slot_root (1 element), dataset_root (4 element), entropy (4 element)]
for j in 0..(num_pub_input-4){
builder.register_public_input(vir_proof.public_inputs[j]);
}
// collect entropy targets
let mut entropy_i = vec![];
for k in (num_pub_input-4)..num_pub_input{
entropy_i.push(vir_proof.public_inputs[k])
}
inner_entropy_targets.push(entropy_i);
proof_targets.push(vir_proof);
}
// assign the proofs with public input
for i in 0..proofs_with_pi.len(){
pw.set_proof_with_pis_target(&proof_targets[i],&proofs_with_pi[i])?;
}
// virtual target for the verifier data
let inner_verifier_data = builder.add_virtual_verifier_data(verifier_data.common.config.fri_config.cap_height);
// assign the verifier data
pw.set_cap_target(
&inner_verifier_data.constants_sigmas_cap,
&verifier_data.verifier_only.constants_sigmas_cap,
)?;
pw.set_hash_target(inner_verifier_data.circuit_digest, verifier_data.verifier_only.circuit_digest)?;
// verify the proofs in-circuit
for i in 0..proofs_with_pi.len() {
builder.verify_proof::<C>(&proof_targets[i],&inner_verifier_data,&verifier_data.common);
}
// register entropy as public input
let outer_entropy_target = builder.add_virtual_hash_public_input();
let entropy_as_hash = HashOut::from_vec(
[
proofs_with_pi[0].public_inputs[num_pub_input-4],
proofs_with_pi[0].public_inputs[num_pub_input-3],
proofs_with_pi[0].public_inputs[num_pub_input-2],
proofs_with_pi[0].public_inputs[num_pub_input-1]
].to_vec()
); // entropy is last 4 elements
pw.set_hash_target(outer_entropy_target, entropy_as_hash)?;
// connect the public input of the recursion circuit to the inner proofs
for i in 0..proofs_with_pi.len() {
for j in 0..4 {
builder.connect(inner_entropy_targets[i][j], outer_entropy_target.elements[j]);
}
}
Ok(())
}
// ---------------------- Simple Approach 2 ---------------------------
// this is still simple recursion approach but written differently,
// ---------------------- Simple recursion Approach 1 ---------------------------
// The simple approach here separates the build (setting the targets) and assigning the witness.
// the public input of the inner-proofs is the public input of the final proof except that
// the entropy is expected to be the same therefore only one entropy public input is in the final proof
pub struct SimpleRecursionCircuit<
I: InnerCircuit,
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
I: InnerCircuit<F, D>,
const N: usize,
>{
C: GenericConfig<D, F = F>,
> {
pub inner_circuit: I,
phantom_data: PhantomData<(F,C)>
}
#[derive(Clone)]
pub struct SimpleRecursionTargets<
const D: usize,
> {
pub proofs_with_pi: Vec<ProofWithPublicInputsTarget<D>>,
pub verifier_data: VerifierCircuitTarget,
@ -108,6 +39,9 @@ pub struct SimpleRecursionTargets<
}
pub struct SimpleRecursionInput<
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
C: GenericConfig<D, F = F>,
>{
pub proofs: Vec<ProofWithPublicInputs<F, C, D>>,
pub verifier_data: VerifierCircuitData<F, C, D>,
@ -115,9 +49,13 @@ pub struct SimpleRecursionInput<
}
impl<
I: InnerCircuit,
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
I: InnerCircuit<F, D>,
const N: usize,
> SimpleRecursionCircuit<I, N>
C: GenericConfig<D, F = F>,
> SimpleRecursionCircuit<F, D, I, N, C> where
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>,
{
pub fn new(
@ -125,20 +63,22 @@ impl<
)->Self{
Self{
inner_circuit,
phantom_data: PhantomData::default(),
}
}
/// contains the circuit logic and returns the witness & public input targets
pub fn build_circuit(
pub fn build_circuit<
>(
&self,
builder: &mut CircuitBuilder::<F, D>,
) -> anyhow::Result<SimpleRecursionTargets> {
builder: &mut CircuitBuilder<F, D>,
) -> Result<SimpleRecursionTargets<D>>{
// the proof virtual targets
let mut proof_targets = vec![];
let mut inner_entropy_targets = vec![];
let inner_common = self.inner_circuit.get_common_data()?;
for i in 0..N {
for _ in 0..N {
let vir_proof = builder.add_virtual_proof_with_pis(&inner_common);
// register the inner public input as public input
// only register the slot index and dataset root, entropy later
@ -183,26 +123,32 @@ impl<
}
/// assign the targets
pub fn assign_witness(
pub fn assign_witness<
>(
&self,
pw: &mut PartialWitness<F>,
targets: &SimpleRecursionTargets,
witnesses: SimpleRecursionInput,
) -> anyhow::Result<()>{
targets: &SimpleRecursionTargets<D>,
witnesses: SimpleRecursionInput<F, D, C>,
) -> Result<()>{
// assign the proofs with public input
for i in 0..N{
pw.set_proof_with_pis_target(&targets.proofs_with_pi[i],&witnesses.proofs[i])?;
pw.set_proof_with_pis_target(&targets.proofs_with_pi[i],&witnesses.proofs[i])
.map_err(|e| {
CircuitError::ProofTargetAssignmentError(format!("proof {}", i), e.to_string())
})?;
}
// assign the verifier data
pw.set_cap_target(
&targets.verifier_data.constants_sigmas_cap,
&witnesses.verifier_data.verifier_only.constants_sigmas_cap,
)?;
pw.set_hash_target(targets.verifier_data.circuit_digest, witnesses.verifier_data.verifier_only.circuit_digest)?;
pw.set_verifier_data_target(&targets.verifier_data, &witnesses.verifier_data.verifier_only)
.map_err(|e| {
CircuitError::VerifierDataTargetAssignmentError(e.to_string())
})?;
// set the entropy hash target
pw.set_hash_target(targets.entropy, witnesses.entropy)?;
pw.set_hash_target(targets.entropy, witnesses.entropy)
.map_err(|e| {
CircuitError::HashTargetAssignmentError("entropy".to_string(), e.to_string())
})?;
Ok(())

View File

@ -0,0 +1,132 @@
// the simple aggregation approach is verifying N proofs in-circuit and generating one final proof
use std::marker::PhantomData;
use plonky2::hash::hash_types::RichField;
use plonky2::iop::witness::{PartialWitness, WitnessWrite};
use plonky2::plonk::circuit_builder::CircuitBuilder;
use plonky2::plonk::circuit_data::{VerifierCircuitData, VerifierCircuitTarget};
use plonky2::plonk::config::{AlgebraicHasher, GenericConfig};
use plonky2::plonk::proof::{ProofWithPublicInputs, ProofWithPublicInputsTarget};
use plonky2_field::extension::Extendable;
use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2;
use crate::error::CircuitError;
use crate::recursion::circuits::inner_circuit::InnerCircuit;
use crate::Result;
// ---------------------- Simple recursion Approach 2 ---------------------------
// The simple approach here separates the build (setting the targets) and assigning the witness.
// ** the Hash of public input of the inner-proofs is the public input of the final proof **
pub struct SimpleRecursionCircuitHashedPI<
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
I: InnerCircuit<F, D>,
const N: usize,
C: GenericConfig<D, F = F>,
> {
pub inner_circuit: I,
phantom_data: PhantomData<(F,C)>
}
#[derive(Clone)]
pub struct SimpleRecursionTargetsHashedPI<
const D: usize,
> {
pub proofs_with_pi: Vec<ProofWithPublicInputsTarget<D>>,
pub verifier_data: VerifierCircuitTarget,
}
pub struct SimpleRecursionInputHashedPI<
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
C: GenericConfig<D, F = F>,
>{
pub proofs: Vec<ProofWithPublicInputs<F, C, D>>,
pub verifier_data: VerifierCircuitData<F, C, D>,
}
impl<
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
I: InnerCircuit<F, D>,
const N: usize,
C: GenericConfig<D, F = F>,
> SimpleRecursionCircuitHashedPI<F, D, I, N, C> where
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>,
{
pub fn new(
inner_circuit: I,
)->Self{
Self{
inner_circuit,
phantom_data: PhantomData::default(),
}
}
/// contains the circuit logic and returns the witness & public input targets
pub fn build_circuit<
H: AlgebraicHasher<F>,
>(
&self,
builder: &mut CircuitBuilder<F, D>,
) -> Result<SimpleRecursionTargetsHashedPI<D>>{
// the proof virtual targets
let mut proof_targets = vec![];
let mut inner_pub_input = vec![];
let inner_common = self.inner_circuit.get_common_data()?;
for _i in 0..N {
let vir_proof = builder.add_virtual_proof_with_pis(&inner_common);
// collect the public input
inner_pub_input.extend_from_slice(&vir_proof.public_inputs);
// collect the proof targets
proof_targets.push(vir_proof);
}
// hash the public input & make it public
let hash_inner_pub_input = builder.hash_n_to_hash_no_pad::<H>(inner_pub_input);
builder.register_public_inputs(&hash_inner_pub_input.elements);
// virtual target for the verifier data
let inner_verifier_data = builder.add_virtual_verifier_data(inner_common.config.fri_config.cap_height);
// verify the proofs in-circuit
for i in 0..N {
builder.verify_proof::<C>(&proof_targets[i],&inner_verifier_data,&inner_common);
}
// return targets
let srt = SimpleRecursionTargetsHashedPI {
proofs_with_pi: proof_targets,
verifier_data: inner_verifier_data,
};
Ok(srt)
}
/// assign the targets
pub fn assign_witness<
>(
&self,
pw: &mut PartialWitness<F>,
targets: &SimpleRecursionTargetsHashedPI<D>,
witnesses: SimpleRecursionInputHashedPI<F, D, C>,
) -> Result<()>{
// assign the proofs with public input
for i in 0..N{
pw.set_proof_with_pis_target(&targets.proofs_with_pi[i],&witnesses.proofs[i])
.map_err(|e| {
CircuitError::ProofTargetAssignmentError(format!("proof {}", i), e.to_string())
})?;
}
// assign the verifier data
pw.set_verifier_data_target(&targets.verifier_data, &witnesses.verifier_data.verifier_only)
.map_err(|e| {
CircuitError::VerifierDataTargetAssignmentError(e.to_string())
})?;
Ok(())
}
}

View File

@ -1,64 +1,91 @@
use plonky2::hash::hash_types::RichField;
use plonky2::plonk::proof::ProofWithPublicInputs;
use plonky2::plonk::circuit_data::{CircuitConfig, CircuitData, VerifierCircuitData};
use plonky2::plonk::circuit_data::{CircuitConfig, VerifierCircuitData};
use plonky2::plonk::circuit_builder::CircuitBuilder;
use plonky2::iop::witness::PartialWitness;
use crate::params::{C, D, F};
use crate::recursion::simple::simple_recursion;
use plonky2::iop::witness::{PartialWitness, WitnessWrite};
use plonky2::plonk::config::{AlgebraicHasher, GenericConfig};
use plonky2_field::extension::Extendable;
use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2;
use crate::error::CircuitError;
use crate::Result;
// recursion tree width or the number of proofs in each node in the tree
const RECURSION_TREE_WIDTH: usize = 2;
/// aggregate sampling proofs
/// This function takes:
/// - N number of proofs (it has to be sampling proofs here)
/// - verifier_data of the sampling circuit
/// - circuit builder
/// - partial witness
///
/// The function doesn't return anything but sets the targets in the builder and assigns the witness
pub fn aggregate_sampling_proofs<
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
C: GenericConfig<D, F = F>,
H: AlgebraicHasher<F>
>(
proofs_with_pi: &Vec<ProofWithPublicInputs<F, C, D>>,
verifier_data: &VerifierCircuitData<F, C, D>,
builder: &mut CircuitBuilder<F, D>,
pw: &mut PartialWitness<F>,
)-> Result<()>where
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
{
// the proof virtual targets
let mut proof_targets = vec![];
let mut inner_pub_input = vec![];
for _i in 0..proofs_with_pi.len() {
let vir_proof = builder.add_virtual_proof_with_pis(&verifier_data.common);
// collect the public input
inner_pub_input.extend_from_slice(&vir_proof.public_inputs);
// collect the proof targets
proof_targets.push(vir_proof);
}
// hash the public input & make it public
let hash_inner_pub_input = builder.hash_n_to_hash_no_pad::<H>(inner_pub_input);
builder.register_public_inputs(&hash_inner_pub_input.elements);
// assign the proofs with public input
for i in 0..proofs_with_pi.len(){
pw.set_proof_with_pis_target(&proof_targets[i],&proofs_with_pi[i])
.map_err(|e| {
CircuitError::ProofTargetAssignmentError(format!("proof {}", i), e.to_string())
})?;
}
// virtual target for the verifier data
let inner_verifier_data = builder.add_virtual_verifier_data(verifier_data.common.config.fri_config.cap_height);
// assign the verifier data
pw.set_verifier_data_target(&inner_verifier_data, &verifier_data.verifier_only)
.map_err(|e| {
CircuitError::VerifierDataTargetAssignmentError(e.to_string())
})?;
// verify the proofs in-circuit
for i in 0..proofs_with_pi.len() {
builder.verify_proof::<C>(&proof_targets[i],&inner_verifier_data,&verifier_data.common);
}
Ok(())
}
/// aggregate sampling proofs in tree like structure
/// uses the const params: `RECURSION_TREE_WIDTH`
/// In this tree approach the building is done at each level -> very slow!
pub fn aggregate_sampling_proofs_tree(
proofs_with_pi: &[ProofWithPublicInputs<F, C, D>],
data: CircuitData<F, C, D>,
) -> anyhow::Result<(ProofWithPublicInputs<F, C, D>, CircuitData<F, C, D>)> {
// base case: if only one proof remains, return it
if proofs_with_pi.len() == 1 {
return Ok((proofs_with_pi[0].clone(), data));
}
let mut new_proofs = vec![];
let mut new_circuit_data: Option<CircuitData<F, C, D>> = None;
// group proofs according to the tree's width
for chunk in proofs_with_pi.chunks(RECURSION_TREE_WIDTH) {
let proofs_chunk = chunk.to_vec();
// Build an inner-circuit to verify and aggregate the proofs in the chunk
let inner_config = CircuitConfig::standard_recursion_config();
let mut inner_builder = CircuitBuilder::<F, D>::new(inner_config);
let mut inner_pw = PartialWitness::new();
// aggregate proofs
simple_recursion::aggregate_sampling_proofs(
&proofs_chunk,
&data.verifier_data(),
&mut inner_builder,
&mut inner_pw,
)?;
// Build the inner-circuit
// this causes major delay - we can load it but better if we split build and prove
let inner_data = inner_builder.build::<C>();
// Prove the inner-circuit
let proof = inner_data.prove(inner_pw)?;
new_proofs.push(proof);
new_circuit_data = Some(inner_data);
}
// Recursively aggregate the new proofs
aggregate_sampling_proofs_tree(&new_proofs, new_circuit_data.unwrap())
}
/// same as above but takes `VerifierCircuitData`
pub fn aggregate_sampling_proofs_tree2(
/// takes `VerifierCircuitData`
pub fn aggregate_sampling_proofs_tree
<
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
C: GenericConfig<D, F = F>,
H: AlgebraicHasher<F>
>(
proofs_with_pi: &[ProofWithPublicInputs<F, C, D>],
vd: VerifierCircuitData<F, C, D>
) -> anyhow::Result<(ProofWithPublicInputs<F, C, D>, VerifierCircuitData<F, C, D>)> {
) -> Result<(ProofWithPublicInputs<F, C, D>, VerifierCircuitData<F, C, D>)> where
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
{
if proofs_with_pi.len() == 1 {
return Ok((proofs_with_pi[0].clone(), vd));
}
@ -73,7 +100,7 @@ pub fn aggregate_sampling_proofs_tree2(
let mut inner_builder = CircuitBuilder::<F, D>::new(inner_config);
let mut inner_pw = PartialWitness::new();
simple_recursion::aggregate_sampling_proofs(
aggregate_sampling_proofs::<F,D,C,H>(
&proofs_chunk,
&vd,
&mut inner_builder,
@ -82,10 +109,11 @@ pub fn aggregate_sampling_proofs_tree2(
let inner_data = inner_builder.build::<C>();
let proof = inner_data.prove(inner_pw)?;
let proof = inner_data.prove(inner_pw)
.map_err(|e| CircuitError::ProofGenerationError(e.to_string()))?;
new_proofs.push(proof);
new_circuit_data = Some(inner_data.verifier_data());
}
aggregate_sampling_proofs_tree2(&new_proofs, new_circuit_data.unwrap())
aggregate_sampling_proofs_tree::<F,D,C,H>(&new_proofs, new_circuit_data.unwrap())
}

View File

@ -1 +1,2 @@
pub mod tree_recursion;
pub mod tree_circuit;
pub mod node_circuit;

View File

@ -0,0 +1,259 @@
use plonky2::hash::hash_types::{HashOutTarget, RichField};
use plonky2_field::extension::Extendable;
use plonky2::plonk::config::{AlgebraicHasher, GenericConfig};
use plonky2::plonk::circuit_data::{CircuitConfig, CircuitData, CommonCircuitData, VerifierCircuitTarget};
use plonky2::plonk::circuit_builder::CircuitBuilder;
use plonky2::plonk::proof::{ProofWithPublicInputs, ProofWithPublicInputsTarget};
use plonky2::iop::witness::{PartialWitness, WitnessWrite};
use plonky2::recursion::dummy_circuit::cyclic_base_proof;
use hashbrown::HashMap;
use plonky2::gates::noop::NoopGate;
use plonky2::iop::target::BoolTarget;
use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2;
use crate::circuits::utils::{select_hash, vec_to_array};
use crate::{error::CircuitError, Result};
use crate::recursion::circuits::inner_circuit::InnerCircuit;
/// Node circuit struct
/// contains necessary data
/// M: number of inner-circuits to run
/// N: number of proofs verified in-circuit (so num of child nodes)
pub struct NodeCircuit<
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
I: InnerCircuit<F, D>,
const M: usize,
const N: usize,
C: GenericConfig<D, F = F>,
>{
pub circ: I,
pub cyclic_target: NodeCircuitTargets<F, D, I,M,N>,
pub cyclic_circuit_data: CircuitData<F, C, D>,
pub common_data: CommonCircuitData<F, D>,
}
/// Node circuit targets
/// assumes that all inner proofs use the same verifier data
#[derive(Clone, Debug)]
pub struct NodeCircuitTargets<
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
I: InnerCircuit<F, D>,
const M: usize,
const N: usize,
>{
pub inner_targets: [I::Targets; M],
pub condition: BoolTarget,
pub inner_proofs_with_pis: [ProofWithPublicInputsTarget<D>; N],
pub verifier_data: VerifierCircuitTarget,
}
impl<
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
I: InnerCircuit<F, D>,
const M: usize,
const N: usize,
C: GenericConfig<D, F = F> + 'static,
> NodeCircuit<F, D, I, M, N, C> where
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
{
/// builds the cyclic recursion circuit using any inner circuit I
/// return the Node circuit
/// TODO: make generic recursion config
pub fn build_circuit<
H: AlgebraicHasher<F>,
>(
inner_circ: I,
) -> Result<(Self)>{
// builder with standard recursion config
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
//build M inner circuits
let inner_t: [I::Targets; M] =
vec_to_array::<M, I::Targets>(
(0..M)
.map(|_| inner_circ.build(&mut builder, false))
.collect::<Result<Vec<_>>>()?
)?;
// common data for recursion
let mut common_data = Self::common_data_for_node()?;
let pub_input_hash = builder.add_virtual_hash_public_input();
let verifier_data_target = builder.add_verifier_data_public_inputs();
common_data.num_public_inputs = builder.num_public_inputs();
// condition
let condition = builder.add_virtual_bool_target_safe();
// inner proofs targets - N proof targets
let inner_cyclic_proof_with_pis: [ProofWithPublicInputsTarget<D>; N] =
vec_to_array::<N,ProofWithPublicInputsTarget<D>>(
(0..N)
.map(|_| builder.add_virtual_proof_with_pis(&common_data))
.collect::<Vec<_>>()
)?;
// get the public input hash from all inner proof targets
let mut inner_pub_input_hashes = vec![];
for i in 0..N {
let inner_cyclic_pis = &inner_cyclic_proof_with_pis[i].public_inputs;
inner_pub_input_hashes.extend_from_slice(&inner_cyclic_pis[0..4]);
}
// hash all the inner public input h = H(h_1 | h_2 | ... | h_N)
let inner_pub_input_hash = builder.hash_n_to_hash_no_pad::<H>(inner_pub_input_hashes);
// get the public input of the inner circuit
let mut outer_pis = vec![];
for i in 0..M {
outer_pis.push( I::get_pub_input_targets(&inner_t[i]));
}
// hash all the public input -> generate one HashOut at the end
// this is not an optimal way to do it, verification might be ugly if M > 1
// TODO: optimize this
let mut outer_pi_hashes = vec![];
for i in 0..M {
let hash_res = builder.hash_n_to_hash_no_pad::<H>(outer_pis[i].clone());
outer_pi_hashes.extend_from_slice(&hash_res.elements)
}
// the final public input hash
let outer_pi_hash = builder.hash_n_to_hash_no_pad::<H>(outer_pi_hashes);
// zero hash for leaves
let zero_hash = HashOutTarget::from_vec([builder.zero(); 4].to_vec());
// if the inner proofs are dummy then use zero hash for public input
let inner_pi_hash_or_zero_hash = select_hash(&mut builder, condition, inner_pub_input_hash, zero_hash);
// now hash the public input of the inner proofs and outer proof, so we have one public hash
let mut hash_input = vec![];
hash_input.extend_from_slice(&outer_pi_hash.elements);
hash_input.extend_from_slice(&inner_pi_hash_or_zero_hash.elements);
let outer_pi_hash = builder.hash_n_to_hash_no_pad::<H>(hash_input);
// connect this up one to `pub_input_hash`
builder.connect_hashes(pub_input_hash,outer_pi_hash);
// verify all N proofs in-circuit
for i in 0..N {
builder.conditionally_verify_cyclic_proof_or_dummy::<C>(
condition,
&inner_cyclic_proof_with_pis[i],
&common_data,
).map_err(|e| CircuitError::ConditionalVerificationError(e.to_string()))?;
}
// build the cyclic circuit
let cyclic_circuit_data = builder.build::<C>();
// assign targets
let cyc_t = NodeCircuitTargets::<F, D, I, M, N>{
inner_targets: inner_t,
condition,
inner_proofs_with_pis: inner_cyclic_proof_with_pis,
verifier_data: verifier_data_target
};
// assign the data
Ok(Self{
circ: inner_circ,
cyclic_target: cyc_t,
cyclic_circuit_data,
common_data,
})
}
/// assigns the targets for the Node circuit
/// takes circuit input
pub fn assign_targets(
&mut self,
circ_input: &[I::Input; M],
proof_options: Option<[ProofWithPublicInputs<F, C, D>; N]>,
pw: &mut PartialWitness<F>,
is_leaf: bool,
) -> Result<()>{
let circ_data = &self.cyclic_circuit_data;
let cyc_targets = &self.cyclic_target;
let common_data = &self.common_data;
for i in 0..M {
self.circ.assign_targets(pw, &cyc_targets.inner_targets[i], &circ_input[i])?;
}
if is_leaf == true {
pw.set_bool_target(cyc_targets.condition, false)
.map_err(|e| CircuitError::BoolTargetAssignmentError("condition".to_string(),e.to_string()))?;
for i in 0..N {
pw.set_proof_with_pis_target::<C, D>(
&cyc_targets.inner_proofs_with_pis[i],
&cyclic_base_proof(
common_data,
&circ_data.verifier_only,
HashMap::new(),
),
).map_err(|e| CircuitError::ProofTargetAssignmentError("inner proofs".to_string(),e.to_string()))?;
}
}else{
pw.set_bool_target(cyc_targets.condition, true)
.map_err(|e| CircuitError::BoolTargetAssignmentError("condition".to_string(),e.to_string()))?;
let proofs = proof_options.ok_or(CircuitError::OptionError("inner proof not given".to_string()))?;
for i in 0..N {
pw.set_proof_with_pis_target(&cyc_targets.inner_proofs_with_pis[i], &proofs[i])
.map_err(|e| CircuitError::ProofTargetAssignmentError("inner proofs".to_string(),e.to_string()))?;
}
}
pw.set_verifier_data_target(&cyc_targets.verifier_data, &circ_data.verifier_only)
.map_err(|e| CircuitError::VerifierDataTargetAssignmentError(e.to_string()))?;
Ok(())
}
/// Generates `CommonCircuitData` usable for node recursion.
/// the circuit being built here depends on M and N so must be re-generated
/// if the params change
pub fn common_data_for_node() -> Result<CommonCircuitData<F, D>>
{
// layer 1
let config = CircuitConfig::standard_recursion_config();
let builder = CircuitBuilder::<F, D>::new(config);
let data = builder.build::<C>();
// layer 2
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config.clone());
let verifier_data = builder.add_virtual_verifier_data(data.common.config.fri_config.cap_height);
// generate and verify N number of 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 data = builder.build::<C>();
// layer 3
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config.clone());
// add a ConstantGate
builder.add_gate(
plonky2::gates::constant::ConstantGate::new(config.num_constants),
vec![],
);
// generate and verify N number of proofs
let verifier_data = builder.add_verifier_data_public_inputs();
for _ in 0..N {
let proof = builder.add_virtual_proof_with_pis(&data.common);
builder.verify_proof::<C>(&proof, &verifier_data, &data.common);
}
// pad. TODO: optimize this padding to only needed number of gates
while builder.num_gates() < 1 << 13 {
builder.add_gate(NoopGate, vec![]);
}
Ok(builder.build::<C>().common)
}
}

View File

@ -0,0 +1,163 @@
use std::array::from_fn;
use plonky2::hash::hash_types::RichField;
use plonky2::iop::witness::{PartialWitness, WitnessWrite};
use plonky2::plonk::config::{AlgebraicHasher, GenericConfig};
use plonky2::plonk::proof::ProofWithPublicInputs;
use plonky2_field::extension::Extendable;
use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2;
use crate::recursion::circuits::inner_circuit::InnerCircuit;
use plonky2::recursion::cyclic_recursion::check_cyclic_proof_verifier_data;
use crate::{error::CircuitError, Result};
use crate::recursion::tree1::node_circuit::NodeCircuit;
/// the tree recursion struct simplifies the process
/// of building, proving and verifying
/// the two consts are:
/// - M: number of inner circuits to run
/// - N: number of inner proofs to verify
pub struct TreeRecursion<
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
I: InnerCircuit<F, D>,
const M: usize,
const N: usize,
C: GenericConfig<D, F = F>,
>{
pub node_circ: NodeCircuit<F,D, I, M, N, C>
}
impl<
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
I: InnerCircuit<F, D>,
const M: usize,
const N: usize,
C: GenericConfig<D, F = F> + 'static,
> TreeRecursion<F, D, I, M, N, C> where
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
{
pub fn build<
H: AlgebraicHasher<F>,
>(
inner_circuit: I,
) -> Result<(Self)>{
Ok(Self {
node_circ: NodeCircuit:: < F,
D,
I,
M,
N,
C>::build_circuit:: < H>(inner_circuit)?
})
}
/// generates a proof - only one node
/// takes M circuit input and N proofs
pub fn prove(
&mut self,
circ_input: &[I::Input; M],
proofs_option: Option<[ProofWithPublicInputs<F, C, D>; N]>,
is_leaf: bool,
) -> Result<ProofWithPublicInputs<F, C, D>>{
let mut pw = PartialWitness::new();
self.node_circ.assign_targets(
circ_input,
proofs_option,
&mut pw,
is_leaf,
)?;
let circ_data = &self.node_circ.cyclic_circuit_data;
let cyc_targets = &self.node_circ.cyclic_target;
pw.set_verifier_data_target(&cyc_targets.verifier_data, &circ_data.verifier_only)
.map_err(|e| CircuitError::VerifierDataTargetAssignmentError(e.to_string()))?;
let proof = circ_data.prove(pw)
.map_err(|e| CircuitError::InvalidProofError(e.to_string()))?;
Ok(proof)
}
/// prove n in a tree structure recursively
/// the function takes
/// - circ_input: vector of circuit inputs
pub fn prove_tree(
&mut self,
circ_input: Vec<I::Input>,
depth: usize,
) -> Result<ProofWithPublicInputs<F, C, D>>{
// Total input size check
let total_input = (N.pow(depth as u32) - 1) / (N - 1);
if circ_input.len() != total_input{
return Err(CircuitError::RecursionTreeError(
"Invalid input size for tree depth".to_string()
));
}
let mut cur_proofs: Vec<ProofWithPublicInputs<F, C, D>> = vec![];
// Iterate from leaf layer to root
for layer in (0..depth).rev() {
let layer_num_nodes = N.pow(layer as u32); // Number of nodes at this layer
let mut next_proofs = Vec::new();
for node_idx in 0..layer_num_nodes {
// Get the inputs for the current node
let node_inputs: [I::Input; M] = from_fn(|i| {
circ_input
.get(node_idx * M + i)
.cloned()
.unwrap_or_else(|| panic!("Index out of bounds at node {node_idx}, input {i}"))
});
let proof = if layer == depth - 1 {
// Leaf layer: no child proofs
self.prove(&node_inputs, None, true)?
} else {
// Non-leaf layer: collect child proofs
let proofs_array: [ProofWithPublicInputs<F, C, D>; N] = cur_proofs
.drain(..N)
.collect::<Vec<_>>()
.try_into()
.map_err(|_| CircuitError::ArrayLengthMismatchError("Incorrect number of proofs for node".to_string()))?;
self.prove(&node_inputs, Some(proofs_array), false)?
};
next_proofs.push(proof);
}
cur_proofs = next_proofs;
}
// Check that exactly one proof remains
if cur_proofs.len() != 1 {
return Err(CircuitError::RecursionTreeError(
format!("Expected exactly 1 final proof, found {}",
cur_proofs.len())
));
}
Ok(cur_proofs.remove(0))
}
/// verifies the proof generated
pub fn verify_proof(
&self,
proof: ProofWithPublicInputs<F, C, D>
) -> Result<()>{
let circ_data = &self.node_circ.cyclic_circuit_data;
check_cyclic_proof_verifier_data(
&proof,
&circ_data.verifier_only,
&circ_data.common,
).map_err(|e| CircuitError::RecursiveProofVerifierDataCheckError(e.to_string()))?;
circ_data.verify(proof).map_err(|e|CircuitError::InvalidProofError(e.to_string()))?;
Ok(())
}
}

View File

@ -1,408 +0,0 @@
use std::array::from_fn;
use hashbrown::HashMap;
use plonky2::hash::hash_types::{HashOut, HashOutTarget, RichField};
use plonky2::iop::target::{BoolTarget, Target};
use plonky2::iop::witness::{PartialWitness, WitnessWrite};
use plonky2::plonk::circuit_builder::CircuitBuilder;
use plonky2::plonk::circuit_data::{CircuitConfig, CircuitData, CommonCircuitData, VerifierCircuitData, VerifierCircuitTarget};
use plonky2::plonk::config::{AlgebraicHasher, GenericConfig};
use plonky2::plonk::proof::{ProofWithPublicInputs, ProofWithPublicInputsTarget};
use plonky2::recursion::dummy_circuit::cyclic_base_proof;
use plonky2_field::extension::Extendable;
use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2;
// use crate::recursion::params::RecursionTreeParams;
use crate::params::{F, D, C, Plonky2Proof, H};
use crate::recursion::circuits::inner_circuit::InnerCircuit;
use anyhow::{anyhow, Result};
use plonky2::gates::noop::NoopGate;
use plonky2::recursion::cyclic_recursion::check_cyclic_proof_verifier_data;
use crate::circuits::utils::select_hash;
/// the tree recursion struct simplifies the process
/// of building, proving and verifying
/// the two consts are:
/// - M: number of inner circuits to run
/// - N: number of inner proofs to verify
pub struct TreeRecursion<
I: InnerCircuit,
const M: usize,
const N: usize,
>{
pub node_circ: NodeCircuit<I, M, N>
}
impl<
I: InnerCircuit,
const M: usize,
const N: usize,
> TreeRecursion<I, M, N> {
pub fn new(node_circ: NodeCircuit<I,M,N>) -> Self{
Self{
node_circ,
}
}
pub fn build(
&mut self
) -> Result<()>{
self.node_circ.build_circuit()
}
/// generates a proof - only one node
/// takes M circuit input and N proofs
pub fn prove(
&mut self,
circ_input: &[I::Input; M],
proofs_option: Option<[ProofWithPublicInputs<F, C, D>; N]>,
is_leaf: bool,
) -> Result<ProofWithPublicInputs<F, C, D>>{
if self.node_circ.cyclic_circuit_data.is_none(){
panic!("circuit data not found") // TODO: replace with err
}
let mut pw = PartialWitness::new();
self.node_circ.assign_targets(
circ_input,
proofs_option,
&mut pw,
is_leaf,
)?;
let circ_data = self.node_circ.cyclic_circuit_data.as_ref().unwrap();
let cyc_targets = self.node_circ.cyclic_target.as_ref().unwrap();
pw.set_verifier_data_target(&cyc_targets.verifier_data, &circ_data.verifier_only)?;
let proof = circ_data.prove(pw)?;
check_cyclic_proof_verifier_data(
&proof,
&circ_data.verifier_only,
&circ_data.common,
)?;
Ok(proof)
}
/// prove n in a tree structure recursively
/// the function takes
/// - circ_input: vector of circuit inputs
pub fn prove_tree(
&mut self,
circ_input: Vec<I::Input>,
depth: usize,
) -> Result<ProofWithPublicInputs<F, C, D>>{
// Total input size check
let total_input = (N.pow(depth as u32) - 1) / (N - 1);
assert_eq!(circ_input.len(), total_input, "Invalid input size for tree depth");
let mut cur_proofs: Vec<ProofWithPublicInputs<F, C, D>> = vec![];
// Iterate from leaf layer to root
for layer in (0..depth).rev() {
let layer_num_nodes = N.pow(layer as u32); // Number of nodes at this layer
let mut next_proofs = Vec::new();
for node_idx in 0..layer_num_nodes {
// Get the inputs for the current node
let node_inputs: [I::Input; M] = from_fn(|i| {
circ_input
.get(node_idx * M + i)
.cloned()
.unwrap_or_else(|| panic!("Index out of bounds at node {node_idx}, input {i}"))
});
let proof = if layer == depth - 1 {
// Leaf layer: no child proofs
self.prove(&node_inputs, None, true)?
} else {
// Non-leaf layer: collect child proofs
let proofs_array: [ProofWithPublicInputs<F, C, D>; N] = cur_proofs
.drain(..N)
.collect::<Vec<_>>()
.try_into()
.map_err(|_| anyhow!("Incorrect number of proofs for node"))?;
self.prove(&node_inputs, Some(proofs_array), false)?
};
next_proofs.push(proof);
}
cur_proofs = next_proofs;
}
// Final root proof
assert_eq!(cur_proofs.len(), 1, "Final proof count incorrect");
Ok(cur_proofs.remove(0))
}
/// verifies the proof generated
pub fn verify_proof(
&self,
proof: ProofWithPublicInputs<F, C, D>
) -> Result<()>{
if self.node_circ.cyclic_circuit_data.is_none() {
panic!("no circuit data or proof found");
}
let circ_data = self.node_circ.cyclic_circuit_data.as_ref().unwrap();
circ_data.verify(proof)?;
Ok(())
}
}
/// Node circuit struct
/// contains necessary data
/// M: number of inner-circuits to run
/// N: number of proofs verified in-circuit (so num of child nodes)
pub struct NodeCircuit<
I: InnerCircuit,
const M: usize,
const N: usize,
>{
pub circ: I,
pub cyclic_target: Option<NodeCircuitTargets<I,M,N>>,
pub cyclic_circuit_data: Option<CircuitData<F, C, D>>,
pub common_data: Option<CommonCircuitData<F, D>>,
}
/// Node circuit targets
/// assumes that all inner proofs use the same verifier data
#[derive(Clone, Debug)]
pub struct NodeCircuitTargets<
I: InnerCircuit,
const M: usize,
const N: usize,
>{
pub inner_targets: [I::Targets; M],
pub condition: BoolTarget,
pub inner_proofs_with_pis: [ProofWithPublicInputsTarget<D>; N],
pub verifier_data: VerifierCircuitTarget,
}
impl<
I: InnerCircuit,
const M: usize,
const N: usize,
> NodeCircuit<I, M, N> {
/// create a new cyclic circuit
pub fn new(circ: I) -> Self{
Self{
circ,
cyclic_target: None,
cyclic_circuit_data: None,
common_data: None,
}
}
/// builds the cyclic recursion circuit using any inner circuit I
/// returns the circuit data
pub fn build_circuit(
&mut self,
) -> Result<()>{
// if the circuit data is already build then no need to rebuild
// if self.cyclic_circuit_data.is_some(){
// return Ok(());
// }
// builder with standard recursion config
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
//build M inner circuits
// let mut inner_t = Vec::with_capacity(M);
// for i in 0..M {
// inner_t.push( self.circ.build(&mut builder)?);
// }
let inner_t: [I::Targets; M] = (0..M)
.map(|_| self.circ.build(&mut builder))
.collect::<Result<Vec<_>>>()?
.try_into()
.map_err(|_| anyhow!("Expected exactly M inner circuits"))?;
// common data for recursion
let mut common_data = self.common_data_for_node()?;
// let outer_pis = I::get_pub_input_targets(&inner_t)?;
let pub_input_hash = builder.add_virtual_hash_public_input();
let verifier_data_target = builder.add_verifier_data_public_inputs();
common_data.num_public_inputs = builder.num_public_inputs();
// condition
let condition = builder.add_virtual_bool_target_safe();
// inner proofs targets - N proof targets
// let mut inner_cyclic_proof_with_pis = vec![];
// for i in 0..N {
// inner_cyclic_proof_with_pis.push(builder.add_virtual_proof_with_pis(&common_data));
// }
let inner_cyclic_proof_with_pis: [ProofWithPublicInputsTarget<D>; N] = (0..N)
.map(|_| builder.add_virtual_proof_with_pis(&common_data))
.collect::<Vec<_>>()
.try_into()
.map_err(|_| anyhow!("Expected exactly N proof targets"))?;
// get the public input hash from all inner proof targets
let mut inner_pub_input_hashes = vec![];
for i in 0..N {
let inner_cyclic_pis = &inner_cyclic_proof_with_pis[i].public_inputs;
inner_pub_input_hashes.extend_from_slice(&inner_cyclic_pis[0..4]);
}
// hash all the inner public input h = H(h_1 | h_2 | ... | h_N)
let inner_pub_input_hash = builder.hash_n_to_hash_no_pad::<H>(inner_pub_input_hashes);
// get the public input of the inner circuit
let mut outer_pis = vec![];
for i in 0..M {
outer_pis.push( I::get_pub_input_targets(&inner_t[i])?);
}
// hash all the public input -> generate one hashout at the end
// this is not an optimal way to do it, verification might be ugly if M > 1
// TODO: optimize this
let mut outer_pi_hashes = vec![];
for i in 0..M {
let hash_res = builder.hash_n_to_hash_no_pad::<H>(outer_pis[i].clone());
outer_pi_hashes.extend_from_slice(&hash_res.elements)
}
// the final public input hash
let outer_pi_hash = builder.hash_n_to_hash_no_pad::<H>(outer_pi_hashes);
// zero hash for leaves
let zero_hash = HashOutTarget::from_vec([builder.zero(); 4].to_vec());
// if the inner proofs are dummy then use zero hash for public input
let inner_pi_hash_or_zero_hash = select_hash(&mut builder, condition, inner_pub_input_hash, zero_hash);
// now hash the public input of the inner proofs and outer proof so we have one public hash
let mut hash_input = vec![];
hash_input.extend_from_slice(&outer_pi_hash.elements);
hash_input.extend_from_slice(&inner_pi_hash_or_zero_hash.elements);
let outer_pi_hash = builder.hash_n_to_hash_no_pad::<H>(hash_input);
// connect this up one to `pub_input_hash`
builder.connect_hashes(pub_input_hash,outer_pi_hash);
// we can connect entropy, since all share same entropy, but might be more work
// TODO: look into entropy
// verify all N proofs in-circuit
for i in 0..N {
builder.conditionally_verify_cyclic_proof_or_dummy::<C>(
condition,
&inner_cyclic_proof_with_pis[i],
&common_data,
)?;
}
// build the cyclic circuit
let cyclic_circuit_data = builder.build::<C>();
// assign targets
let cyc_t = NodeCircuitTargets::<I, M, N>{
inner_targets: inner_t,
condition,
inner_proofs_with_pis: inner_cyclic_proof_with_pis,
verifier_data: verifier_data_target
};
// assign the data
self.cyclic_circuit_data = Some(cyclic_circuit_data);
self.common_data = Some(common_data);
self.cyclic_target = Some(cyc_t);
Ok(())
}
/// assigns the targets for the Node circuit
/// takes circuit input
pub fn assign_targets(
&mut self,
circ_input: &[I::Input; M],
proof_options: Option<[ProofWithPublicInputs<F, C, D>; N]>,
pw: &mut PartialWitness<F>,
is_leaf: bool,
) -> Result<()>{
if self.cyclic_circuit_data.is_none(){
panic!("circuit data not found") // TODO: replace with err
}
let circ_data = self.cyclic_circuit_data.as_ref().unwrap();
let cyc_targets = self.cyclic_target.as_ref().unwrap();
let common_data = self.common_data.as_ref().unwrap();
for i in 0..M {
self.circ.assign_targets(pw, &cyc_targets.inner_targets[i], &circ_input[i])?;
}
if(is_leaf == true) {
pw.set_bool_target(cyc_targets.condition, false)?;
for i in 0..N {
pw.set_proof_with_pis_target::<C, D>(
&cyc_targets.inner_proofs_with_pis[i],
&cyclic_base_proof(
common_data,
&circ_data.verifier_only,
HashMap::new(),
),
)?;
}
}else{
pw.set_bool_target(cyc_targets.condition, true)?;
let proofs = proof_options.unwrap(); // add error check
for i in 0..N {
pw.set_proof_with_pis_target(&cyc_targets.inner_proofs_with_pis[i], &proofs[i])?;
}
}
pw.set_verifier_data_target(&cyc_targets.verifier_data, &circ_data.verifier_only)?;
Ok(())
}
/// Generates `CommonCircuitData` usable for node recursion.
/// the circuit being built here depends on M and N so must be re-generated
/// if the params change
pub fn common_data_for_node(&self) -> Result<CommonCircuitData<F, D>>
{
// layer 1
let config = CircuitConfig::standard_recursion_config();
let builder = CircuitBuilder::<F, D>::new(config);
let data = builder.build::<C>();
// layer 2
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config.clone());
let verifier_data = builder.add_virtual_verifier_data(data.common.config.fri_config.cap_height);
// generate and verify N number of 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 data = builder.build::<C>();
// layer 3
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config.clone());
// add a ConstantGate
builder.add_gate(
plonky2::gates::constant::ConstantGate::new(config.num_constants),
vec![],
);
// build M inner circuits
for i in 0..M {
self.circ.build(&mut builder)?;
}
// generate and verify N number of proofs
let verifier_data = builder.add_verifier_data_public_inputs();
for _ in 0..N {
let proof = builder.add_virtual_proof_with_pis(&data.common);
builder.verify_proof::<C>(&proof, &verifier_data, &data.common);
}
// pad. TODO: optimize this padding to only needed number of gates
while builder.num_gates() < 1 << 13 {
builder.add_gate(NoopGate, vec![]);
}
Ok(builder.build::<C>().common)
}
}

View File

@ -0,0 +1,70 @@
use std::marker::PhantomData;
use plonky2::plonk::circuit_data::{CommonCircuitData, VerifierOnlyCircuitData};
use plonky2::plonk::proof::ProofWithPublicInputs;
use plonky2::recursion::dummy_circuit::{cyclic_base_proof, dummy_circuit, dummy_proof};
use hashbrown::HashMap;
use plonky2::hash::hash_types::RichField;
use plonky2::plonk::config::{AlgebraicHasher, GenericConfig};
use plonky2_field::extension::Extendable;
use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2;
use crate::{error::CircuitError, Result};
use crate::circuits::utils::vec_to_array;
/// A generator for creating dummy proofs.
pub struct DummyProofGen<
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
C: GenericConfig<D, F = F>,
> where
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
{
phantom_data: PhantomData<(F,C)>,
}
impl<
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
C: GenericConfig<D, F = F>,
> DummyProofGen<F, D, C>
where
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
{
/// Generates a single dummy leaf proof.
pub fn gen_dummy_leaf_proof(
common_data: &CommonCircuitData<F, D>,
) -> Result<ProofWithPublicInputs<F, C, D>> {
dummy_proof::<F, C, D>(&dummy_circuit::<F, C, D>(common_data), HashMap::new())
.map_err(|e| CircuitError::DummyProofGenerationError(e.to_string()))
}
/// Generates a single dummy node proof.
pub fn get_dummy_node_proof(
node_common: &CommonCircuitData<F, D>,
node_verifier_only_data: &VerifierOnlyCircuitData<C, D>,
) -> ProofWithPublicInputs<F, C, D> {
cyclic_base_proof(node_common, node_verifier_only_data, HashMap::new())
}
/// Generates an array of `N` dummy leaf proofs.
pub fn gen_n_dummy_leaf_proofs<const N: usize>(
common_data: &CommonCircuitData<F, D>,
) -> Result<[ProofWithPublicInputs<F, C, D>; N]> {
let dummy_proof = Self::gen_dummy_leaf_proof(common_data)?;
let n_dummy_vec = (0..N).map(|_| dummy_proof.clone()).collect::<Vec<_>>();
vec_to_array::<N, ProofWithPublicInputs<F, C, D>>(n_dummy_vec)
}
/// Generates an array of `N` dummy node proofs.
pub fn gen_n_dummy_node_proofs<const N: usize>(
node_common: &CommonCircuitData<F, D>,
node_verifier_only_data: &VerifierOnlyCircuitData<C, D>,
) -> Result<[ProofWithPublicInputs<F, C, D>; N]> {
let dummy_proof = Self::get_dummy_node_proof(node_common, node_verifier_only_data);
let n_dummy_vec = (0..N).map(|_| dummy_proof.clone()).collect::<Vec<_>>();
vec_to_array::<N, ProofWithPublicInputs<F, C, D>>(n_dummy_vec)
}
}

View File

@ -1,44 +1,69 @@
use std::marker::PhantomData;
use plonky2::hash::hash_types::RichField;
use plonky2::iop::witness::{PartialWitness, WitnessWrite};
use plonky2::plonk::circuit_builder::CircuitBuilder;
use plonky2::plonk::circuit_data::{CircuitConfig, CircuitData, CommonCircuitData, VerifierCircuitData, VerifierCircuitTarget};
use plonky2::plonk::circuit_data::{CircuitConfig, CircuitData, VerifierCircuitData, VerifierCircuitTarget};
use plonky2::plonk::config::{AlgebraicHasher, GenericConfig};
use plonky2::plonk::proof::{ProofWithPublicInputs, ProofWithPublicInputsTarget};
use crate::circuits::params::CircuitParams;
use crate::circuits::sample_cells::SampleCircuit;
use crate::params::{C, D, F, H};
use plonky2_field::extension::Extendable;
use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2;
use crate::recursion::circuits::inner_circuit::InnerCircuit;
use crate::recursion::circuits::sampling_inner_circuit::SamplingRecursion;
use crate::{error::CircuitError,Result};
/// recursion Inner circuit for the sampling circuit
/// recursion leaf circuit for the recursion tree circuit
#[derive(Clone, Debug)]
pub struct LeafCircuit<
I: InnerCircuit
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
I: InnerCircuit<F, D>
> {
pub inner_circ: I
pub inner_circ: I,
phantom_data: PhantomData<F>
}
impl<I: InnerCircuit> LeafCircuit<I> {
impl<
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
I: InnerCircuit<F, D>
> LeafCircuit<F,D,I> {
pub fn new(inner_circ: I) -> Self {
Self{
// sampling_circ: SampleCircuit::new(CircuitParams::default()),
inner_circ,
phantom_data:PhantomData::default(),
}
}
}
#[derive(Clone, Debug)]
pub struct LeafTargets {
pub struct LeafTargets <
const D: usize,
>{
pub inner_proof: ProofWithPublicInputsTarget<D>,
pub verifier_data: VerifierCircuitTarget,
}
#[derive(Clone, Debug)]
pub struct LeafInput{
pub struct LeafInput<
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
C: GenericConfig<D, F = F>,
>{
pub inner_proof: ProofWithPublicInputs<F, C, D>,
pub verifier_data: VerifierCircuitData<F, C, D>
}
impl<I: InnerCircuit> LeafCircuit<I>{
impl<
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
I: InnerCircuit<F, D>,
> LeafCircuit<F,D,I>{
/// build the leaf circuit
pub fn build(&self, builder: &mut CircuitBuilder<F, D>) -> anyhow::Result<LeafTargets> {
pub fn build<
C: GenericConfig<D, F = F>,
H: AlgebraicHasher<F>,
>(&self, builder: &mut CircuitBuilder<F, D>) -> Result<LeafTargets<D>>
where
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
{
let common = self.inner_circ.get_common_data()?;
@ -67,34 +92,47 @@ impl<I: InnerCircuit> LeafCircuit<I>{
}
/// assign the leaf targets with given input
pub fn assign_targets(&self, pw: &mut PartialWitness<F>, targets: &LeafTargets, input: &LeafInput) -> anyhow::Result<()> {
pub fn assign_targets<
C: GenericConfig<D, F = F>,
H: AlgebraicHasher<F>,
>(&self, pw: &mut PartialWitness<F>, targets: &LeafTargets<D>, input: &LeafInput<F, D, C>) -> Result<()>
where
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
{
// assign the proof
pw.set_proof_with_pis_target(&targets.inner_proof, &input.inner_proof)?;
pw.set_proof_with_pis_target(&targets.inner_proof,&input.inner_proof)
.map_err(|e| {
CircuitError::ProofTargetAssignmentError("inner-proof".to_string(), e.to_string())
})?;
// assign the verifier data
pw.set_cap_target(
&targets.verifier_data.constants_sigmas_cap,
&input.verifier_data.verifier_only.constants_sigmas_cap,
)?;
pw.set_hash_target(targets.verifier_data.circuit_digest, input.verifier_data.verifier_only.circuit_digest)?;
pw.set_verifier_data_target(&targets.verifier_data, &input.verifier_data.verifier_only)
.map_err(|e| {
CircuitError::VerifierDataTargetAssignmentError(e.to_string())
})?;
Ok(())
}
/// returns the leaf circuit data
/// TODO: make generic recursion config
pub fn get_circuit_data<
C: GenericConfig<D, F = F>,
H: AlgebraicHasher<F>,
>(&self) -> Result<CircuitData<F, C, D>>
where
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
{
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
self.build::<C,H>(&mut builder)?;
let circ_data = builder.build::<C>();
Ok(circ_data)
}
}
/// returns the leaf circuit data
/// NOTE: this is for the default leaf only
/// TODO: adjust for varying leaf types
pub fn circuit_data_for_leaf() -> anyhow::Result<CircuitData<F, C, D>>{
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
let inner_circ = SamplingRecursion::default();
let leaf = LeafCircuit::new(inner_circ);
leaf.build(&mut builder)?;
let circ_data = builder.build::<C>();
Ok(circ_data)
}

View File

@ -1,3 +1,4 @@
pub mod leaf_circuit;
pub mod tree_recursion2;
pub mod utils;
pub mod dummy_gen;
pub mod node_circuit;
pub mod tree_circuit;

View File

@ -0,0 +1,294 @@
use plonky2::gates::constant::ConstantGate;
use plonky2::gates::noop::NoopGate;
use plonky2::hash::hash_types::RichField;
use plonky2::iop::target::BoolTarget;
use plonky2::iop::witness::{PartialWitness, WitnessWrite};
use plonky2::plonk::circuit_builder::CircuitBuilder;
use plonky2::plonk::circuit_data::{CircuitConfig, CircuitData, CommonCircuitData, VerifierCircuitTarget, VerifierOnlyCircuitData};
use plonky2::plonk::config::{AlgebraicHasher, GenericConfig};
use plonky2::plonk::proof::{ProofWithPublicInputs, ProofWithPublicInputsTarget};
use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2;
use crate::recursion::circuits::inner_circuit::InnerCircuit;
use plonky2_field::extension::Extendable;
use crate::circuits::utils::{select_hash, vec_to_array};
use crate::{error::CircuitError, Result};
use crate::recursion::tree2::leaf_circuit::LeafCircuit;
/// Node circuit struct
/// contains necessary data
/// N: number of proofs verified in-circuit (so num of child nodes)
pub struct NodeCircuit<
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
C: GenericConfig<D, F = F>,
const N: usize,
>{
pub node_targets: NodeCircuitTargets<D, N>,
pub node_data: NodeData<F, D, C>,
}
/// Node circuit targets
/// assumes that all leaf proofs use the same verifier data
#[derive(Clone, Debug)]
pub struct NodeCircuitTargets<
const D: usize,
const N: usize,
>{
pub leaf_proofs: [ProofWithPublicInputsTarget<D>; N],
pub condition: BoolTarget,
pub node_proofs: [ProofWithPublicInputsTarget<D>; N],
pub leaf_verifier_data: VerifierCircuitTarget,
}
/// Node common data and verifier data
#[derive(Debug)]
pub struct NodeData<
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
C: GenericConfig<D, F = F>,
>{
pub node_circuit_data: CircuitData<F, C, D>,
pub inner_node_common_data: CommonCircuitData<F, D>,
pub leaf_circuit_data: CircuitData<F, C, D>,
}
impl<
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
C: GenericConfig<D, F = F> + 'static,
const N: usize,
> NodeCircuit<F, D, C, N>
where
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
{
/// builds the node circuit
/// the circuit data and targets are stored in the node struct
/// TODO: make generic recursion config
pub fn build_circuit<
I: InnerCircuit<F, D>,
H: AlgebraicHasher<F>
>(
leaf_circuit:LeafCircuit<F, D, I>
) -> Result<NodeCircuit<F, D, C, N>>{
// builder with standard recursion config
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
// circuit data for leaf
let leaf_circ_data = leaf_circuit.get_circuit_data::<C,H>()?;
// common data for leaf
let leaf_common = leaf_circ_data.common.clone();
// virtual proofs for leaf proofs
let mut leaf_proofs = vec![];
for _i in 0..N {
let vir_proof = builder.add_virtual_proof_with_pis(&leaf_common);
leaf_proofs.push(vir_proof);
}
// get the public input hash from all inner proof targets
let mut leaf_pub_input_hashes = vec![];
for i in 0..N {
let inner_cyclic_pis = &leaf_proofs[i].public_inputs;
leaf_pub_input_hashes.extend_from_slice(&inner_cyclic_pis[0..4]);
}
// hash the public input so H(H_0, ..., H_N)
let leaf_pub_input_hash = builder.hash_n_to_hash_no_pad::<H>(leaf_pub_input_hashes);
// leaf verifier data
// TODO: double check that it is ok for this verifier data to be private/witness
let leaf_verifier_data = builder.add_virtual_verifier_data(leaf_common.config.fri_config.cap_height);
// condition
let condition = builder.add_virtual_bool_target_safe();
// verify leaf proofs in-circuit if it is a leaf node,
// meaning that we are on bottom layer of the tree
for i in 0..N{
builder.conditionally_verify_proof_or_dummy::<C>(
condition,
&leaf_proofs[i],
&leaf_verifier_data,
&leaf_common
).map_err(|e| CircuitError::ConditionalVerificationError(e.to_string()))?;
}
// common data for recursion
let mut common_data = Self::get_common_data_for_node()?;
// public input hash. defined here so that is public_input[0..4]
let pub_input_hash = builder.add_virtual_hash_public_input();
// verifier data for the recursion.
let _verifier_data_target = builder.add_verifier_data_public_inputs();
common_data.num_public_inputs = builder.num_public_inputs();
// flipped condition. used to conditionally verify the node proofs (recursive proofs)
let one = builder.one();
let flipped_condition = BoolTarget::new_unsafe(builder.sub(one,condition.target));
let inner_cyclic_proof_with_pis: [ProofWithPublicInputsTarget<D>; N] =
vec_to_array::<N, ProofWithPublicInputsTarget<D>>(
(0..N)
.map(|_| builder.add_virtual_proof_with_pis(&common_data))
.collect::<Vec<_>>()
)?;
// get the public input hash from all inner proof targets
let mut inner_pub_input_hashes = vec![];
for i in 0..N {
let inner_cyclic_pis = &inner_cyclic_proof_with_pis[i].public_inputs;
inner_pub_input_hashes.extend_from_slice(&inner_cyclic_pis[0..4]);
}
// hash all the node public input h = H(h_1 | h_2 | ... | h_N)
// TODO: optimize by removing the need for 2 hashes and instead select then hash
let inner_pub_input_hash = builder.hash_n_to_hash_no_pad::<H>(inner_pub_input_hashes);
let node_hash_or_leaf_hash = select_hash(&mut builder, condition, leaf_pub_input_hash, inner_pub_input_hash);
builder.connect_hashes(pub_input_hash,node_hash_or_leaf_hash);
// verify all N proofs in-circuit
for i in 0..N {
builder.conditionally_verify_cyclic_proof_or_dummy::<C>(
flipped_condition,
&inner_cyclic_proof_with_pis[i],
&common_data,
).map_err(|e| CircuitError::ConditionalVerificationError(e.to_string()))?;
}
// build the node circuit
let node_circuit_data = builder.build::<C>();
// collect the leaf proofs
let leaf_proofs: [ProofWithPublicInputsTarget<D>; N] =
vec_to_array::<N, ProofWithPublicInputsTarget<D>>(
(0..N).map(|i| {
leaf_proofs[i].clone()
}).collect::<Vec<_>>()
)?;
// store targets
let node_targets = NodeCircuitTargets::<D, N>{
leaf_proofs,
condition,
node_proofs: inner_cyclic_proof_with_pis,
leaf_verifier_data
};
let node_data = NodeData{
node_circuit_data,
inner_node_common_data: common_data,
leaf_circuit_data: leaf_circ_data,
};
let node = NodeCircuit{
node_targets,
node_data,
};
Ok(node)
}
/// assigns the targets for the Node circuit - takes
/// - either leaf or circuit proofs
/// - leaf circuit data
/// - partial witness
/// - bool value, true if leaf node, otherwise false.
pub fn assign_targets(
node_targets: NodeCircuitTargets<D, N>,
leaf_proofs: [ProofWithPublicInputs<F, C, D>; N],
node_proofs: [ProofWithPublicInputs<F, C, D>; N],
leaf_verifier_only_data: &VerifierOnlyCircuitData<C, D>,
pw: &mut PartialWitness<F>,
is_leaf: bool,
) -> Result<()>{
if is_leaf == true {
let dummy_node = node_proofs;
// assign the condition
pw.set_bool_target(node_targets.condition, true)
.map_err(|e| CircuitError::BoolTargetAssignmentError("condition".to_string(),e.to_string()))?;
for i in 0..N {
// assign the node proofs with dummy
pw.set_proof_with_pis_target::<C, D>(
&node_targets.node_proofs[i],
&dummy_node[i],
).map_err(|e| CircuitError::ProofTargetAssignmentError("dummy node proofs".to_string(),e.to_string()))?;
// assign the leaf proof with real proofs
pw.set_proof_with_pis_target(
&node_targets.leaf_proofs[i],
&leaf_proofs[i]
).map_err(|e| CircuitError::ProofTargetAssignmentError("leaf proofs".to_string(),e.to_string()))?;
}
}else{
// assign the condition
pw.set_bool_target(node_targets.condition, false)
.map_err(|e| CircuitError::BoolTargetAssignmentError("condition".to_string(),e.to_string()))?;
// dummy leaf
let dummy_leaf = leaf_proofs;
for i in 0..N {
// assign the node proofs
pw.set_proof_with_pis_target(&node_targets.node_proofs[i], &node_proofs[i])
.map_err(|e| CircuitError::ProofTargetAssignmentError("node proofs".to_string(),e.to_string()))?;
// assign leaf proofs with dummy
pw.set_proof_with_pis_target::<C, D>(
&node_targets.leaf_proofs[i],
&dummy_leaf[i],
).map_err(|e| CircuitError::ProofTargetAssignmentError("dummy leaf proofs".to_string(),e.to_string()))?;
}
}
// assign the verifier data (only for the leaf proofs)
pw.set_verifier_data_target(&node_targets.leaf_verifier_data, leaf_verifier_only_data)
.map_err(|e| CircuitError::VerifierDataTargetAssignmentError(e.to_string()))?;
Ok(())
}
/// Generates `CommonCircuitData` usable for node recursion.
/// the circuit being built here depends on M and N so must be re-generated
/// if the params change
pub fn get_common_data_for_node() -> Result<CommonCircuitData<F, D>>
{
// layer 1
let config = CircuitConfig::standard_recursion_config();
let builder = CircuitBuilder::<F, D>::new(config);
let data = builder.build::<C>();
// layer 2
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config.clone());
let verifier_data = builder.add_virtual_verifier_data(data.common.config.fri_config.cap_height);
// generate and verify N number of proofs
for _ in 0..1 {
let proof = builder.add_virtual_proof_with_pis(&data.common);
builder.verify_proof::<C>(&proof, &verifier_data, &data.common);
}
let data = builder.build::<C>();
// layer 3
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config.clone());
// add a ConstantGate
builder.add_gate(
ConstantGate::new(config.num_constants),
vec![],
);
// generate and verify N number of proofs
let verifier_data = builder.add_verifier_data_public_inputs();
for _ in 0..N {
let proof = builder.add_virtual_proof_with_pis(&data.common);
builder.verify_proof::<C>(&proof, &verifier_data, &data.common);
}
// pad. TODO: optimize this padding to only needed number of gates
while builder.num_gates() < 1 << 14 {
builder.add_gate(NoopGate, vec![]);
}
Ok(builder.build::<C>().common)
}
}

View File

@ -0,0 +1,174 @@
use plonky2::hash::hash_types::RichField;
use plonky2::iop::witness::PartialWitness;
use plonky2::plonk::config::{AlgebraicHasher, GenericConfig};
use plonky2::plonk::proof::ProofWithPublicInputs;
use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2;
use crate::recursion::circuits::inner_circuit::InnerCircuit;
use plonky2::recursion::cyclic_recursion::check_cyclic_proof_verifier_data;
use plonky2_field::extension::Extendable;
use crate::recursion::tree2::dummy_gen::DummyProofGen;
use crate::{error::CircuitError, Result};
use crate::circuits::utils::vec_to_array;
use crate::recursion::tree2::leaf_circuit::LeafCircuit;
use crate::recursion::tree2::node_circuit::NodeCircuit;
/// the tree recursion struct simplifies the process
/// of building, proving and verifying
/// - N: number of inner proofs to verify in the node circuit
pub struct TreeRecursion<
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
C: GenericConfig<D, F = F> + 'static,
const N: usize,
> where
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
{
pub node: NodeCircuit<F, D, C, N>
}
impl<
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
C: GenericConfig<D, F = F> + 'static,
const N: usize,
> TreeRecursion<F, D, C, N> where
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
{
pub fn build<
I: InnerCircuit<F, D>,
H: AlgebraicHasher<F>,
>(
leaf_circuit: LeafCircuit<F, D, I>
) -> Result<Self>{
Ok(
Self{
node: NodeCircuit::<F, D, C, N>::build_circuit::<I,H>(leaf_circuit)?,
}
)
}
/// generates a proof - only one node
/// takes N proofs
pub fn prove(
&mut self,
leaf_proofs: [ProofWithPublicInputs<F, C, D>; N],
node_proofs: [ProofWithPublicInputs<F, C, D>; N],
is_leaf: bool,
) -> Result<ProofWithPublicInputs<F, C, D>>{
let mut pw = PartialWitness::new();
NodeCircuit::assign_targets(
self.node.node_targets.clone(),
leaf_proofs,
node_proofs,
&self.node.node_data.leaf_circuit_data.verifier_only,
&mut pw,
is_leaf,
)?;
let proof = self.node.node_data.node_circuit_data.prove(pw)
.map_err(|e| CircuitError::ProofGenerationError(e.to_string()))?;
Ok(proof)
}
/// prove n leaf proofs in a tree structure
/// the function uses circuit data from self takes
/// - leaf_proofs: vector of circuit inputs
/// NOTE: Expects the number of leaf proofs to be divisible by N, e.g. by 2 if binary tree
pub fn prove_tree(
&mut self,
leaf_proofs: Vec<ProofWithPublicInputs<F, C, D>>,
) -> Result<ProofWithPublicInputs<F, C, D>> {
// 1. Check the total number of leaf_proofs is divisible by N
if leaf_proofs.len() % N != 0 {
return
Err(CircuitError::RecursionTreeError(format!(
"input proofs must be divisible by {}, got {}", N, leaf_proofs.len())
))
}
// 2. Prepare the dummy proofs
let dummy_node_proofs = DummyProofGen::<F, D, C>::gen_n_dummy_node_proofs(
&self.node.node_data.inner_node_common_data,
&self.node.node_data.node_circuit_data.verifier_only,
)?;
let dummy_leaf_proofs = DummyProofGen::<F, D, C>::gen_n_dummy_leaf_proofs(
&self.node.node_data.leaf_circuit_data.common
)?;
// 3. Work through levels of proofs until only one remains
let mut current_level_proofs = leaf_proofs;
// Keep reducing until were left with 1 proof
let mut level: usize = 0;
while current_level_proofs.len() >= N {
let mut next_level_proofs = Vec::new();
// Process in chunks of N
for chunk in current_level_proofs.chunks_exact(N) {
// Convert the chunk slice into a fixed-size array
let chunk_array: [ProofWithPublicInputs<F, C, D>; N] =
vec_to_array::<N,ProofWithPublicInputs<F, C, D>>(chunk.to_vec())?;
// Decide leaf or node based on level
// assumes the first chunk is the leaf
let (leaf_chunk, node_chunk, is_leaf) = if level == 0 {
(chunk_array, dummy_node_proofs.clone(), true)
} else {
(dummy_leaf_proofs.clone(), chunk_array, false)
};
let node = self.prove(
leaf_chunk,
node_chunk,
is_leaf,
)?;
next_level_proofs.push(node);
}
current_level_proofs = next_level_proofs;
level = level + 1;
}
// 4. Check that exactly one proof remains
if current_level_proofs.len() != 1 {
return Err(CircuitError::RecursionTreeError(
format!("Expected exactly 1 final proof, found {}",
current_level_proofs.len())
));
}
// 5. Return the final root proof
Ok(current_level_proofs.remove(0))
}
/// verifies the proof generated
/// TODO: separate prover from verifier.
pub fn verify_proof(
&self,
proof: ProofWithPublicInputs<F, C, D>,
is_leaf: bool,
) -> Result<()>{
if !is_leaf {
check_cyclic_proof_verifier_data(
&proof,
&self.node.node_data.node_circuit_data.verifier_only,
&self.node.node_data.node_circuit_data.common,
).map_err(|e| CircuitError::InvalidProofError(e.to_string()))?;
}
self.node.node_data.node_circuit_data.verify(proof)
.map_err(|e| CircuitError::InvalidProofError(e.to_string()))?;
Ok(())
}
}

View File

@ -1,390 +0,0 @@
use plonky2::iop::target::BoolTarget;
use plonky2::iop::witness::{PartialWitness, WitnessWrite};
use plonky2::plonk::circuit_builder::CircuitBuilder;
use plonky2::plonk::circuit_data::{CircuitConfig, CircuitData, CommonCircuitData, VerifierCircuitTarget, VerifierOnlyCircuitData};
use plonky2::plonk::config::GenericConfig;
use plonky2::plonk::proof::{ProofWithPublicInputs, ProofWithPublicInputsTarget};
use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2;
use crate::params::{C, D, F, H};
use crate::recursion::circuits::inner_circuit::InnerCircuit;
use anyhow::{anyhow, Result};
use plonky2::recursion::cyclic_recursion::check_cyclic_proof_verifier_data;
// use serde::de::Unexpected::Option;
use crate::circuits::utils::select_hash;
use crate::recursion::tree2::leaf_circuit;
use crate::recursion::tree2::utils;
use crate::recursion::tree2::utils::{get_dummy_leaf_proof, get_dummy_node_proof};
/// the tree recursion struct simplifies the process
/// of building, proving and verifying
/// - N: number of inner proofs to verify in the node circuit
pub struct TreeRecursion<
const N: usize,
>{
pub node: NodeCircuit<N>
}
impl<
const N: usize,
> TreeRecursion<N> {
// pub fn new(node_circ: NodeCircuit<N>) -> Self{
// Self{
// node_circ,
// }
// }
pub fn build(
) -> Result<Self>{
Ok(
Self{
node: NodeCircuit::<N>::build_circuit()?,
}
)
}
/// generates a proof - only one node
/// takes N proofs
pub fn prove(
&mut self,
// node_targets: NodeCircuitTargets<N>,
leaf_proof_options: Option<[ProofWithPublicInputs<F, C, D>; N]>,
node_proof_options: Option<[ProofWithPublicInputs<F, C, D>; N]>,
// leaf_verifier_only_data: &VerifierOnlyCircuitData<C, D>,
is_leaf: bool,
) -> Result<ProofWithPublicInputs<F, C, D>>{
let mut pw = PartialWitness::new();
NodeCircuit::assign_targets(
self.node.node_targets.clone(),
leaf_proof_options,
node_proof_options,
&self.node.node_data.leaf_circuit_data.verifier_only,
&mut pw,
is_leaf,
)?;
let proof = self.node.node_data.node_circuit_data.prove(pw)?;
//TODO: move this to verify function
if !is_leaf {
check_cyclic_proof_verifier_data(
&proof,
&self.node.node_data.node_circuit_data.verifier_only,
&self.node.node_data.node_circuit_data.common,
)?;
}
Ok(proof)
}
/// prove n leaf proofs in a tree structure
/// the function uses circuit data from self takes
/// - leaf_proofs: vector of circuit inputs
pub fn prove_tree(
&mut self,
leaf_proofs: Vec<ProofWithPublicInputs<F, C, D>>,
) -> Result<ProofWithPublicInputs<F, C, D>> {
// 1. Check the total number of leaf_proofs is divisible by N
if leaf_proofs.len() % N != 0 {
return Err(anyhow!(
"input proofs must be divisible by {}, got {}",
N,
leaf_proofs.len()
));
}
// 2. Prepare the dummy proofs
// let node_targets = self.node.node_targets.clone();
let dummy_node_proof = get_dummy_node_proof(
&self.node.node_data.inner_node_common_data,
&self.node.node_data.node_circuit_data.verifier_only,
);
let dummy_node_proofs: [ProofWithPublicInputs<F, C, D>; N] = (0..N)
.map(|_| dummy_node_proof.clone())
.collect::<Vec<_>>()
.try_into()
.map_err(|_| anyhow!("Expected exactly N node dummy proofs"))?;
let dummy_leaf_proof = get_dummy_leaf_proof(&self.node.node_data.leaf_circuit_data.common);
let dummy_leaf_proofs: [ProofWithPublicInputs<F, C, D>; N] = (0..N)
.map(|_| dummy_leaf_proof.clone())
.collect::<Vec<_>>()
.try_into()
.map_err(|_| anyhow!("Expected exactly N leaf dummy proofs"))?;
// 3. Work through levels of proofs until only one remains
let mut current_level_proofs = leaf_proofs;
// Keep reducing until were left with 1 proof
let mut level: usize = 0;
while current_level_proofs.len() >= N {
let mut next_level_proofs = Vec::new();
// Process in chunks of N
for chunk in current_level_proofs.chunks_exact(N) {
// Convert the chunk slice into a fixed-size array
let chunk_array: [ProofWithPublicInputs<F, C, D>; N] = chunk
.to_vec() // create a Vec
.try_into()
.map_err(|_| anyhow!("Failed to convert to array of size N"))?;
// Decide which side is the leaf or node
// The logic here assumes the "first" chunk is the leaf
let (leaf_chunk, node_chunk, is_leaf) = if level == 0 {
(chunk_array, dummy_node_proofs.clone(), true)
} else {
(dummy_leaf_proofs.clone(), chunk_array, false)
};
let node = self.prove(
// node_targets.clone(),
Some(leaf_chunk),
Some(node_chunk),
is_leaf,
)?;
next_level_proofs.push(node);
}
current_level_proofs = next_level_proofs;
level = level + 1;
}
// 4. Check that exactly one proof remains
if current_level_proofs.len() != 1 {
return Err(anyhow!(
"Expected exactly 1 final proof, found {}",
current_level_proofs.len()
));
}
// 5. Return the final root proof
Ok(current_level_proofs.remove(0))
}
/// verifies the proof generated
/// TODO: separate prover from verifier.
pub fn verify_proof(
&self,
proof: ProofWithPublicInputs<F, C, D>
) -> Result<()>{
self.node.node_data.node_circuit_data.verify(proof)?;
Ok(())
}
}
/// Node circuit struct
/// contains necessary data
/// N: number of proofs verified in-circuit (so num of child nodes)
pub struct NodeCircuit<
const N: usize,
>{
pub node_targets: NodeCircuitTargets<N>,
pub node_data: NodeData,
}
/// Node circuit targets
/// assumes that all leaf proofs use the same verifier data
#[derive(Clone, Debug)]
pub struct NodeCircuitTargets<
const N: usize,
>{
pub leaf_proofs: [ProofWithPublicInputsTarget<D>; N],
pub condition: BoolTarget,
pub node_proofs: [ProofWithPublicInputsTarget<D>; N],
pub leaf_verifier_data: VerifierCircuitTarget,
}
/// Node common data and verifier data
#[derive(Debug)]
pub struct NodeData<
>{
pub node_circuit_data: CircuitData<F, C, D>,
pub inner_node_common_data: CommonCircuitData<F, D>,
pub leaf_circuit_data: CircuitData<F, C, D>,
}
impl<
const N: usize,
> NodeCircuit< N> {
/// builds the node circuit
/// the circuit data and targets are stored in the node struct
pub fn build_circuit(
) -> Result<NodeCircuit<N>>{
// builder with standard recursion config
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
// circuit data for leaf
let leaf_circ_data = leaf_circuit::circuit_data_for_leaf()?;
// common data for leaf
let leaf_common = leaf_circ_data.common.clone();
// virtual proofs for leaf proofs
let mut leaf_proofs = vec![];
for i in 0..N {
let vir_proof = builder.add_virtual_proof_with_pis(&leaf_common);
leaf_proofs.push(vir_proof);
}
// get the public input hash from all inner proof targets
let mut leaf_pub_input_hashes = vec![];
for i in 0..N {
let inner_cyclic_pis = &leaf_proofs[i].public_inputs;
leaf_pub_input_hashes.extend_from_slice(&inner_cyclic_pis[0..4]);
}
// hash the public input so H(H_0, ..., H_N)
let leaf_pub_input_hash = builder.hash_n_to_hash_no_pad::<H>(leaf_pub_input_hashes);
// leaf verifier data
// TODO: double check that it is ok for this verifier data to be private/witness
let leaf_verifier_data = builder.add_virtual_verifier_data(leaf_common.config.fri_config.cap_height);
// condition
let condition = builder.add_virtual_bool_target_safe();
// verify leaf proofs in-circuit if it is a leaf node,
// meaning that we are on bottom layer of the tree
for i in 0..N{
builder.conditionally_verify_proof_or_dummy::<C>(condition,&leaf_proofs[i],&leaf_verifier_data, &leaf_common)?;
}
// common data for recursion
let mut common_data = utils::common_data_for_node::<N>()?;
// public input hash. defined here so that is public_input[0..4]
let pub_input_hash = builder.add_virtual_hash_public_input();
// verifier data for the recursion.
let _verifier_data_target = builder.add_verifier_data_public_inputs();
common_data.num_public_inputs = builder.num_public_inputs();
// flipped condition. used to conditionally verify the node proofs (recursive proofs)
let one = builder.one();
let flipped_condition = BoolTarget::new_unsafe(builder.sub(one,condition.target));
let inner_cyclic_proof_with_pis: [ProofWithPublicInputsTarget<D>; N] = (0..N)
.map(|_| builder.add_virtual_proof_with_pis(&common_data))
.collect::<Vec<_>>()
.try_into()
.map_err(|_| anyhow!("Expected exactly N proof targets"))?;
// get the public input hash from all inner proof targets
let mut inner_pub_input_hashes = vec![];
for i in 0..N {
let inner_cyclic_pis = &inner_cyclic_proof_with_pis[i].public_inputs;
inner_pub_input_hashes.extend_from_slice(&inner_cyclic_pis[0..4]);
}
// hash all the node public input h = H(h_1 | h_2 | ... | h_N)
// TODO: optimize by removing the need for 2 hashes and instead select then hash
let inner_pub_input_hash = builder.hash_n_to_hash_no_pad::<H>(inner_pub_input_hashes);
let node_hash_or_leaf_hash = select_hash(&mut builder, condition, leaf_pub_input_hash, inner_pub_input_hash);
builder.connect_hashes(pub_input_hash,node_hash_or_leaf_hash);
// verify all N proofs in-circuit
for i in 0..N {
builder.conditionally_verify_cyclic_proof_or_dummy::<C>(
flipped_condition,
&inner_cyclic_proof_with_pis[i],
&common_data,
)?;
}
// build the node circuit
let node_circuit_data = builder.build::<C>();
// collect the leaf proofs
let leaf_proofs: [ProofWithPublicInputsTarget<D>; N] = (0..N)
.map(|i| {
leaf_proofs[i].clone()
})
.collect::<Vec<_>>()
.try_into()
.map_err(|_| anyhow!("Expected exactly M inner circuits"))?;
// store targets
let node_targets = NodeCircuitTargets::<N>{
leaf_proofs,
condition,
node_proofs: inner_cyclic_proof_with_pis,
leaf_verifier_data
};
let node_data = NodeData{
node_circuit_data,
inner_node_common_data: common_data,
leaf_circuit_data: leaf_circ_data,
};
let node = NodeCircuit{
node_targets,
node_data,
};
Ok(node)
}
/// assigns the targets for the Node circuit - takes
/// - either leaf or circuit proofs
/// - leaf circuit data
/// - partial witness
/// - bool value, true if leaf node, otherwise false.
pub fn assign_targets(
node_targets: NodeCircuitTargets<N>,
leaf_proof_options: Option<[ProofWithPublicInputs<F, C, D>; N]>,
node_proof_options: Option<[ProofWithPublicInputs<F, C, D>; N]>,
leaf_verifier_only_data: &VerifierOnlyCircuitData<C, D>,
pw: &mut PartialWitness<F>,
is_leaf: bool,
) -> Result<()>{
if(is_leaf == true) {
let leaf_proofs: [ProofWithPublicInputs<F, C, D>; N] = leaf_proof_options.unwrap();
// dummy
let dummy_node = node_proof_options.unwrap();
// assign the condition
pw.set_bool_target(node_targets.condition, true)?;
for i in 0..N {
// assign the node proofs with dummy
pw.set_proof_with_pis_target::<C, D>(
&node_targets.node_proofs[i],
&dummy_node[i],
)?;
// assign the leaf proof with real proofs
pw.set_proof_with_pis_target(
&node_targets.leaf_proofs[i],
&leaf_proofs[i]
)?;
}
}else{
// assign the condition
pw.set_bool_target(node_targets.condition, false)?;
// node proofs
let node_proofs = node_proof_options.unwrap(); // add error check
// dummy leaf
let dummy_leaf = leaf_proof_options.unwrap();
for i in 0..N {
// assign the node proofs
pw.set_proof_with_pis_target(&node_targets.node_proofs[i], &node_proofs[i])?;
// assign leaf proofs with dummy
pw.set_proof_with_pis_target::<C, D>(
&node_targets.leaf_proofs[i],
&dummy_leaf[i],
)?;
}
}
// assign the verifier data (only for the leaf proofs)
pw.set_verifier_data_target(&node_targets.leaf_verifier_data, leaf_verifier_only_data)?;
Ok(())
}
}

View File

@ -1,69 +0,0 @@
use plonky2::plonk::circuit_data::{CircuitConfig, CommonCircuitData, VerifierOnlyCircuitData};
use plonky2::plonk::circuit_builder::CircuitBuilder;
use plonky2::gates::noop::NoopGate;
use plonky2::plonk::proof::ProofWithPublicInputs;
use plonky2::recursion::dummy_circuit::{cyclic_base_proof, dummy_circuit, dummy_proof};
use hashbrown::HashMap;
use crate::params::{C, D, F};
/// Generates `CommonCircuitData` usable for node recursion.
/// the circuit being built here depends on M and N so must be re-generated
/// if the params change
pub fn common_data_for_node<const N: usize>() -> anyhow::Result<CommonCircuitData<F, D>>
{
// layer 1
let config = CircuitConfig::standard_recursion_config();
let builder = CircuitBuilder::<F, D>::new(config);
let data = builder.build::<C>();
// layer 2
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config.clone());
let verifier_data = builder.add_virtual_verifier_data(data.common.config.fri_config.cap_height);
// generate and verify N number of proofs
for _ in 0..1 {
let proof = builder.add_virtual_proof_with_pis(&data.common);
builder.verify_proof::<C>(&proof, &verifier_data, &data.common);
}
let data = builder.build::<C>();
// layer 3
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config.clone());
// add a ConstantGate
builder.add_gate(
plonky2::gates::constant::ConstantGate::new(config.num_constants),
vec![],
);
// generate and verify N number of proofs
let verifier_data = builder.add_verifier_data_public_inputs();
for _ in 0..N {
let proof = builder.add_virtual_proof_with_pis(&data.common);
builder.verify_proof::<C>(&proof, &verifier_data, &data.common);
}
// pad. TODO: optimize this padding to only needed number of gates
while builder.num_gates() < 1 << 14 {
builder.add_gate(NoopGate, vec![]);
}
Ok(builder.build::<C>().common)
}
// creates a dummy proof with given common circuit data
pub fn get_dummy_leaf_proof(common_data: &CommonCircuitData<F, D>) -> ProofWithPublicInputs<F, C, D> {
dummy_proof::<F, C, D>(
&dummy_circuit::<F, C, D>(common_data),
HashMap::new(),
).unwrap()
}
pub fn get_dummy_node_proof(node_common: &CommonCircuitData<F, D>, node_verifier_only_data: &VerifierOnlyCircuitData<C, D>) -> ProofWithPublicInputs<F, C, D>{
cyclic_base_proof(
node_common,
node_verifier_only_data,
HashMap::new(),
)
}