mirror of
https://github.com/codex-storage/proof-aggregation.git
synced 2025-02-10 13:46:55 +00:00
refactor aggregation circuits and add error handling
This commit is contained in:
parent
d63a309e02
commit
c8a8ec0f5e
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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()
|
||||
)))
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
@ -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>;
|
||||
|
@ -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>)>;
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
}
|
@ -3,4 +3,3 @@ pub mod circuits;
|
||||
pub mod simple;
|
||||
pub mod tree1;
|
||||
pub mod tree2;
|
||||
pub mod params;
|
||||
|
@ -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>;
|
||||
|
@ -1,2 +1,3 @@
|
||||
pub mod simple_recursion;
|
||||
pub mod simple_recursion_hashed_pi;
|
||||
pub mod simple_tree_recursion;
|
||||
|
@ -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(())
|
||||
|
||||
|
@ -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(())
|
||||
|
||||
}
|
||||
}
|
@ -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())
|
||||
}
|
||||
|
@ -1 +1,2 @@
|
||||
pub mod tree_recursion;
|
||||
pub mod tree_circuit;
|
||||
pub mod node_circuit;
|
||||
|
259
codex-plonky2-circuits/src/recursion/tree1/node_circuit.rs
Normal file
259
codex-plonky2-circuits/src/recursion/tree1/node_circuit.rs
Normal 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)
|
||||
}
|
||||
|
||||
}
|
163
codex-plonky2-circuits/src/recursion/tree1/tree_circuit.rs
Normal file
163
codex-plonky2-circuits/src/recursion/tree1/tree_circuit.rs
Normal 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(())
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
70
codex-plonky2-circuits/src/recursion/tree2/dummy_gen.rs
Normal file
70
codex-plonky2-circuits/src/recursion/tree2/dummy_gen.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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;
|
||||
|
294
codex-plonky2-circuits/src/recursion/tree2/node_circuit.rs
Normal file
294
codex-plonky2-circuits/src/recursion/tree2/node_circuit.rs
Normal 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)
|
||||
}
|
||||
|
||||
}
|
174
codex-plonky2-circuits/src/recursion/tree2/tree_circuit.rs
Normal file
174
codex-plonky2-circuits/src/recursion/tree2/tree_circuit.rs
Normal 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 we’re 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(())
|
||||
}
|
||||
}
|
||||
|
@ -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 we’re 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(())
|
||||
}
|
||||
|
||||
}
|
@ -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(),
|
||||
)
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user