From b69b682df276cb878f77260d1d9f82b96f7561ed Mon Sep 17 00:00:00 2001 From: M Alghazwi Date: Thu, 9 Jan 2025 10:32:14 +0100 Subject: [PATCH] re-organize simple recursion --- .../src/recursion/simple_recursion.rs | 199 +++++++++++------- .../src/recursion/simple_recursion2.rs | 138 ------------ .../src/recursion/simple_tree_recursion.rs | 91 ++++++++ 3 files changed, 210 insertions(+), 218 deletions(-) delete mode 100644 codex-plonky2-circuits/src/recursion/simple_recursion2.rs create mode 100644 codex-plonky2-circuits/src/recursion/simple_tree_recursion.rs diff --git a/codex-plonky2-circuits/src/recursion/simple_recursion.rs b/codex-plonky2-circuits/src/recursion/simple_recursion.rs index da4392a..5e069e3 100644 --- a/codex-plonky2-circuits/src/recursion/simple_recursion.rs +++ b/codex-plonky2-circuits/src/recursion/simple_recursion.rs @@ -1,22 +1,24 @@ // this file is mainly draft implementation and experimentation of multiple simple approaches -// NOTE: will be deleted later on ... +// the simple aggregation approach is verifying N proofs in-circuit and generating one final proof -use plonky2::hash::hash_types::{HashOut, HashOutTarget, RichField}; -use plonky2::iop::target::Target; +use plonky2::hash::hash_types::{HashOut, HashOutTarget}; use plonky2::iop::witness::{PartialWitness, WitnessWrite}; use plonky2::plonk::circuit_builder::CircuitBuilder; -use plonky2::plonk::circuit_data::{CircuitConfig, CircuitData, VerifierCircuitData, VerifierCircuitTarget}; +use plonky2::plonk::circuit_data::{VerifierCircuitData, VerifierCircuitTarget}; use plonky2::plonk::config::GenericConfig; use plonky2::plonk::proof::{ProofWithPublicInputs, ProofWithPublicInputsTarget}; -use plonky2_field::extension::Extendable; -use plonky2_field::goldilocks_field::GoldilocksField; -use plonky2_poseidon2::config::Poseidon2GoldilocksConfig; use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2; -use plonky2_poseidon2::serialization::{DefaultGateSerializer, DefaultGeneratorSerializer}; -use crate::circuits::utils::read_bytes_from_file; -use crate::recursion::params::{F,C,D,Plonky2Proof}; +use crate::recursion::inner_circuit::InnerCircuit; +use crate::recursion::params::{C, D, F, Plonky2Proof}; /// 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, @@ -85,86 +87,123 @@ pub fn aggregate_sampling_proofs< Ok(()) } -// recursion tree width or the number of proofs in each node in the tree -const RECURSION_TREE_WIDTH: usize = 2; +// ---------------------- Simple Approach 2 --------------------------- +// this is still simple recursion approach but written differently, +// The simple approach here separates the build (setting the targets) and assigning the witness. -/// aggregate sampling proofs in tree like structure -/// uses the const params: `RECURSION_TREE_WIDTH` -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 - 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()) +pub struct SimpleRecursionCircuit< + I: InnerCircuit, + const N: usize, +>{ + pub inner_circuit: I, } -/// same as above but takes `VerifierCircuitData` -pub fn aggregate_sampling_proofs_tree2( - proofs_with_pi: &[ProofWithPublicInputs], - vd: VerifierCircuitData -) -> anyhow::Result<(ProofWithPublicInputs, VerifierCircuitData)> { - if proofs_with_pi.len() == 1 { - return Ok((proofs_with_pi[0].clone(), vd)); +#[derive(Clone)] +pub struct SimpleRecursionTargets< +> { + pub proofs_with_pi: Vec>, + pub verifier_data: VerifierCircuitTarget, + pub entropy: HashOutTarget, +} + +pub struct SimpleRecursionInput< +>{ + pub proofs: Vec>, + pub verifier_data: VerifierCircuitData, + pub entropy: HashOut, +} + +impl< + I: InnerCircuit, + const N: usize, +> SimpleRecursionCircuit +{ + + pub fn new( + inner_circuit: I, + )->Self{ + Self{ + inner_circuit, + } } - let mut new_proofs = vec![]; - let mut new_circuit_data: Option> = None; + /// contains the circuit logic and returns the witness & public input targets + pub fn build_circuit( + &self, + builder: &mut CircuitBuilder::, + ) -> anyhow::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 chunk in proofs_with_pi.chunks(RECURSION_TREE_WIDTH) { - let proofs_chunk = chunk.to_vec(); + for i 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 + // assuming public input are ordered: + // [slot_root (1 element), dataset_root (4 element), entropy (4 element)] + let num_pub_input = vir_proof.public_inputs.len(); + 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); + } + // virtual target for the verifier data + let inner_verifier_data = builder.add_virtual_verifier_data(inner_common.config.fri_config.cap_height); - let inner_config = CircuitConfig::standard_recursion_config(); - let mut inner_builder = CircuitBuilder::::new(inner_config); - let mut inner_pw = PartialWitness::new(); + // verify the proofs in-circuit + for i in 0..N { + builder.verify_proof::(&proof_targets[i],&inner_verifier_data,&inner_common); + } - aggregate_sampling_proofs( - &proofs_chunk, - &vd, - &mut inner_builder, - &mut inner_pw, + // register entropy as public input + let outer_entropy_target = builder.add_virtual_hash_public_input(); + + // connect the public input of the recursion circuit to the inner proofs + for i in 0..N { + for j in 0..4 { + builder.connect(inner_entropy_targets[i][j], outer_entropy_target.elements[j]); + } + } + // return targets + let srt = SimpleRecursionTargets { + proofs_with_pi: proof_targets, + verifier_data: inner_verifier_data, + entropy: outer_entropy_target, + }; + Ok(srt) + } + + /// assign the targets + pub fn assign_witness( + &self, + pw: &mut PartialWitness, + targets: &SimpleRecursionTargets, + witnesses: SimpleRecursionInput, + ) -> anyhow::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])?; + } + + // 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)?; - let inner_data = inner_builder.build::(); + // set the entropy hash target + pw.set_hash_target(targets.entropy, witnesses.entropy)?; + + Ok(()) - let proof = inner_data.prove(inner_pw)?; - new_proofs.push(proof); - new_circuit_data = Some(inner_data.verifier_data()); } - - aggregate_sampling_proofs_tree2(&new_proofs, new_circuit_data.unwrap()) } \ No newline at end of file diff --git a/codex-plonky2-circuits/src/recursion/simple_recursion2.rs b/codex-plonky2-circuits/src/recursion/simple_recursion2.rs deleted file mode 100644 index e00b344..0000000 --- a/codex-plonky2-circuits/src/recursion/simple_recursion2.rs +++ /dev/null @@ -1,138 +0,0 @@ -// this is still simple recursion approach but written differently, -// still needs to be improved/removed. - -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::{AlgebraicHasher, GenericConfig}; -use plonky2::plonk::proof::{ProofWithPublicInputs, ProofWithPublicInputsTarget}; -use plonky2_field::extension::Extendable; -use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2; -use crate::recursion::params::RecursionTreeParams; - -pub struct SimpleRecursionCircuit< - F: RichField + Extendable + Poseidon2, - C: GenericConfig, - const D: usize, ->{ - pub recursion_tree_params: RecursionTreeParams, - pub verifier_data: VerifierCircuitData -} - -#[derive(Clone)] -pub struct SimpleRecursionTargets< - const D: usize, -> { - pub proofs_with_pi: Vec>, - pub verifier_data: VerifierCircuitTarget, - pub entropy: HashOutTarget, -} - -pub struct SimpleRecursionInput< - F: RichField + Extendable + Poseidon2, - C: GenericConfig, - const D: usize, ->{ - pub proofs: Vec>, - pub verifier_data: VerifierCircuitData, - pub entropy: HashOut, -} - -impl< - F: RichField + Extendable + Poseidon2, - C: GenericConfig, - const D: usize, -> SimpleRecursionCircuit where - C::Hasher: AlgebraicHasher, -{ - - pub fn new( - recursion_tree_params: RecursionTreeParams, - verifier_data: VerifierCircuitData - )->Self{ - Self{ - recursion_tree_params, - verifier_data, - } - } - - /// contains the circuit logic and returns the witness & public input targets - pub fn build_circuit( - &self, - builder: &mut CircuitBuilder::, - ) -> SimpleRecursionTargets { - // the proof virtual targets - let mut proof_targets = vec![]; - let mut inner_entropy_targets = vec![]; - - for i in 0..self.recursion_tree_params.tree_width { - let vir_proof = builder.add_virtual_proof_with_pis(&self.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)] - let num_pub_input = vir_proof.public_inputs.len(); - 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); - } - // virtual target for the verifier data - let inner_verifier_data = builder.add_virtual_verifier_data(self.verifier_data.common.config.fri_config.cap_height); - - // verify the proofs in-circuit - for i in 0..self.recursion_tree_params.tree_width { - builder.verify_proof::(&proof_targets[i],&inner_verifier_data,&self.verifier_data.common); - } - - // register entropy as public input - let outer_entropy_target = builder.add_virtual_hash_public_input(); - - // connect the public input of the recursion circuit to the inner proofs - for i in 0..self.recursion_tree_params.tree_width { - for j in 0..4 { - builder.connect(inner_entropy_targets[i][j], outer_entropy_target.elements[j]); - } - } - // return targets - SimpleRecursionTargets { - proofs_with_pi: proof_targets, - verifier_data: inner_verifier_data, - entropy: outer_entropy_target, - } - - } - - /// assign the targets - pub fn assign_witness( - &self, - pw: &mut PartialWitness, - targets: &SimpleRecursionTargets, - witnesses: SimpleRecursionInput, - ) -> anyhow::Result<()>{ - // assign the proofs with public input - for i in 0..self.recursion_tree_params.tree_width{ - pw.set_proof_with_pis_target(&targets.proofs_with_pi[i],&witnesses.proofs[i])?; - } - - // 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)?; - - // set the entropy hash target - pw.set_hash_target(targets.entropy, witnesses.entropy)?; - - Ok(()) - - } -} \ No newline at end of file diff --git a/codex-plonky2-circuits/src/recursion/simple_tree_recursion.rs b/codex-plonky2-circuits/src/recursion/simple_tree_recursion.rs new file mode 100644 index 0000000..0c27af6 --- /dev/null +++ b/codex-plonky2-circuits/src/recursion/simple_tree_recursion.rs @@ -0,0 +1,91 @@ +use plonky2::plonk::proof::ProofWithPublicInputs; +use plonky2::plonk::circuit_data::{CircuitConfig, CircuitData, VerifierCircuitData}; +use plonky2::plonk::circuit_builder::CircuitBuilder; +use plonky2::iop::witness::PartialWitness; +use crate::recursion::params::{C, D, F}; +use crate::recursion::simple_recursion; + +// recursion tree width or the number of proofs in each node in the tree +const RECURSION_TREE_WIDTH: usize = 2; + +/// 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( + proofs_with_pi: &[ProofWithPublicInputs], + vd: VerifierCircuitData +) -> anyhow::Result<(ProofWithPublicInputs, VerifierCircuitData)> { + if proofs_with_pi.len() == 1 { + return Ok((proofs_with_pi[0].clone(), vd)); + } + + let mut new_proofs = vec![]; + let mut new_circuit_data: Option> = None; + + for chunk in proofs_with_pi.chunks(RECURSION_TREE_WIDTH) { + let proofs_chunk = chunk.to_vec(); + + let inner_config = CircuitConfig::standard_recursion_config(); + let mut inner_builder = CircuitBuilder::::new(inner_config); + let mut inner_pw = PartialWitness::new(); + + simple_recursion::aggregate_sampling_proofs( + &proofs_chunk, + &vd, + &mut inner_builder, + &mut inner_pw, + )?; + + let inner_data = inner_builder.build::(); + + let proof = inner_data.prove(inner_pw)?; + new_proofs.push(proof); + new_circuit_data = Some(inner_data.verifier_data()); + } + + aggregate_sampling_proofs_tree2(&new_proofs, new_circuit_data.unwrap()) +}