diff --git a/codex-plonky2-circuits/src/circuits/merkle_circuit.rs b/codex-plonky2-circuits/src/circuits/merkle_circuit.rs index f0d6ab1..3d43d2e 100644 --- a/codex-plonky2-circuits/src/circuits/merkle_circuit.rs +++ b/codex-plonky2-circuits/src/circuits/merkle_circuit.rs @@ -2,7 +2,6 @@ // consistent with the one in codex: // https://github.com/codex-storage/codex-storage-proofs-circuits/blob/master/circuit/codex/merkle.circom -// use anyhow::Result; use plonky2::{ field::{extension::Extendable, types::Field}, hash::hash_types::{HashOutTarget, RichField, NUM_HASH_OUT_ELTS}, @@ -15,7 +14,6 @@ use std::marker::PhantomData; use plonky2::plonk::config::AlgebraicHasher; use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2; use crate::circuits::keyed_compress::key_compress_circuit; -// use crate::circuits::params::HF; use crate::circuits::utils::{add_assign_hash_out_target, mul_hash_out_target}; use crate::Result; use crate::error::CircuitError; diff --git a/codex-plonky2-circuits/src/circuits/sample_cells.rs b/codex-plonky2-circuits/src/circuits/sample_cells.rs index cfdea9d..ff8c59b 100644 --- a/codex-plonky2-circuits/src/circuits/sample_cells.rs +++ b/codex-plonky2-circuits/src/circuits/sample_cells.rs @@ -131,7 +131,7 @@ impl< /// samples and registers the public input pub fn sample_slot_circuit_with_public_input( &self, - builder: &mut CircuitBuilder::, + builder: &mut CircuitBuilder, ) -> Result { let targets = self.sample_slot_circuit(builder)?; let mut pub_targets = vec![]; @@ -246,7 +246,7 @@ impl< let ctr_target = builder.constant(F::from_canonical_u64((i+1) as u64)); let mut ctr = builder.add_virtual_hash(); for i in 0..ctr.elements.len() { - if(i==0){ + if i==0 { ctr.elements[i] = ctr_target; }else{ ctr.elements[i] = zero.clone(); @@ -321,7 +321,7 @@ impl< } /// calculate the cell index = H( entropy | slotRoot | counter ) `mod` nCells - pub fn calculate_cell_index_bits(&self, builder: &mut CircuitBuilder::, entropy: &HashOutTarget, slot_root: &HashOutTarget, ctr: &HashOutTarget, mask_bits: Vec) -> Result> { + pub fn calculate_cell_index_bits(&self, builder: &mut CircuitBuilder, entropy: &HashOutTarget, slot_root: &HashOutTarget, ctr: &HashOutTarget, mask_bits: Vec) -> Result> { let mut hash_inputs:Vec= Vec::new(); hash_inputs.extend_from_slice(&entropy.elements); hash_inputs.extend_from_slice(&slot_root.elements); diff --git a/codex-plonky2-circuits/src/circuits/utils.rs b/codex-plonky2-circuits/src/circuits/utils.rs index 49f95e9..0fc910d 100644 --- a/codex-plonky2-circuits/src/circuits/utils.rs +++ b/codex-plonky2-circuits/src/circuits/utils.rs @@ -16,7 +16,7 @@ pub fn ceiling_log2< F: RichField + Extendable + Poseidon2, const D: usize, >( - builder: &mut CircuitBuilder::, + builder: &mut CircuitBuilder, inp: Target, n: usize, )-> (Vec, Vec){ @@ -29,7 +29,7 @@ pub fn ceiling_log2< aux[n] = BoolTarget::new_unsafe(one.clone()); let mut mask: Vec = vec![BoolTarget::new_unsafe(zero.clone()); n + 1]; for i in (0..n).rev(){ - let diff = (builder.sub(one.clone(), last_bits[i].target)); + let diff = builder.sub(one.clone(), last_bits[i].target); let aux_i = builder.mul( aux[i+1].target, diff); aux[i] = BoolTarget::new_unsafe(aux_i); mask[i] = BoolTarget::new_unsafe(builder.sub(one.clone(), aux[i].target)); @@ -102,7 +102,7 @@ pub fn add_assign_hash_out_target< const D: usize, >(builder: &mut CircuitBuilder, mut_hot: &mut HashOutTarget, hot: &HashOutTarget) { for i in 0..NUM_HASH_OUT_ELTS { - mut_hot.elements[i] = (builder.add(mut_hot.elements[i], hot.elements[i])); + mut_hot.elements[i] = builder.add(mut_hot.elements[i], hot.elements[i]); } } @@ -125,4 +125,13 @@ pub fn select_hash< HashOutTarget { elements: core::array::from_fn(|i| builder.select(b, h0.elements[i], h1.elements[i])), } -} \ No newline at end of file +} + +/// Converts a Vec into a fixed-size array [T; N], returning an error if the lengths don't match. +pub fn vec_to_array(vec: Vec) -> Result<[T; N]> { + vec.try_into().map_err(|v: Vec| CircuitError::ArrayLengthMismatchError(format!( + "Expected exactly {} elements, got {}", + N, + v.len() + ))) +} diff --git a/codex-plonky2-circuits/src/error.rs b/codex-plonky2-circuits/src/error.rs index bc62762..74ba35c 100644 --- a/codex-plonky2-circuits/src/error.rs +++ b/codex-plonky2-circuits/src/error.rs @@ -15,12 +15,6 @@ pub enum CircuitError { #[error("Path bits and max depth mismatch: path bits length {0}, max depth {1}")] PathBitsMaxDepthMismatch(usize, usize), - #[error("Sibling hash at depth {0} has invalid length: expected {1}, found {2}")] - SiblingHashInvalidLength(usize, usize, usize), - - #[error("Invalid path bits: expected {0}, found {1}")] - InvalidPathBits(usize, usize), - #[error("Insufficient input elements for chunk; expected {0}, found {1}")] InsufficientInputs (usize, usize), @@ -44,4 +38,34 @@ pub enum CircuitError { #[error("Failed to assign HashTarget {0}: {1}")] HashTargetAssignmentError(String, String), + + #[error("Failed to assign ProofTarget {0}: {1}")] + ProofTargetAssignmentError(String, String), + + #[error("Failed to assign VerifierDataTarget {0}")] + VerifierDataTargetAssignmentError(String), + + #[error("Array Length Mismatch Error {0}")] + ArrayLengthMismatchError(String), + + #[error("Proof Verification Failed {0}")] + InvalidProofError(String), + + #[error("Proof Generation Failed {0}")] + ProofGenerationError(String), + + #[error("Error in Recursion Tree: {0}")] + RecursionTreeError(String), + + #[error("Dummy Proof Generation Error: {0}")] + DummyProofGenerationError(String), + + #[error("Conditional Verification Error: {0}")] + ConditionalVerificationError(String), + + #[error("Recursive Proof VerifierData Check Failed: {0}")] + RecursiveProofVerifierDataCheckError(String), + + #[error("Expected Option {0} to contain value")] + OptionError(String), } \ No newline at end of file diff --git a/codex-plonky2-circuits/src/lib.rs b/codex-plonky2-circuits/src/lib.rs index 27a298c..c67e5be 100644 --- a/codex-plonky2-circuits/src/lib.rs +++ b/codex-plonky2-circuits/src/lib.rs @@ -1,6 +1,5 @@ pub mod circuits; -// pub mod merkle_tree; -// pub mod recursion; +pub mod recursion; pub mod error; pub type Result = core::result::Result; diff --git a/codex-plonky2-circuits/src/recursion/circuits/inner_circuit.rs b/codex-plonky2-circuits/src/recursion/circuits/inner_circuit.rs index 2d37720..1eb5c3f 100644 --- a/codex-plonky2-circuits/src/recursion/circuits/inner_circuit.rs +++ b/codex-plonky2-circuits/src/recursion/circuits/inner_circuit.rs @@ -1,22 +1,27 @@ +use plonky2::hash::hash_types::RichField; use plonky2::iop::target::Target; use plonky2::iop::witness::PartialWitness; use plonky2::plonk::circuit_builder::CircuitBuilder; use plonky2::plonk::circuit_data::CommonCircuitData; +use plonky2_field::extension::Extendable; +use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2; use crate::Result; -use crate::params::{F, D}; /// InnerCircuit is the trait used to define the logic of the circuit and assign witnesses /// to that circuit instance. pub trait InnerCircuit< - // TODO: make it generic for F and D ? + F: RichField + Extendable + Poseidon2, + const D: usize, > { type Targets; type Input:Clone; /// build the circuit logic and return targets to be assigned later + /// based on register_pi, registers the public input or not. fn build( &self, builder: &mut CircuitBuilder, + register_pi: bool ) -> Result; /// assign the actual witness values for the current instance of the circuit. @@ -33,8 +38,7 @@ pub trait InnerCircuit< targets: &Self::Targets, ) -> Vec; - /// from the set of the targets, return only the targets which are public - /// TODO: this can probably be replaced with enum for Public/Private targets + /// get the common data for the inner-circuit fn get_common_data( &self ) -> Result<(CommonCircuitData)>; diff --git a/codex-plonky2-circuits/src/recursion/circuits/sampling_inner_circuit.rs b/codex-plonky2-circuits/src/recursion/circuits/sampling_inner_circuit.rs index fd4cd7b..ab40189 100644 --- a/codex-plonky2-circuits/src/recursion/circuits/sampling_inner_circuit.rs +++ b/codex-plonky2-circuits/src/recursion/circuits/sampling_inner_circuit.rs @@ -1,44 +1,60 @@ +use std::marker::PhantomData; +use plonky2::hash::hash_types::RichField; use plonky2::iop::target::Target; use plonky2::iop::witness::PartialWitness; use plonky2::plonk::circuit_builder::CircuitBuilder; use plonky2::plonk::circuit_data::{CircuitConfig, CommonCircuitData}; +use plonky2::plonk::config::{AlgebraicHasher, GenericConfig}; +use plonky2_field::extension::Extendable; +use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2; use crate::circuits::params::CircuitParams; use crate::circuits::sample_cells::{SampleCircuit, SampleCircuitInput, SampleTargets}; -use crate::params::{D, F, C}; use crate::recursion::circuits::inner_circuit::InnerCircuit; use crate::Result; /// recursion Inner circuit for the sampling circuit #[derive(Clone, Debug)] -pub struct SamplingRecursion { - pub sampling_circ: SampleCircuit, +pub struct SamplingRecursion< + F: RichField + Extendable + Poseidon2, + const D: usize, + H: AlgebraicHasher, + C: GenericConfig, +> { + pub sampling_circ: SampleCircuit, + phantom_data: PhantomData, } -impl SamplingRecursion { - pub fn new(circ_params: CircuitParams) -> Self{ - let sampling_circ = SampleCircuit::new(circ_params); +impl< + F: RichField + Extendable + Poseidon2, + const D: usize, + H: AlgebraicHasher, + C: GenericConfig, +> SamplingRecursion { + pub fn new(circ_params:CircuitParams) -> Self { Self{ - sampling_circ, - } - } -} - -impl Default for SamplingRecursion { - fn default() -> Self { - Self{ - sampling_circ: SampleCircuit::new(CircuitParams::default()) + sampling_circ: SampleCircuit::new(circ_params), + phantom_data: PhantomData::default(), } } } -impl InnerCircuit for SamplingRecursion{ +impl< + F: RichField + Extendable + Poseidon2, + const D: usize, + H: AlgebraicHasher, + C: GenericConfig, +> InnerCircuit for SamplingRecursion { type Targets = SampleTargets; type Input = SampleCircuitInput; /// build the circuit - fn build(&self, builder: &mut CircuitBuilder) -> Result { - self.sampling_circ.sample_slot_circuit(builder) + fn build(&self, builder: &mut CircuitBuilder, register_pi: bool) -> Result { + 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, targets: &Self::Targets, input: &Self::Input) -> Result<()> { @@ -58,6 +74,7 @@ impl InnerCircuit for SamplingRecursion{ /// return the common circuit data for the sampling circuit /// uses the `standard_recursion_config` + /// TODO: make it generic for any config fn get_common_data(&self) -> Result<(CommonCircuitData)> { let config = CircuitConfig::standard_recursion_config(); let mut builder = CircuitBuilder::::new(config); diff --git a/codex-plonky2-circuits/src/recursion/cyclic/mod.rs b/codex-plonky2-circuits/src/recursion/cyclic/mod.rs index cab57e9..70ad20f 100644 --- a/codex-plonky2-circuits/src/recursion/cyclic/mod.rs +++ b/codex-plonky2-circuits/src/recursion/cyclic/mod.rs @@ -3,8 +3,8 @@ // into another cyclic circle. use hashbrown::HashMap; -use plonky2::hash::hash_types::{HashOut, HashOutTarget, RichField}; -use plonky2::iop::target::{BoolTarget, Target}; +use plonky2::hash::hash_types::{HashOutTarget, RichField}; +use plonky2::iop::target::{BoolTarget}; use plonky2::iop::witness::{PartialWitness, WitnessWrite}; use plonky2::plonk::circuit_builder::CircuitBuilder; use plonky2::plonk::circuit_data::{CircuitConfig, CircuitData, CommonCircuitData, VerifierCircuitTarget}; @@ -12,31 +12,37 @@ use plonky2::plonk::config::{AlgebraicHasher, GenericConfig}; use plonky2::plonk::proof::{ProofWithPublicInputs, ProofWithPublicInputsTarget}; use plonky2::recursion::dummy_circuit::cyclic_base_proof; use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2; -use crate::params::{F,D,C,Plonky2Proof,H}; use crate::recursion::circuits::inner_circuit::InnerCircuit; use plonky2::gates::noop::NoopGate; use plonky2::recursion::cyclic_recursion::check_cyclic_proof_verifier_data; +use plonky2_field::extension::Extendable; use crate::circuits::utils::select_hash; +use crate::error::CircuitError; use crate::Result; /// cyclic circuit struct /// contains necessary data /// note: only keeps track of latest proof not all proofs. pub struct CyclicCircuit< - I: InnerCircuit, + F: RichField + Extendable + Poseidon2, + const D: usize, + I: InnerCircuit, + C: GenericConfig, >{ pub layer: usize, pub circ: I, - pub cyclic_target: Option>, - pub cyclic_circuit_data: Option>, - pub common_data: Option>, + pub cyclic_target: CyclicCircuitTargets, + pub cyclic_circuit_data: CircuitData, + pub common_data: CommonCircuitData, pub latest_proof: Option>, } /// targets need to be assigned for the cyclic circuit #[derive(Clone)] pub struct CyclicCircuitTargets< - I: InnerCircuit, + F: RichField + Extendable + Poseidon2, + const D: usize, + I: InnerCircuit, >{ pub inner_targets: I::Targets, pub condition: BoolTarget, @@ -45,40 +51,32 @@ pub struct CyclicCircuitTargets< } impl< - I: InnerCircuit, -> CyclicCircuit { - - /// create a new cyclic circuit - pub fn new(circ: I) -> Self{ - Self{ - layer: 0, - circ, - cyclic_target: None, - cyclic_circuit_data: None, - common_data: None, - latest_proof: None, - } - } + F: RichField + Extendable + Poseidon2, + const D: usize, + I: InnerCircuit, + C: GenericConfig + 'static, +> CyclicCircuit where + >::Hasher: AlgebraicHasher +{ /// builds the cyclic recursion circuit using any inner circuit I - /// returns the circuit data - pub fn build_circuit( - &mut self, - ) -> Result<()>{ - // if the circuit data is already build then no need to rebuild - if self.cyclic_circuit_data.is_some(){ - return Ok(()); - } + /// return the circuit data + pub fn build_circuit< + H: AlgebraicHasher, + >( + // &mut self, + inner_circuit: I + ) -> Result<(Self)>{ // builder with standard recursion config let config = CircuitConfig::standard_recursion_config(); let mut builder = CircuitBuilder::::new(config); //build the inner circuit - let inner_t = self.circ.build(& mut builder)?; + let inner_t = inner_circuit.build(& mut builder, false)?; // common data for recursion - let mut common_data = common_data_for_cyclic_recursion(); + let mut common_data = Self::common_data_for_cyclic_recursion(); // the hash of the public input let pub_input_hash = builder.add_virtual_hash_public_input(); // verifier data for inner proofs @@ -93,9 +91,9 @@ impl< let inner_cyclic_proof_with_pis = builder.add_virtual_proof_with_pis(&common_data); // get the hash of the pub input let inner_cyclic_pis = &inner_cyclic_proof_with_pis.public_inputs; - let inner_pub_input_hash = HashOutTarget::try_from(&inner_cyclic_pis[0..4]).unwrap(); + let inner_pub_input_hash = HashOutTarget::from_vec(inner_cyclic_pis[0..4].to_vec()); // now hash the current public input - let outer_pis = I::get_pub_input_targets(&inner_t)?; + let outer_pis = I::get_pub_input_targets(&inner_t); let outer_pi_hash = builder.hash_n_to_hash_no_pad::(outer_pis); let zero_hash = HashOutTarget::from_vec([builder.zero(); 4].to_vec()); // if leaf pad with zeros @@ -108,30 +106,34 @@ impl< // connect this up one to `pub_input_hash` builder.connect_hashes(pub_input_hash,outer_pi_hash); - // connect entropy? - // verify proof in-circuit builder.conditionally_verify_cyclic_proof_or_dummy::( condition, &inner_cyclic_proof_with_pis, &common_data, - )?; + ).map_err(|e| CircuitError::ConditionalVerificationError(e.to_string()))?; // build the cyclic circuit let cyclic_circuit_data = builder.build::(); // assign targets - let cyc_t = CyclicCircuitTargets::{ + let cyc_t = CyclicCircuitTargets::{ inner_targets: inner_t, condition, inner_cyclic_proof_with_pis, verifier_data: verifier_data_target }; - // assign the data - self.cyclic_circuit_data = Some(cyclic_circuit_data); - self.common_data = Some(common_data); - self.cyclic_target = Some(cyc_t); - Ok(()) + + Ok( + Self{ + layer: 0, + circ: inner_circuit, + cyclic_target: cyc_t, + cyclic_circuit_data, + common_data, + latest_proof: None, + } + ) } /// generates a proof with only one recursion layer @@ -141,21 +143,20 @@ impl< circ_input: &I::Input, ) -> Result>{ - if self.cyclic_circuit_data.is_none(){ - panic!("circuit data not found") // TODO: replace with err - } - - let circ_data = self.cyclic_circuit_data.as_ref().unwrap(); - let cyc_targets = self.cyclic_target.as_ref().unwrap(); - let common_data = self.common_data.as_ref().unwrap(); + let circ_data = &self.cyclic_circuit_data; + let cyc_targets = &self.cyclic_target; + let common_data = &self.common_data; // assign targets let mut pw = PartialWitness::new(); self.circ.assign_targets(&mut pw,&cyc_targets.inner_targets,&circ_input)?; // if leaf add dummy proof - if(self.layer == 0) { - pw.set_bool_target(cyc_targets.condition, false)?; + if self.layer == 0 { + pw.set_bool_target(cyc_targets.condition, false) + .map_err(|e| + CircuitError::BoolTargetAssignmentError("condition".to_string(),e.to_string()), + )?; pw.set_proof_with_pis_target::( &cyc_targets.inner_cyclic_proof_with_pis, &cyclic_base_proof( @@ -163,22 +164,40 @@ impl< &circ_data.verifier_only, HashMap::new(), ), + ).map_err(|e| + CircuitError::ProofTargetAssignmentError("cyclic proof".to_string(),e.to_string()), )?; }else{ // else add last proof - pw.set_bool_target(cyc_targets.condition, true)?; - let last_proof = self.latest_proof.as_ref().unwrap(); - pw.set_proof_with_pis_target(&cyc_targets.inner_cyclic_proof_with_pis, last_proof)?; + pw.set_bool_target(cyc_targets.condition, true) + .map_err(|e| + CircuitError::BoolTargetAssignmentError("condition".to_string(),e.to_string()), + )?; + + let last_proof = self.latest_proof + .as_ref() + .ok_or_else(|| CircuitError::OptionError("cyclic proof".to_string()))? + .clone(); + + pw.set_proof_with_pis_target(&cyc_targets.inner_cyclic_proof_with_pis, &last_proof) + .map_err(|e| + CircuitError::ProofTargetAssignmentError("cyclic proof".to_string(),e.to_string()), + )?; } // assign verifier data - pw.set_verifier_data_target(&cyc_targets.verifier_data, &circ_data.verifier_only)?; + pw.set_verifier_data_target(&cyc_targets.verifier_data, &circ_data.verifier_only) + .map_err(|e| CircuitError::VerifierDataTargetAssignmentError(e.to_string()))?; // prove - let proof = circ_data.prove(pw)?; + let proof = circ_data.prove(pw).map_err( + |e| CircuitError::InvalidProofError(e.to_string()) + )?; // check that the correct verifier data is consistent check_cyclic_proof_verifier_data( &proof, &circ_data.verifier_only, &circ_data.common, + ).map_err( + |e| CircuitError::RecursiveProofVerifierDataCheckError(e.to_string()) )?; self.latest_proof = Some(proof.clone()); @@ -188,65 +207,76 @@ impl< /// prove n recursive layers /// the function takes - /// - n: the number of layers and /// - circ_input: vector of n inputs pub fn prove_n_layers( &mut self, - n: usize, circ_input: Vec, ) -> Result>{ - // asserts that n equals the number of input - assert_eq!(n, circ_input.len()); // TODO: replace with err - - for i in 0..n { + for i in 0..circ_input.len() { self.prove_one_layer(&circ_input[i])?; } - Ok(self.latest_proof.clone().unwrap()) + let latest_proofs = self.latest_proof.clone().ok_or(CircuitError::OptionError("proof not found".to_string()))?; + + Ok(latest_proofs) } /// verifies the latest proof generated pub fn verify_latest_proof( &mut self, ) -> Result<()>{ - if(self.cyclic_circuit_data.is_none() || self.latest_proof.is_none()){ - panic!("no circuit data or proof found"); // TODO: replace with err - } - let circ_data = self.cyclic_circuit_data.as_ref().unwrap(); - let proof = self.latest_proof.clone().unwrap(); - circ_data.verify(proof)?; + let proof = self.latest_proof + .as_ref() + .ok_or_else(|| CircuitError::OptionError("cyclic proof".to_string()))? + .clone(); + + // check that the correct verifier data is consistent + //TODO: test if it works with only one layer proof + check_cyclic_proof_verifier_data( + &proof, + &self.cyclic_circuit_data.verifier_only, + &self.cyclic_circuit_data.common, + ).map_err( + |e| CircuitError::RecursiveProofVerifierDataCheckError(e.to_string()) + )?; + + + self.cyclic_circuit_data.verify(proof).map_err( + |e| CircuitError::InvalidProofError(e.to_string()) + )?; Ok(()) } + + /// Generates `CommonCircuitData` usable for recursion. + pub fn common_data_for_cyclic_recursion() -> CommonCircuitData + { + // layer 1 + let config = CircuitConfig::standard_recursion_config(); + let builder = CircuitBuilder::::new(config); + let data = builder.build::(); + // layer 2 + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::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::(&proof, &verifier_data, &data.common); + let data = builder.build::(); + // layer 3 + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::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::(&proof, &verifier_data, &data.common); + // pad with noop gates + while builder.num_gates() < 1 << 12 { + builder.add_gate(NoopGate, vec![]); + } + builder.build::().common + } } -/// Generates `CommonCircuitData` usable for recursion. -pub fn common_data_for_cyclic_recursion() -> CommonCircuitData -{ - // layer 1 - let config = CircuitConfig::standard_recursion_config(); - let builder = CircuitBuilder::::new(config); - let data = builder.build::(); - // layer 2 - let config = CircuitConfig::standard_recursion_config(); - let mut builder = CircuitBuilder::::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::(&proof, &verifier_data, &data.common); - let data = builder.build::(); - // layer 3 - let config = CircuitConfig::standard_recursion_config(); - let mut builder = CircuitBuilder::::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::(&proof, &verifier_data, &data.common); - // pad with noop gates - while builder.num_gates() < 1 << 12 { - builder.add_gate(NoopGate, vec![]); - } - builder.build::().common -} \ No newline at end of file diff --git a/codex-plonky2-circuits/src/recursion/mod.rs b/codex-plonky2-circuits/src/recursion/mod.rs index cf65683..d924c51 100644 --- a/codex-plonky2-circuits/src/recursion/mod.rs +++ b/codex-plonky2-circuits/src/recursion/mod.rs @@ -3,4 +3,3 @@ pub mod circuits; pub mod simple; pub mod tree1; pub mod tree2; -pub mod params; diff --git a/codex-plonky2-circuits/src/recursion/params.rs b/codex-plonky2-circuits/src/recursion/params.rs deleted file mode 100644 index 235d7d7..0000000 --- a/codex-plonky2-circuits/src/recursion/params.rs +++ /dev/null @@ -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; - diff --git a/codex-plonky2-circuits/src/recursion/simple/mod.rs b/codex-plonky2-circuits/src/recursion/simple/mod.rs index 98e64d5..224ff24 100644 --- a/codex-plonky2-circuits/src/recursion/simple/mod.rs +++ b/codex-plonky2-circuits/src/recursion/simple/mod.rs @@ -1,2 +1,3 @@ pub mod simple_recursion; +pub mod simple_recursion_hashed_pi; pub mod simple_tree_recursion; diff --git a/codex-plonky2-circuits/src/recursion/simple/simple_recursion.rs b/codex-plonky2-circuits/src/recursion/simple/simple_recursion.rs index 50737b1..4e0cc1b 100644 --- a/codex-plonky2-circuits/src/recursion/simple/simple_recursion.rs +++ b/codex-plonky2-circuits/src/recursion/simple/simple_recursion.rs @@ -1,106 +1,37 @@ -// this file is mainly draft implementation and experimentation of multiple simple approaches // the simple aggregation approach is verifying N proofs in-circuit and generating one final proof -use plonky2::hash::hash_types::{HashOut, HashOutTarget}; +use std::marker::PhantomData; +use plonky2::hash::hash_types::{HashOut, HashOutTarget, RichField}; use plonky2::iop::witness::{PartialWitness, WitnessWrite}; use plonky2::plonk::circuit_builder::CircuitBuilder; use plonky2::plonk::circuit_data::{VerifierCircuitData, VerifierCircuitTarget}; -use plonky2::plonk::config::GenericConfig; +use plonky2::plonk::config::{AlgebraicHasher, GenericConfig}; use plonky2::plonk::proof::{ProofWithPublicInputs, ProofWithPublicInputsTarget}; +use plonky2_field::extension::Extendable; use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2; +use crate::error::CircuitError; use crate::recursion::circuits::inner_circuit::InnerCircuit; -use crate::params::{C, D, F, Plonky2Proof}; use crate::Result; -/// aggregate sampling proofs -/// This function takes: -/// - N number of proofs (it has to be sampling proofs here) -/// - verifier_data of the sampling circuit -/// - circuit builder -/// - partial witness -/// -/// The function doesn't return anything but sets the targets in the builder and assigns the witness -pub fn aggregate_sampling_proofs< ->( - proofs_with_pi: &Vec, - verifier_data: &VerifierCircuitData, - builder: &mut CircuitBuilder::, - pw: &mut PartialWitness, -)-> 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::(&proof_targets[i],&inner_verifier_data,&verifier_data.common); - } - - // register entropy as public input - let outer_entropy_target = builder.add_virtual_hash_public_input(); - let entropy_as_hash = HashOut::from_vec( - [ - proofs_with_pi[0].public_inputs[num_pub_input-4], - proofs_with_pi[0].public_inputs[num_pub_input-3], - proofs_with_pi[0].public_inputs[num_pub_input-2], - proofs_with_pi[0].public_inputs[num_pub_input-1] - ].to_vec() - ); // entropy is last 4 elements - pw.set_hash_target(outer_entropy_target, entropy_as_hash)?; - // connect the public input of the recursion circuit to the inner proofs - for i in 0..proofs_with_pi.len() { - for j in 0..4 { - builder.connect(inner_entropy_targets[i][j], outer_entropy_target.elements[j]); - } - } - - Ok(()) -} - -// ---------------------- Simple Approach 2 --------------------------- -// this is still simple recursion approach but written differently, +// ---------------------- Simple recursion Approach 1 --------------------------- // The simple approach here separates the build (setting the targets) and assigning the witness. +// the public input of the inner-proofs is the public input of the final proof except that +// the entropy is expected to be the same therefore only one entropy public input is in the final proof pub struct SimpleRecursionCircuit< - I: InnerCircuit, + F: RichField + Extendable + Poseidon2, + const D: usize, + I: InnerCircuit, const N: usize, ->{ + C: GenericConfig, +> { pub inner_circuit: I, + phantom_data: PhantomData<(F,C)> } #[derive(Clone)] pub struct SimpleRecursionTargets< + const D: usize, > { pub proofs_with_pi: Vec>, pub verifier_data: VerifierCircuitTarget, @@ -108,6 +39,9 @@ pub struct SimpleRecursionTargets< } pub struct SimpleRecursionInput< + F: RichField + Extendable + Poseidon2, + const D: usize, + C: GenericConfig, >{ pub proofs: Vec>, pub verifier_data: VerifierCircuitData, @@ -115,9 +49,13 @@ pub struct SimpleRecursionInput< } impl< - I: InnerCircuit, + F: RichField + Extendable + Poseidon2, + const D: usize, + I: InnerCircuit, const N: usize, -> SimpleRecursionCircuit + C: GenericConfig, +> SimpleRecursionCircuit where + >::Hasher: AlgebraicHasher, { pub fn new( @@ -125,20 +63,22 @@ impl< )->Self{ Self{ inner_circuit, + phantom_data: PhantomData::default(), } } /// contains the circuit logic and returns the witness & public input targets - pub fn build_circuit( + pub fn build_circuit< + >( &self, - builder: &mut CircuitBuilder::, - ) -> anyhow::Result { + builder: &mut CircuitBuilder, + ) -> Result>{ // the proof virtual targets let mut proof_targets = vec![]; let mut inner_entropy_targets = vec![]; let inner_common = self.inner_circuit.get_common_data()?; - for i in 0..N { + for _ in 0..N { let vir_proof = builder.add_virtual_proof_with_pis(&inner_common); // register the inner public input as public input // only register the slot index and dataset root, entropy later @@ -183,26 +123,32 @@ impl< } /// assign the targets - pub fn assign_witness( + pub fn assign_witness< + >( &self, pw: &mut PartialWitness, - targets: &SimpleRecursionTargets, - witnesses: SimpleRecursionInput, - ) -> anyhow::Result<()>{ + targets: &SimpleRecursionTargets, + witnesses: SimpleRecursionInput, + ) -> Result<()>{ // assign the proofs with public input for i in 0..N{ - pw.set_proof_with_pis_target(&targets.proofs_with_pi[i],&witnesses.proofs[i])?; + pw.set_proof_with_pis_target(&targets.proofs_with_pi[i],&witnesses.proofs[i]) + .map_err(|e| { + CircuitError::ProofTargetAssignmentError(format!("proof {}", i), e.to_string()) + })?; } // assign the verifier data - pw.set_cap_target( - &targets.verifier_data.constants_sigmas_cap, - &witnesses.verifier_data.verifier_only.constants_sigmas_cap, - )?; - pw.set_hash_target(targets.verifier_data.circuit_digest, witnesses.verifier_data.verifier_only.circuit_digest)?; + pw.set_verifier_data_target(&targets.verifier_data, &witnesses.verifier_data.verifier_only) + .map_err(|e| { + CircuitError::VerifierDataTargetAssignmentError(e.to_string()) + })?; // set the entropy hash target - pw.set_hash_target(targets.entropy, witnesses.entropy)?; + pw.set_hash_target(targets.entropy, witnesses.entropy) + .map_err(|e| { + CircuitError::HashTargetAssignmentError("entropy".to_string(), e.to_string()) + })?; Ok(()) diff --git a/codex-plonky2-circuits/src/recursion/simple/simple_recursion_hashed_pi.rs b/codex-plonky2-circuits/src/recursion/simple/simple_recursion_hashed_pi.rs new file mode 100644 index 0000000..adf29a0 --- /dev/null +++ b/codex-plonky2-circuits/src/recursion/simple/simple_recursion_hashed_pi.rs @@ -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 + Poseidon2, + const D: usize, + I: InnerCircuit, + const N: usize, + C: GenericConfig, +> { + pub inner_circuit: I, + phantom_data: PhantomData<(F,C)> +} + +#[derive(Clone)] +pub struct SimpleRecursionTargetsHashedPI< + const D: usize, +> { + pub proofs_with_pi: Vec>, + pub verifier_data: VerifierCircuitTarget, +} + +pub struct SimpleRecursionInputHashedPI< + F: RichField + Extendable + Poseidon2, + const D: usize, + C: GenericConfig, +>{ + pub proofs: Vec>, + pub verifier_data: VerifierCircuitData, +} + +impl< + F: RichField + Extendable + Poseidon2, + const D: usize, + I: InnerCircuit, + const N: usize, + C: GenericConfig, +> SimpleRecursionCircuitHashedPI where + >::Hasher: AlgebraicHasher, +{ + + 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, + >( + &self, + builder: &mut CircuitBuilder, + ) -> Result>{ + // 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::(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::(&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, + targets: &SimpleRecursionTargetsHashedPI, + witnesses: SimpleRecursionInputHashedPI, + ) -> 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(()) + + } +} \ No newline at end of file diff --git a/codex-plonky2-circuits/src/recursion/simple/simple_tree_recursion.rs b/codex-plonky2-circuits/src/recursion/simple/simple_tree_recursion.rs index 1bf71ed..b717777 100644 --- a/codex-plonky2-circuits/src/recursion/simple/simple_tree_recursion.rs +++ b/codex-plonky2-circuits/src/recursion/simple/simple_tree_recursion.rs @@ -1,64 +1,91 @@ +use plonky2::hash::hash_types::RichField; use plonky2::plonk::proof::ProofWithPublicInputs; -use plonky2::plonk::circuit_data::{CircuitConfig, CircuitData, VerifierCircuitData}; +use plonky2::plonk::circuit_data::{CircuitConfig, VerifierCircuitData}; use plonky2::plonk::circuit_builder::CircuitBuilder; -use plonky2::iop::witness::PartialWitness; -use crate::params::{C, D, F}; -use crate::recursion::simple::simple_recursion; +use plonky2::iop::witness::{PartialWitness, WitnessWrite}; +use plonky2::plonk::config::{AlgebraicHasher, GenericConfig}; +use plonky2_field::extension::Extendable; +use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2; +use crate::error::CircuitError; +use crate::Result; // recursion tree width or the number of proofs in each node in the tree const RECURSION_TREE_WIDTH: usize = 2; +/// aggregate sampling proofs +/// This function takes: +/// - N number of proofs (it has to be sampling proofs here) +/// - verifier_data of the sampling circuit +/// - circuit builder +/// - partial witness +/// +/// The function doesn't return anything but sets the targets in the builder and assigns the witness +pub fn aggregate_sampling_proofs< + F: RichField + Extendable + Poseidon2, + const D: usize, + C: GenericConfig, + H: AlgebraicHasher +>( + proofs_with_pi: &Vec>, + verifier_data: &VerifierCircuitData, + builder: &mut CircuitBuilder, + pw: &mut PartialWitness, +)-> Result<()>where + >::Hasher: AlgebraicHasher +{ + // 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::(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::(&proof_targets[i],&inner_verifier_data,&verifier_data.common); + } + + Ok(()) +} + /// aggregate sampling proofs in tree like structure /// uses the const params: `RECURSION_TREE_WIDTH` /// In this tree approach the building is done at each level -> very slow! -pub fn aggregate_sampling_proofs_tree( - proofs_with_pi: &[ProofWithPublicInputs], - data: CircuitData, -) -> anyhow::Result<(ProofWithPublicInputs, CircuitData)> { - // base case: if only one proof remains, return it - if proofs_with_pi.len() == 1 { - return Ok((proofs_with_pi[0].clone(), data)); - } - - let mut new_proofs = vec![]; - let mut new_circuit_data: Option> = 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::::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::(); - - // Prove the inner-circuit - let proof = inner_data.prove(inner_pw)?; - new_proofs.push(proof); - new_circuit_data = Some(inner_data); - } - - // Recursively aggregate the new proofs - aggregate_sampling_proofs_tree(&new_proofs, new_circuit_data.unwrap()) -} - -/// same as above but takes `VerifierCircuitData` -pub fn aggregate_sampling_proofs_tree2( +/// takes `VerifierCircuitData` +pub fn aggregate_sampling_proofs_tree +< + F: RichField + Extendable + Poseidon2, + const D: usize, + C: GenericConfig, + H: AlgebraicHasher +>( proofs_with_pi: &[ProofWithPublicInputs], vd: VerifierCircuitData -) -> anyhow::Result<(ProofWithPublicInputs, VerifierCircuitData)> { +) -> Result<(ProofWithPublicInputs, VerifierCircuitData)> where + >::Hasher: AlgebraicHasher +{ if proofs_with_pi.len() == 1 { return Ok((proofs_with_pi[0].clone(), vd)); } @@ -73,7 +100,7 @@ pub fn aggregate_sampling_proofs_tree2( let mut inner_builder = CircuitBuilder::::new(inner_config); let mut inner_pw = PartialWitness::new(); - simple_recursion::aggregate_sampling_proofs( + aggregate_sampling_proofs::( &proofs_chunk, &vd, &mut inner_builder, @@ -82,10 +109,11 @@ pub fn aggregate_sampling_proofs_tree2( let inner_data = inner_builder.build::(); - let proof = inner_data.prove(inner_pw)?; + let proof = inner_data.prove(inner_pw) + .map_err(|e| CircuitError::ProofGenerationError(e.to_string()))?; new_proofs.push(proof); new_circuit_data = Some(inner_data.verifier_data()); } - aggregate_sampling_proofs_tree2(&new_proofs, new_circuit_data.unwrap()) + aggregate_sampling_proofs_tree::(&new_proofs, new_circuit_data.unwrap()) } diff --git a/codex-plonky2-circuits/src/recursion/tree1/mod.rs b/codex-plonky2-circuits/src/recursion/tree1/mod.rs index ca40b68..d4d7a1b 100644 --- a/codex-plonky2-circuits/src/recursion/tree1/mod.rs +++ b/codex-plonky2-circuits/src/recursion/tree1/mod.rs @@ -1 +1,2 @@ -pub mod tree_recursion; +pub mod tree_circuit; +pub mod node_circuit; diff --git a/codex-plonky2-circuits/src/recursion/tree1/node_circuit.rs b/codex-plonky2-circuits/src/recursion/tree1/node_circuit.rs new file mode 100644 index 0000000..ef3c1b3 --- /dev/null +++ b/codex-plonky2-circuits/src/recursion/tree1/node_circuit.rs @@ -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 + Poseidon2, + const D: usize, + I: InnerCircuit, + const M: usize, + const N: usize, + C: GenericConfig, +>{ + pub circ: I, + pub cyclic_target: NodeCircuitTargets, + pub cyclic_circuit_data: CircuitData, + pub common_data: CommonCircuitData, +} + +/// Node circuit targets +/// assumes that all inner proofs use the same verifier data +#[derive(Clone, Debug)] +pub struct NodeCircuitTargets< + F: RichField + Extendable + Poseidon2, + const D: usize, + I: InnerCircuit, + const M: usize, + const N: usize, +>{ + pub inner_targets: [I::Targets; M], + pub condition: BoolTarget, + pub inner_proofs_with_pis: [ProofWithPublicInputsTarget; N], + pub verifier_data: VerifierCircuitTarget, +} + +impl< + F: RichField + Extendable + Poseidon2, + const D: usize, + I: InnerCircuit, + const M: usize, + const N: usize, + C: GenericConfig + 'static, +> NodeCircuit where + >::Hasher: AlgebraicHasher +{ + + /// 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, + >( + inner_circ: I, + ) -> Result<(Self)>{ + + // builder with standard recursion config + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::new(config); + + //build M inner circuits + let inner_t: [I::Targets; M] = + vec_to_array::( + (0..M) + .map(|_| inner_circ.build(&mut builder, false)) + .collect::>>()? + )?; + + // 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; N] = + vec_to_array::>( + (0..N) + .map(|_| builder.add_virtual_proof_with_pis(&common_data)) + .collect::>() + )?; + + // 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::(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::(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::(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::(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::( + 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::(); + + // assign targets + let cyc_t = NodeCircuitTargets::{ + 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; N]>, + pw: &mut PartialWitness, + 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::( + &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> + { + // layer 1 + let config = CircuitConfig::standard_recursion_config(); + let builder = CircuitBuilder::::new(config); + let data = builder.build::(); + + // layer 2 + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::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::(&proof, &verifier_data, &data.common); + } + let data = builder.build::(); + + // layer 3 + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::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::(&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::().common) + } + +} diff --git a/codex-plonky2-circuits/src/recursion/tree1/tree_circuit.rs b/codex-plonky2-circuits/src/recursion/tree1/tree_circuit.rs new file mode 100644 index 0000000..463b0e4 --- /dev/null +++ b/codex-plonky2-circuits/src/recursion/tree1/tree_circuit.rs @@ -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 + Poseidon2, + const D: usize, + I: InnerCircuit, + const M: usize, + const N: usize, + C: GenericConfig, +>{ + pub node_circ: NodeCircuit +} + +impl< + F: RichField + Extendable + Poseidon2, + const D: usize, + I: InnerCircuit, + const M: usize, + const N: usize, + C: GenericConfig + 'static, +> TreeRecursion where + >::Hasher: AlgebraicHasher +{ + + pub fn build< + H: AlgebraicHasher, + >( + 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; N]>, + is_leaf: bool, + ) -> Result>{ + + 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, + depth: usize, + ) -> Result>{ + // 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> = 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; N] = cur_proofs + .drain(..N) + .collect::>() + .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 + ) -> 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(()) + } +} diff --git a/codex-plonky2-circuits/src/recursion/tree1/tree_recursion.rs b/codex-plonky2-circuits/src/recursion/tree1/tree_recursion.rs deleted file mode 100644 index 6ae3469..0000000 --- a/codex-plonky2-circuits/src/recursion/tree1/tree_recursion.rs +++ /dev/null @@ -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 -} - -impl< - I: InnerCircuit, - const M: usize, - const N: usize, -> TreeRecursion { - - pub fn new(node_circ: NodeCircuit) -> 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; N]>, - is_leaf: bool, - ) -> Result>{ - - 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, - depth: usize, - ) -> Result>{ - // 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> = 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; N] = cur_proofs - .drain(..N) - .collect::>() - .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 - ) -> 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>, - pub cyclic_circuit_data: Option>, - pub common_data: Option>, -} - -/// 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; N], - pub verifier_data: VerifierCircuitTarget, -} - -impl< - I: InnerCircuit, - const M: usize, - const N: usize, -> NodeCircuit { - - /// 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::::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::>>()? - .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; N] = (0..N) - .map(|_| builder.add_virtual_proof_with_pis(&common_data)) - .collect::>() - .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::(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::(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::(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::(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::( - condition, - &inner_cyclic_proof_with_pis[i], - &common_data, - )?; - } - - // build the cyclic circuit - let cyclic_circuit_data = builder.build::(); - - // assign targets - let cyc_t = NodeCircuitTargets::{ - 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; N]>, - pw: &mut PartialWitness, - 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::( - &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> - { - // layer 1 - let config = CircuitConfig::standard_recursion_config(); - let builder = CircuitBuilder::::new(config); - let data = builder.build::(); - - // layer 2 - let config = CircuitConfig::standard_recursion_config(); - let mut builder = CircuitBuilder::::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::(&proof, &verifier_data, &data.common); - } - let data = builder.build::(); - - // layer 3 - let config = CircuitConfig::standard_recursion_config(); - let mut builder = CircuitBuilder::::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::(&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::().common) - } - -} diff --git a/codex-plonky2-circuits/src/recursion/tree2/dummy_gen.rs b/codex-plonky2-circuits/src/recursion/tree2/dummy_gen.rs new file mode 100644 index 0000000..a051d29 --- /dev/null +++ b/codex-plonky2-circuits/src/recursion/tree2/dummy_gen.rs @@ -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 + Poseidon2, + const D: usize, + C: GenericConfig, + > where + >::Hasher: AlgebraicHasher +{ + phantom_data: PhantomData<(F,C)>, +} + +impl< + F: RichField + Extendable + Poseidon2, + const D: usize, + C: GenericConfig, +> DummyProofGen + where + >::Hasher: AlgebraicHasher +{ + + /// Generates a single dummy leaf proof. + pub fn gen_dummy_leaf_proof( + common_data: &CommonCircuitData, + ) -> Result> { + dummy_proof::(&dummy_circuit::(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, + node_verifier_only_data: &VerifierOnlyCircuitData, + ) -> ProofWithPublicInputs { + 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( + common_data: &CommonCircuitData, + ) -> Result<[ProofWithPublicInputs; N]> { + let dummy_proof = Self::gen_dummy_leaf_proof(common_data)?; + let n_dummy_vec = (0..N).map(|_| dummy_proof.clone()).collect::>(); + vec_to_array::>(n_dummy_vec) + } + + /// Generates an array of `N` dummy node proofs. + pub fn gen_n_dummy_node_proofs( + node_common: &CommonCircuitData, + node_verifier_only_data: &VerifierOnlyCircuitData, + ) -> Result<[ProofWithPublicInputs; 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_to_array::>(n_dummy_vec) + } +} + + + diff --git a/codex-plonky2-circuits/src/recursion/tree2/leaf_circuit.rs b/codex-plonky2-circuits/src/recursion/tree2/leaf_circuit.rs index 4650b45..1a396c9 100644 --- a/codex-plonky2-circuits/src/recursion/tree2/leaf_circuit.rs +++ b/codex-plonky2-circuits/src/recursion/tree2/leaf_circuit.rs @@ -1,44 +1,69 @@ +use std::marker::PhantomData; +use plonky2::hash::hash_types::RichField; use plonky2::iop::witness::{PartialWitness, WitnessWrite}; use plonky2::plonk::circuit_builder::CircuitBuilder; -use plonky2::plonk::circuit_data::{CircuitConfig, CircuitData, CommonCircuitData, VerifierCircuitData, VerifierCircuitTarget}; +use plonky2::plonk::circuit_data::{CircuitConfig, CircuitData, VerifierCircuitData, VerifierCircuitTarget}; +use plonky2::plonk::config::{AlgebraicHasher, GenericConfig}; use plonky2::plonk::proof::{ProofWithPublicInputs, ProofWithPublicInputsTarget}; -use crate::circuits::params::CircuitParams; -use crate::circuits::sample_cells::SampleCircuit; -use crate::params::{C, D, F, H}; +use plonky2_field::extension::Extendable; +use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2; use crate::recursion::circuits::inner_circuit::InnerCircuit; -use crate::recursion::circuits::sampling_inner_circuit::SamplingRecursion; +use crate::{error::CircuitError,Result}; -/// recursion Inner circuit for the sampling circuit +/// recursion leaf circuit for the recursion tree circuit #[derive(Clone, Debug)] pub struct LeafCircuit< - I: InnerCircuit + F: RichField + Extendable + Poseidon2, + const D: usize, + I: InnerCircuit > { - pub inner_circ: I + pub inner_circ: I, + phantom_data: PhantomData } -impl LeafCircuit { +impl< + F: RichField + Extendable + Poseidon2, + const D: usize, + I: InnerCircuit +> LeafCircuit { pub fn new(inner_circ: I) -> Self { Self{ - // sampling_circ: SampleCircuit::new(CircuitParams::default()), inner_circ, + phantom_data:PhantomData::default(), } } } #[derive(Clone, Debug)] -pub struct LeafTargets { +pub struct LeafTargets < + const D: usize, +>{ pub inner_proof: ProofWithPublicInputsTarget, pub verifier_data: VerifierCircuitTarget, } #[derive(Clone, Debug)] -pub struct LeafInput{ +pub struct LeafInput< + F: RichField + Extendable + Poseidon2, + const D: usize, + C: GenericConfig, +>{ pub inner_proof: ProofWithPublicInputs, pub verifier_data: VerifierCircuitData } -impl LeafCircuit{ +impl< + F: RichField + Extendable + Poseidon2, + const D: usize, + I: InnerCircuit, +> LeafCircuit{ /// build the leaf circuit - pub fn build(&self, builder: &mut CircuitBuilder) -> anyhow::Result { + pub fn build< + C: GenericConfig, + H: AlgebraicHasher, + >(&self, builder: &mut CircuitBuilder) -> Result> + where + >::Hasher: AlgebraicHasher + { let common = self.inner_circ.get_common_data()?; @@ -67,34 +92,47 @@ impl LeafCircuit{ } /// assign the leaf targets with given input - pub fn assign_targets(&self, pw: &mut PartialWitness, targets: &LeafTargets, input: &LeafInput) -> anyhow::Result<()> { + pub fn assign_targets< + C: GenericConfig, + H: AlgebraicHasher, + >(&self, pw: &mut PartialWitness, targets: &LeafTargets, input: &LeafInput) -> Result<()> + where + >::Hasher: AlgebraicHasher + { // assign the proof - pw.set_proof_with_pis_target(&targets.inner_proof, &input.inner_proof)?; + pw.set_proof_with_pis_target(&targets.inner_proof,&input.inner_proof) + .map_err(|e| { + CircuitError::ProofTargetAssignmentError("inner-proof".to_string(), e.to_string()) + })?; // assign the verifier data - pw.set_cap_target( - &targets.verifier_data.constants_sigmas_cap, - &input.verifier_data.verifier_only.constants_sigmas_cap, - )?; - pw.set_hash_target(targets.verifier_data.circuit_digest, input.verifier_data.verifier_only.circuit_digest)?; + pw.set_verifier_data_target(&targets.verifier_data, &input.verifier_data.verifier_only) + .map_err(|e| { + CircuitError::VerifierDataTargetAssignmentError(e.to_string()) + })?; Ok(()) } + /// returns the leaf circuit data + /// TODO: make generic recursion config + pub fn get_circuit_data< + C: GenericConfig, + H: AlgebraicHasher, + >(&self) -> Result> + where + >::Hasher: AlgebraicHasher + { + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::new(config); + + self.build::(&mut builder)?; + + let circ_data = builder.build::(); + + 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>{ - let config = CircuitConfig::standard_recursion_config(); - let mut builder = CircuitBuilder::::new(config); - let inner_circ = SamplingRecursion::default(); - let leaf = LeafCircuit::new(inner_circ); - leaf.build(&mut builder)?; - - let circ_data = builder.build::(); - - Ok(circ_data) -} diff --git a/codex-plonky2-circuits/src/recursion/tree2/mod.rs b/codex-plonky2-circuits/src/recursion/tree2/mod.rs index 5d322f9..827e1e4 100644 --- a/codex-plonky2-circuits/src/recursion/tree2/mod.rs +++ b/codex-plonky2-circuits/src/recursion/tree2/mod.rs @@ -1,3 +1,4 @@ pub mod leaf_circuit; -pub mod tree_recursion2; -pub mod utils; +pub mod dummy_gen; +pub mod node_circuit; +pub mod tree_circuit; diff --git a/codex-plonky2-circuits/src/recursion/tree2/node_circuit.rs b/codex-plonky2-circuits/src/recursion/tree2/node_circuit.rs new file mode 100644 index 0000000..36668cb --- /dev/null +++ b/codex-plonky2-circuits/src/recursion/tree2/node_circuit.rs @@ -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 + Poseidon2, + const D: usize, + C: GenericConfig, + const N: usize, +>{ + pub node_targets: NodeCircuitTargets, + pub node_data: NodeData, +} + +/// 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; N], + pub condition: BoolTarget, + pub node_proofs: [ProofWithPublicInputsTarget; N], + pub leaf_verifier_data: VerifierCircuitTarget, +} + +/// Node common data and verifier data +#[derive(Debug)] +pub struct NodeData< + F: RichField + Extendable + Poseidon2, + const D: usize, + C: GenericConfig, +>{ + pub node_circuit_data: CircuitData, + pub inner_node_common_data: CommonCircuitData, + pub leaf_circuit_data: CircuitData, +} + +impl< + F: RichField + Extendable + Poseidon2, + const D: usize, + C: GenericConfig + 'static, + const N: usize, +> NodeCircuit + where + >::Hasher: AlgebraicHasher +{ + + /// 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, + H: AlgebraicHasher + >( + leaf_circuit:LeafCircuit + ) -> Result>{ + + // builder with standard recursion config + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::new(config); + + // circuit data for leaf + let leaf_circ_data = leaf_circuit.get_circuit_data::()?; + // 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::(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::( + 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; N] = + vec_to_array::>( + (0..N) + .map(|_| builder.add_virtual_proof_with_pis(&common_data)) + .collect::>() + )?; + + // 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::(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::( + 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::(); + + // collect the leaf proofs + let leaf_proofs: [ProofWithPublicInputsTarget; N] = + vec_to_array::>( + (0..N).map(|i| { + leaf_proofs[i].clone() + }).collect::>() + )?; + + // store targets + let node_targets = NodeCircuitTargets::{ + 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, + leaf_proofs: [ProofWithPublicInputs; N], + node_proofs: [ProofWithPublicInputs; N], + leaf_verifier_only_data: &VerifierOnlyCircuitData, + pw: &mut PartialWitness, + 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::( + &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::( + &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> + { + // layer 1 + let config = CircuitConfig::standard_recursion_config(); + let builder = CircuitBuilder::::new(config); + let data = builder.build::(); + + // layer 2 + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::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::(&proof, &verifier_data, &data.common); + } + let data = builder.build::(); + + // layer 3 + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::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::(&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::().common) + } + +} \ No newline at end of file diff --git a/codex-plonky2-circuits/src/recursion/tree2/tree_circuit.rs b/codex-plonky2-circuits/src/recursion/tree2/tree_circuit.rs new file mode 100644 index 0000000..bde3de6 --- /dev/null +++ b/codex-plonky2-circuits/src/recursion/tree2/tree_circuit.rs @@ -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 + Poseidon2, + const D: usize, + C: GenericConfig + 'static, + const N: usize, +> where + >::Hasher: AlgebraicHasher +{ + pub node: NodeCircuit +} + +impl< + F: RichField + Extendable + Poseidon2, + const D: usize, + C: GenericConfig + 'static, + const N: usize, +> TreeRecursion where + >::Hasher: AlgebraicHasher +{ + + + + pub fn build< + I: InnerCircuit, + H: AlgebraicHasher, + >( + leaf_circuit: LeafCircuit + ) -> Result{ + Ok( + Self{ + node: NodeCircuit::::build_circuit::(leaf_circuit)?, + } + ) + } + + /// generates a proof - only one node + /// takes N proofs + pub fn prove( + &mut self, + leaf_proofs: [ProofWithPublicInputs; N], + node_proofs: [ProofWithPublicInputs; N], + is_leaf: bool, + ) -> Result>{ + + 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>, + ) -> Result> { + // 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::::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::::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; N] = + vec_to_array::>(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, + 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(()) + } +} + diff --git a/codex-plonky2-circuits/src/recursion/tree2/tree_recursion2.rs b/codex-plonky2-circuits/src/recursion/tree2/tree_recursion2.rs deleted file mode 100644 index 561e4c0..0000000 --- a/codex-plonky2-circuits/src/recursion/tree2/tree_recursion2.rs +++ /dev/null @@ -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 -} - -impl< - const N: usize, -> TreeRecursion { - - // pub fn new(node_circ: NodeCircuit) -> Self{ - // Self{ - // node_circ, - // } - // } - - pub fn build( - ) -> Result{ - Ok( - Self{ - node: NodeCircuit::::build_circuit()?, - } - ) - } - - /// generates a proof - only one node - /// takes N proofs - pub fn prove( - &mut self, - // node_targets: NodeCircuitTargets, - leaf_proof_options: Option<[ProofWithPublicInputs; N]>, - node_proof_options: Option<[ProofWithPublicInputs; N]>, - // leaf_verifier_only_data: &VerifierOnlyCircuitData, - is_leaf: bool, - ) -> Result>{ - - 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>, - ) -> Result> { - // 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; N] = (0..N) - .map(|_| dummy_node_proof.clone()) - .collect::>() - .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; N] = (0..N) - .map(|_| dummy_leaf_proof.clone()) - .collect::>() - .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; 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 - ) -> 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, - 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; N], - pub condition: BoolTarget, - pub node_proofs: [ProofWithPublicInputsTarget; N], - pub leaf_verifier_data: VerifierCircuitTarget, -} - -/// Node common data and verifier data -#[derive(Debug)] -pub struct NodeData< ->{ - pub node_circuit_data: CircuitData, - pub inner_node_common_data: CommonCircuitData, - pub leaf_circuit_data: CircuitData, -} - -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>{ - - // builder with standard recursion config - let config = CircuitConfig::standard_recursion_config(); - let mut builder = CircuitBuilder::::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::(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::(condition,&leaf_proofs[i],&leaf_verifier_data, &leaf_common)?; - } - - // common data for recursion - let mut common_data = utils::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; N] = (0..N) - .map(|_| builder.add_virtual_proof_with_pis(&common_data)) - .collect::>() - .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::(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::( - flipped_condition, - &inner_cyclic_proof_with_pis[i], - &common_data, - )?; - } - - // build the node circuit - let node_circuit_data = builder.build::(); - - // collect the leaf proofs - let leaf_proofs: [ProofWithPublicInputsTarget; N] = (0..N) - .map(|i| { - leaf_proofs[i].clone() - }) - .collect::>() - .try_into() - .map_err(|_| anyhow!("Expected exactly M inner circuits"))?; - - // store targets - let node_targets = NodeCircuitTargets::{ - 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, - leaf_proof_options: Option<[ProofWithPublicInputs; N]>, - node_proof_options: Option<[ProofWithPublicInputs; N]>, - leaf_verifier_only_data: &VerifierOnlyCircuitData, - pw: &mut PartialWitness, - is_leaf: bool, - ) -> Result<()>{ - - if(is_leaf == true) { - let leaf_proofs: [ProofWithPublicInputs; 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::( - &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::( - &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(()) - } - -} \ No newline at end of file diff --git a/codex-plonky2-circuits/src/recursion/tree2/utils.rs b/codex-plonky2-circuits/src/recursion/tree2/utils.rs deleted file mode 100644 index e8a4949..0000000 --- a/codex-plonky2-circuits/src/recursion/tree2/utils.rs +++ /dev/null @@ -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() -> anyhow::Result> -{ - // layer 1 - let config = CircuitConfig::standard_recursion_config(); - let builder = CircuitBuilder::::new(config); - let data = builder.build::(); - - // layer 2 - let config = CircuitConfig::standard_recursion_config(); - let mut builder = CircuitBuilder::::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::(&proof, &verifier_data, &data.common); - } - let data = builder.build::(); - - // layer 3 - let config = CircuitConfig::standard_recursion_config(); - let mut builder = CircuitBuilder::::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::(&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::().common) -} - -// creates a dummy proof with given common circuit data -pub fn get_dummy_leaf_proof(common_data: &CommonCircuitData) -> ProofWithPublicInputs { - dummy_proof::( - &dummy_circuit::(common_data), - HashMap::new(), - ).unwrap() -} - -pub fn get_dummy_node_proof(node_common: &CommonCircuitData, node_verifier_only_data: &VerifierOnlyCircuitData) -> ProofWithPublicInputs{ - cyclic_base_proof( - node_common, - node_verifier_only_data, - HashMap::new(), - ) -} - -