mirror of
https://github.com/logos-storage/proof-aggregation.git
synced 2026-01-02 13:53:13 +00:00
re-organize simple recursion
This commit is contained in:
parent
e61a17800d
commit
b69b682df2
@ -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<Plonky2Proof>,
|
||||
@ -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<F, C, D>],
|
||||
data: CircuitData<F, C, D>,
|
||||
) -> anyhow::Result<(ProofWithPublicInputs<F, C, D>, CircuitData<F, C, D>)> {
|
||||
// base case: if only one proof remains, return it
|
||||
if proofs_with_pi.len() == 1 {
|
||||
return Ok((proofs_with_pi[0].clone(), data));
|
||||
}
|
||||
|
||||
let mut new_proofs = vec![];
|
||||
let mut new_circuit_data: Option<CircuitData<F, C, D>> = None;
|
||||
|
||||
// group proofs according to the tree's width
|
||||
for chunk in proofs_with_pi.chunks(RECURSION_TREE_WIDTH) {
|
||||
let proofs_chunk = chunk.to_vec();
|
||||
|
||||
// Build an inner-circuit to verify and aggregate the proofs in the chunk
|
||||
let inner_config = CircuitConfig::standard_recursion_config();
|
||||
let mut inner_builder = CircuitBuilder::<F, D>::new(inner_config);
|
||||
let mut inner_pw = PartialWitness::new();
|
||||
|
||||
// aggregate proofs
|
||||
aggregate_sampling_proofs(
|
||||
&proofs_chunk,
|
||||
&data.verifier_data(),
|
||||
&mut inner_builder,
|
||||
&mut inner_pw,
|
||||
)?;
|
||||
|
||||
// Build the inner-circuit
|
||||
// this causes major delay - we can load it but better if we split build and prove
|
||||
let inner_data = inner_builder.build::<C>();
|
||||
|
||||
// Prove the inner-circuit
|
||||
let proof = inner_data.prove(inner_pw)?;
|
||||
new_proofs.push(proof);
|
||||
new_circuit_data = Some(inner_data);
|
||||
}
|
||||
|
||||
// Recursively aggregate the new proofs
|
||||
aggregate_sampling_proofs_tree(&new_proofs, new_circuit_data.unwrap())
|
||||
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<F, C, D>],
|
||||
vd: VerifierCircuitData<F, C, D>
|
||||
) -> anyhow::Result<(ProofWithPublicInputs<F, C, D>, VerifierCircuitData<F, C, D>)> {
|
||||
if proofs_with_pi.len() == 1 {
|
||||
return Ok((proofs_with_pi[0].clone(), vd));
|
||||
#[derive(Clone)]
|
||||
pub struct SimpleRecursionTargets<
|
||||
> {
|
||||
pub proofs_with_pi: Vec<ProofWithPublicInputsTarget<D>>,
|
||||
pub verifier_data: VerifierCircuitTarget,
|
||||
pub entropy: HashOutTarget,
|
||||
}
|
||||
|
||||
pub struct SimpleRecursionInput<
|
||||
>{
|
||||
pub proofs: Vec<ProofWithPublicInputs<F, C, D>>,
|
||||
pub verifier_data: VerifierCircuitData<F, C, D>,
|
||||
pub entropy: HashOut<F>,
|
||||
}
|
||||
|
||||
impl<
|
||||
I: InnerCircuit,
|
||||
const N: usize,
|
||||
> SimpleRecursionCircuit<I, N>
|
||||
{
|
||||
|
||||
pub fn new(
|
||||
inner_circuit: I,
|
||||
)->Self{
|
||||
Self{
|
||||
inner_circuit,
|
||||
}
|
||||
}
|
||||
|
||||
let mut new_proofs = vec![];
|
||||
let mut new_circuit_data: Option<VerifierCircuitData<F, C, D>> = None;
|
||||
/// contains the circuit logic and returns the witness & public input targets
|
||||
pub fn build_circuit(
|
||||
&self,
|
||||
builder: &mut CircuitBuilder::<F, D>,
|
||||
) -> anyhow::Result<SimpleRecursionTargets> {
|
||||
// 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::<F, D>::new(inner_config);
|
||||
let mut inner_pw = PartialWitness::new();
|
||||
// verify the proofs in-circuit
|
||||
for i in 0..N {
|
||||
builder.verify_proof::<C>(&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<F>,
|
||||
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::<C>();
|
||||
// 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())
|
||||
}
|
||||
@ -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<D> + Poseidon2,
|
||||
C: GenericConfig<D, F = F>,
|
||||
const D: usize,
|
||||
>{
|
||||
pub recursion_tree_params: RecursionTreeParams,
|
||||
pub verifier_data: VerifierCircuitData<F, C, D>
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SimpleRecursionTargets<
|
||||
const D: usize,
|
||||
> {
|
||||
pub proofs_with_pi: Vec<ProofWithPublicInputsTarget<D>>,
|
||||
pub verifier_data: VerifierCircuitTarget,
|
||||
pub entropy: HashOutTarget,
|
||||
}
|
||||
|
||||
pub struct SimpleRecursionInput<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
C: GenericConfig<D, F = F>,
|
||||
const D: usize,
|
||||
>{
|
||||
pub proofs: Vec<ProofWithPublicInputs<F, C, D>>,
|
||||
pub verifier_data: VerifierCircuitData<F, C, D>,
|
||||
pub entropy: HashOut<F>,
|
||||
}
|
||||
|
||||
impl<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
C: GenericConfig<D, F = F>,
|
||||
const D: usize,
|
||||
> SimpleRecursionCircuit<F,C,D> where
|
||||
C::Hasher: AlgebraicHasher<F>,
|
||||
{
|
||||
|
||||
pub fn new(
|
||||
recursion_tree_params: RecursionTreeParams,
|
||||
verifier_data: VerifierCircuitData<F, C, D>
|
||||
)->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::<F, D>,
|
||||
) -> SimpleRecursionTargets<D> {
|
||||
// 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::<C>(&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<F>,
|
||||
targets: &SimpleRecursionTargets<D>,
|
||||
witnesses: SimpleRecursionInput<F, C, D>,
|
||||
) -> 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(())
|
||||
|
||||
}
|
||||
}
|
||||
@ -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<F, C, D>],
|
||||
data: CircuitData<F, C, D>,
|
||||
) -> anyhow::Result<(ProofWithPublicInputs<F, C, D>, CircuitData<F, C, D>)> {
|
||||
// base case: if only one proof remains, return it
|
||||
if proofs_with_pi.len() == 1 {
|
||||
return Ok((proofs_with_pi[0].clone(), data));
|
||||
}
|
||||
|
||||
let mut new_proofs = vec![];
|
||||
let mut new_circuit_data: Option<CircuitData<F, C, D>> = None;
|
||||
|
||||
// group proofs according to the tree's width
|
||||
for chunk in proofs_with_pi.chunks(RECURSION_TREE_WIDTH) {
|
||||
let proofs_chunk = chunk.to_vec();
|
||||
|
||||
// Build an inner-circuit to verify and aggregate the proofs in the chunk
|
||||
let inner_config = CircuitConfig::standard_recursion_config();
|
||||
let mut inner_builder = CircuitBuilder::<F, D>::new(inner_config);
|
||||
let mut inner_pw = PartialWitness::new();
|
||||
|
||||
// aggregate proofs
|
||||
simple_recursion::aggregate_sampling_proofs(
|
||||
&proofs_chunk,
|
||||
&data.verifier_data(),
|
||||
&mut inner_builder,
|
||||
&mut inner_pw,
|
||||
)?;
|
||||
|
||||
// Build the inner-circuit
|
||||
// this causes major delay - we can load it but better if we split build and prove
|
||||
let inner_data = inner_builder.build::<C>();
|
||||
|
||||
// Prove the inner-circuit
|
||||
let proof = inner_data.prove(inner_pw)?;
|
||||
new_proofs.push(proof);
|
||||
new_circuit_data = Some(inner_data);
|
||||
}
|
||||
|
||||
// Recursively aggregate the new proofs
|
||||
aggregate_sampling_proofs_tree(&new_proofs, new_circuit_data.unwrap())
|
||||
}
|
||||
|
||||
/// same as above but takes `VerifierCircuitData`
|
||||
pub fn aggregate_sampling_proofs_tree2(
|
||||
proofs_with_pi: &[ProofWithPublicInputs<F, C, D>],
|
||||
vd: VerifierCircuitData<F, C, D>
|
||||
) -> anyhow::Result<(ProofWithPublicInputs<F, C, D>, VerifierCircuitData<F, C, D>)> {
|
||||
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<VerifierCircuitData<F, C, D>> = 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::<F, D>::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::<C>();
|
||||
|
||||
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())
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user