mirror of
https://github.com/logos-storage/proof-aggregation.git
synced 2026-01-03 22:33:08 +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:
|
// consistent with the one in codex:
|
||||||
// https://github.com/codex-storage/codex-storage-proofs-circuits/blob/master/circuit/codex/merkle.circom
|
// https://github.com/codex-storage/codex-storage-proofs-circuits/blob/master/circuit/codex/merkle.circom
|
||||||
|
|
||||||
// use anyhow::Result;
|
|
||||||
use plonky2::{
|
use plonky2::{
|
||||||
field::{extension::Extendable, types::Field},
|
field::{extension::Extendable, types::Field},
|
||||||
hash::hash_types::{HashOutTarget, RichField, NUM_HASH_OUT_ELTS},
|
hash::hash_types::{HashOutTarget, RichField, NUM_HASH_OUT_ELTS},
|
||||||
@ -15,7 +14,6 @@ use std::marker::PhantomData;
|
|||||||
use plonky2::plonk::config::AlgebraicHasher;
|
use plonky2::plonk::config::AlgebraicHasher;
|
||||||
use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2;
|
use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2;
|
||||||
use crate::circuits::keyed_compress::key_compress_circuit;
|
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::circuits::utils::{add_assign_hash_out_target, mul_hash_out_target};
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
use crate::error::CircuitError;
|
use crate::error::CircuitError;
|
||||||
|
|||||||
@ -131,7 +131,7 @@ impl<
|
|||||||
/// samples and registers the public input
|
/// samples and registers the public input
|
||||||
pub fn sample_slot_circuit_with_public_input(
|
pub fn sample_slot_circuit_with_public_input(
|
||||||
&self,
|
&self,
|
||||||
builder: &mut CircuitBuilder::<F, D>,
|
builder: &mut CircuitBuilder<F, D>,
|
||||||
) -> Result<SampleTargets> {
|
) -> Result<SampleTargets> {
|
||||||
let targets = self.sample_slot_circuit(builder)?;
|
let targets = self.sample_slot_circuit(builder)?;
|
||||||
let mut pub_targets = vec![];
|
let mut pub_targets = vec![];
|
||||||
@ -246,7 +246,7 @@ impl<
|
|||||||
let ctr_target = builder.constant(F::from_canonical_u64((i+1) as u64));
|
let ctr_target = builder.constant(F::from_canonical_u64((i+1) as u64));
|
||||||
let mut ctr = builder.add_virtual_hash();
|
let mut ctr = builder.add_virtual_hash();
|
||||||
for i in 0..ctr.elements.len() {
|
for i in 0..ctr.elements.len() {
|
||||||
if(i==0){
|
if i==0 {
|
||||||
ctr.elements[i] = ctr_target;
|
ctr.elements[i] = ctr_target;
|
||||||
}else{
|
}else{
|
||||||
ctr.elements[i] = zero.clone();
|
ctr.elements[i] = zero.clone();
|
||||||
@ -321,7 +321,7 @@ impl<
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// calculate the cell index = H( entropy | slotRoot | counter ) `mod` nCells
|
/// 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();
|
let mut hash_inputs:Vec<Target>= Vec::new();
|
||||||
hash_inputs.extend_from_slice(&entropy.elements);
|
hash_inputs.extend_from_slice(&entropy.elements);
|
||||||
hash_inputs.extend_from_slice(&slot_root.elements);
|
hash_inputs.extend_from_slice(&slot_root.elements);
|
||||||
|
|||||||
@ -16,7 +16,7 @@ pub fn ceiling_log2<
|
|||||||
F: RichField + Extendable<D> + Poseidon2,
|
F: RichField + Extendable<D> + Poseidon2,
|
||||||
const D: usize,
|
const D: usize,
|
||||||
>(
|
>(
|
||||||
builder: &mut CircuitBuilder::<F, D>,
|
builder: &mut CircuitBuilder<F, D>,
|
||||||
inp: Target,
|
inp: Target,
|
||||||
n: usize,
|
n: usize,
|
||||||
)-> (Vec<BoolTarget>, Vec<BoolTarget>){
|
)-> (Vec<BoolTarget>, Vec<BoolTarget>){
|
||||||
@ -29,7 +29,7 @@ pub fn ceiling_log2<
|
|||||||
aux[n] = BoolTarget::new_unsafe(one.clone());
|
aux[n] = BoolTarget::new_unsafe(one.clone());
|
||||||
let mut mask: Vec<BoolTarget> = vec![BoolTarget::new_unsafe(zero.clone()); n + 1];
|
let mut mask: Vec<BoolTarget> = vec![BoolTarget::new_unsafe(zero.clone()); n + 1];
|
||||||
for i in (0..n).rev(){
|
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);
|
let aux_i = builder.mul( aux[i+1].target, diff);
|
||||||
aux[i] = BoolTarget::new_unsafe(aux_i);
|
aux[i] = BoolTarget::new_unsafe(aux_i);
|
||||||
mask[i] = BoolTarget::new_unsafe(builder.sub(one.clone(), aux[i].target));
|
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,
|
const D: usize,
|
||||||
>(builder: &mut CircuitBuilder<F, D>, mut_hot: &mut HashOutTarget, hot: &HashOutTarget) {
|
>(builder: &mut CircuitBuilder<F, D>, mut_hot: &mut HashOutTarget, hot: &HashOutTarget) {
|
||||||
for i in 0..NUM_HASH_OUT_ELTS {
|
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 {
|
HashOutTarget {
|
||||||
elements: core::array::from_fn(|i| builder.select(b, h0.elements[i], h1.elements[i])),
|
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}")]
|
#[error("Path bits and max depth mismatch: path bits length {0}, max depth {1}")]
|
||||||
PathBitsMaxDepthMismatch(usize, usize),
|
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}")]
|
#[error("Insufficient input elements for chunk; expected {0}, found {1}")]
|
||||||
InsufficientInputs (usize, usize),
|
InsufficientInputs (usize, usize),
|
||||||
|
|
||||||
@ -44,4 +38,34 @@ pub enum CircuitError {
|
|||||||
|
|
||||||
#[error("Failed to assign HashTarget {0}: {1}")]
|
#[error("Failed to assign HashTarget {0}: {1}")]
|
||||||
HashTargetAssignmentError(String, String),
|
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 circuits;
|
||||||
// pub mod merkle_tree;
|
pub mod recursion;
|
||||||
// pub mod recursion;
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
|
||||||
pub type Result<T> = core::result::Result<T, error::CircuitError>;
|
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::target::Target;
|
||||||
use plonky2::iop::witness::PartialWitness;
|
use plonky2::iop::witness::PartialWitness;
|
||||||
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
||||||
use plonky2::plonk::circuit_data::CommonCircuitData;
|
use plonky2::plonk::circuit_data::CommonCircuitData;
|
||||||
|
use plonky2_field::extension::Extendable;
|
||||||
|
use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2;
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
use crate::params::{F, D};
|
|
||||||
|
|
||||||
/// InnerCircuit is the trait used to define the logic of the circuit and assign witnesses
|
/// InnerCircuit is the trait used to define the logic of the circuit and assign witnesses
|
||||||
/// to that circuit instance.
|
/// to that circuit instance.
|
||||||
pub trait InnerCircuit<
|
pub trait InnerCircuit<
|
||||||
// TODO: make it generic for F and D ?
|
F: RichField + Extendable<D> + Poseidon2,
|
||||||
|
const D: usize,
|
||||||
> {
|
> {
|
||||||
type Targets;
|
type Targets;
|
||||||
type Input:Clone;
|
type Input:Clone;
|
||||||
|
|
||||||
/// build the circuit logic and return targets to be assigned later
|
/// build the circuit logic and return targets to be assigned later
|
||||||
|
/// based on register_pi, registers the public input or not.
|
||||||
fn build(
|
fn build(
|
||||||
&self,
|
&self,
|
||||||
builder: &mut CircuitBuilder<F, D>,
|
builder: &mut CircuitBuilder<F, D>,
|
||||||
|
register_pi: bool
|
||||||
) -> Result<Self::Targets>;
|
) -> Result<Self::Targets>;
|
||||||
|
|
||||||
/// assign the actual witness values for the current instance of the circuit.
|
/// assign the actual witness values for the current instance of the circuit.
|
||||||
@ -33,8 +38,7 @@ pub trait InnerCircuit<
|
|||||||
targets: &Self::Targets,
|
targets: &Self::Targets,
|
||||||
) -> Vec<Target>;
|
) -> Vec<Target>;
|
||||||
|
|
||||||
/// from the set of the targets, return only the targets which are public
|
/// get the common data for the inner-circuit
|
||||||
/// TODO: this can probably be replaced with enum for Public/Private targets
|
|
||||||
fn get_common_data(
|
fn get_common_data(
|
||||||
&self
|
&self
|
||||||
) -> Result<(CommonCircuitData<F, D>)>;
|
) -> 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::target::Target;
|
||||||
use plonky2::iop::witness::PartialWitness;
|
use plonky2::iop::witness::PartialWitness;
|
||||||
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
||||||
use plonky2::plonk::circuit_data::{CircuitConfig, CommonCircuitData};
|
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::params::CircuitParams;
|
||||||
use crate::circuits::sample_cells::{SampleCircuit, SampleCircuitInput, SampleTargets};
|
use crate::circuits::sample_cells::{SampleCircuit, SampleCircuitInput, SampleTargets};
|
||||||
use crate::params::{D, F, C};
|
|
||||||
use crate::recursion::circuits::inner_circuit::InnerCircuit;
|
use crate::recursion::circuits::inner_circuit::InnerCircuit;
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
|
|
||||||
/// recursion Inner circuit for the sampling circuit
|
/// recursion Inner circuit for the sampling circuit
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct SamplingRecursion {
|
pub struct SamplingRecursion<
|
||||||
pub sampling_circ: SampleCircuit<F,D>,
|
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 {
|
impl<
|
||||||
pub fn new(circ_params: CircuitParams) -> Self{
|
F: RichField + Extendable<D> + Poseidon2,
|
||||||
let sampling_circ = SampleCircuit::new(circ_params);
|
const D: usize,
|
||||||
|
H: AlgebraicHasher<F>,
|
||||||
|
C: GenericConfig<D, F = F>,
|
||||||
|
> SamplingRecursion<F, D, H, C> {
|
||||||
|
pub fn new(circ_params:CircuitParams) -> Self {
|
||||||
Self{
|
Self{
|
||||||
sampling_circ,
|
sampling_circ: SampleCircuit::new(circ_params),
|
||||||
}
|
phantom_data: PhantomData::default(),
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for SamplingRecursion {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self{
|
|
||||||
sampling_circ: SampleCircuit::new(CircuitParams::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 Targets = SampleTargets;
|
||||||
type Input = SampleCircuitInput<F, D>;
|
type Input = SampleCircuitInput<F, D>;
|
||||||
|
|
||||||
/// build the circuit
|
/// build the circuit
|
||||||
fn build(&self, builder: &mut CircuitBuilder<F, D>) -> Result<Self::Targets> {
|
fn build(&self, builder: &mut CircuitBuilder<F, D>, register_pi: bool) -> Result<Self::Targets> {
|
||||||
self.sampling_circ.sample_slot_circuit(builder)
|
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<()> {
|
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
|
/// return the common circuit data for the sampling circuit
|
||||||
/// uses the `standard_recursion_config`
|
/// uses the `standard_recursion_config`
|
||||||
|
/// TODO: make it generic for any config
|
||||||
fn get_common_data(&self) -> Result<(CommonCircuitData<F, D>)> {
|
fn get_common_data(&self) -> Result<(CommonCircuitData<F, D>)> {
|
||||||
let config = CircuitConfig::standard_recursion_config();
|
let config = CircuitConfig::standard_recursion_config();
|
||||||
let mut builder = CircuitBuilder::<F, D>::new(config);
|
let mut builder = CircuitBuilder::<F, D>::new(config);
|
||||||
|
|||||||
@ -3,8 +3,8 @@
|
|||||||
// into another cyclic circle.
|
// into another cyclic circle.
|
||||||
|
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use plonky2::hash::hash_types::{HashOut, HashOutTarget, RichField};
|
use plonky2::hash::hash_types::{HashOutTarget, RichField};
|
||||||
use plonky2::iop::target::{BoolTarget, Target};
|
use plonky2::iop::target::{BoolTarget};
|
||||||
use plonky2::iop::witness::{PartialWitness, WitnessWrite};
|
use plonky2::iop::witness::{PartialWitness, WitnessWrite};
|
||||||
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
||||||
use plonky2::plonk::circuit_data::{CircuitConfig, CircuitData, CommonCircuitData, VerifierCircuitTarget};
|
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::plonk::proof::{ProofWithPublicInputs, ProofWithPublicInputsTarget};
|
||||||
use plonky2::recursion::dummy_circuit::cyclic_base_proof;
|
use plonky2::recursion::dummy_circuit::cyclic_base_proof;
|
||||||
use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2;
|
use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2;
|
||||||
use crate::params::{F,D,C,Plonky2Proof,H};
|
|
||||||
use crate::recursion::circuits::inner_circuit::InnerCircuit;
|
use crate::recursion::circuits::inner_circuit::InnerCircuit;
|
||||||
use plonky2::gates::noop::NoopGate;
|
use plonky2::gates::noop::NoopGate;
|
||||||
use plonky2::recursion::cyclic_recursion::check_cyclic_proof_verifier_data;
|
use plonky2::recursion::cyclic_recursion::check_cyclic_proof_verifier_data;
|
||||||
|
use plonky2_field::extension::Extendable;
|
||||||
use crate::circuits::utils::select_hash;
|
use crate::circuits::utils::select_hash;
|
||||||
|
use crate::error::CircuitError;
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
|
|
||||||
/// cyclic circuit struct
|
/// cyclic circuit struct
|
||||||
/// contains necessary data
|
/// contains necessary data
|
||||||
/// note: only keeps track of latest proof not all proofs.
|
/// note: only keeps track of latest proof not all proofs.
|
||||||
pub struct CyclicCircuit<
|
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 layer: usize,
|
||||||
pub circ: I,
|
pub circ: I,
|
||||||
pub cyclic_target: Option<CyclicCircuitTargets<I>>,
|
pub cyclic_target: CyclicCircuitTargets<F, D, I>,
|
||||||
pub cyclic_circuit_data: Option<CircuitData<F, C, D>>,
|
pub cyclic_circuit_data: CircuitData<F, C, D>,
|
||||||
pub common_data: Option<CommonCircuitData<F, D>>,
|
pub common_data: CommonCircuitData<F, D>,
|
||||||
pub latest_proof: Option<ProofWithPublicInputs<F, C, D>>,
|
pub latest_proof: Option<ProofWithPublicInputs<F, C, D>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// targets need to be assigned for the cyclic circuit
|
/// targets need to be assigned for the cyclic circuit
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CyclicCircuitTargets<
|
pub struct CyclicCircuitTargets<
|
||||||
I: InnerCircuit,
|
F: RichField + Extendable<D> + Poseidon2,
|
||||||
|
const D: usize,
|
||||||
|
I: InnerCircuit<F, D>,
|
||||||
>{
|
>{
|
||||||
pub inner_targets: I::Targets,
|
pub inner_targets: I::Targets,
|
||||||
pub condition: BoolTarget,
|
pub condition: BoolTarget,
|
||||||
@ -45,40 +51,32 @@ pub struct CyclicCircuitTargets<
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<
|
impl<
|
||||||
I: InnerCircuit,
|
F: RichField + Extendable<D> + Poseidon2,
|
||||||
> CyclicCircuit<I> {
|
const D: usize,
|
||||||
|
I: InnerCircuit<F, D>,
|
||||||
/// create a new cyclic circuit
|
C: GenericConfig<D, F = F> + 'static,
|
||||||
pub fn new(circ: I) -> Self{
|
> CyclicCircuit<F, D, I, C> where
|
||||||
Self{
|
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
|
||||||
layer: 0,
|
{
|
||||||
circ,
|
|
||||||
cyclic_target: None,
|
|
||||||
cyclic_circuit_data: None,
|
|
||||||
common_data: None,
|
|
||||||
latest_proof: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// builds the cyclic recursion circuit using any inner circuit I
|
/// builds the cyclic recursion circuit using any inner circuit I
|
||||||
/// returns the circuit data
|
/// return the circuit data
|
||||||
pub fn build_circuit(
|
pub fn build_circuit<
|
||||||
&mut self,
|
H: AlgebraicHasher<F>,
|
||||||
) -> Result<()>{
|
>(
|
||||||
// if the circuit data is already build then no need to rebuild
|
// &mut self,
|
||||||
if self.cyclic_circuit_data.is_some(){
|
inner_circuit: I
|
||||||
return Ok(());
|
) -> Result<(Self)>{
|
||||||
}
|
|
||||||
|
|
||||||
// builder with standard recursion config
|
// builder with standard recursion config
|
||||||
let config = CircuitConfig::standard_recursion_config();
|
let config = CircuitConfig::standard_recursion_config();
|
||||||
let mut builder = CircuitBuilder::<F, D>::new(config);
|
let mut builder = CircuitBuilder::<F, D>::new(config);
|
||||||
|
|
||||||
//build the inner circuit
|
//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
|
// 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
|
// the hash of the public input
|
||||||
let pub_input_hash = builder.add_virtual_hash_public_input();
|
let pub_input_hash = builder.add_virtual_hash_public_input();
|
||||||
// verifier data for inner proofs
|
// verifier data for inner proofs
|
||||||
@ -93,9 +91,9 @@ impl<
|
|||||||
let inner_cyclic_proof_with_pis = builder.add_virtual_proof_with_pis(&common_data);
|
let inner_cyclic_proof_with_pis = builder.add_virtual_proof_with_pis(&common_data);
|
||||||
// get the hash of the pub input
|
// get the hash of the pub input
|
||||||
let inner_cyclic_pis = &inner_cyclic_proof_with_pis.public_inputs;
|
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
|
// 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 outer_pi_hash = builder.hash_n_to_hash_no_pad::<H>(outer_pis);
|
||||||
let zero_hash = HashOutTarget::from_vec([builder.zero(); 4].to_vec());
|
let zero_hash = HashOutTarget::from_vec([builder.zero(); 4].to_vec());
|
||||||
// if leaf pad with zeros
|
// if leaf pad with zeros
|
||||||
@ -108,30 +106,34 @@ impl<
|
|||||||
// connect this up one to `pub_input_hash`
|
// connect this up one to `pub_input_hash`
|
||||||
builder.connect_hashes(pub_input_hash,outer_pi_hash);
|
builder.connect_hashes(pub_input_hash,outer_pi_hash);
|
||||||
|
|
||||||
// connect entropy?
|
|
||||||
|
|
||||||
// verify proof in-circuit
|
// verify proof in-circuit
|
||||||
builder.conditionally_verify_cyclic_proof_or_dummy::<C>(
|
builder.conditionally_verify_cyclic_proof_or_dummy::<C>(
|
||||||
condition,
|
condition,
|
||||||
&inner_cyclic_proof_with_pis,
|
&inner_cyclic_proof_with_pis,
|
||||||
&common_data,
|
&common_data,
|
||||||
)?;
|
).map_err(|e| CircuitError::ConditionalVerificationError(e.to_string()))?;
|
||||||
|
|
||||||
// build the cyclic circuit
|
// build the cyclic circuit
|
||||||
let cyclic_circuit_data = builder.build::<C>();
|
let cyclic_circuit_data = builder.build::<C>();
|
||||||
|
|
||||||
// assign targets
|
// assign targets
|
||||||
let cyc_t = CyclicCircuitTargets::<I>{
|
let cyc_t = CyclicCircuitTargets::<F,D,I>{
|
||||||
inner_targets: inner_t,
|
inner_targets: inner_t,
|
||||||
condition,
|
condition,
|
||||||
inner_cyclic_proof_with_pis,
|
inner_cyclic_proof_with_pis,
|
||||||
verifier_data: verifier_data_target
|
verifier_data: verifier_data_target
|
||||||
};
|
};
|
||||||
// assign the data
|
|
||||||
self.cyclic_circuit_data = Some(cyclic_circuit_data);
|
Ok(
|
||||||
self.common_data = Some(common_data);
|
Self{
|
||||||
self.cyclic_target = Some(cyc_t);
|
layer: 0,
|
||||||
Ok(())
|
circ: inner_circuit,
|
||||||
|
cyclic_target: cyc_t,
|
||||||
|
cyclic_circuit_data,
|
||||||
|
common_data,
|
||||||
|
latest_proof: None,
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// generates a proof with only one recursion layer
|
/// generates a proof with only one recursion layer
|
||||||
@ -141,21 +143,20 @@ impl<
|
|||||||
circ_input: &I::Input,
|
circ_input: &I::Input,
|
||||||
) -> Result<ProofWithPublicInputs<F, C, D>>{
|
) -> Result<ProofWithPublicInputs<F, C, D>>{
|
||||||
|
|
||||||
if self.cyclic_circuit_data.is_none(){
|
let circ_data = &self.cyclic_circuit_data;
|
||||||
panic!("circuit data not found") // TODO: replace with err
|
let cyc_targets = &self.cyclic_target;
|
||||||
}
|
let common_data = &self.common_data;
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
// assign targets
|
// assign targets
|
||||||
let mut pw = PartialWitness::new();
|
let mut pw = PartialWitness::new();
|
||||||
self.circ.assign_targets(&mut pw,&cyc_targets.inner_targets,&circ_input)?;
|
self.circ.assign_targets(&mut pw,&cyc_targets.inner_targets,&circ_input)?;
|
||||||
|
|
||||||
// if leaf add dummy proof
|
// if leaf add dummy proof
|
||||||
if(self.layer == 0) {
|
if self.layer == 0 {
|
||||||
pw.set_bool_target(cyc_targets.condition, false)?;
|
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>(
|
pw.set_proof_with_pis_target::<C, D>(
|
||||||
&cyc_targets.inner_cyclic_proof_with_pis,
|
&cyc_targets.inner_cyclic_proof_with_pis,
|
||||||
&cyclic_base_proof(
|
&cyclic_base_proof(
|
||||||
@ -163,22 +164,40 @@ impl<
|
|||||||
&circ_data.verifier_only,
|
&circ_data.verifier_only,
|
||||||
HashMap::new(),
|
HashMap::new(),
|
||||||
),
|
),
|
||||||
|
).map_err(|e|
|
||||||
|
CircuitError::ProofTargetAssignmentError("cyclic proof".to_string(),e.to_string()),
|
||||||
)?;
|
)?;
|
||||||
}else{ // else add last proof
|
}else{ // else add last proof
|
||||||
pw.set_bool_target(cyc_targets.condition, true)?;
|
pw.set_bool_target(cyc_targets.condition, true)
|
||||||
let last_proof = self.latest_proof.as_ref().unwrap();
|
.map_err(|e|
|
||||||
pw.set_proof_with_pis_target(&cyc_targets.inner_cyclic_proof_with_pis, last_proof)?;
|
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
|
// 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
|
// 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 that the correct verifier data is consistent
|
||||||
check_cyclic_proof_verifier_data(
|
check_cyclic_proof_verifier_data(
|
||||||
&proof,
|
&proof,
|
||||||
&circ_data.verifier_only,
|
&circ_data.verifier_only,
|
||||||
&circ_data.common,
|
&circ_data.common,
|
||||||
|
).map_err(
|
||||||
|
|e| CircuitError::RecursiveProofVerifierDataCheckError(e.to_string())
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
self.latest_proof = Some(proof.clone());
|
self.latest_proof = Some(proof.clone());
|
||||||
@ -188,65 +207,76 @@ impl<
|
|||||||
|
|
||||||
/// prove n recursive layers
|
/// prove n recursive layers
|
||||||
/// the function takes
|
/// the function takes
|
||||||
/// - n: the number of layers and
|
|
||||||
/// - circ_input: vector of n inputs
|
/// - circ_input: vector of n inputs
|
||||||
pub fn prove_n_layers(
|
pub fn prove_n_layers(
|
||||||
&mut self,
|
&mut self,
|
||||||
n: usize,
|
|
||||||
circ_input: Vec<I::Input>,
|
circ_input: Vec<I::Input>,
|
||||||
) -> Result<ProofWithPublicInputs<F, C, D>>{
|
) -> Result<ProofWithPublicInputs<F, C, D>>{
|
||||||
|
|
||||||
// asserts that n equals the number of input
|
for i in 0..circ_input.len() {
|
||||||
assert_eq!(n, circ_input.len()); // TODO: replace with err
|
|
||||||
|
|
||||||
for i in 0..n {
|
|
||||||
self.prove_one_layer(&circ_input[i])?;
|
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
|
/// verifies the latest proof generated
|
||||||
pub fn verify_latest_proof(
|
pub fn verify_latest_proof(
|
||||||
&mut self,
|
&mut self,
|
||||||
) -> Result<()>{
|
) -> 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(())
|
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 simple;
|
||||||
pub mod tree1;
|
pub mod tree1;
|
||||||
pub mod tree2;
|
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;
|
||||||
|
pub mod simple_recursion_hashed_pi;
|
||||||
pub mod simple_tree_recursion;
|
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
|
// 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::iop::witness::{PartialWitness, WitnessWrite};
|
||||||
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
||||||
use plonky2::plonk::circuit_data::{VerifierCircuitData, VerifierCircuitTarget};
|
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::plonk::proof::{ProofWithPublicInputs, ProofWithPublicInputsTarget};
|
||||||
|
use plonky2_field::extension::Extendable;
|
||||||
use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2;
|
use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2;
|
||||||
|
use crate::error::CircuitError;
|
||||||
use crate::recursion::circuits::inner_circuit::InnerCircuit;
|
use crate::recursion::circuits::inner_circuit::InnerCircuit;
|
||||||
use crate::params::{C, D, F, Plonky2Proof};
|
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
|
|
||||||
/// aggregate sampling proofs
|
// ---------------------- Simple recursion Approach 1 ---------------------------
|
||||||
/// 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,
|
|
||||||
// The simple approach here separates the build (setting the targets) and assigning the witness.
|
// 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<
|
pub struct SimpleRecursionCircuit<
|
||||||
I: InnerCircuit,
|
F: RichField + Extendable<D> + Poseidon2,
|
||||||
|
const D: usize,
|
||||||
|
I: InnerCircuit<F, D>,
|
||||||
const N: usize,
|
const N: usize,
|
||||||
>{
|
C: GenericConfig<D, F = F>,
|
||||||
|
> {
|
||||||
pub inner_circuit: I,
|
pub inner_circuit: I,
|
||||||
|
phantom_data: PhantomData<(F,C)>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SimpleRecursionTargets<
|
pub struct SimpleRecursionTargets<
|
||||||
|
const D: usize,
|
||||||
> {
|
> {
|
||||||
pub proofs_with_pi: Vec<ProofWithPublicInputsTarget<D>>,
|
pub proofs_with_pi: Vec<ProofWithPublicInputsTarget<D>>,
|
||||||
pub verifier_data: VerifierCircuitTarget,
|
pub verifier_data: VerifierCircuitTarget,
|
||||||
@ -108,6 +39,9 @@ pub struct SimpleRecursionTargets<
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct SimpleRecursionInput<
|
pub struct SimpleRecursionInput<
|
||||||
|
F: RichField + Extendable<D> + Poseidon2,
|
||||||
|
const D: usize,
|
||||||
|
C: GenericConfig<D, F = F>,
|
||||||
>{
|
>{
|
||||||
pub proofs: Vec<ProofWithPublicInputs<F, C, D>>,
|
pub proofs: Vec<ProofWithPublicInputs<F, C, D>>,
|
||||||
pub verifier_data: VerifierCircuitData<F, C, D>,
|
pub verifier_data: VerifierCircuitData<F, C, D>,
|
||||||
@ -115,9 +49,13 @@ pub struct SimpleRecursionInput<
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<
|
impl<
|
||||||
I: InnerCircuit,
|
F: RichField + Extendable<D> + Poseidon2,
|
||||||
|
const D: usize,
|
||||||
|
I: InnerCircuit<F, D>,
|
||||||
const N: usize,
|
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(
|
pub fn new(
|
||||||
@ -125,20 +63,22 @@ impl<
|
|||||||
)->Self{
|
)->Self{
|
||||||
Self{
|
Self{
|
||||||
inner_circuit,
|
inner_circuit,
|
||||||
|
phantom_data: PhantomData::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// contains the circuit logic and returns the witness & public input targets
|
/// contains the circuit logic and returns the witness & public input targets
|
||||||
pub fn build_circuit(
|
pub fn build_circuit<
|
||||||
|
>(
|
||||||
&self,
|
&self,
|
||||||
builder: &mut CircuitBuilder::<F, D>,
|
builder: &mut CircuitBuilder<F, D>,
|
||||||
) -> anyhow::Result<SimpleRecursionTargets> {
|
) -> Result<SimpleRecursionTargets<D>>{
|
||||||
// the proof virtual targets
|
// the proof virtual targets
|
||||||
let mut proof_targets = vec![];
|
let mut proof_targets = vec![];
|
||||||
let mut inner_entropy_targets = vec![];
|
let mut inner_entropy_targets = vec![];
|
||||||
let inner_common = self.inner_circuit.get_common_data()?;
|
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);
|
let vir_proof = builder.add_virtual_proof_with_pis(&inner_common);
|
||||||
// register the inner public input as public input
|
// register the inner public input as public input
|
||||||
// only register the slot index and dataset root, entropy later
|
// only register the slot index and dataset root, entropy later
|
||||||
@ -183,26 +123,32 @@ impl<
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// assign the targets
|
/// assign the targets
|
||||||
pub fn assign_witness(
|
pub fn assign_witness<
|
||||||
|
>(
|
||||||
&self,
|
&self,
|
||||||
pw: &mut PartialWitness<F>,
|
pw: &mut PartialWitness<F>,
|
||||||
targets: &SimpleRecursionTargets,
|
targets: &SimpleRecursionTargets<D>,
|
||||||
witnesses: SimpleRecursionInput,
|
witnesses: SimpleRecursionInput<F, D, C>,
|
||||||
) -> anyhow::Result<()>{
|
) -> Result<()>{
|
||||||
// assign the proofs with public input
|
// assign the proofs with public input
|
||||||
for i in 0..N{
|
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
|
// assign the verifier data
|
||||||
pw.set_cap_target(
|
pw.set_verifier_data_target(&targets.verifier_data, &witnesses.verifier_data.verifier_only)
|
||||||
&targets.verifier_data.constants_sigmas_cap,
|
.map_err(|e| {
|
||||||
&witnesses.verifier_data.verifier_only.constants_sigmas_cap,
|
CircuitError::VerifierDataTargetAssignmentError(e.to_string())
|
||||||
)?;
|
})?;
|
||||||
pw.set_hash_target(targets.verifier_data.circuit_digest, witnesses.verifier_data.verifier_only.circuit_digest)?;
|
|
||||||
|
|
||||||
// set the entropy hash target
|
// 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(())
|
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::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::plonk::circuit_builder::CircuitBuilder;
|
||||||
use plonky2::iop::witness::PartialWitness;
|
use plonky2::iop::witness::{PartialWitness, WitnessWrite};
|
||||||
use crate::params::{C, D, F};
|
use plonky2::plonk::config::{AlgebraicHasher, GenericConfig};
|
||||||
use crate::recursion::simple::simple_recursion;
|
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
|
// recursion tree width or the number of proofs in each node in the tree
|
||||||
const RECURSION_TREE_WIDTH: usize = 2;
|
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
|
/// aggregate sampling proofs in tree like structure
|
||||||
/// uses the const params: `RECURSION_TREE_WIDTH`
|
/// uses the const params: `RECURSION_TREE_WIDTH`
|
||||||
/// In this tree approach the building is done at each level -> very slow!
|
/// In this tree approach the building is done at each level -> very slow!
|
||||||
pub fn aggregate_sampling_proofs_tree(
|
/// takes `VerifierCircuitData`
|
||||||
proofs_with_pi: &[ProofWithPublicInputs<F, C, D>],
|
pub fn aggregate_sampling_proofs_tree
|
||||||
data: CircuitData<F, C, D>,
|
<
|
||||||
) -> anyhow::Result<(ProofWithPublicInputs<F, C, D>, CircuitData<F, C, D>)> {
|
F: RichField + Extendable<D> + Poseidon2,
|
||||||
// base case: if only one proof remains, return it
|
const D: usize,
|
||||||
if proofs_with_pi.len() == 1 {
|
C: GenericConfig<D, F = F>,
|
||||||
return Ok((proofs_with_pi[0].clone(), data));
|
H: AlgebraicHasher<F>
|
||||||
}
|
>(
|
||||||
|
|
||||||
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(
|
|
||||||
proofs_with_pi: &[ProofWithPublicInputs<F, C, D>],
|
proofs_with_pi: &[ProofWithPublicInputs<F, C, D>],
|
||||||
vd: VerifierCircuitData<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 {
|
if proofs_with_pi.len() == 1 {
|
||||||
return Ok((proofs_with_pi[0].clone(), vd));
|
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_builder = CircuitBuilder::<F, D>::new(inner_config);
|
||||||
let mut inner_pw = PartialWitness::new();
|
let mut inner_pw = PartialWitness::new();
|
||||||
|
|
||||||
simple_recursion::aggregate_sampling_proofs(
|
aggregate_sampling_proofs::<F,D,C,H>(
|
||||||
&proofs_chunk,
|
&proofs_chunk,
|
||||||
&vd,
|
&vd,
|
||||||
&mut inner_builder,
|
&mut inner_builder,
|
||||||
@ -82,10 +109,11 @@ pub fn aggregate_sampling_proofs_tree2(
|
|||||||
|
|
||||||
let inner_data = inner_builder.build::<C>();
|
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_proofs.push(proof);
|
||||||
new_circuit_data = Some(inner_data.verifier_data());
|
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::iop::witness::{PartialWitness, WitnessWrite};
|
||||||
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
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 plonky2::plonk::proof::{ProofWithPublicInputs, ProofWithPublicInputsTarget};
|
||||||
use crate::circuits::params::CircuitParams;
|
use plonky2_field::extension::Extendable;
|
||||||
use crate::circuits::sample_cells::SampleCircuit;
|
use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2;
|
||||||
use crate::params::{C, D, F, H};
|
|
||||||
use crate::recursion::circuits::inner_circuit::InnerCircuit;
|
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)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct LeafCircuit<
|
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 {
|
pub fn new(inner_circ: I) -> Self {
|
||||||
Self{
|
Self{
|
||||||
// sampling_circ: SampleCircuit::new(CircuitParams::default()),
|
|
||||||
inner_circ,
|
inner_circ,
|
||||||
|
phantom_data:PhantomData::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct LeafTargets {
|
pub struct LeafTargets <
|
||||||
|
const D: usize,
|
||||||
|
>{
|
||||||
pub inner_proof: ProofWithPublicInputsTarget<D>,
|
pub inner_proof: ProofWithPublicInputsTarget<D>,
|
||||||
pub verifier_data: VerifierCircuitTarget,
|
pub verifier_data: VerifierCircuitTarget,
|
||||||
}
|
}
|
||||||
#[derive(Clone, Debug)]
|
#[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 inner_proof: ProofWithPublicInputs<F, C, D>,
|
||||||
pub verifier_data: VerifierCircuitData<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
|
/// 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()?;
|
let common = self.inner_circ.get_common_data()?;
|
||||||
|
|
||||||
@ -67,34 +92,47 @@ impl<I: InnerCircuit> LeafCircuit<I>{
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// assign the leaf targets with given input
|
/// 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
|
// 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
|
// assign the verifier data
|
||||||
pw.set_cap_target(
|
pw.set_verifier_data_target(&targets.verifier_data, &input.verifier_data.verifier_only)
|
||||||
&targets.verifier_data.constants_sigmas_cap,
|
.map_err(|e| {
|
||||||
&input.verifier_data.verifier_only.constants_sigmas_cap,
|
CircuitError::VerifierDataTargetAssignmentError(e.to_string())
|
||||||
)?;
|
})?;
|
||||||
pw.set_hash_target(targets.verifier_data.circuit_digest, input.verifier_data.verifier_only.circuit_digest)?;
|
|
||||||
|
|
||||||
Ok(())
|
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 leaf_circuit;
|
||||||
pub mod tree_recursion2;
|
pub mod dummy_gen;
|
||||||
pub mod utils;
|
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