mirror of
https://github.com/codex-storage/proof-aggregation.git
synced 2025-02-10 21:56:24 +00:00
add cyclic and tree recursion and refactor
This commit is contained in:
parent
cf75968473
commit
e5985184df
@ -8,7 +8,7 @@ use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2Hash;
|
|||||||
// hash function used. this is hackish way of doing it because
|
// hash function used. this is hackish way of doing it because
|
||||||
// H::Hash is not consistent with HashOut<F> and causing a lot of headache
|
// H::Hash is not consistent with HashOut<F> and causing a lot of headache
|
||||||
// will look into this later.
|
// will look into this later.
|
||||||
pub type HF = Poseidon2Hash;
|
pub type HF = PoseidonHash;
|
||||||
|
|
||||||
/// params used for the circuits
|
/// params used for the circuits
|
||||||
/// should be defined prior to building the circuit
|
/// should be defined prior to building the circuit
|
||||||
|
249
codex-plonky2-circuits/src/recursion/cyclic_recursion.rs
Normal file
249
codex-plonky2-circuits/src/recursion/cyclic_recursion.rs
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
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::{F,D,C,Plonky2Proof,H};
|
||||||
|
use crate::recursion::inner_circuit::InnerCircuit;
|
||||||
|
use anyhow::Result;
|
||||||
|
use plonky2::gates::noop::NoopGate;
|
||||||
|
use plonky2::recursion::cyclic_recursion::check_cyclic_proof_verifier_data;
|
||||||
|
use crate::circuits::utils::select_hash;
|
||||||
|
|
||||||
|
/// cyclic circuit struct
|
||||||
|
/// contains necessary data
|
||||||
|
/// note: only keeps track of latest proof not all proofs.
|
||||||
|
pub struct CyclicCircuit<
|
||||||
|
I: InnerCircuit,
|
||||||
|
>{
|
||||||
|
pub layer: usize,
|
||||||
|
pub circ: I,
|
||||||
|
pub cyclic_target: Option<CyclicCircuitTargets<I>>,
|
||||||
|
pub cyclic_circuit_data: Option<CircuitData<F, C, D>>,
|
||||||
|
pub common_data: Option<CommonCircuitData<F, D>>,
|
||||||
|
pub latest_proof: Option<ProofWithPublicInputs<F, C, D>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// targets need to be assigned for the cyclic circuit
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct CyclicCircuitTargets<
|
||||||
|
I: InnerCircuit,
|
||||||
|
>{
|
||||||
|
pub inner_targets: I::Targets,
|
||||||
|
pub condition: BoolTarget,
|
||||||
|
pub inner_cyclic_proof_with_pis: ProofWithPublicInputsTarget<D>,
|
||||||
|
pub verifier_data: VerifierCircuitTarget,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
I: InnerCircuit,
|
||||||
|
> CyclicCircuit<I> {
|
||||||
|
|
||||||
|
/// create a new cyclic circuit
|
||||||
|
pub fn new(circ: I) -> Self{
|
||||||
|
Self{
|
||||||
|
layer: 0,
|
||||||
|
circ,
|
||||||
|
cyclic_target: None,
|
||||||
|
cyclic_circuit_data: None,
|
||||||
|
common_data: None,
|
||||||
|
latest_proof: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// builds the cyclic recursion circuit using any inner circuit I
|
||||||
|
/// returns the circuit data
|
||||||
|
pub fn build_circuit(
|
||||||
|
&mut self,
|
||||||
|
) -> Result<()>{
|
||||||
|
// if the circuit data is already build then no need to rebuild
|
||||||
|
if self.cyclic_circuit_data.is_some(){
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// builder with standard recursion config
|
||||||
|
let config = CircuitConfig::standard_recursion_config();
|
||||||
|
let mut builder = CircuitBuilder::<F, D>::new(config);
|
||||||
|
|
||||||
|
//build the inner circuit
|
||||||
|
let inner_t = self.circ.build(& mut builder)?;
|
||||||
|
|
||||||
|
// common data for recursion
|
||||||
|
let mut common_data = common_data_for_recursion();
|
||||||
|
// the hash of the public input
|
||||||
|
let pub_input_hash = builder.add_virtual_hash_public_input();
|
||||||
|
// verifier data for inner proofs
|
||||||
|
let verifier_data_target = builder.add_verifier_data_public_inputs();
|
||||||
|
// common data should have same num of public input as inner proofs
|
||||||
|
common_data.num_public_inputs = builder.num_public_inputs();
|
||||||
|
|
||||||
|
// condition
|
||||||
|
let condition = builder.add_virtual_bool_target_safe();
|
||||||
|
|
||||||
|
// inner proof with public input
|
||||||
|
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();
|
||||||
|
// now hash the current public input
|
||||||
|
let outer_pis = I::get_pub_input_targets(&inner_t)?;
|
||||||
|
let outer_pi_hash = builder.hash_n_to_hash_no_pad::<H>(outer_pis);
|
||||||
|
let zero_hash = HashOutTarget::from_vec([builder.zero(); 4].to_vec());
|
||||||
|
// if leaf pad with zeros
|
||||||
|
let inner_pi_hash_or_zero_hash = select_hash(&mut builder, condition, inner_pub_input_hash, zero_hash);
|
||||||
|
// hash current public input with previous hash
|
||||||
|
let mut hash_input = vec![];
|
||||||
|
hash_input.extend_from_slice(&outer_pi_hash.elements);
|
||||||
|
hash_input.extend_from_slice(&inner_pi_hash_or_zero_hash.elements);
|
||||||
|
let outer_pi_hash = builder.hash_n_to_hash_no_pad::<H>(hash_input);
|
||||||
|
// connect this up one to `pub_input_hash`
|
||||||
|
builder.connect_hashes(pub_input_hash,outer_pi_hash);
|
||||||
|
|
||||||
|
// connect entropy?
|
||||||
|
|
||||||
|
// verify proof in-circuit
|
||||||
|
builder.conditionally_verify_cyclic_proof_or_dummy::<C>(
|
||||||
|
condition,
|
||||||
|
&inner_cyclic_proof_with_pis,
|
||||||
|
&common_data,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// build the cyclic circuit
|
||||||
|
let cyclic_circuit_data = builder.build::<C>();
|
||||||
|
|
||||||
|
// assign targets
|
||||||
|
let cyc_t = CyclicCircuitTargets::<I>{
|
||||||
|
inner_targets: inner_t,
|
||||||
|
condition,
|
||||||
|
inner_cyclic_proof_with_pis,
|
||||||
|
verifier_data: verifier_data_target
|
||||||
|
};
|
||||||
|
// assign the data
|
||||||
|
self.cyclic_circuit_data = Some(cyclic_circuit_data);
|
||||||
|
self.common_data = Some(common_data);
|
||||||
|
self.cyclic_target = Some(cyc_t);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// generates a proof with only one recursion layer
|
||||||
|
/// takes circuit input
|
||||||
|
pub fn prove_one_layer(
|
||||||
|
&mut self,
|
||||||
|
circ_input: &I::Input,
|
||||||
|
) -> Result<ProofWithPublicInputs<F, C, D>>{
|
||||||
|
|
||||||
|
if self.cyclic_circuit_data.is_none(){
|
||||||
|
panic!("circuit data not found") // TODO: replace with err
|
||||||
|
}
|
||||||
|
|
||||||
|
let circ_data = self.cyclic_circuit_data.as_ref().unwrap();
|
||||||
|
let cyc_targets = self.cyclic_target.as_ref().unwrap();
|
||||||
|
let common_data = self.common_data.as_ref().unwrap();
|
||||||
|
|
||||||
|
// 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)?;
|
||||||
|
pw.set_proof_with_pis_target::<C, D>(
|
||||||
|
&cyc_targets.inner_cyclic_proof_with_pis,
|
||||||
|
&cyclic_base_proof(
|
||||||
|
common_data,
|
||||||
|
&circ_data.verifier_only,
|
||||||
|
HashMap::new(),
|
||||||
|
),
|
||||||
|
)?;
|
||||||
|
}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)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// assign verifier data
|
||||||
|
pw.set_verifier_data_target(&cyc_targets.verifier_data, &circ_data.verifier_only)?;
|
||||||
|
// prove
|
||||||
|
let proof = circ_data.prove(pw)?;
|
||||||
|
// check that the correct verifier data is consistent
|
||||||
|
check_cyclic_proof_verifier_data(
|
||||||
|
&proof,
|
||||||
|
&circ_data.verifier_only,
|
||||||
|
&circ_data.common,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
self.latest_proof = Some(proof.clone());
|
||||||
|
self.layer = self.layer + 1;
|
||||||
|
Ok(proof)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// prove n recursive layers
|
||||||
|
/// the function takes
|
||||||
|
/// - n: the number of layers and
|
||||||
|
/// - circ_input: vector of n inputs
|
||||||
|
pub fn prove_n_layers(
|
||||||
|
&mut self,
|
||||||
|
n: usize,
|
||||||
|
circ_input: Vec<I::Input>,
|
||||||
|
) -> Result<ProofWithPublicInputs<F, C, D>>{
|
||||||
|
|
||||||
|
// asserts that n equals the number of input
|
||||||
|
assert_eq!(n, circ_input.len());
|
||||||
|
|
||||||
|
for i in 0..n {
|
||||||
|
self.prove_one_layer(&circ_input[i])?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(self.latest_proof.clone().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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");
|
||||||
|
}
|
||||||
|
let circ_data = self.cyclic_circuit_data.as_ref().unwrap();
|
||||||
|
let proof = self.latest_proof.clone().unwrap();
|
||||||
|
|
||||||
|
circ_data.verify(proof)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates `CommonCircuitData` usable for recursion.
|
||||||
|
pub fn common_data_for_recursion() -> CommonCircuitData<F, D>
|
||||||
|
{
|
||||||
|
// layer 1
|
||||||
|
let config = CircuitConfig::standard_recursion_config();
|
||||||
|
let builder = CircuitBuilder::<F, D>::new(config);
|
||||||
|
let data = builder.build::<C>();
|
||||||
|
// layer 2
|
||||||
|
let config = CircuitConfig::standard_recursion_config();
|
||||||
|
let mut builder = CircuitBuilder::<F, D>::new(config);
|
||||||
|
let proof = builder.add_virtual_proof_with_pis(&data.common);
|
||||||
|
let verifier_data =
|
||||||
|
builder.add_virtual_verifier_data(data.common.config.fri_config.cap_height);
|
||||||
|
builder.verify_proof::<C>(&proof, &verifier_data, &data.common);
|
||||||
|
let data = builder.build::<C>();
|
||||||
|
// layer 3
|
||||||
|
let config = CircuitConfig::standard_recursion_config();
|
||||||
|
let mut builder = CircuitBuilder::<F, D>::new(config);
|
||||||
|
let proof = builder.add_virtual_proof_with_pis(&data.common);
|
||||||
|
let verifier_data =
|
||||||
|
builder.add_virtual_verifier_data(data.common.config.fri_config.cap_height);
|
||||||
|
builder.verify_proof::<C>(&proof, &verifier_data, &data.common);
|
||||||
|
// pad with noop gates
|
||||||
|
while builder.num_gates() < 1 << 12 {
|
||||||
|
builder.add_gate(NoopGate, vec![]);
|
||||||
|
}
|
||||||
|
builder.build::<C>().common
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
use plonky2::iop::target::BoolTarget;
|
use plonky2::iop::target::{BoolTarget, Target};
|
||||||
use plonky2::iop::witness::PartialWitness;
|
use plonky2::iop::witness::PartialWitness;
|
||||||
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
||||||
use crate::recursion::params::{F,C,D};
|
use crate::recursion::params::{F,C,D};
|
||||||
@ -13,13 +13,21 @@ pub trait InnerCircuit<
|
|||||||
|
|
||||||
/// build the circuit logic and return targets to be assigned later
|
/// build the circuit logic and return targets to be assigned later
|
||||||
fn build(
|
fn build(
|
||||||
|
&self,
|
||||||
builder: &mut CircuitBuilder<F, D>,
|
builder: &mut CircuitBuilder<F, D>,
|
||||||
) -> anyhow::Result<Self::Targets>;
|
) -> anyhow::Result<Self::Targets>;
|
||||||
|
|
||||||
/// assign the actual witness values for the current instance of the circuit.
|
/// assign the actual witness values for the current instance of the circuit.
|
||||||
fn assign_targets(
|
fn assign_targets(
|
||||||
|
&self,
|
||||||
pw: &mut PartialWitness<F>,
|
pw: &mut PartialWitness<F>,
|
||||||
targets: &Self::Targets,
|
targets: &Self::Targets,
|
||||||
input: &Self::Input,
|
input: &Self::Input,
|
||||||
) -> anyhow::Result<()>;
|
) -> anyhow::Result<()>;
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
fn get_pub_input_targets(
|
||||||
|
targets: &Self::Targets,
|
||||||
|
) -> anyhow::Result<(Vec<Target>)>;
|
||||||
}
|
}
|
@ -1,8 +1,8 @@
|
|||||||
pub mod structs;
|
pub mod inner_circuit;
|
||||||
pub mod traits;
|
|
||||||
pub mod simple_recursion;
|
pub mod simple_recursion;
|
||||||
pub mod simple_recursion2;
|
pub mod simple_recursion2;
|
||||||
pub mod tree_recursion;
|
pub mod tree_recursion;
|
||||||
pub mod params;
|
pub mod params;
|
||||||
pub mod sampling;
|
pub mod sampling_inner_circuit;
|
||||||
|
pub mod cyclic_recursion;
|
||||||
|
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
|
use plonky2::hash::poseidon::PoseidonHash;
|
||||||
|
use plonky2::plonk::config::PoseidonGoldilocksConfig;
|
||||||
use plonky2::plonk::proof::ProofWithPublicInputs;
|
use plonky2::plonk::proof::ProofWithPublicInputs;
|
||||||
use plonky2_field::goldilocks_field::GoldilocksField;
|
use plonky2_field::goldilocks_field::GoldilocksField;
|
||||||
use plonky2_poseidon2::config::Poseidon2GoldilocksConfig;
|
use plonky2_poseidon2::config::Poseidon2GoldilocksConfig;
|
||||||
|
|
||||||
// recursion param
|
// recursion param
|
||||||
|
// TODO: make it more generic or use global params
|
||||||
pub type F = GoldilocksField;
|
pub type F = GoldilocksField;
|
||||||
pub const D: usize = 2;
|
pub const D: usize = 2;
|
||||||
pub type C = Poseidon2GoldilocksConfig;
|
pub type C = PoseidonGoldilocksConfig;
|
||||||
|
pub type H = PoseidonHash;
|
||||||
pub type Plonky2Proof = ProofWithPublicInputs<F, C, D>;
|
pub type Plonky2Proof = ProofWithPublicInputs<F, C, D>;
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
use plonky2::iop::target::BoolTarget;
|
|
||||||
use plonky2::iop::witness::PartialWitness;
|
|
||||||
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
|
||||||
use crate::circuits::params::CircuitParams;
|
|
||||||
use crate::circuits::sample_cells::{SampleCircuit, SampleCircuitInput, SampleTargets};
|
|
||||||
use crate::recursion::params::{D, F};
|
|
||||||
use crate::recursion::traits::InnerCircuit;
|
|
||||||
|
|
||||||
pub struct SamplingRecursion {}
|
|
||||||
|
|
||||||
impl InnerCircuit for SamplingRecursion{
|
|
||||||
type Targets = SampleTargets;
|
|
||||||
type Input = SampleCircuitInput<F, D>;
|
|
||||||
|
|
||||||
/// build the circuit
|
|
||||||
/// TODO: this build the circuit with default circuit params -> make it generic
|
|
||||||
fn build(builder: &mut CircuitBuilder<F, D>) -> anyhow::Result<Self::Targets> {
|
|
||||||
let circ = SampleCircuit::new(CircuitParams::default());
|
|
||||||
Ok(circ.sample_slot_circuit(builder))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assign_targets(pw: &mut PartialWitness<F>, targets: &Self::Targets, input: &Self::Input) -> anyhow::Result<()> {
|
|
||||||
let circ = SampleCircuit::<F,D>::new(CircuitParams::default());
|
|
||||||
// circ.sample_slot_assign_witness(pw,targets,input);
|
|
||||||
todo!()
|
|
||||||
// Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,48 @@
|
|||||||
|
use plonky2::iop::target::{BoolTarget, Target};
|
||||||
|
use plonky2::iop::witness::PartialWitness;
|
||||||
|
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
||||||
|
use crate::circuits::params::CircuitParams;
|
||||||
|
use crate::circuits::sample_cells::{SampleCircuit, SampleCircuitInput, SampleTargets};
|
||||||
|
use crate::recursion::params::{D, F};
|
||||||
|
use crate::recursion::inner_circuit::InnerCircuit;
|
||||||
|
use crate::circuits::params;
|
||||||
|
|
||||||
|
/// recursion Inner circuit for the sampling circuit
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct SamplingRecursion {
|
||||||
|
pub sampling_circ: SampleCircuit<F,D>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SamplingRecursion {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self{
|
||||||
|
sampling_circ: SampleCircuit::new(CircuitParams::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl InnerCircuit for SamplingRecursion{
|
||||||
|
type Targets = SampleTargets;
|
||||||
|
type Input = SampleCircuitInput<F, D>;
|
||||||
|
|
||||||
|
/// build the circuit
|
||||||
|
fn build(&self, builder: &mut CircuitBuilder<F, D>) -> anyhow::Result<Self::Targets> {
|
||||||
|
Ok(self.sampling_circ.sample_slot_circuit(builder))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assign_targets(&self, pw: &mut PartialWitness<F>, targets: &Self::Targets, input: &Self::Input) -> anyhow::Result<()> {
|
||||||
|
Ok(self.sampling_circ.sample_slot_assign_witness(pw, targets, input))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// returns the public input specific for this circuit which are:
|
||||||
|
/// `[slot_index, dataset_root, entropy]`
|
||||||
|
fn get_pub_input_targets(targets: &Self::Targets) -> anyhow::Result<(Vec<Target>)> {
|
||||||
|
let mut pub_targets = vec![];
|
||||||
|
pub_targets.push(targets.slot_index.clone());
|
||||||
|
pub_targets.extend_from_slice(&targets.dataset_root.elements);
|
||||||
|
pub_targets.extend_from_slice(&targets.entropy.elements);
|
||||||
|
|
||||||
|
Ok(pub_targets)
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,6 @@
|
|||||||
|
// this file is mainly draft implementation and experimentation of multiple simple approaches
|
||||||
|
// NOTE: will be deleted later on ...
|
||||||
|
|
||||||
use plonky2::hash::hash_types::{HashOut, HashOutTarget, RichField};
|
use plonky2::hash::hash_types::{HashOut, HashOutTarget, RichField};
|
||||||
use plonky2::iop::target::Target;
|
use plonky2::iop::target::Target;
|
||||||
use plonky2::iop::witness::{PartialWitness, WitnessWrite};
|
use plonky2::iop::witness::{PartialWitness, WitnessWrite};
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
// 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::hash::hash_types::{HashOut, HashOutTarget, RichField};
|
||||||
use plonky2::iop::witness::{PartialWitness, WitnessWrite};
|
use plonky2::iop::witness::{PartialWitness, WitnessWrite};
|
||||||
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
||||||
|
@ -1,69 +1,368 @@
|
|||||||
|
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use plonky2::hash::hash_types::{HashOut, HashOutTarget, RichField};
|
use plonky2::hash::hash_types::{HashOut, HashOutTarget, RichField};
|
||||||
use plonky2::iop::target::Target;
|
use plonky2::iop::target::{BoolTarget, Target};
|
||||||
use plonky2::iop::witness::{PartialWitness, WitnessWrite};
|
use plonky2::iop::witness::{PartialWitness, WitnessWrite};
|
||||||
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
||||||
use plonky2::plonk::circuit_data::{CircuitData, VerifierCircuitData, VerifierCircuitTarget};
|
use plonky2::plonk::circuit_data::{CircuitConfig, CircuitData, CommonCircuitData, VerifierCircuitData, VerifierCircuitTarget};
|
||||||
use plonky2::plonk::config::{AlgebraicHasher, GenericConfig};
|
use plonky2::plonk::config::{AlgebraicHasher, GenericConfig};
|
||||||
use plonky2::plonk::proof::{ProofWithPublicInputs, ProofWithPublicInputsTarget};
|
use plonky2::plonk::proof::{ProofWithPublicInputs, ProofWithPublicInputsTarget};
|
||||||
use plonky2::recursion::dummy_circuit::cyclic_base_proof;
|
use plonky2::recursion::dummy_circuit::cyclic_base_proof;
|
||||||
use plonky2_field::extension::Extendable;
|
use plonky2_field::extension::Extendable;
|
||||||
use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2;
|
use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2;
|
||||||
use crate::recursion::params::RecursionTreeParams;
|
use crate::recursion::params::RecursionTreeParams;
|
||||||
use crate::recursion::params::{F,D,C,Plonky2Proof};
|
use crate::recursion::params::{F,D,C,Plonky2Proof,H};
|
||||||
use crate::recursion::traits::InnerCircuit;
|
use crate::recursion::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;
|
||||||
|
|
||||||
pub struct RecursionCircuit<
|
/// 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,
|
I: InnerCircuit,
|
||||||
|
const M: usize,
|
||||||
|
const N: usize,
|
||||||
>{
|
>{
|
||||||
pub recursion_tree_params: RecursionTreeParams,
|
node_circ: NodeCircuit<I, M, N>
|
||||||
pub inner_circuit_targets: Vec<I::Targets>,
|
|
||||||
pub proof_targets: Vec<ProofWithPublicInputsTarget<D>>,
|
|
||||||
pub verifier_data_targets: VerifierCircuitTarget,
|
|
||||||
pub verifier_data: VerifierCircuitData<F, C, D>,
|
|
||||||
pub condition_targets: Vec<Target>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct RecursionCircuitInput<
|
|
||||||
I: InnerCircuit,
|
|
||||||
>{
|
|
||||||
pub inner_circuit_input: Vec<I::Targets>,
|
|
||||||
pub proofs: Plonky2Proof,
|
|
||||||
pub conditions: Vec<F>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<
|
impl<
|
||||||
I: InnerCircuit,
|
I: InnerCircuit,
|
||||||
> RecursionCircuit<I> {
|
const M: usize,
|
||||||
|
const N: usize,
|
||||||
|
> TreeRecursion<I, M, N> {
|
||||||
|
|
||||||
pub fn build_circuit(
|
pub fn new(node_circ: NodeCircuit<I,M,N>) -> Self{
|
||||||
|
Self{
|
||||||
|
node_circ,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(
|
||||||
|
&mut self
|
||||||
|
) -> Result<()>{
|
||||||
|
self.node_circ.build_circuit()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// generates a proof - only one node
|
||||||
|
/// takes M circuit input and N proofs
|
||||||
|
pub fn prove(
|
||||||
|
&mut self,
|
||||||
|
circ_input: &[I::Input; M],
|
||||||
|
proofs_option: Option<[ProofWithPublicInputs<F, C, D>; N]>,
|
||||||
|
is_leaf: bool,
|
||||||
|
) -> Result<ProofWithPublicInputs<F, C, D>>{
|
||||||
|
|
||||||
|
if self.node_circ.cyclic_circuit_data.is_none(){
|
||||||
|
panic!("circuit data not found") // TODO: replace with err
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut pw = PartialWitness::new();
|
||||||
|
self.node_circ.assign_targets(
|
||||||
|
circ_input,
|
||||||
|
proofs_option,
|
||||||
|
&mut pw,
|
||||||
|
is_leaf,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let circ_data = self.node_circ.cyclic_circuit_data.as_ref().unwrap();
|
||||||
|
let cyc_targets = self.node_circ.cyclic_target.as_ref().unwrap();
|
||||||
|
|
||||||
|
pw.set_verifier_data_target(&cyc_targets.verifier_data, &circ_data.verifier_only)?;
|
||||||
|
let proof = circ_data.prove(pw)?;
|
||||||
|
check_cyclic_proof_verifier_data(
|
||||||
|
&proof,
|
||||||
|
&circ_data.verifier_only,
|
||||||
|
&circ_data.common,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(proof)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// prove n in a tree structure recursively
|
||||||
|
/// the function takes
|
||||||
|
/// - circ_input: vector of circuit inputs
|
||||||
|
pub fn prove_n_nodes(
|
||||||
|
&mut self,
|
||||||
|
circ_input: Vec<I::Input>,
|
||||||
|
) -> Result<ProofWithPublicInputs<F, C, D>>{
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// verifies the proof generated
|
||||||
|
pub fn verify_proof(
|
||||||
&self,
|
&self,
|
||||||
builder: &mut CircuitBuilder::<F, D>,
|
proof: ProofWithPublicInputs<F, C, D>
|
||||||
) -> Self {
|
) -> Result<()>{
|
||||||
todo!()
|
if self.node_circ.cyclic_circuit_data.is_none() {
|
||||||
}
|
panic!("no circuit data or proof found");
|
||||||
|
}
|
||||||
pub fn get_circuit_data() -> CircuitData<F, C, D>{
|
let circ_data = self.node_circ.cyclic_circuit_data.as_ref().unwrap();
|
||||||
todo!()
|
circ_data.verify(proof)?;
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_dummy_proof(circuit_data: CircuitData<F, C, D>) -> Plonky2Proof {
|
|
||||||
let verifier_data = circuit_data.verifier_data();
|
|
||||||
let dummy_proof_with_pis = cyclic_base_proof(
|
|
||||||
&circuit_data.common,
|
|
||||||
&verifier_data.verifier_only,
|
|
||||||
HashMap::new(),
|
|
||||||
);
|
|
||||||
dummy_proof_with_pis
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn assign_witness(
|
|
||||||
&self,
|
|
||||||
pw: &mut PartialWitness<F>,
|
|
||||||
witnesses: RecursionCircuitInput<I>
|
|
||||||
)-> anyhow::Result<()>{
|
|
||||||
todo!()
|
|
||||||
// Ok(())
|
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Node circuit struct
|
||||||
|
/// contains necessary data
|
||||||
|
/// M: number of inner-circuits to run
|
||||||
|
/// N: number of proofs verified in-circuit (so num of child nodes)
|
||||||
|
pub struct NodeCircuit<
|
||||||
|
I: InnerCircuit,
|
||||||
|
const M: usize,
|
||||||
|
const N: usize,
|
||||||
|
>{
|
||||||
|
pub circ: I,
|
||||||
|
pub cyclic_target: Option<NodeCircuitTargets<I,M,N>>,
|
||||||
|
pub cyclic_circuit_data: Option<CircuitData<F, C, D>>,
|
||||||
|
pub common_data: Option<CommonCircuitData<F, D>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Node circuit targets
|
||||||
|
/// assumes that all inner proofs use the same verifier data
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct NodeCircuitTargets<
|
||||||
|
I: InnerCircuit,
|
||||||
|
const M: usize,
|
||||||
|
const N: usize,
|
||||||
|
>{
|
||||||
|
pub inner_targets: [I::Targets; M],
|
||||||
|
pub condition: BoolTarget,
|
||||||
|
pub inner_proofs_with_pis: [ProofWithPublicInputsTarget<D>; N],
|
||||||
|
pub verifier_data: VerifierCircuitTarget,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
I: InnerCircuit,
|
||||||
|
const M: usize,
|
||||||
|
const N: usize,
|
||||||
|
> NodeCircuit<I, M, N> {
|
||||||
|
|
||||||
|
/// create a new cyclic circuit
|
||||||
|
pub fn new(circ: I) -> Self{
|
||||||
|
Self{
|
||||||
|
circ,
|
||||||
|
cyclic_target: None,
|
||||||
|
cyclic_circuit_data: None,
|
||||||
|
common_data: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// builds the cyclic recursion circuit using any inner circuit I
|
||||||
|
/// returns the circuit data
|
||||||
|
pub fn build_circuit(
|
||||||
|
&mut self,
|
||||||
|
) -> Result<()>{
|
||||||
|
// if the circuit data is already build then no need to rebuild
|
||||||
|
if self.cyclic_circuit_data.is_some(){
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// builder with standard recursion config
|
||||||
|
let config = CircuitConfig::standard_recursion_config();
|
||||||
|
let mut builder = CircuitBuilder::<F, D>::new(config);
|
||||||
|
|
||||||
|
//build M inner circuits
|
||||||
|
// let mut inner_t = Vec::with_capacity(M);
|
||||||
|
// for i in 0..M {
|
||||||
|
// inner_t.push( self.circ.build(&mut builder)?);
|
||||||
|
// }
|
||||||
|
|
||||||
|
let inner_t: [I::Targets; M] = (0..M)
|
||||||
|
.map(|_| self.circ.build(&mut builder))
|
||||||
|
.collect::<Result<Vec<_>>>()?
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| anyhow!("Expected exactly M inner circuits"))?;
|
||||||
|
|
||||||
|
// common data for recursion
|
||||||
|
let mut common_data = self.common_data_for_node()?;
|
||||||
|
// let outer_pis = I::get_pub_input_targets(&inner_t)?;
|
||||||
|
let pub_input_hash = builder.add_virtual_hash_public_input();
|
||||||
|
let verifier_data_target = builder.add_verifier_data_public_inputs();
|
||||||
|
common_data.num_public_inputs = builder.num_public_inputs();
|
||||||
|
|
||||||
|
// condition
|
||||||
|
let condition = builder.add_virtual_bool_target_safe();
|
||||||
|
|
||||||
|
// inner proofs targets - N proof targets
|
||||||
|
// let mut inner_cyclic_proof_with_pis = vec![];
|
||||||
|
// for i in 0..N {
|
||||||
|
// inner_cyclic_proof_with_pis.push(builder.add_virtual_proof_with_pis(&common_data));
|
||||||
|
// }
|
||||||
|
|
||||||
|
let inner_cyclic_proof_with_pis: [ProofWithPublicInputsTarget<D>; N] = (0..N)
|
||||||
|
.map(|_| builder.add_virtual_proof_with_pis(&common_data))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| anyhow!("Expected exactly N proof targets"))?;
|
||||||
|
|
||||||
|
// get the public input hash from all inner proof targets
|
||||||
|
let mut inner_pub_input_hashes = vec![];
|
||||||
|
for i in 0..N {
|
||||||
|
let inner_cyclic_pis = &inner_cyclic_proof_with_pis[i].public_inputs;
|
||||||
|
inner_pub_input_hashes.extend_from_slice(&inner_cyclic_pis[0..4]);
|
||||||
|
}
|
||||||
|
// hash all the inner public input h = H(h_1 | h_2 | ... | h_N)
|
||||||
|
let inner_pub_input_hash = builder.hash_n_to_hash_no_pad::<H>(inner_pub_input_hashes);
|
||||||
|
|
||||||
|
// get the public input of the inner circuit
|
||||||
|
let mut outer_pis = vec![];
|
||||||
|
for i in 0..M {
|
||||||
|
outer_pis.push( I::get_pub_input_targets(&inner_t[i])?);
|
||||||
|
}
|
||||||
|
// hash all the public input -> generate one hashout at the end
|
||||||
|
// this is not an optimal way to do it, verification might be ugly if M > 1
|
||||||
|
// TODO: optimize this
|
||||||
|
let mut outer_pi_hashes = vec![];
|
||||||
|
for i in 0..M {
|
||||||
|
let hash_res = builder.hash_n_to_hash_no_pad::<H>(outer_pis[i].clone());
|
||||||
|
outer_pi_hashes.extend_from_slice(&hash_res.elements)
|
||||||
|
}
|
||||||
|
// the final public input hash
|
||||||
|
let outer_pi_hash = builder.hash_n_to_hash_no_pad::<H>(outer_pi_hashes);
|
||||||
|
// zero hash for leaves
|
||||||
|
let zero_hash = HashOutTarget::from_vec([builder.zero(); 4].to_vec());
|
||||||
|
// if the inner proofs are dummy then use zero hash for public input
|
||||||
|
let inner_pi_hash_or_zero_hash = select_hash(&mut builder, condition, inner_pub_input_hash, zero_hash);
|
||||||
|
|
||||||
|
// now hash the public input of the inner proofs and outer proof so we have one public hash
|
||||||
|
let mut hash_input = vec![];
|
||||||
|
hash_input.extend_from_slice(&outer_pi_hash.elements);
|
||||||
|
hash_input.extend_from_slice(&inner_pi_hash_or_zero_hash.elements);
|
||||||
|
let outer_pi_hash = builder.hash_n_to_hash_no_pad::<H>(hash_input);
|
||||||
|
// connect this up one to `pub_input_hash`
|
||||||
|
builder.connect_hashes(pub_input_hash,outer_pi_hash);
|
||||||
|
|
||||||
|
// we can connect entropy, since all share same entropy, but might be more work
|
||||||
|
// TODO: look into entropy
|
||||||
|
|
||||||
|
// verify all N proofs in-circuit
|
||||||
|
for i in 0..N {
|
||||||
|
builder.conditionally_verify_cyclic_proof_or_dummy::<C>(
|
||||||
|
condition,
|
||||||
|
&inner_cyclic_proof_with_pis[i],
|
||||||
|
&common_data,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// build the cyclic circuit
|
||||||
|
let cyclic_circuit_data = builder.build::<C>();
|
||||||
|
|
||||||
|
// assign targets
|
||||||
|
let cyc_t = NodeCircuitTargets::<I, M, N>{
|
||||||
|
inner_targets: inner_t,
|
||||||
|
condition,
|
||||||
|
inner_proofs_with_pis: inner_cyclic_proof_with_pis,
|
||||||
|
verifier_data: verifier_data_target
|
||||||
|
};
|
||||||
|
// assign the data
|
||||||
|
self.cyclic_circuit_data = Some(cyclic_circuit_data);
|
||||||
|
self.common_data = Some(common_data);
|
||||||
|
self.cyclic_target = Some(cyc_t);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// assigns the targets for the Node circuit
|
||||||
|
/// takes circuit input
|
||||||
|
pub fn assign_targets(
|
||||||
|
&mut self,
|
||||||
|
circ_input: &[I::Input; M],
|
||||||
|
proof_options: Option<[ProofWithPublicInputs<F, C, D>; N]>,
|
||||||
|
pw: &mut PartialWitness<F>,
|
||||||
|
is_leaf: bool,
|
||||||
|
) -> Result<()>{
|
||||||
|
|
||||||
|
if self.cyclic_circuit_data.is_none(){
|
||||||
|
panic!("circuit data not found") // TODO: replace with err
|
||||||
|
}
|
||||||
|
|
||||||
|
let circ_data = self.cyclic_circuit_data.as_ref().unwrap();
|
||||||
|
let cyc_targets = self.cyclic_target.as_ref().unwrap();
|
||||||
|
let common_data = self.common_data.as_ref().unwrap();
|
||||||
|
|
||||||
|
for i in 0..M {
|
||||||
|
self.circ.assign_targets(pw, &cyc_targets.inner_targets[i], &circ_input[i])?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(is_leaf == true) {
|
||||||
|
pw.set_bool_target(cyc_targets.condition, false)?;
|
||||||
|
for i in 0..N {
|
||||||
|
pw.set_proof_with_pis_target::<C, D>(
|
||||||
|
&cyc_targets.inner_proofs_with_pis[i],
|
||||||
|
&cyclic_base_proof(
|
||||||
|
common_data,
|
||||||
|
&circ_data.verifier_only,
|
||||||
|
HashMap::new(),
|
||||||
|
),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
pw.set_bool_target(cyc_targets.condition, true)?;
|
||||||
|
let proofs = proof_options.unwrap(); // add error check
|
||||||
|
for i in 0..N {
|
||||||
|
pw.set_proof_with_pis_target(&cyc_targets.inner_proofs_with_pis[i], &proofs[i])?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pw.set_verifier_data_target(&cyc_targets.verifier_data, &circ_data.verifier_only)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates `CommonCircuitData` usable for node recursion.
|
||||||
|
/// the circuit being built here depends on M and N so must be re-generated
|
||||||
|
/// if the params change
|
||||||
|
pub fn common_data_for_node(&self) -> Result<CommonCircuitData<F, D>>
|
||||||
|
{
|
||||||
|
// layer 1
|
||||||
|
let config = CircuitConfig::standard_recursion_config();
|
||||||
|
let builder = CircuitBuilder::<F, D>::new(config);
|
||||||
|
let data = builder.build::<C>();
|
||||||
|
|
||||||
|
// layer 2
|
||||||
|
let config = CircuitConfig::standard_recursion_config();
|
||||||
|
let mut builder = CircuitBuilder::<F, D>::new(config.clone());
|
||||||
|
let verifier_data = builder.add_virtual_verifier_data(data.common.config.fri_config.cap_height);
|
||||||
|
// generate and verify N number of proofs
|
||||||
|
for _ in 0..N {
|
||||||
|
let proof = builder.add_virtual_proof_with_pis(&data.common);
|
||||||
|
builder.verify_proof::<C>(&proof, &verifier_data, &data.common);
|
||||||
|
}
|
||||||
|
let data = builder.build::<C>();
|
||||||
|
|
||||||
|
// layer 3
|
||||||
|
let config = CircuitConfig::standard_recursion_config();
|
||||||
|
let mut builder = CircuitBuilder::<F, D>::new(config.clone());
|
||||||
|
|
||||||
|
// add a ConstantGate
|
||||||
|
builder.add_gate(
|
||||||
|
plonky2::gates::constant::ConstantGate::new(config.num_constants),
|
||||||
|
vec![],
|
||||||
|
);
|
||||||
|
|
||||||
|
// build M inner circuits
|
||||||
|
for i in 0..M {
|
||||||
|
self.circ.build(&mut builder)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate and verify N number of proofs
|
||||||
|
let verifier_data = builder.add_verifier_data_public_inputs();
|
||||||
|
for _ in 0..N {
|
||||||
|
let proof = builder.add_virtual_proof_with_pis(&data.common);
|
||||||
|
builder.verify_proof::<C>(&proof, &verifier_data, &data.common);
|
||||||
|
}
|
||||||
|
// pad. TODO: optimize this padding to only needed number of gates
|
||||||
|
while builder.num_gates() < 1 << 13 {
|
||||||
|
builder.add_gate(NoopGate, vec![]);
|
||||||
|
}
|
||||||
|
Ok(builder.build::<C>().common)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user