mirror of
https://github.com/logos-storage/proof-aggregation.git
synced 2026-01-02 13:53:13 +00:00
remove old experiments
This commit is contained in:
parent
a4b4089fc3
commit
06ae6ffd30
@ -1,6 +1,5 @@
|
||||
[workspace]
|
||||
members = ["codex-plonky2-circuits","plonky2_poseidon2","proof-input","workflow"]
|
||||
exclude = ["goldibear_experiments"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.dependencies]
|
||||
|
||||
13
goldibear_experiments/.gitignore
vendored
13
goldibear_experiments/.gitignore
vendored
@ -1,13 +0,0 @@
|
||||
#IDE Related
|
||||
.idea
|
||||
|
||||
# Cargo build
|
||||
/target
|
||||
Cargo.lock
|
||||
|
||||
# Profile-guided optimization
|
||||
/tmp
|
||||
pgo-data.profdata
|
||||
|
||||
# MacOS nuisances
|
||||
.DS_Store
|
||||
@ -1,5 +0,0 @@
|
||||
Plonky2 Goldibear Experiments
|
||||
================================
|
||||
|
||||
Few experiments done to test the [Plonky2_Goldibear](https://github.com/telosnetwork/plonky2_goldibear/tree/main).
|
||||
The storage circuits are adapted for that version of plonky2 and tested.
|
||||
@ -1,13 +0,0 @@
|
||||
#IDE Related
|
||||
.idea
|
||||
|
||||
# Cargo build
|
||||
/target
|
||||
Cargo.lock
|
||||
|
||||
# Profile-guided optimization
|
||||
/tmp
|
||||
pgo-data.profdata
|
||||
|
||||
# MacOS nuisances
|
||||
.DS_Store
|
||||
@ -1,24 +0,0 @@
|
||||
[package]
|
||||
name = "codex-plonky2-circuits"
|
||||
description = "Codex storage proofs circuits for Plonky2"
|
||||
authors = ["Mohammed Alghazwi <m.ghazwi@gmail.com>"]
|
||||
readme = "README.md"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = { version = "1.0.89"}
|
||||
unroll = { version = "0.1.5"}
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
plonky2 = { git = "https://github.com/telosnetwork/plonky2_goldibear.git"}
|
||||
plonky2_field = { git = "https://github.com/telosnetwork/plonky2_goldibear.git" }
|
||||
thiserror = "2.0.10"
|
||||
itertools = { version = "0.12.1"}
|
||||
plonky2_maybe_rayon = { git = "https://github.com/telosnetwork/plonky2_goldibear.git" }
|
||||
hashbrown = "0.14.5"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.5.1", default-features = false }
|
||||
tynm = { version = "0.1.6", default-features = false }
|
||||
|
||||
@ -1,34 +0,0 @@
|
||||
use plonky2::hash::hash_types::{ HashOutTarget, RichField};
|
||||
use plonky2::hash::hashing::PlonkyPermutation;
|
||||
use plonky2::iop::target::Target;
|
||||
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
||||
use plonky2::plonk::config::AlgebraicHasher;
|
||||
use plonky2_field::types::HasExtension;
|
||||
use crate::circuits::params::NUM_HASH_OUT_ELTS;
|
||||
|
||||
/// Compression function which takes two 256 bit inputs (HashOutTarget) and key Target
|
||||
/// and returns a 256 bit output (HashOutTarget / 4 Targets).
|
||||
pub fn key_compress_circuit<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
H: AlgebraicHasher<F, NUM_HASH_OUT_ELTS>,
|
||||
>(
|
||||
builder: &mut CircuitBuilder<F, D, NUM_HASH_OUT_ELTS>,
|
||||
x: HashOutTarget<NUM_HASH_OUT_ELTS>,
|
||||
y: HashOutTarget<NUM_HASH_OUT_ELTS>,
|
||||
key: Target,
|
||||
) -> HashOutTarget<NUM_HASH_OUT_ELTS> {
|
||||
let zero = builder.zero();
|
||||
let mut state = H::AlgebraicPermutation::new(core::iter::repeat(zero));
|
||||
|
||||
state.set_from_slice(&x.elements, 0);
|
||||
state.set_from_slice(&y.elements, NUM_HASH_OUT_ELTS);
|
||||
state.set_elt(key, NUM_HASH_OUT_ELTS*2);
|
||||
|
||||
state = builder.permute::<H>(state);
|
||||
|
||||
HashOutTarget {
|
||||
elements: state.squeeze()[..NUM_HASH_OUT_ELTS].try_into().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,166 +0,0 @@
|
||||
// Plonky2 Circuit implementation of "safe" merkle tree
|
||||
// consistent with the one in codex:
|
||||
// https://github.com/codex-storage/codex-storage-proofs-circuits/blob/master/circuit/codex/merkle.circom
|
||||
|
||||
use plonky2::{
|
||||
hash::hash_types::{HashOutTarget, RichField, },
|
||||
iop::target::BoolTarget,
|
||||
plonk::circuit_builder::CircuitBuilder,
|
||||
};
|
||||
use std::marker::PhantomData;
|
||||
use plonky2::plonk::config::AlgebraicHasher;
|
||||
use plonky2_field::types::HasExtension;
|
||||
use crate::circuits::keyed_compress::key_compress_circuit;
|
||||
use crate::circuits::params::NUM_HASH_OUT_ELTS;
|
||||
use crate::circuits::utils::{add_assign_hash_out_target, mul_hash_out_target};
|
||||
use crate::Result;
|
||||
use crate::error::CircuitError;
|
||||
|
||||
// Constants for the keys used in compression
|
||||
pub const KEY_NONE: u64 = 0x0;
|
||||
pub const KEY_BOTTOM_LAYER: u64 = 0x1;
|
||||
pub const KEY_ODD: u64 = 0x2;
|
||||
pub const KEY_ODD_AND_BOTTOM_LAYER: u64 = 0x3;
|
||||
|
||||
/// Merkle tree targets representing the input to the circuit
|
||||
#[derive(Clone)]
|
||||
pub struct MerkleTreeTargets{
|
||||
pub leaf: HashOutTarget<NUM_HASH_OUT_ELTS>,
|
||||
pub path_bits: Vec<BoolTarget>,
|
||||
pub last_bits: Vec<BoolTarget>,
|
||||
pub mask_bits: Vec<BoolTarget>,
|
||||
pub merkle_path: MerkleProofTarget,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct MerkleProofTarget {
|
||||
/// The Merkle digest of each sibling subtree, staying from the bottommost layer.
|
||||
pub path: Vec<HashOutTarget<NUM_HASH_OUT_ELTS>>,
|
||||
}
|
||||
|
||||
/// Merkle tree circuit contains the functions for
|
||||
/// building, proving and verifying the circuit.
|
||||
#[derive(Clone)]
|
||||
pub struct MerkleTreeCircuit<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
H: AlgebraicHasher<F, NUM_HASH_OUT_ELTS>,
|
||||
> {
|
||||
pub phantom_data: PhantomData<(F,H)>,
|
||||
}
|
||||
|
||||
impl<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
H: AlgebraicHasher<F, NUM_HASH_OUT_ELTS>,
|
||||
> MerkleTreeCircuit<F, D, H> {
|
||||
|
||||
|
||||
pub fn new() -> Self{
|
||||
Self{
|
||||
phantom_data: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Reconstructs the Merkle root from a leaf and Merkle path using a “mask” approach.
|
||||
///
|
||||
/// # input
|
||||
///
|
||||
/// * `builder` - A circuit builder.
|
||||
/// * `targets` - The Merkle targets.
|
||||
/// * `max_depth` - The maximum depth of the tree.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `HashOutTarget` representing the reconstructed Merkle root in-circuit.
|
||||
///
|
||||
pub fn reconstruct_merkle_root_circuit_with_mask(
|
||||
builder: &mut CircuitBuilder<F, D, NUM_HASH_OUT_ELTS>,
|
||||
targets: &mut MerkleTreeTargets,
|
||||
max_depth: usize,
|
||||
) -> Result<HashOutTarget<NUM_HASH_OUT_ELTS>> {
|
||||
let mut state: Vec<HashOutTarget<NUM_HASH_OUT_ELTS>> = Vec::with_capacity(max_depth+1);
|
||||
state.push(targets.leaf);
|
||||
let zero = builder.zero();
|
||||
let one = builder.one();
|
||||
let two = builder.two();
|
||||
|
||||
// --- Basic checks on input sizes.
|
||||
let path_len = targets.path_bits.len();
|
||||
let proof_len = targets.merkle_path.path.len();
|
||||
let mask_len = targets.mask_bits.len();
|
||||
let last_len = targets.last_bits.len();
|
||||
|
||||
if path_len != proof_len {
|
||||
return Err(CircuitError::PathBitsLengthMismatch(path_len, proof_len));
|
||||
}
|
||||
|
||||
if mask_len != path_len + 1 {
|
||||
return Err(CircuitError::MaskBitsLengthMismatch(mask_len, path_len+1));
|
||||
}
|
||||
|
||||
if last_len != path_len {
|
||||
return Err(CircuitError::LastBitsLengthMismatch(last_len, path_len));
|
||||
}
|
||||
|
||||
if path_len != max_depth {
|
||||
return Err(CircuitError::PathBitsMaxDepthMismatch(path_len, max_depth));
|
||||
}
|
||||
|
||||
// compute is_last
|
||||
let mut is_last = vec![BoolTarget::new_unsafe(zero); max_depth + 1];
|
||||
is_last[max_depth] = BoolTarget::new_unsafe(one); // set isLast[max_depth] to 1 (true)
|
||||
for i in (0..max_depth).rev() {
|
||||
let eq_out = builder.is_equal(targets.path_bits[i].target , targets.last_bits[i].target);
|
||||
is_last[i] = builder.and( is_last[i + 1] , eq_out);
|
||||
}
|
||||
|
||||
let mut i: usize = 0;
|
||||
for (&bit, &sibling) in targets.path_bits.iter().zip(&targets.merkle_path.path) {
|
||||
|
||||
// logic: we add KEY_BOTTOM_LAYER if i == 0, otherwise KEY_NONE.
|
||||
let bottom_key_val = if i == 0 {
|
||||
KEY_BOTTOM_LAYER
|
||||
} else {
|
||||
KEY_NONE
|
||||
};
|
||||
let bottom = builder.constant(F::from_canonical_u64(bottom_key_val));
|
||||
|
||||
// compute: odd = isLast[i] * (1-pathBits[i]);
|
||||
// compute: key = bottom + 2*odd
|
||||
let mut odd = builder.sub(one, targets.path_bits[i].target);
|
||||
odd = builder.mul(is_last[i].target, odd);
|
||||
odd = builder.mul(two, odd);
|
||||
let key = builder.add(bottom,odd);
|
||||
|
||||
// select left and right based on path_bit
|
||||
let mut left = vec![];
|
||||
let mut right = vec![];
|
||||
for j in 0..NUM_HASH_OUT_ELTS {
|
||||
left.push( builder.select(bit, sibling.elements[j], state[i].elements[j]));
|
||||
right.push( builder.select(bit, state[i].elements[j], sibling.elements[j]));
|
||||
}
|
||||
|
||||
// Compress them with a keyed-hash function
|
||||
let combined_hash = key_compress_circuit::<F, D, H>
|
||||
(builder,
|
||||
HashOutTarget::from_vec(left),
|
||||
HashOutTarget::from_vec(right),
|
||||
key);
|
||||
state.push(combined_hash);
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
// select the right layer using the mask bits
|
||||
let mut reconstructed_root = HashOutTarget::from_vec([builder.zero();4].to_vec());
|
||||
for k in 0..max_depth {
|
||||
let diff = builder.sub(targets.mask_bits[k].target, targets.mask_bits[k+1].target);
|
||||
let mul_result = mul_hash_out_target(builder,&diff,&mut state[k+1]);
|
||||
add_assign_hash_out_target(builder,&mut reconstructed_root, &mul_result);
|
||||
}
|
||||
|
||||
Ok(reconstructed_root)
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
pub mod merkle_circuit;
|
||||
pub mod sample_cells;
|
||||
pub mod utils;
|
||||
pub mod params;
|
||||
pub mod keyed_compress;
|
||||
pub mod sponge;
|
||||
@ -1,64 +0,0 @@
|
||||
// global params for the circuits
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use std::env;
|
||||
use plonky2::hash::hash_types::GOLDILOCKS_NUM_HASH_OUT_ELTS;
|
||||
|
||||
/// params used for the circuits
|
||||
/// should be defined prior to building the circuit
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CircuitParams{
|
||||
pub max_depth: usize,
|
||||
pub max_log2_n_slots: usize,
|
||||
pub block_tree_depth: usize,
|
||||
pub n_field_elems_per_cell: usize,
|
||||
pub n_samples: usize,
|
||||
}
|
||||
|
||||
impl CircuitParams {
|
||||
/// Creates a new `CircuitParams` struct from environment.
|
||||
///
|
||||
/// - `MAX_DEPTH`:The maximum slot depth
|
||||
/// - `MAX_LOG2_N_SLOTS`:The maximum log2 number of slots
|
||||
/// - `BLOCK_TREE_DEPTH`:The block tree depth
|
||||
/// - `N_FIELD_ELEMS_PER_CELL`: The number of field elements per cell
|
||||
/// - `N_SAMPLES`: number of samples
|
||||
///
|
||||
/// Returns an error if any environment variable is missing or fails to parse.
|
||||
pub fn from_env() -> Result<Self> {
|
||||
let max_depth = env::var("MAX_DEPTH")
|
||||
.context("MAX_DEPTH is not set")?
|
||||
.parse::<usize>()
|
||||
.context("MAX_DEPTH must be a valid usize")?;
|
||||
|
||||
let max_log2_n_slots = env::var("MAX_LOG2_N_SLOTS")
|
||||
.context("MAX_LOG2_N_SLOTS is not set")?
|
||||
.parse::<usize>()
|
||||
.context("MAX_LOG2_N_SLOTS must be a valid usize")?;
|
||||
|
||||
let block_tree_depth = env::var("BLOCK_TREE_DEPTH")
|
||||
.context("BLOCK_TREE_DEPTH is not set")?
|
||||
.parse::<usize>()
|
||||
.context("BLOCK_TREE_DEPTH must be a valid usize")?;
|
||||
|
||||
let n_field_elems_per_cell = env::var("N_FIELD_ELEMS_PER_CELL")
|
||||
.context("N_FIELD_ELEMS_PER_CELL is not set")?
|
||||
.parse::<usize>()
|
||||
.context("N_FIELD_ELEMS_PER_CELL must be a valid usize")?;
|
||||
|
||||
let n_samples = env::var("N_SAMPLES")
|
||||
.context("N_SAMPLES is not set")?
|
||||
.parse::<usize>()
|
||||
.context("N_SAMPLES must be a valid usize")?;
|
||||
|
||||
Ok(CircuitParams {
|
||||
max_depth,
|
||||
max_log2_n_slots,
|
||||
block_tree_depth,
|
||||
n_field_elems_per_cell,
|
||||
n_samples,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub const NUM_HASH_OUT_ELTS: usize = GOLDILOCKS_NUM_HASH_OUT_ELTS;
|
||||
@ -1,404 +0,0 @@
|
||||
// Sample cells
|
||||
// consistent with:
|
||||
// https://github.com/codex-storage/codex-storage-proofs-circuits/blob/master/circuit/codex/sample_cells.circom
|
||||
// circuit consists of:
|
||||
// - reconstruct the dataset merkle root using the slot root as leaf
|
||||
// - samples multiple cells by calling the sample_cells
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use plonky2::{
|
||||
hash::{
|
||||
hash_types::{HashOut, HashOutTarget, RichField},
|
||||
hashing::PlonkyPermutation,
|
||||
},
|
||||
iop::{
|
||||
target::{BoolTarget, Target},
|
||||
witness::{PartialWitness, WitnessWrite},
|
||||
},
|
||||
plonk::circuit_builder::CircuitBuilder,
|
||||
};
|
||||
use plonky2::plonk::config::AlgebraicHasher;
|
||||
use plonky2_field::types::HasExtension;
|
||||
|
||||
use crate::{
|
||||
circuits::{
|
||||
merkle_circuit::{MerkleProofTarget, MerkleTreeCircuit, MerkleTreeTargets},
|
||||
params::{CircuitParams, NUM_HASH_OUT_ELTS},
|
||||
sponge::{hash_n_no_padding, hash_n_with_padding},
|
||||
utils::{assign_hash_out_targets, ceiling_log2},
|
||||
},
|
||||
Result,
|
||||
error::CircuitError,
|
||||
};
|
||||
|
||||
/// circuit for sampling a slot in a dataset merkle tree
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SampleCircuit<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
H: AlgebraicHasher<F, NUM_HASH_OUT_ELTS>,
|
||||
> {
|
||||
params: CircuitParams,
|
||||
phantom_data: PhantomData<(F,H)>,
|
||||
}
|
||||
|
||||
impl<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
H: AlgebraicHasher<F, NUM_HASH_OUT_ELTS>,
|
||||
> SampleCircuit<F, D, H> {
|
||||
pub fn new(params: CircuitParams) -> Self{
|
||||
Self{
|
||||
params,
|
||||
phantom_data: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// struct of input to the circuit as targets
|
||||
/// used to build the circuit and can be assigned after building
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SampleTargets {
|
||||
|
||||
pub entropy: HashOutTarget<NUM_HASH_OUT_ELTS>, // public input
|
||||
pub dataset_root: HashOutTarget<NUM_HASH_OUT_ELTS>, // public input
|
||||
pub slot_index: Target, // public input
|
||||
|
||||
pub slot_root: HashOutTarget<NUM_HASH_OUT_ELTS>,
|
||||
pub n_cells_per_slot: Target,
|
||||
pub n_slots_per_dataset: Target,
|
||||
|
||||
pub slot_proof: MerkleProofTarget,
|
||||
|
||||
pub cell_data: Vec<CellTarget>,
|
||||
pub merkle_paths: Vec<MerkleProofTarget>,
|
||||
}
|
||||
|
||||
/// circuit input as field elements
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct SampleCircuitInput<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
>{
|
||||
pub entropy: HashOut<F, NUM_HASH_OUT_ELTS>, // public input
|
||||
pub dataset_root: HashOut<F, NUM_HASH_OUT_ELTS>, // public input
|
||||
pub slot_index: F, // public input
|
||||
|
||||
pub slot_root: HashOut<F, NUM_HASH_OUT_ELTS>,
|
||||
pub n_cells_per_slot: F,
|
||||
pub n_slots_per_dataset: F,
|
||||
|
||||
pub slot_proof: Vec<HashOut<F, NUM_HASH_OUT_ELTS>>,
|
||||
|
||||
pub cell_data: Vec<Cell<F,D>>,
|
||||
pub merkle_paths: Vec<MerklePath<F,D>>,
|
||||
|
||||
}
|
||||
|
||||
/// merkle path from leaf to root as vec of HashOut (4 Goldilocks field elems)
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct MerklePath<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
> {
|
||||
pub path: Vec<HashOut<F, NUM_HASH_OUT_ELTS>>
|
||||
}
|
||||
|
||||
/// a vec of cell targets
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct CellTarget {
|
||||
pub data: Vec<Target>
|
||||
}
|
||||
|
||||
/// cell data as field elements
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Cell<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
> {
|
||||
pub data: Vec<F>,
|
||||
}
|
||||
|
||||
//------- circuit impl --------
|
||||
impl<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
H: AlgebraicHasher<F, NUM_HASH_OUT_ELTS>,
|
||||
> SampleCircuit<F, D, H> {
|
||||
|
||||
/// samples and registers the public input
|
||||
pub fn sample_slot_circuit_with_public_input(
|
||||
&self,
|
||||
builder: &mut CircuitBuilder<F, D, NUM_HASH_OUT_ELTS>,
|
||||
) -> Result<SampleTargets> {
|
||||
let targets = self.sample_slot_circuit(builder)?;
|
||||
let mut pub_targets = vec![];
|
||||
pub_targets.push(targets.slot_index);
|
||||
pub_targets.extend_from_slice(&targets.dataset_root.elements);
|
||||
pub_targets.extend_from_slice(&targets.entropy.elements);
|
||||
builder.register_public_inputs(&pub_targets);
|
||||
Ok(targets)
|
||||
}
|
||||
|
||||
/// in-circuit sampling
|
||||
/// WARNING: no public input are registered when calling this function
|
||||
pub fn sample_slot_circuit(
|
||||
&self,
|
||||
builder: &mut CircuitBuilder::<F, D, NUM_HASH_OUT_ELTS>,
|
||||
) -> Result<SampleTargets> {
|
||||
// circuit params
|
||||
let CircuitParams {
|
||||
max_depth,
|
||||
max_log2_n_slots,
|
||||
block_tree_depth,
|
||||
n_field_elems_per_cell,
|
||||
n_samples,
|
||||
} = self.params;
|
||||
|
||||
// constants
|
||||
let zero = builder.zero();
|
||||
let one = builder.one();
|
||||
|
||||
// ***** prove slot root is in dataset tree *********
|
||||
|
||||
// Create virtual target for slot root and index
|
||||
let slot_root = builder.add_virtual_hash();
|
||||
let slot_index = builder.add_virtual_target();// public input
|
||||
// let slot_index = builder.add_virtual_public_input();// public input
|
||||
|
||||
// dataset path bits (binary decomposition of leaf_index)
|
||||
let d_path_bits = builder.split_le(slot_index,max_log2_n_slots);
|
||||
|
||||
// create virtual target for n_slots_per_dataset
|
||||
let n_slots_per_dataset = builder.add_virtual_target();
|
||||
|
||||
// dataset last bits and mask bits
|
||||
let (d_last_bits, d_mask_bits) =
|
||||
ceiling_log2(builder, n_slots_per_dataset, max_log2_n_slots);
|
||||
|
||||
// dataset Merkle path (sibling hashes from leaf to root)
|
||||
let d_merkle_path = MerkleProofTarget {
|
||||
path: (0..max_log2_n_slots).map(|_| builder.add_virtual_hash()).collect(),
|
||||
};
|
||||
|
||||
// create MerkleTreeTargets struct
|
||||
let mut d_targets = MerkleTreeTargets{
|
||||
leaf: slot_root,
|
||||
path_bits: d_path_bits,
|
||||
last_bits: d_last_bits,
|
||||
mask_bits: d_mask_bits,
|
||||
merkle_path: d_merkle_path,
|
||||
};
|
||||
|
||||
// dataset reconstructed root
|
||||
let d_reconstructed_root =
|
||||
MerkleTreeCircuit::<F,D, H>::reconstruct_merkle_root_circuit_with_mask(builder, &mut d_targets, max_log2_n_slots)?;
|
||||
|
||||
// expected Merkle root
|
||||
let d_expected_root = builder.add_virtual_hash(); // public input
|
||||
// let d_expected_root = builder.add_virtual_hash_public_input(); // public input
|
||||
|
||||
// check equality with expected root
|
||||
for i in 0..NUM_HASH_OUT_ELTS {
|
||||
builder.connect(d_expected_root.elements[i], d_reconstructed_root.elements[i]);
|
||||
}
|
||||
|
||||
//*********** do the sampling ************
|
||||
|
||||
let mut data_targets =vec![];
|
||||
let mut slot_sample_proofs = vec![];
|
||||
let entropy_target = builder.add_virtual_hash(); // public input
|
||||
// let entropy_target = builder.add_virtual_hash_public_input(); // public input
|
||||
|
||||
// virtual target for n_cells_per_slot
|
||||
let n_cells_per_slot = builder.add_virtual_target();
|
||||
|
||||
// calculate last index = n_cells_per_slot-1
|
||||
let slot_last_index = builder.sub(n_cells_per_slot, one);
|
||||
|
||||
// create the mask bits
|
||||
// TODO: re-use this for block and slot trees
|
||||
let mask_bits = builder.split_le(slot_last_index,max_depth);
|
||||
|
||||
// last and mask bits for block tree
|
||||
let mut b_last_bits = builder.split_le(slot_last_index,max_depth);
|
||||
let mut b_mask_bits = builder.split_le(slot_last_index,max_depth);
|
||||
|
||||
// last and mask bits for the slot tree
|
||||
let mut s_last_bits = b_last_bits.split_off(block_tree_depth);
|
||||
let mut s_mask_bits = b_mask_bits.split_off(block_tree_depth);
|
||||
|
||||
// pad mask bits with 0
|
||||
b_mask_bits.push(BoolTarget::new_unsafe(zero.clone()));
|
||||
s_mask_bits.push(BoolTarget::new_unsafe(zero.clone()));
|
||||
|
||||
for i in 0..n_samples{
|
||||
// cell data targets
|
||||
let mut data_i = (0..n_field_elems_per_cell).map(|_| builder.add_virtual_target()).collect::<Vec<_>>();
|
||||
// hash the cell data
|
||||
let mut hash_inputs:Vec<Target>= Vec::new();
|
||||
hash_inputs.extend_from_slice(&data_i);
|
||||
// let data_i_hash = builder.hash_n_to_hash_no_pad::<HF>(hash_inputs);
|
||||
let data_i_hash = hash_n_no_padding::<F,D,H>(builder, hash_inputs)?;
|
||||
// make the counter into hash digest
|
||||
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 {
|
||||
ctr.elements[i] = ctr_target;
|
||||
}else{
|
||||
ctr.elements[i] = zero.clone();
|
||||
}
|
||||
}
|
||||
// paths for block and slot
|
||||
let mut b_path_bits = self.calculate_cell_index_bits(builder, &entropy_target, &d_targets.leaf, &ctr, mask_bits.clone())?;
|
||||
let mut s_path_bits = b_path_bits.split_off(block_tree_depth);
|
||||
|
||||
let mut b_merkle_path = MerkleProofTarget {
|
||||
path: (0..block_tree_depth).map(|_| builder.add_virtual_hash()).collect(),
|
||||
};
|
||||
|
||||
let mut s_merkle_path = MerkleProofTarget {
|
||||
path: (0..(max_depth - block_tree_depth)).map(|_| builder.add_virtual_hash()).collect(),
|
||||
};
|
||||
|
||||
let mut block_targets = MerkleTreeTargets {
|
||||
leaf: data_i_hash,
|
||||
path_bits:b_path_bits,
|
||||
last_bits: b_last_bits.clone(),
|
||||
mask_bits: b_mask_bits.clone(),
|
||||
merkle_path: b_merkle_path,
|
||||
};
|
||||
|
||||
// reconstruct block root
|
||||
let b_root = MerkleTreeCircuit::<F,D,H>::reconstruct_merkle_root_circuit_with_mask(builder, &mut block_targets, block_tree_depth)?;
|
||||
|
||||
let mut slot_targets = MerkleTreeTargets {
|
||||
leaf: b_root,
|
||||
path_bits:s_path_bits,
|
||||
last_bits:s_last_bits.clone(),
|
||||
mask_bits:s_mask_bits.clone(),
|
||||
merkle_path:s_merkle_path,
|
||||
};
|
||||
|
||||
// reconstruct slot root with block root as leaf
|
||||
let slot_reconstructed_root = MerkleTreeCircuit::<F,D,H>::reconstruct_merkle_root_circuit_with_mask(builder, &mut slot_targets, max_depth-block_tree_depth)?;
|
||||
|
||||
// check equality with expected root
|
||||
for i in 0..NUM_HASH_OUT_ELTS {
|
||||
builder.connect( d_targets.leaf.elements[i], slot_reconstructed_root.elements[i]);
|
||||
}
|
||||
|
||||
// combine block and slot path to get the full path so we can assign it later.
|
||||
let mut slot_sample_proof_target = MerkleProofTarget{
|
||||
path: block_targets.merkle_path.path,
|
||||
};
|
||||
slot_sample_proof_target.path.extend_from_slice(&slot_targets.merkle_path.path);
|
||||
|
||||
let cell_i = CellTarget{
|
||||
data: data_i
|
||||
};
|
||||
data_targets.push(cell_i);
|
||||
slot_sample_proofs.push(slot_sample_proof_target);
|
||||
|
||||
}
|
||||
|
||||
let st = SampleTargets {
|
||||
entropy: entropy_target,
|
||||
dataset_root: d_expected_root,
|
||||
slot_index,
|
||||
slot_root: d_targets.leaf,
|
||||
n_cells_per_slot,
|
||||
n_slots_per_dataset,
|
||||
slot_proof: d_targets.merkle_path,
|
||||
cell_data: data_targets,
|
||||
merkle_paths: slot_sample_proofs,
|
||||
};
|
||||
|
||||
Ok(st)
|
||||
}
|
||||
|
||||
/// calculate the cell index = H( entropy | slotRoot | counter ) `mod` nCells
|
||||
pub fn calculate_cell_index_bits(&self, builder: &mut CircuitBuilder<F, D, NUM_HASH_OUT_ELTS>, entropy: &HashOutTarget<NUM_HASH_OUT_ELTS>, slot_root: &HashOutTarget<NUM_HASH_OUT_ELTS>, ctr: &HashOutTarget<NUM_HASH_OUT_ELTS>, mask_bits: Vec<BoolTarget>) -> Result<Vec<BoolTarget>> {
|
||||
let mut hash_inputs:Vec<Target>= Vec::new();
|
||||
hash_inputs.extend_from_slice(&entropy.elements);
|
||||
hash_inputs.extend_from_slice(&slot_root.elements);
|
||||
hash_inputs.extend_from_slice(&ctr.elements);
|
||||
|
||||
let hash_out = hash_n_with_padding::<F,D,H>(builder, hash_inputs)?;
|
||||
let cell_index_bits = builder.low_bits(hash_out.elements[0], self.params.max_depth, true);
|
||||
|
||||
let mut masked_cell_index_bits = vec![];
|
||||
|
||||
// extract the lowest 32 bits using the bit mask
|
||||
for i in 0..self.params.max_depth{
|
||||
masked_cell_index_bits.push(BoolTarget::new_unsafe(builder.mul(mask_bits[i].target, cell_index_bits[i].target)));
|
||||
}
|
||||
|
||||
Ok(masked_cell_index_bits)
|
||||
}
|
||||
|
||||
/// helper method to assign the targets in the circuit to actual field elems
|
||||
pub fn sample_slot_assign_witness(
|
||||
&self,
|
||||
pw: &mut PartialWitness<F>,
|
||||
targets: &SampleTargets,
|
||||
witnesses: &SampleCircuitInput<F, D>,
|
||||
) -> Result<()>{
|
||||
// circuit params
|
||||
let CircuitParams {
|
||||
max_depth,
|
||||
n_field_elems_per_cell,
|
||||
n_samples,
|
||||
..
|
||||
} = self.params;
|
||||
|
||||
// assign n_cells_per_slot
|
||||
pw.set_target(targets.n_cells_per_slot, witnesses.n_cells_per_slot)
|
||||
;
|
||||
|
||||
// assign n_slots_per_dataset
|
||||
pw.set_target(targets.n_slots_per_dataset, witnesses.n_slots_per_dataset)
|
||||
;
|
||||
|
||||
// assign dataset proof
|
||||
for (i, sibling_hash) in witnesses.slot_proof.iter().enumerate() {
|
||||
pw.set_hash_target(targets.slot_proof.path[i], *sibling_hash)
|
||||
;
|
||||
}
|
||||
// assign slot index
|
||||
pw.set_target(targets.slot_index, witnesses.slot_index)
|
||||
;
|
||||
|
||||
// assign the expected Merkle root of dataset to the target
|
||||
pw.set_hash_target(targets.dataset_root, witnesses.dataset_root)
|
||||
;
|
||||
|
||||
// assign the sampled slot
|
||||
pw.set_hash_target(targets.slot_root, witnesses.slot_root)
|
||||
;
|
||||
|
||||
// assign entropy
|
||||
assign_hash_out_targets(pw, &targets.entropy, &witnesses.entropy)?;
|
||||
|
||||
// do the sample N times
|
||||
for i in 0..n_samples {
|
||||
// assign cell data
|
||||
let leaf = witnesses.cell_data[i].data.clone();
|
||||
for j in 0..n_field_elems_per_cell{
|
||||
pw.set_target(targets.cell_data[i].data[j], leaf[j])
|
||||
;
|
||||
}
|
||||
// assign proof for that cell
|
||||
let cell_proof = witnesses.merkle_paths[i].path.clone();
|
||||
for k in 0..max_depth {
|
||||
pw.set_hash_target(targets.merkle_paths[i].path[k], cell_proof[k])
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,185 +0,0 @@
|
||||
use plonky2::hash::hash_types::{HashOutTarget, RichField};
|
||||
use plonky2::hash::hashing::PlonkyPermutation;
|
||||
use plonky2::iop::target::Target;
|
||||
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
||||
use plonky2::plonk::config::AlgebraicHasher;
|
||||
use plonky2_field::types::HasExtension;
|
||||
use crate::error::CircuitError;
|
||||
use crate::Result;
|
||||
use crate::circuits::params::NUM_HASH_OUT_ELTS;
|
||||
|
||||
/// hash n targets (field elements) into hash digest / HashOutTarget (4 Goldilocks field elements)
|
||||
/// this function uses the 10* padding
|
||||
pub fn hash_n_with_padding<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
H: AlgebraicHasher<F, NUM_HASH_OUT_ELTS>
|
||||
>(
|
||||
builder: &mut CircuitBuilder<F, D, NUM_HASH_OUT_ELTS>,
|
||||
inputs: Vec<Target>,
|
||||
) -> Result<HashOutTarget<NUM_HASH_OUT_ELTS>> {
|
||||
Ok(
|
||||
HashOutTarget::from_vec(
|
||||
hash_n_to_m_with_padding::<F,D,H>(builder, inputs, NUM_HASH_OUT_ELTS)?
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pub fn hash_n_to_m_with_padding<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
H: AlgebraicHasher<F, NUM_HASH_OUT_ELTS>
|
||||
>(
|
||||
builder: &mut CircuitBuilder<F, D, NUM_HASH_OUT_ELTS>,
|
||||
inputs: Vec<Target>,
|
||||
num_outputs: usize,
|
||||
) -> Result<Vec<Target>> {
|
||||
let rate = H::AlgebraicPermutation::RATE;
|
||||
let width = H::AlgebraicPermutation::WIDTH; // rate + capacity
|
||||
let zero = builder.zero();
|
||||
let one = builder.one();
|
||||
let mut state = H::AlgebraicPermutation::new(core::iter::repeat(zero).take(width));
|
||||
|
||||
// Set the domain separator at index 8
|
||||
let dom_sep_value = rate as u64 + 256 * 12 + 65536 * 63;
|
||||
let dom_sep = builder.constant(F::from_canonical_u64(dom_sep_value));
|
||||
state.set_elt(dom_sep, 8);
|
||||
|
||||
let n = inputs.len();
|
||||
let num_chunks = (n + rate) / rate; // 10* padding
|
||||
let mut input_iter = inputs.iter();
|
||||
|
||||
// Process the first (num_chunks - 1) chunks
|
||||
for _ in 0..(num_chunks - 1) {
|
||||
let mut chunk = Vec::with_capacity(rate);
|
||||
for _ in 0..rate {
|
||||
if let Some(&input) = input_iter.next() {
|
||||
chunk.push(input);
|
||||
} else {
|
||||
// should not happen here
|
||||
return Err(CircuitError::InsufficientInputs(rate,chunk.len()));
|
||||
}
|
||||
}
|
||||
// Add the chunk to the state
|
||||
for j in 0..rate {
|
||||
state.set_elt(builder.add(state.as_ref()[j], chunk[j]), j);
|
||||
}
|
||||
// Apply permutation
|
||||
state = builder.permute::<H>(state);
|
||||
}
|
||||
|
||||
// Process the last chunk with 10* padding
|
||||
let rem = num_chunks * rate - n; // 0 < rem <= rate
|
||||
let ofs = rate - rem; // Offset where padding starts
|
||||
|
||||
let mut last_chunk = Vec::with_capacity(rate);
|
||||
for _ in 0..ofs {
|
||||
if let Some(&input) = input_iter.next() {
|
||||
last_chunk.push(input);
|
||||
} else {
|
||||
last_chunk.push(zero); // Pad zeros if no more inputs
|
||||
}
|
||||
}
|
||||
|
||||
// Add the '1' padding bit
|
||||
last_chunk.push(one);
|
||||
|
||||
// Pad zeros to reach the full rate
|
||||
while last_chunk.len() < rate {
|
||||
last_chunk.push(zero);
|
||||
}
|
||||
|
||||
// Add the last chunk to the state
|
||||
for j in 0..rate {
|
||||
state.set_elt(builder.add(state.as_ref()[j], last_chunk[j]), j);
|
||||
}
|
||||
// Apply permutation
|
||||
state = builder.permute::<H>(state);
|
||||
|
||||
// Squeeze until we have the desired number of outputs
|
||||
let mut outputs = Vec::with_capacity(num_outputs);
|
||||
loop {
|
||||
for &s in state.squeeze() {
|
||||
outputs.push(s);
|
||||
if outputs.len() == num_outputs {
|
||||
return Ok(outputs);
|
||||
}
|
||||
}
|
||||
state = builder.permute::<H>(state);
|
||||
}
|
||||
}
|
||||
|
||||
/// hash n targets (field elements) into hash digest / HashOutTarget (4 Goldilocks field elements)
|
||||
/// this function uses doesn't pad and expects input to be divisible by rate
|
||||
/// rate is fixed at 8 for now.
|
||||
pub fn hash_n_no_padding<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
H: AlgebraicHasher<F, NUM_HASH_OUT_ELTS>
|
||||
>(
|
||||
builder: &mut CircuitBuilder<F, D, NUM_HASH_OUT_ELTS>,
|
||||
inputs: Vec<Target>,
|
||||
) -> Result<HashOutTarget<NUM_HASH_OUT_ELTS>> {
|
||||
Ok(
|
||||
HashOutTarget::from_vec(
|
||||
hash_n_to_m_no_padding::<F, D, H>(builder, inputs, NUM_HASH_OUT_ELTS)?
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pub fn hash_n_to_m_no_padding<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
H: AlgebraicHasher<F, NUM_HASH_OUT_ELTS>
|
||||
>(
|
||||
builder: &mut CircuitBuilder<F, D, NUM_HASH_OUT_ELTS>,
|
||||
inputs: Vec<Target>,
|
||||
num_outputs: usize,
|
||||
) -> Result<Vec<Target>> {
|
||||
let rate = H::AlgebraicPermutation::RATE;
|
||||
let width = H::AlgebraicPermutation::WIDTH; // rate + capacity
|
||||
let zero = builder.zero();
|
||||
let mut state = H::AlgebraicPermutation::new(core::iter::repeat(zero).take(width));
|
||||
|
||||
// Set the domain separator at index 8
|
||||
let dom_sep_value = rate as u64 + 256 * 12 + 65536 * 8;
|
||||
let dom_sep = builder.constant(F::from_canonical_u64(dom_sep_value));
|
||||
state.set_elt(dom_sep, 8);
|
||||
|
||||
let n = inputs.len();
|
||||
if n % rate != 0 {
|
||||
return Err(CircuitError::SpongeInputLengthMismatch(n, rate));
|
||||
}
|
||||
let num_chunks = n / rate; // 10* padding
|
||||
let mut input_iter = inputs.iter();
|
||||
|
||||
// Process all chunks
|
||||
for _ in 0..num_chunks {
|
||||
let mut chunk = Vec::with_capacity(rate);
|
||||
for _ in 0..rate {
|
||||
if let Some(&input) = input_iter.next() {
|
||||
chunk.push(input);
|
||||
} else {
|
||||
// should not happen here
|
||||
return Err(CircuitError::InsufficientInputs(rate,chunk.len()));
|
||||
}
|
||||
}
|
||||
// Add the chunk to the state
|
||||
for j in 0..rate {
|
||||
state.set_elt(builder.add(state.as_ref()[j], chunk[j]), j);
|
||||
}
|
||||
// Apply permutation
|
||||
state = builder.permute::<H>(state);
|
||||
}
|
||||
// Squeeze until we have the desired number of outputs
|
||||
let mut outputs = Vec::with_capacity(num_outputs);
|
||||
loop {
|
||||
for &s in state.squeeze() {
|
||||
outputs.push(s);
|
||||
if outputs.len() == num_outputs {
|
||||
return Ok(outputs);
|
||||
}
|
||||
}
|
||||
state = builder.permute::<H>(state);
|
||||
}
|
||||
}
|
||||
@ -1,144 +0,0 @@
|
||||
use std::{fs, io};
|
||||
use std::path::Path;
|
||||
use itertools::Itertools;
|
||||
use plonky2::hash::hash_types::{HashOut, HashOutTarget, RichField};
|
||||
use plonky2::iop::witness::{PartialWitness, WitnessWrite};
|
||||
use plonky2::iop::target::{BoolTarget, Target};
|
||||
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
||||
use plonky2_field::types::HasExtension;
|
||||
use crate::Result;
|
||||
use crate::error::CircuitError;
|
||||
use crate::circuits::params::NUM_HASH_OUT_ELTS;
|
||||
|
||||
// --------- helper functions ---------
|
||||
|
||||
/// computes the `last_index` (the binary decomposition of `inp-1`) and the `mask_bits`
|
||||
pub fn ceiling_log2<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
>(
|
||||
builder: &mut CircuitBuilder<F, D, NUM_HASH_OUT_ELTS>,
|
||||
inp: Target,
|
||||
n: usize,
|
||||
) -> (Vec<BoolTarget>, Vec<BoolTarget>){
|
||||
let one = builder.one();
|
||||
let zero = builder.zero();
|
||||
let last_index = builder.sub(inp, one.clone());
|
||||
let last_bits = builder.split_le(last_index,n);
|
||||
|
||||
let mut aux: Vec<BoolTarget> = vec![BoolTarget::new_unsafe(zero.clone()); n + 1];
|
||||
aux[n] = BoolTarget::new_unsafe(one.clone());
|
||||
let mut mask: Vec<BoolTarget> = 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 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));
|
||||
}
|
||||
|
||||
(last_bits, mask)
|
||||
}
|
||||
|
||||
/// assign a vec of bool values to a vec of BoolTargets
|
||||
pub fn assign_bool_targets<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
>(
|
||||
pw: &mut PartialWitness<F>,
|
||||
bool_targets: &Vec<BoolTarget>,
|
||||
bools: Vec<bool>,
|
||||
) -> Result<()>{
|
||||
if bools.len() > bool_targets.len() {
|
||||
return Err(CircuitError::AssignmentLengthMismatch (
|
||||
bool_targets.len(),
|
||||
bools.len(),
|
||||
)
|
||||
);
|
||||
}
|
||||
for (i, bit) in bools.iter().enumerate() {
|
||||
pw.set_bool_target(bool_targets[i], *bit);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// assign a vec of field elems to hash out target elements
|
||||
/// TODO: change to HashOut
|
||||
pub fn assign_hash_out_targets<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
>(
|
||||
pw: &mut PartialWitness<F>,
|
||||
hash_out_elements_targets: &HashOutTarget<NUM_HASH_OUT_ELTS>,
|
||||
hash_out_elements: &HashOut<F, NUM_HASH_OUT_ELTS>,
|
||||
) -> Result<()>{
|
||||
|
||||
// Assign each field element to its corresponding target
|
||||
for (j, (&target, &element)) in hash_out_elements_targets.elements.iter().zip(hash_out_elements.elements.iter()).enumerate() {
|
||||
pw.set_target(target, element)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// helper fn to multiply a HashOutTarget by a Target
|
||||
pub fn mul_hash_out_target<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
>(builder: &mut CircuitBuilder<F, D, NUM_HASH_OUT_ELTS>, t: &Target, hash_target: &mut HashOutTarget<NUM_HASH_OUT_ELTS>) -> HashOutTarget<NUM_HASH_OUT_ELTS> {
|
||||
let mut mul_elements = vec![];
|
||||
for i in 0..NUM_HASH_OUT_ELTS {
|
||||
mul_elements.push(builder.mul(hash_target.elements[i], *t));
|
||||
}
|
||||
HashOutTarget::from_vec(mul_elements)
|
||||
}
|
||||
|
||||
/// helper fn to add AND assign a HashOutTarget (hot) to a mutable HashOutTarget (mut_hot)
|
||||
pub fn add_assign_hash_out_target<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
>(builder: &mut CircuitBuilder<F, D, NUM_HASH_OUT_ELTS>, mut_hot: &mut HashOutTarget<NUM_HASH_OUT_ELTS>, hot: &HashOutTarget<NUM_HASH_OUT_ELTS>) {
|
||||
for i in 0..NUM_HASH_OUT_ELTS {
|
||||
mut_hot.elements[i] = builder.add(mut_hot.elements[i], hot.elements[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads the contents of the specified file and returns them as a vector of bytes using `std::fs::read`.
|
||||
pub fn read_bytes_from_file<P: AsRef<Path>>(path: P) -> io::Result<Vec<u8>> {
|
||||
fs::read(path)
|
||||
}
|
||||
|
||||
/// select hash helper method
|
||||
/// Computes `if b { h0 } else { h1 }`.
|
||||
pub fn select_hash<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
>(
|
||||
builder: &mut CircuitBuilder<F, D, NUM_HASH_OUT_ELTS>,
|
||||
b: BoolTarget,
|
||||
h0: HashOutTarget<NUM_HASH_OUT_ELTS>,
|
||||
h1: HashOutTarget<NUM_HASH_OUT_ELTS>,
|
||||
) -> HashOutTarget<NUM_HASH_OUT_ELTS> {
|
||||
HashOutTarget {
|
||||
elements: core::array::from_fn(|i| builder.select(b, h0.elements[i], h1.elements[i])),
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a Vec<T> into a fixed-size array [T; N], returning an error if the lengths don't match.
|
||||
pub fn vec_to_array<const N: usize, T>(vec: Vec<T>) -> Result<[T; N]> {
|
||||
vec.try_into().map_err(|v: Vec<T>| CircuitError::ArrayLengthMismatchError(format!(
|
||||
"Expected exactly {} elements, got {}",
|
||||
N,
|
||||
v.len()
|
||||
)))
|
||||
}
|
||||
|
||||
/// Computes `if b { v0 } else { v1 }`.
|
||||
pub fn select_vec<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
>(builder: &mut CircuitBuilder<F, D, NUM_HASH_OUT_ELTS>, b: BoolTarget, v0: &[Target], v1: &[Target]) -> Vec<Target> {
|
||||
v0.iter()
|
||||
.zip_eq(v1)
|
||||
.map(|(t0, t1)| builder.select(b, *t0, *t1))
|
||||
.collect()
|
||||
}
|
||||
@ -1,71 +0,0 @@
|
||||
use thiserror::Error;
|
||||
|
||||
/// Custom error types for the Circuits.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum CircuitError {
|
||||
#[error("Path bits length mismatch: expected {0}, found {1}")]
|
||||
PathBitsLengthMismatch(usize, usize),
|
||||
|
||||
#[error("Mask bits length mismatch: expected {0}, found {1}")]
|
||||
MaskBitsLengthMismatch(usize, usize),
|
||||
|
||||
#[error("Last bits length mismatch: expected {0}, found {1}")]
|
||||
LastBitsLengthMismatch(usize, usize),
|
||||
|
||||
#[error("Path bits and max depth mismatch: path bits length {0}, max depth {1}")]
|
||||
PathBitsMaxDepthMismatch(usize, usize),
|
||||
|
||||
#[error("Insufficient input elements for chunk; expected {0}, found {1}")]
|
||||
InsufficientInputs (usize, usize),
|
||||
|
||||
#[error("Sponge: Input length ({0}) must be divisible by rate ({1}) for no padding")]
|
||||
SpongeInputLengthMismatch(usize, usize),
|
||||
|
||||
#[error("Assignment length mismatch: expected at least {0}, found {1}")]
|
||||
AssignmentLengthMismatch(usize, usize),
|
||||
|
||||
#[error("Failed to assign Target at index {0}: {1}")]
|
||||
ArrayTargetAssignmentError(usize, String),
|
||||
|
||||
#[error("Failed to assign Target {0}: {1}")]
|
||||
TargetAssignmentError(String, String),
|
||||
|
||||
#[error("Failed to assign BoolTarget at index {0}: {1}")]
|
||||
ArrayBoolTargetAssignmentError(usize, String),
|
||||
|
||||
#[error("Failed to assign BoolTarget {0}: {1}")]
|
||||
BoolTargetAssignmentError(String, String),
|
||||
|
||||
#[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),
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
pub mod circuits;
|
||||
pub mod error;
|
||||
|
||||
pub type Result<T> = core::result::Result<T, error::CircuitError>;
|
||||
13
goldibear_experiments/proof-input/.gitignore
vendored
13
goldibear_experiments/proof-input/.gitignore
vendored
@ -1,13 +0,0 @@
|
||||
#IDE Related
|
||||
.idea
|
||||
|
||||
# Cargo build
|
||||
/target
|
||||
Cargo.lock
|
||||
|
||||
# Profile-guided optimization
|
||||
/tmp
|
||||
pgo-data.profdata
|
||||
|
||||
# MacOS nuisances
|
||||
.DS_Store
|
||||
@ -1,21 +0,0 @@
|
||||
[package]
|
||||
name = "proof-input"
|
||||
description = "proof input generation library"
|
||||
authors = ["Mohammed Alghazwi <m.ghazwi@gmail.com>"]
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.0", features = ["derive"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
anyhow = { version = "1.0.89"}
|
||||
plonky2 = { git = "https://github.com/telosnetwork/plonky2_goldibear.git"}
|
||||
plonky2_field = { git = "https://github.com/telosnetwork/plonky2_goldibear.git"}
|
||||
# --- local ---
|
||||
codex-plonky2-circuits = { path = "../codex-plonky2-circuits" }
|
||||
criterion = "0.5.1"
|
||||
|
||||
[[bench]]
|
||||
name = "sample_cells"
|
||||
harness = false
|
||||
@ -1,103 +0,0 @@
|
||||
use anyhow::Result;
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use plonky2::iop::witness::PartialWitness;
|
||||
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
||||
use plonky2::plonk::circuit_data::CircuitConfig;
|
||||
use plonky2::plonk::config::GenericConfig;
|
||||
|
||||
use codex_plonky2_circuits::circuits::sample_cells::SampleCircuit;
|
||||
use proof_input::gen_input::gen_testing_circuit_input;
|
||||
use proof_input::params::{D, C, F, HF, Params};
|
||||
use codex_plonky2_circuits::circuits::params::NUM_HASH_OUT_ELTS;
|
||||
|
||||
/// Benchmark for building, proving, and verifying the Plonky2 circuit.
|
||||
fn bench_prove_verify(c: &mut Criterion) -> Result<()>{
|
||||
|
||||
let n_samples = 100;
|
||||
// get default parameters
|
||||
let params = Params::default();
|
||||
let mut test_params = params.input_params;
|
||||
test_params.n_samples = n_samples;
|
||||
|
||||
let mut circuit_params = params.circuit_params;
|
||||
circuit_params.n_samples = n_samples;
|
||||
|
||||
// gen the circuit input
|
||||
let circ_input = gen_testing_circuit_input::<F,D>(&test_params);
|
||||
|
||||
// Create the circuit configuration
|
||||
let config = CircuitConfig::standard_recursion_config_gl();
|
||||
let mut builder = CircuitBuilder::<F, D, NUM_HASH_OUT_ELTS>::new(config);
|
||||
|
||||
// Initialize the SampleCircuit with the parameters
|
||||
let circ = SampleCircuit::<F,D,HF>::new(circuit_params.clone());
|
||||
let targets = circ.sample_slot_circuit_with_public_input(&mut builder)?;
|
||||
|
||||
// Create a PartialWitness and assign the circuit input
|
||||
let mut pw = PartialWitness::new();
|
||||
circ.sample_slot_assign_witness(&mut pw, &targets, &circ_input.clone());
|
||||
|
||||
// Benchmark Group: Separate benchmarks for building, proving, and verifying
|
||||
let mut group = c.benchmark_group("Sampling Circuit Benchmark");
|
||||
|
||||
// Benchmark the Circuit Building Phase
|
||||
group.bench_function("Build Circuit", |b| {
|
||||
b.iter(|| {
|
||||
let config = CircuitConfig::standard_recursion_config_gl();
|
||||
let mut local_builder = CircuitBuilder::<F, D, NUM_HASH_OUT_ELTS>::new(config);
|
||||
let _targets = circ.sample_slot_circuit_with_public_input(&mut local_builder);
|
||||
let _data = local_builder.build::<C>();
|
||||
})
|
||||
});
|
||||
|
||||
// Build the circuit once for proving and verifying benchmarks
|
||||
let build_start = std::time::Instant::now();
|
||||
let data = builder.build::<C>();
|
||||
let build_duration = build_start.elapsed();
|
||||
println!("Build time: {:?}", build_duration);
|
||||
println!("Circuit size (degree bits): {:?}", data.common.degree_bits());
|
||||
|
||||
let num_constr: usize = data.common
|
||||
.gates
|
||||
.iter()
|
||||
.map(|gate| gate.0.num_constraints())
|
||||
.sum();
|
||||
|
||||
println!("Number of constraints: {}", num_constr);
|
||||
println!("Number of gates used: {}", data.common.gates.len());
|
||||
|
||||
// Benchmark the Proving Phase
|
||||
group.bench_function("Prove Circuit", |b| {
|
||||
b.iter(|| {
|
||||
let local_pw = pw.clone();
|
||||
data.prove(local_pw).expect("Failed to prove circuit")
|
||||
})
|
||||
});
|
||||
|
||||
// Generate the proof once for verification benchmarking
|
||||
let prove_start = std::time::Instant::now();
|
||||
let proof_with_pis = data.prove(pw.clone()).expect("Failed to prove circuit");
|
||||
let prove_duration = prove_start.elapsed();
|
||||
println!("prove time: {:?}", prove_duration);
|
||||
let verifier_data = data.verifier_data();
|
||||
|
||||
println!("Proof size: {} bytes", proof_with_pis.to_bytes().len());
|
||||
|
||||
// Benchmark the Verifying Phase
|
||||
group.bench_function("Verify Proof", |b| {
|
||||
b.iter(|| {
|
||||
verifier_data.verify(proof_with_pis.clone()).expect("Failed to verify proof");
|
||||
})
|
||||
});
|
||||
|
||||
group.finish();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Criterion benchmark group
|
||||
criterion_group!{
|
||||
name = prove_verify_benches;
|
||||
config = Criterion::default().sample_size(10);
|
||||
targets = bench_prove_verify
|
||||
}
|
||||
criterion_main!(prove_verify_benches);
|
||||
@ -1,257 +0,0 @@
|
||||
// Data structure used to generate the proof input
|
||||
|
||||
use plonky2::hash::hash_types::{HashOut, RichField};
|
||||
use codex_plonky2_circuits::circuits::sample_cells::Cell;
|
||||
use plonky2_field::types::{HasExtension, Sample};
|
||||
use crate::merkle_tree::merkle_safe::{MerkleProof, MerkleTree};
|
||||
use crate::params::{InputParams, HF};
|
||||
use crate::sponge::hash_bytes_no_padding;
|
||||
use crate::utils::{bits_le_padded_to_usize, calculate_cell_index_bits, usize_to_bits_le};
|
||||
use codex_plonky2_circuits::circuits::params::NUM_HASH_OUT_ELTS;
|
||||
|
||||
// ----------------- slot tree -----------------
|
||||
#[derive(Clone)]
|
||||
pub struct SlotTree<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
> {
|
||||
pub tree: MerkleTree<F, D>, // slot tree
|
||||
pub block_trees: Vec<MerkleTree<F,D>>, // vec of block trees
|
||||
pub cell_data: Vec<Cell<F, D>>, // cell data as field elements
|
||||
pub params: InputParams, // parameters
|
||||
}
|
||||
|
||||
impl<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
> SlotTree<F, D> {
|
||||
/// Create a slot tree with fake data, for testing only
|
||||
pub fn new_default(params: &InputParams) -> Self {
|
||||
// generate fake cell data
|
||||
let cell_data = (0..params.n_cells)
|
||||
.map(|_| new_random_cell(params))
|
||||
.collect::<Vec<_>>();
|
||||
Self::new(cell_data, params.clone())
|
||||
}
|
||||
|
||||
/// Create a new slot tree with the supplied cell data and parameters
|
||||
pub fn new(cells: Vec<Cell<F, D>>, params: InputParams) -> Self {
|
||||
let leaves: Vec<HashOut<F, NUM_HASH_OUT_ELTS>> = cells
|
||||
.iter()
|
||||
.map(|element| hash_bytes_no_padding::<F,D,HF>(&element.data))
|
||||
.collect();
|
||||
let zero = HashOut {
|
||||
elements: [F::zero(); 4],
|
||||
};
|
||||
let n_blocks = params.n_blocks_test();
|
||||
let n_cells_in_blocks = params.n_cells_in_blocks();
|
||||
|
||||
let block_trees = (0..n_blocks)
|
||||
.map(|i| {
|
||||
let start = i * n_cells_in_blocks;
|
||||
let end = (i + 1) * n_cells_in_blocks;
|
||||
Self::get_block_tree(&leaves[start..end].to_vec())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let block_roots = block_trees
|
||||
.iter()
|
||||
.map(|t| t.root().unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
let slot_tree = MerkleTree::<F,D>::new(&block_roots, zero).unwrap();
|
||||
Self {
|
||||
tree: slot_tree,
|
||||
block_trees,
|
||||
cell_data: cells,
|
||||
params,
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a proof for the given leaf index
|
||||
/// The path in the proof is a combined block and slot path to make up the full path
|
||||
pub fn get_proof(&self, index: usize) -> MerkleProof<F,D> {
|
||||
let block_index = index / self.params.n_cells_in_blocks();
|
||||
let leaf_index = index % self.params.n_cells_in_blocks();
|
||||
let block_proof = self.block_trees[block_index].get_proof(leaf_index).unwrap();
|
||||
let slot_proof = self.tree.get_proof(block_index).unwrap();
|
||||
|
||||
// Combine the paths from the block and slot proofs
|
||||
let mut combined_path = block_proof.path.clone();
|
||||
combined_path.extend(slot_proof.path.clone());
|
||||
|
||||
MerkleProof::<F,D> {
|
||||
index,
|
||||
path: combined_path,
|
||||
nleaves: self.cell_data.len(),
|
||||
zero: block_proof.zero.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_block_tree(leaves: &Vec<HashOut<F, NUM_HASH_OUT_ELTS>>) -> MerkleTree<F,D> {
|
||||
let zero = HashOut {
|
||||
elements: [F::zero(); 4],
|
||||
};
|
||||
// Build the Merkle tree
|
||||
let block_tree = MerkleTree::<F,D>::new(leaves, zero).unwrap();
|
||||
block_tree
|
||||
}
|
||||
}
|
||||
|
||||
// -------------- Dataset Tree -------------
|
||||
/// Dataset tree containing all slot trees
|
||||
#[derive(Clone)]
|
||||
pub struct DatasetTree<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
> {
|
||||
pub tree: MerkleTree<F,D>, // dataset tree
|
||||
pub slot_trees: Vec<SlotTree<F, D>>, // vec of slot trees
|
||||
pub params: InputParams, // parameters
|
||||
}
|
||||
|
||||
/// Dataset Merkle proof struct, containing the dataset proof and sampled proofs.
|
||||
#[derive(Clone)]
|
||||
pub struct DatasetProof<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
> {
|
||||
pub slot_index: F,
|
||||
pub entropy: HashOut<F, NUM_HASH_OUT_ELTS>,
|
||||
pub dataset_proof: MerkleProof<F,D>, // proof for dataset level tree
|
||||
pub slot_proofs: Vec<MerkleProof<F,D>>, // proofs for sampled slot
|
||||
pub cell_data: Vec<Cell<F,D>>,
|
||||
}
|
||||
|
||||
impl<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
> DatasetTree<F, D> {
|
||||
/// Dataset tree with fake data, for testing only
|
||||
pub fn new_default(params: &InputParams) -> Self {
|
||||
let mut slot_trees = vec![];
|
||||
let n_slots = 1 << params.dataset_depth_test();
|
||||
for _ in 0..n_slots {
|
||||
slot_trees.push(SlotTree::<F, D>::new_default(params));
|
||||
}
|
||||
Self::new(slot_trees, params.clone())
|
||||
}
|
||||
|
||||
/// Create data for only the specified slot index in params
|
||||
pub fn new_for_testing(params: &InputParams) -> Self {
|
||||
let mut slot_trees = vec![];
|
||||
// let n_slots = 1 << params.dataset_depth();
|
||||
let n_slots = params.n_slots;
|
||||
// zero hash
|
||||
let zero = HashOut {
|
||||
elements: [F::zero(); 4],
|
||||
};
|
||||
let zero_slot = SlotTree::<F, D> {
|
||||
tree: MerkleTree::<F,D>::new(&[zero.clone()], zero.clone()).unwrap(),
|
||||
block_trees: vec![],
|
||||
cell_data: vec![],
|
||||
params: params.clone(),
|
||||
};
|
||||
for i in 0..n_slots {
|
||||
if i == params.testing_slot_index {
|
||||
slot_trees.push(SlotTree::<F, D>::new_default(params));
|
||||
} else {
|
||||
slot_trees.push(zero_slot.clone());
|
||||
}
|
||||
}
|
||||
// get the roots of slot trees
|
||||
let slot_roots = slot_trees
|
||||
.iter()
|
||||
.map(|t| t.tree.root().unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
let dataset_tree = MerkleTree::<F,D>::new(&slot_roots, zero).unwrap();
|
||||
Self {
|
||||
tree: dataset_tree,
|
||||
slot_trees,
|
||||
params: params.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as default but with supplied slot trees
|
||||
pub fn new(slot_trees: Vec<SlotTree<F, D>>, params: InputParams) -> Self {
|
||||
// get the roots of slot trees
|
||||
let slot_roots = slot_trees
|
||||
.iter()
|
||||
.map(|t| t.tree.root().unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
// zero hash
|
||||
let zero = HashOut {
|
||||
elements: [F::zero(); 4],
|
||||
};
|
||||
let dataset_tree = MerkleTree::<F,D>::new(&slot_roots, zero).unwrap();
|
||||
Self {
|
||||
tree: dataset_tree,
|
||||
slot_trees,
|
||||
params,
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a proof for the given slot index
|
||||
/// Also takes entropy so it can use it to sample the slot
|
||||
/// note: proofs are padded based on the params in self
|
||||
pub fn sample_slot(&self, index: usize, entropy: usize) -> DatasetProof<F,D> {
|
||||
let mut dataset_proof = self.tree.get_proof(index).unwrap();
|
||||
Self::pad_proof(&mut dataset_proof, self.params.dataset_max_depth());
|
||||
|
||||
let slot = &self.slot_trees[index];
|
||||
let slot_root = slot.tree.root().unwrap();
|
||||
let mut slot_proofs = vec![];
|
||||
let mut cell_data = vec![];
|
||||
let entropy_field = F::from_canonical_u64(entropy as u64);
|
||||
let mut entropy_as_digest = HashOut::<F, NUM_HASH_OUT_ELTS>::zero();
|
||||
entropy_as_digest.elements[0] = entropy_field;
|
||||
|
||||
// get the index for cell from H(slot_root|counter|entropy)
|
||||
let mask_bits = usize_to_bits_le(self.params.n_cells-1, self.params.max_depth+1);
|
||||
for i in 0..self.params.n_samples {
|
||||
let cell_index_bits = calculate_cell_index_bits(
|
||||
&entropy_as_digest.elements.to_vec(),
|
||||
slot_root,
|
||||
i + 1,
|
||||
self.params.max_depth,
|
||||
mask_bits.clone()
|
||||
);
|
||||
let cell_index = bits_le_padded_to_usize(&cell_index_bits);
|
||||
let mut s_proof = slot.get_proof(cell_index);
|
||||
Self::pad_proof(&mut s_proof, self.params.max_depth);
|
||||
slot_proofs.push(s_proof);
|
||||
let data_i = slot.cell_data[cell_index].data.clone();
|
||||
let cell_i = Cell::<F,D>{
|
||||
data: data_i
|
||||
};
|
||||
cell_data.push(cell_i);
|
||||
}
|
||||
|
||||
DatasetProof {
|
||||
slot_index: F::from_canonical_u64(index as u64),
|
||||
entropy: entropy_as_digest,
|
||||
dataset_proof,
|
||||
slot_proofs,
|
||||
cell_data,
|
||||
}
|
||||
}
|
||||
/// pad the proof with 0s until max_depth
|
||||
pub fn pad_proof(merkle_proof: &mut MerkleProof<F,D>, max_depth: usize){
|
||||
for i in merkle_proof.path.len()..max_depth{
|
||||
merkle_proof.path.push(HashOut::<F, NUM_HASH_OUT_ELTS>::zero());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ------------ helper functions -------------
|
||||
|
||||
/// Create a new cell with random data, using the parameters from `Params`
|
||||
pub fn new_random_cell<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
>(params: &InputParams) -> Cell<F,D> {
|
||||
let data = (0..params.n_field_elems_per_cell())
|
||||
.map(|_| F::rand())
|
||||
.collect::<Vec<_>>();
|
||||
Cell::<F,D> {
|
||||
data,
|
||||
}
|
||||
}
|
||||
@ -1,234 +0,0 @@
|
||||
use plonky2::hash::hash_types::RichField;
|
||||
use plonky2::plonk::config::{GenericConfig, Hasher};
|
||||
use crate::params::{Params,InputParams};
|
||||
use crate::utils::{bits_le_padded_to_usize, calculate_cell_index_bits, ceiling_log2, usize_to_bits_le};
|
||||
use crate::merkle_tree::merkle_safe::MerkleProof;
|
||||
use codex_plonky2_circuits::circuits::sample_cells::{MerklePath, SampleCircuit, SampleCircuitInput, SampleTargets};
|
||||
use plonky2::iop::witness::PartialWitness;
|
||||
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
||||
use plonky2::plonk::circuit_data::{CircuitConfig, CircuitData};
|
||||
use plonky2::plonk::proof::ProofWithPublicInputs;
|
||||
use codex_plonky2_circuits::circuits::params::CircuitParams;
|
||||
use plonky2_field::types::HasExtension;
|
||||
use crate::data_structs::DatasetTree;
|
||||
use crate::sponge::hash_bytes_no_padding;
|
||||
use crate::params::{C, D, F, HF};
|
||||
use codex_plonky2_circuits::circuits::params::NUM_HASH_OUT_ELTS;
|
||||
|
||||
/// generates circuit input (SampleCircuitInput) from fake data for testing
|
||||
/// which can be later stored into json see json.rs
|
||||
pub fn gen_testing_circuit_input<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
>(params: &InputParams) -> SampleCircuitInput<F,D>{
|
||||
let dataset_t = DatasetTree::<F, D>::new_for_testing(¶ms);
|
||||
|
||||
let slot_index = params.testing_slot_index; // samples the specified slot
|
||||
let entropy = params.entropy; // Use the entropy from Params
|
||||
|
||||
let proof = dataset_t.sample_slot(slot_index, entropy);
|
||||
let slot_root = dataset_t.slot_trees[slot_index].tree.root().unwrap();
|
||||
|
||||
let mut slot_paths = vec![];
|
||||
for i in 0..params.n_samples {
|
||||
let path = proof.slot_proofs[i].path.clone();
|
||||
let mp = MerklePath::<F,D>{
|
||||
path,
|
||||
};
|
||||
slot_paths.push(mp);
|
||||
}
|
||||
|
||||
SampleCircuitInput::<F, D> {
|
||||
entropy: proof.entropy,
|
||||
dataset_root: dataset_t.tree.root().unwrap(),
|
||||
slot_index: proof.slot_index.clone(),
|
||||
slot_root,
|
||||
n_cells_per_slot: F::from_canonical_usize(params.n_cells),
|
||||
n_slots_per_dataset: F::from_canonical_usize(params.n_slots),
|
||||
slot_proof: proof.dataset_proof.path.clone(),
|
||||
cell_data: proof.cell_data.clone(),
|
||||
merkle_paths: slot_paths,
|
||||
}
|
||||
}
|
||||
|
||||
/// verifies the given circuit input.
|
||||
/// this is non circuit version for sanity check
|
||||
pub fn verify_circuit_input<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
>(circ_input: SampleCircuitInput<F,D>, params: &InputParams) -> bool{
|
||||
let slot_index = circ_input.slot_index.as_canonical_u64();
|
||||
let slot_root = circ_input.slot_root.clone();
|
||||
// check dataset level proof
|
||||
let slot_proof = circ_input.slot_proof.clone();
|
||||
let dataset_path_bits = usize_to_bits_le(slot_index as usize, params.dataset_max_depth());
|
||||
let (dataset_last_bits, dataset_mask_bits) = ceiling_log2(params.n_slots, params.dataset_max_depth());
|
||||
let reconstructed_slot_root = MerkleProof::<F,D>::reconstruct_root2(
|
||||
slot_root,
|
||||
dataset_path_bits,
|
||||
dataset_last_bits,
|
||||
slot_proof,
|
||||
dataset_mask_bits,
|
||||
params.max_slots.trailing_zeros() as usize,
|
||||
).unwrap();
|
||||
// assert reconstructed equals dataset root
|
||||
assert_eq!(reconstructed_slot_root, circ_input.dataset_root.clone());
|
||||
|
||||
// check each sampled cell
|
||||
// get the index for cell from H(slot_root|counter|entropy)
|
||||
let mask_bits = usize_to_bits_le(params.n_cells -1, params.max_depth);
|
||||
for i in 0..params.n_samples {
|
||||
let cell_index_bits = calculate_cell_index_bits(
|
||||
&circ_input.entropy.elements.to_vec(),
|
||||
slot_root,
|
||||
i + 1,
|
||||
params.max_depth,
|
||||
mask_bits.clone(),
|
||||
);
|
||||
|
||||
let cell_index = bits_le_padded_to_usize(&cell_index_bits);
|
||||
|
||||
let s_res = verify_cell_proof(&circ_input, ¶ms, cell_index, i);
|
||||
if s_res.unwrap() == false {
|
||||
println!("call {} is false", i);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Verify the given proof for slot tree, checks equality with the given root
|
||||
pub fn verify_cell_proof<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
>(circ_input: &SampleCircuitInput<F,D>, params: &InputParams, cell_index: usize, ctr: usize) -> anyhow::Result<bool> {
|
||||
let mut block_path_bits = usize_to_bits_le(cell_index, params.max_depth);
|
||||
let last_index = params.n_cells - 1;
|
||||
let mut block_last_bits = usize_to_bits_le(last_index, params.max_depth);
|
||||
|
||||
let split_point = params.bot_depth();
|
||||
|
||||
let slot_last_bits = block_last_bits.split_off(split_point);
|
||||
let slot_path_bits = block_path_bits.split_off(split_point);
|
||||
|
||||
// pub type HP = <PoseidonHash as Hasher<F>>::Permutation;
|
||||
let leaf_hash = hash_bytes_no_padding::<F,D,HF>(&circ_input.cell_data[ctr].data);
|
||||
|
||||
let mut block_path = circ_input.merkle_paths[ctr].path.clone();
|
||||
let slot_path = block_path.split_off(split_point);
|
||||
|
||||
let mut block_mask_bits = usize_to_bits_le(last_index, params.max_depth+1);
|
||||
let mut slot_mask_bits = block_mask_bits.split_off(split_point);
|
||||
|
||||
block_mask_bits.push(false);
|
||||
slot_mask_bits.push(false);
|
||||
|
||||
let block_res = MerkleProof::<F,D>::reconstruct_root2(
|
||||
leaf_hash,
|
||||
block_path_bits.clone(),
|
||||
block_last_bits.clone(),
|
||||
block_path,
|
||||
block_mask_bits,
|
||||
params.bot_depth(),
|
||||
);
|
||||
let reconstructed_root = MerkleProof::<F,D>::reconstruct_root2(
|
||||
block_res.unwrap(),
|
||||
slot_path_bits,
|
||||
slot_last_bits,
|
||||
slot_path,
|
||||
slot_mask_bits,
|
||||
params.max_depth - params.bot_depth(),
|
||||
);
|
||||
|
||||
Ok(reconstructed_root.unwrap() == circ_input.slot_root)
|
||||
}
|
||||
|
||||
|
||||
/// returns exactly M default circuit input
|
||||
pub fn get_m_default_circ_input<const M: usize>() -> [SampleCircuitInput<F,D>; M]{
|
||||
let params = Params::default().input_params;
|
||||
// let one_circ_input = gen_testing_circuit_input::<F,D>(¶ms);
|
||||
// let circ_input: [SampleCircuitInput<F,D>; M] = (0..M)
|
||||
// .map(|_| one_circ_input.clone())
|
||||
// .collect::<Vec<_>>()
|
||||
// .try_into().unwrap();
|
||||
// circ_input
|
||||
get_m_circ_input::<M>(params)
|
||||
}
|
||||
|
||||
/// returns exactly M default circuit input
|
||||
pub fn get_m_circ_input<const M: usize>(params: InputParams) -> [SampleCircuitInput<F,D>; M]{
|
||||
// let params = Params::default().input_params;
|
||||
let one_circ_input = gen_testing_circuit_input::<F,D>(¶ms);
|
||||
let circ_input: [SampleCircuitInput<F,D>; M] = (0..M)
|
||||
.map(|_| one_circ_input.clone())
|
||||
.collect::<Vec<_>>()
|
||||
.try_into().unwrap();
|
||||
circ_input
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::time::Instant;
|
||||
use super::*;
|
||||
use plonky2::plonk::circuit_data::CircuitConfig;
|
||||
use plonky2::iop::witness::PartialWitness;
|
||||
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
||||
use codex_plonky2_circuits::circuits::params::CircuitParams;
|
||||
use codex_plonky2_circuits::circuits::sample_cells::SampleCircuit;
|
||||
// use crate::params::{C, D, F};
|
||||
|
||||
// Test sample cells (non-circuit)
|
||||
#[test]
|
||||
fn test_gen_verify_proof(){
|
||||
let params = Params::default().input_params;
|
||||
let w = gen_testing_circuit_input::<F,D>(¶ms);
|
||||
assert!(verify_circuit_input::<F,D>(w, ¶ms));
|
||||
}
|
||||
|
||||
// Test sample cells in-circuit for a selected slot
|
||||
#[test]
|
||||
fn test_proof_in_circuit() -> anyhow::Result<()> {
|
||||
// get input
|
||||
let mut params = Params::default();
|
||||
let mut input_params = params.input_params;
|
||||
input_params.n_samples = 10;
|
||||
let circ_input = gen_testing_circuit_input::<F,D>(&input_params);
|
||||
|
||||
// Create the circuit
|
||||
let config = CircuitConfig::standard_recursion_config_gl();
|
||||
let mut builder = CircuitBuilder::<F, D, NUM_HASH_OUT_ELTS>::new(config);
|
||||
|
||||
let mut circuit_params = params.circuit_params;
|
||||
circuit_params.n_samples = 10;
|
||||
|
||||
// build the circuit
|
||||
let circ = SampleCircuit::<F,D,HF>::new(circuit_params.clone());
|
||||
let mut targets = circ.sample_slot_circuit_with_public_input(&mut builder)?;
|
||||
|
||||
// Create a PartialWitness and assign
|
||||
let mut pw = PartialWitness::new();
|
||||
|
||||
// assign a witness
|
||||
circ.sample_slot_assign_witness(&mut pw, &targets, &circ_input)?;
|
||||
|
||||
// Build the circuit
|
||||
let data = builder.build::<C>();
|
||||
println!("circuit size = {:?}", data.common.degree_bits());
|
||||
|
||||
// Prove the circuit with the assigned witness
|
||||
let start_time = Instant::now();
|
||||
let proof_with_pis = data.prove(pw)?;
|
||||
println!("prove_time = {:?}", start_time.elapsed());
|
||||
|
||||
// Verify the proof
|
||||
let verifier_data = data.verifier_data();
|
||||
assert!(
|
||||
verifier_data.verify(proof_with_pis).is_ok(),
|
||||
"Merkle proof verification failed"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
|
||||
pub mod gen_input;
|
||||
pub mod params;
|
||||
pub mod utils;
|
||||
pub mod sponge;
|
||||
pub mod merkle_tree;
|
||||
pub mod data_structs;
|
||||
@ -1,27 +0,0 @@
|
||||
use plonky2::hash::hash_types::{HashOut, RichField};
|
||||
use plonky2::hash::hashing::PlonkyPermutation;
|
||||
use plonky2::plonk::config::Hasher;
|
||||
use codex_plonky2_circuits::circuits::params::NUM_HASH_OUT_ELTS;
|
||||
use plonky2_field::types::HasExtension;
|
||||
|
||||
/// Compression function which takes two 256 bit inputs (HashOut) and u64 key (which is converted to field element in the function)
|
||||
/// and returns a 256 bit output (HashOut / 4 Goldilocks field elems).
|
||||
pub fn key_compress<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
H:Hasher<F>
|
||||
>(x: HashOut<F, NUM_HASH_OUT_ELTS>, y: HashOut<F, NUM_HASH_OUT_ELTS>, key: u64) -> HashOut<F, NUM_HASH_OUT_ELTS> {
|
||||
|
||||
let key_field = F::from_canonical_u64(key);
|
||||
|
||||
let mut perm = H::Permutation::new(core::iter::repeat(F::zero()));
|
||||
perm.set_from_slice(&x.elements, 0);
|
||||
perm.set_from_slice(&y.elements, NUM_HASH_OUT_ELTS);
|
||||
perm.set_elt(key_field,NUM_HASH_OUT_ELTS*2);
|
||||
|
||||
perm.permute();
|
||||
|
||||
HashOut {
|
||||
elements: perm.squeeze()[..NUM_HASH_OUT_ELTS].try_into().unwrap(),
|
||||
}
|
||||
}
|
||||
@ -1,247 +0,0 @@
|
||||
// Implementation of "safe" merkle tree
|
||||
// consistent with the one in codex:
|
||||
// https://github.com/codex-storage/nim-codex/blob/master/codex/merkletree/merkletree.nim
|
||||
|
||||
use anyhow::{ensure, Result};
|
||||
// use plonky2::field::goldilocks_field::GoldilocksField;
|
||||
use plonky2::hash::hash_types::{HashOut, RichField};
|
||||
// use plonky2::hash::poseidon::PoseidonHash;
|
||||
use plonky2::plonk::config::Hasher;
|
||||
use std::ops::Shr;
|
||||
use plonky2_field::types::{ HasExtension};
|
||||
use crate::merkle_tree::key_compress::key_compress;
|
||||
use crate::params::HF;
|
||||
use codex_plonky2_circuits::circuits::params::NUM_HASH_OUT_ELTS;
|
||||
|
||||
// Constants for the keys used in compression
|
||||
pub const KEY_NONE: u64 = 0x0;
|
||||
pub const KEY_BOTTOM_LAYER: u64 = 0x1;
|
||||
pub const KEY_ODD: u64 = 0x2;
|
||||
pub const KEY_ODD_AND_BOTTOM_LAYER: u64 = 0x3;
|
||||
|
||||
/// Merkle tree struct, containing the layers, compression function, and zero hash.
|
||||
#[derive(Clone)]
|
||||
pub struct MerkleTree<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
> {
|
||||
pub layers: Vec<Vec<HashOut<F, NUM_HASH_OUT_ELTS>>>,
|
||||
pub zero: HashOut<F, NUM_HASH_OUT_ELTS>,
|
||||
}
|
||||
|
||||
impl<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
> MerkleTree<F, D> {
|
||||
/// Constructs a new Merkle tree from the given leaves.
|
||||
pub fn new(
|
||||
leaves: &[HashOut<F, NUM_HASH_OUT_ELTS>],
|
||||
zero: HashOut<F, NUM_HASH_OUT_ELTS>,
|
||||
) -> Result<Self> {
|
||||
let layers = merkle_tree_worker::<F, D>(leaves, zero, true)?;
|
||||
Ok(Self {
|
||||
layers,
|
||||
zero,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the depth of the Merkle tree.
|
||||
pub fn depth(&self) -> usize {
|
||||
self.layers.len() - 1
|
||||
}
|
||||
|
||||
/// Returns the number of leaves in the Merkle tree.
|
||||
pub fn leaves_count(&self) -> usize {
|
||||
self.layers[0].len()
|
||||
}
|
||||
|
||||
/// Returns the root hash of the Merkle tree.
|
||||
pub fn root(&self) -> Result<HashOut<F, NUM_HASH_OUT_ELTS>> {
|
||||
let last_layer = self.layers.last().ok_or_else(|| anyhow::anyhow!("Empty tree"))?;
|
||||
ensure!(last_layer.len() == 1, "Invalid Merkle tree");
|
||||
Ok(last_layer[0])
|
||||
}
|
||||
|
||||
/// Generates a Merkle proof for a given leaf index.
|
||||
pub fn get_proof(&self, index: usize) -> Result<MerkleProof<F, D>> {
|
||||
let depth = self.depth();
|
||||
let nleaves = self.leaves_count();
|
||||
|
||||
ensure!(index < nleaves, "Index out of bounds");
|
||||
|
||||
let mut path = Vec::with_capacity(depth);
|
||||
let mut k = index;
|
||||
let mut m = nleaves;
|
||||
|
||||
for i in 0..depth {
|
||||
let j = k ^ 1;
|
||||
let sibling = if j < m {
|
||||
self.layers[i][j]
|
||||
} else {
|
||||
self.zero
|
||||
};
|
||||
path.push(sibling);
|
||||
k = k >> 1;
|
||||
m = (m + 1) >> 1;
|
||||
}
|
||||
|
||||
Ok(MerkleProof {
|
||||
index,
|
||||
path,
|
||||
nleaves,
|
||||
zero: self.zero,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the Merkle tree layers.
|
||||
fn merkle_tree_worker<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
>(
|
||||
xs: &[HashOut<F, NUM_HASH_OUT_ELTS>],
|
||||
zero: HashOut<F, NUM_HASH_OUT_ELTS>,
|
||||
is_bottom_layer: bool,
|
||||
) -> Result<Vec<Vec<HashOut<F, NUM_HASH_OUT_ELTS>>>> {
|
||||
let m = xs.len();
|
||||
if !is_bottom_layer && m == 1 {
|
||||
return Ok(vec![xs.to_vec()]);
|
||||
}
|
||||
|
||||
let halfn = m / 2;
|
||||
let n = 2 * halfn;
|
||||
let is_odd = n != m;
|
||||
|
||||
let mut ys = Vec::with_capacity(halfn + if is_odd { 1 } else { 0 });
|
||||
|
||||
for i in 0..halfn {
|
||||
let key = if is_bottom_layer { KEY_BOTTOM_LAYER } else { KEY_NONE };
|
||||
let h = key_compress::<F, D, HF>(xs[2 * i], xs[2 * i + 1], key);
|
||||
ys.push(h);
|
||||
}
|
||||
|
||||
if is_odd {
|
||||
let key = if is_bottom_layer {
|
||||
KEY_ODD_AND_BOTTOM_LAYER
|
||||
} else {
|
||||
KEY_ODD
|
||||
};
|
||||
let h = key_compress::<F, D, HF>(xs[n], zero, key);
|
||||
ys.push(h);
|
||||
}
|
||||
|
||||
let mut layers = vec![xs.to_vec()];
|
||||
let mut upper_layers = merkle_tree_worker::<F, D>(&ys, zero, false)?;
|
||||
layers.append(&mut upper_layers);
|
||||
|
||||
Ok(layers)
|
||||
}
|
||||
|
||||
/// Merkle proof struct, containing the index, path, and other necessary data.
|
||||
#[derive(Clone)]
|
||||
pub struct MerkleProof<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
> {
|
||||
pub index: usize, // Index of the leaf
|
||||
pub path: Vec<HashOut<F, NUM_HASH_OUT_ELTS>>, // Sibling hashes from the leaf to the root
|
||||
pub nleaves: usize, // Total number of leaves
|
||||
pub zero: HashOut<F, NUM_HASH_OUT_ELTS>,
|
||||
}
|
||||
|
||||
impl<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
> MerkleProof<F, D> {
|
||||
/// Reconstructs the root hash from the proof and the given leaf.
|
||||
pub fn reconstruct_root(&self, leaf: HashOut<F, NUM_HASH_OUT_ELTS>) -> Result<HashOut<F, NUM_HASH_OUT_ELTS>> {
|
||||
let mut m = self.nleaves;
|
||||
let mut j = self.index;
|
||||
let mut h = leaf;
|
||||
let mut bottom_flag = KEY_BOTTOM_LAYER;
|
||||
|
||||
for p in &self.path {
|
||||
let odd_index = (j & 1) != 0;
|
||||
if odd_index {
|
||||
// The index of the child is odd
|
||||
h = key_compress::<F, D, HF>(*p, h, bottom_flag);
|
||||
} else {
|
||||
if j == m - 1 {
|
||||
// Single child -> so odd node
|
||||
h = key_compress::<F, D, HF>(h, *p, bottom_flag + 2);
|
||||
} else {
|
||||
// Even node
|
||||
h = key_compress::<F, D, HF>(h, *p, bottom_flag);
|
||||
}
|
||||
}
|
||||
bottom_flag = KEY_NONE;
|
||||
j = j.shr(1);
|
||||
m = (m + 1).shr(1);
|
||||
}
|
||||
|
||||
Ok(h)
|
||||
}
|
||||
|
||||
/// reconstruct the root using path_bits and last_bits in similar way as the circuit
|
||||
/// this is used for testing - sanity check
|
||||
pub fn reconstruct_root2(leaf: HashOut<F, NUM_HASH_OUT_ELTS>, path_bits: Vec<bool>, last_bits:Vec<bool>, path: Vec<HashOut<F, NUM_HASH_OUT_ELTS>>, mask_bits:Vec<bool>, depth: usize) -> Result<HashOut<F, NUM_HASH_OUT_ELTS>> {
|
||||
let is_last = compute_is_last(path_bits.clone(),last_bits);
|
||||
|
||||
let mut h = vec![];
|
||||
h.push(leaf);
|
||||
let mut i = 0;
|
||||
|
||||
for p in &path {
|
||||
let bottom = if(i==0){
|
||||
KEY_BOTTOM_LAYER
|
||||
}else{
|
||||
KEY_NONE
|
||||
};
|
||||
|
||||
let odd = (is_last[i] as usize) * (1-(path_bits[i] as usize));
|
||||
|
||||
let key = bottom + (2 * (odd as u64));
|
||||
let odd_index = path_bits[i];
|
||||
if odd_index {
|
||||
h.push(key_compress::<F, D, HF>(*p, h[i], key));
|
||||
} else {
|
||||
h.push(key_compress::<F,D,HF>(h[i], *p, key));
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
let mut reconstructed_root = HashOut::<F, NUM_HASH_OUT_ELTS>::zero();
|
||||
for k in 0..depth{
|
||||
let diff = (mask_bits[k] as u64) - (mask_bits[k+1] as u64);
|
||||
let mul_res: Vec<F> = h[k+1].elements.iter().map(|e| e.mul(F::from_canonical_u64(diff))).collect();
|
||||
reconstructed_root = HashOut::<F, NUM_HASH_OUT_ELTS>::from_vec(
|
||||
mul_res.iter().zip(reconstructed_root.elements).map(|(e1,e2)| e1.add(e2)).collect()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(reconstructed_root)
|
||||
}
|
||||
|
||||
/// Verifies the proof against a given root and leaf.
|
||||
pub fn verify(&self, leaf: HashOut<F, NUM_HASH_OUT_ELTS>, root: HashOut<F, NUM_HASH_OUT_ELTS>) -> Result<bool> {
|
||||
let reconstructed_root = self.reconstruct_root(leaf)?;
|
||||
Ok(reconstructed_root == root)
|
||||
}
|
||||
}
|
||||
|
||||
///helper function to compute is_last
|
||||
fn compute_is_last(path_bits: Vec<bool>, last_bits: Vec<bool>) -> Vec<bool> {
|
||||
let max_depth = path_bits.len();
|
||||
|
||||
// Initialize isLast vector
|
||||
let mut is_last = vec![false; max_depth + 1];
|
||||
is_last[max_depth] = true; // Set isLast[max_depth] to 1 (true)
|
||||
|
||||
// Iterate over eq and isLast in reverse order
|
||||
for i in (0..max_depth).rev() {
|
||||
let eq_out = path_bits[i] == last_bits[i]; // eq[i].out
|
||||
is_last[i] = is_last[i + 1] && eq_out; // isLast[i] = isLast[i+1] * eq[i].out
|
||||
}
|
||||
|
||||
is_last
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
pub mod merkle_safe;
|
||||
pub mod key_compress;
|
||||
@ -1,224 +0,0 @@
|
||||
// params for generating input for proof circuit
|
||||
|
||||
use plonky2::hash::poseidon_goldilocks::Poseidon64Hash;
|
||||
use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig};
|
||||
use std::env;
|
||||
use anyhow::{Result, Context};
|
||||
use codex_plonky2_circuits::circuits::params::{CircuitParams, NUM_HASH_OUT_ELTS};
|
||||
|
||||
// test types
|
||||
pub const D: usize = 2;
|
||||
pub type C = PoseidonGoldilocksConfig;
|
||||
pub type F = <C as GenericConfig<D, NUM_HASH_OUT_ELTS>>::F; // this is the goldilocks field
|
||||
pub type HF = Poseidon64Hash;
|
||||
|
||||
// hardcoded default params for generating proof input
|
||||
const DEFAULT_MAX_DEPTH: usize = 32; // depth of big tree (slot tree depth, includes block tree depth)
|
||||
const DEFAULT_MAX_SLOTS: usize = 256; // maximum number of slots
|
||||
const DEFAULT_CELL_SIZE: usize = 2048; // cell size in bytes
|
||||
const DEFAULT_BLOCK_SIZE: usize = 65536; // block size in bytes
|
||||
const DEFAULT_N_SAMPLES: usize = 5; // number of samples to prove
|
||||
|
||||
const DEFAULT_ENTROPY: usize = 1234567; // external randomness
|
||||
const DEFAULT_SEED: usize = 12345; // seed for creating fake data TODO: not used now
|
||||
|
||||
const DEFAULT_N_SLOTS: usize = 11; // number of slots in the dataset
|
||||
const DEFAULT_SLOT_INDEX: usize = 3; // the index of the slot to be sampled
|
||||
const DEFAULT_N_CELLS: usize = 512; // number of cells in each slot
|
||||
|
||||
/// Params struct
|
||||
#[derive(Clone)]
|
||||
pub struct Params {
|
||||
pub circuit_params: CircuitParams,
|
||||
pub input_params: InputParams,
|
||||
}
|
||||
|
||||
/// test params
|
||||
#[derive(Clone)]
|
||||
pub struct InputParams{
|
||||
pub max_depth: usize,
|
||||
pub max_slots: usize,
|
||||
pub cell_size: usize,
|
||||
pub block_size: usize,
|
||||
pub n_samples: usize,
|
||||
pub entropy: usize,
|
||||
pub seed: usize,
|
||||
pub n_slots: usize,
|
||||
pub testing_slot_index: usize,
|
||||
pub n_cells: usize,
|
||||
}
|
||||
|
||||
/// Implement the Default trait for Params using the hardcoded constants
|
||||
impl Default for Params {
|
||||
fn default() -> Self {
|
||||
let input_params = InputParams {
|
||||
max_depth: DEFAULT_MAX_DEPTH,
|
||||
max_slots: DEFAULT_MAX_SLOTS,
|
||||
cell_size: DEFAULT_CELL_SIZE,
|
||||
block_size: DEFAULT_BLOCK_SIZE,
|
||||
n_samples: DEFAULT_N_SAMPLES,
|
||||
entropy: DEFAULT_ENTROPY,
|
||||
seed: DEFAULT_SEED,
|
||||
n_slots: DEFAULT_N_SLOTS,
|
||||
testing_slot_index: DEFAULT_SLOT_INDEX,
|
||||
n_cells: DEFAULT_N_CELLS,
|
||||
};
|
||||
let circuit_params = input_params.get_circuit_params();
|
||||
|
||||
Params{
|
||||
circuit_params,
|
||||
input_params,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement a new function to create Params with custom values
|
||||
impl InputParams {
|
||||
// GOLDILOCKS_F_SIZE
|
||||
pub fn goldilocks_f_size(&self) -> usize {
|
||||
64
|
||||
}
|
||||
|
||||
// N_FIELD_ELEMS_PER_CELL
|
||||
pub fn n_field_elems_per_cell(&self) -> usize {
|
||||
(self.cell_size + 62) / 62 * 8
|
||||
}
|
||||
|
||||
// BOT_DEPTH
|
||||
pub fn bot_depth(&self) -> usize {
|
||||
log2(self.block_size / self.cell_size)
|
||||
}
|
||||
|
||||
// N_CELLS_IN_BLOCKS
|
||||
pub fn n_cells_in_blocks(&self) -> usize {
|
||||
1 << self.bot_depth()
|
||||
}
|
||||
|
||||
// N_BLOCKS
|
||||
pub fn n_blocks(&self) -> usize {
|
||||
1 << (self.max_depth - self.bot_depth())
|
||||
}
|
||||
|
||||
// Depth of test input
|
||||
pub fn depth_test(&self) -> usize {
|
||||
self.n_cells.trailing_zeros() as usize
|
||||
}
|
||||
|
||||
// N_BLOCKS for the test input
|
||||
pub fn n_blocks_test(&self) -> usize {
|
||||
1 << (self.depth_test() - self.bot_depth())
|
||||
}
|
||||
|
||||
// DATASET_DEPTH
|
||||
pub fn dataset_max_depth(&self) -> usize {
|
||||
ceiling_log2(self.max_slots)
|
||||
}
|
||||
|
||||
// DATASET_DEPTH for test
|
||||
pub fn dataset_depth_test(&self) -> usize {
|
||||
ceiling_log2(self.n_slots)
|
||||
}
|
||||
|
||||
pub fn get_circuit_params(&self) -> CircuitParams{
|
||||
CircuitParams{
|
||||
max_depth: self.max_depth,
|
||||
max_log2_n_slots: self.dataset_max_depth(),
|
||||
block_tree_depth: self.bot_depth(),
|
||||
n_field_elems_per_cell: self.n_field_elems_per_cell(),
|
||||
n_samples:self.n_samples,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn log2(x: usize) -> usize {
|
||||
assert!(x.is_power_of_two(), "Input must be a power of 2.");
|
||||
x.trailing_zeros() as usize
|
||||
}
|
||||
|
||||
pub fn ceiling_log2(x: usize) -> usize {
|
||||
if x <= 1 {
|
||||
return 0;
|
||||
}
|
||||
usize::BITS as usize - x.saturating_sub(1).leading_zeros() as usize
|
||||
}
|
||||
|
||||
/// load test params from env
|
||||
impl InputParams {
|
||||
pub fn from_env() -> Result<Self> {
|
||||
let max_depth = env::var("MAXDEPTH")
|
||||
.context("MAXDEPTH not set")?
|
||||
.parse::<usize>()
|
||||
.context("Invalid MAXDEPTH")?;
|
||||
|
||||
let max_slots = env::var("MAXSLOTS")
|
||||
.context("MAXSLOTS not set")?
|
||||
.parse::<usize>()
|
||||
.context("Invalid MAXSLOTS")?;
|
||||
|
||||
let cell_size = env::var("CELLSIZE")
|
||||
.context("CELLSIZE not set")?
|
||||
.parse::<usize>()
|
||||
.context("Invalid CELLSIZE")?;
|
||||
|
||||
let block_size = env::var("BLOCKSIZE")
|
||||
.context("BLOCKSIZE not set")?
|
||||
.parse::<usize>()
|
||||
.context("Invalid BLOCKSIZE")?;
|
||||
|
||||
let n_samples = env::var("NSAMPLES")
|
||||
.context("NSAMPLES not set")?
|
||||
.parse::<usize>()
|
||||
.context("Invalid NSAMPLES")?;
|
||||
|
||||
let entropy = env::var("ENTROPY")
|
||||
.context("ENTROPY not set")?
|
||||
.parse::<usize>()
|
||||
.context("Invalid ENTROPY")?;
|
||||
|
||||
let seed = env::var("SEED")
|
||||
.context("SEED not set")?
|
||||
.parse::<usize>()
|
||||
.context("Invalid SEED")?;
|
||||
|
||||
let n_slots = env::var("NSLOTS")
|
||||
.context("NSLOTS not set")?
|
||||
.parse::<usize>()
|
||||
.context("Invalid NSLOTS")?;
|
||||
|
||||
let testing_slot_index = env::var("SLOTINDEX")
|
||||
.context("SLOTINDEX not set")?
|
||||
.parse::<usize>()
|
||||
.context("Invalid SLOTINDEX")?;
|
||||
|
||||
let n_cells = env::var("NCELLS")
|
||||
.context("NCELLS not set")?
|
||||
.parse::<usize>()
|
||||
.context("Invalid NCELLS")?;
|
||||
|
||||
Ok(InputParams {
|
||||
max_depth,
|
||||
max_slots,
|
||||
cell_size,
|
||||
block_size,
|
||||
n_samples,
|
||||
entropy,
|
||||
seed,
|
||||
n_slots,
|
||||
testing_slot_index,
|
||||
n_cells,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// load params from env
|
||||
impl Params {
|
||||
pub fn from_env() -> Result<Self> {
|
||||
let input_params = InputParams::from_env()?;
|
||||
let circuit_params = input_params.get_circuit_params();
|
||||
|
||||
Ok(Params{
|
||||
circuit_params,
|
||||
input_params,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,292 +0,0 @@
|
||||
use plonky2::hash::hash_types::{HashOut, RichField};
|
||||
use plonky2::plonk::config::Hasher;
|
||||
use plonky2::hash::hashing::PlonkyPermutation;
|
||||
use plonky2_field::types::{HasExtension};
|
||||
use codex_plonky2_circuits::circuits::params::NUM_HASH_OUT_ELTS;
|
||||
|
||||
/// sponge function similar to the in-circuit one
|
||||
/// used here for testing / sanity check
|
||||
pub fn hash_n_with_padding<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
H: Hasher<F>
|
||||
>(
|
||||
inputs: &[F],
|
||||
) -> HashOut<F, NUM_HASH_OUT_ELTS>{
|
||||
HashOut::<F, NUM_HASH_OUT_ELTS>::from_vec(hash_n_to_m_with_padding::<F,D,H::Permutation>(inputs, NUM_HASH_OUT_ELTS))
|
||||
}
|
||||
|
||||
pub fn hash_n_to_m_with_padding<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
P: PlonkyPermutation<F>
|
||||
>(
|
||||
inputs: &[F],
|
||||
num_outputs: usize,
|
||||
) -> Vec<F> {
|
||||
let rate = P::RATE;
|
||||
let width = P::WIDTH; // rate + capacity
|
||||
let zero = F::zero();
|
||||
let one = F::one();
|
||||
let mut perm = P::new(core::iter::repeat(zero).take(width));
|
||||
|
||||
// Set the domain separator at index 8
|
||||
let domsep_value = F::from_canonical_u64(rate as u64 + 256 * 12 + 65536 * 63);
|
||||
perm.set_elt(domsep_value, 8);
|
||||
|
||||
let N = inputs.len();
|
||||
let num_chunks = (N + rate) / rate; // Calculate number of chunks with 10* padding
|
||||
let mut input_iter = inputs.iter();
|
||||
|
||||
// Process all chunks except the last one
|
||||
for _ in 0..(num_chunks - 1) {
|
||||
let mut chunk = Vec::with_capacity(rate);
|
||||
for _ in 0..rate {
|
||||
if let Some(&input) = input_iter.next() {
|
||||
chunk.push(input);
|
||||
} else {
|
||||
// should not happen here
|
||||
panic!("Insufficient input elements for chunk; expected more elements.");
|
||||
}
|
||||
}
|
||||
// Add the chunk to the state
|
||||
for j in 0..rate {
|
||||
perm.set_elt(perm.as_ref()[j] + chunk[j],j);
|
||||
}
|
||||
// Apply permutation
|
||||
perm.permute();
|
||||
}
|
||||
|
||||
// Process the last chunk with 10* padding
|
||||
let rem = num_chunks * rate - N; // Number of padding elements (0 < rem <= rate)
|
||||
let ofs = rate - rem; // Offset where padding starts
|
||||
|
||||
let mut last_chunk = Vec::with_capacity(rate);
|
||||
// Absorb remaining inputs
|
||||
for _ in 0..ofs {
|
||||
if let Some(&input) = input_iter.next() {
|
||||
last_chunk.push(input);
|
||||
} else {
|
||||
last_chunk.push(zero);
|
||||
}
|
||||
}
|
||||
// Add the '1' padding bit
|
||||
last_chunk.push(one);
|
||||
// Pad with zeros to reach the full rate
|
||||
while last_chunk.len() < rate {
|
||||
last_chunk.push(zero);
|
||||
}
|
||||
|
||||
// Add the last chunk to the state
|
||||
for j in 0..rate {
|
||||
perm.set_elt(perm.as_ref()[j] + last_chunk[j],j);
|
||||
}
|
||||
// Apply permutation
|
||||
perm.permute();
|
||||
|
||||
// Squeeze outputs until we have the desired number
|
||||
let mut outputs = Vec::with_capacity(num_outputs);
|
||||
loop {
|
||||
for &item in perm.squeeze() {
|
||||
outputs.push(item);
|
||||
if outputs.len() == num_outputs {
|
||||
return outputs;
|
||||
}
|
||||
}
|
||||
perm.permute();
|
||||
}
|
||||
}
|
||||
|
||||
/// sponge function for bytes with no padding
|
||||
/// expects the input to be divisible by rate
|
||||
/// note: rate is fixed at 8 for now
|
||||
/// used here for testing / sanity check
|
||||
pub fn hash_bytes_no_padding<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
H: Hasher<F>
|
||||
>(
|
||||
inputs: &[F],
|
||||
) -> HashOut<F, NUM_HASH_OUT_ELTS>{
|
||||
HashOut::<F, NUM_HASH_OUT_ELTS>::from_vec(hash_bytes_to_m_no_padding::<F, D, H::Permutation>(inputs, NUM_HASH_OUT_ELTS))
|
||||
}
|
||||
|
||||
pub fn hash_bytes_to_m_no_padding<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize,
|
||||
P: PlonkyPermutation<F>
|
||||
>(
|
||||
inputs: &[F],
|
||||
num_outputs: usize,
|
||||
) -> Vec<F> {
|
||||
let rate = P::RATE;
|
||||
let width = P::WIDTH; // rate + capacity
|
||||
let zero = F::zero();
|
||||
let one = F::one();
|
||||
let mut perm = P::new(core::iter::repeat(zero).take(width));
|
||||
|
||||
// Set the domain separator at index 8
|
||||
let domsep_value = F::from_canonical_u64(rate as u64 + 256 * 12 + 65536 * 8);
|
||||
perm.set_elt(domsep_value, 8);
|
||||
|
||||
let n = inputs.len();
|
||||
assert_eq!(n % rate, 0, "Input length ({}) must be divisible by rate ({})", n, rate);
|
||||
let num_chunks = n / rate; // Calculate number of chunks
|
||||
let mut input_iter = inputs.iter();
|
||||
|
||||
// Process all chunks
|
||||
for _ in 0..num_chunks {
|
||||
let mut chunk = Vec::with_capacity(rate);
|
||||
for _ in 0..rate {
|
||||
if let Some(&input) = input_iter.next() {
|
||||
chunk.push(input);
|
||||
} else {
|
||||
// should not happen here
|
||||
panic!("Insufficient input elements for chunk; expected more elements.");
|
||||
}
|
||||
}
|
||||
// Add the chunk to the state
|
||||
for j in 0..rate {
|
||||
perm.set_elt(perm.as_ref()[j] + chunk[j],j);
|
||||
}
|
||||
// Apply permutation
|
||||
perm.permute();
|
||||
}
|
||||
|
||||
// Squeeze outputs until we have the desired number
|
||||
let mut outputs = Vec::with_capacity(num_outputs);
|
||||
loop {
|
||||
for &item in perm.squeeze() {
|
||||
outputs.push(item);
|
||||
if outputs.len() == num_outputs {
|
||||
return outputs;
|
||||
}
|
||||
}
|
||||
perm.permute();
|
||||
}
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use crate::sponge::hash_n_with_padding;
|
||||
// use crate::params::{D, F, HF};
|
||||
//
|
||||
//
|
||||
// #[test]
|
||||
// fn test_sponge_hash_rate_8() {
|
||||
//
|
||||
// struct TestCase {
|
||||
// n: usize,
|
||||
// digest: [u64; 4],
|
||||
// }
|
||||
//
|
||||
// let test_cases: Vec<TestCase> = vec![
|
||||
// TestCase { n: 0, digest: [0x509f3a747e4a6fca, 0xd6f21d91afb92eb3, 0xf65ef4075dcfb169, 0xbceaf22e0cd21b3d] },
|
||||
// TestCase { n: 1, digest: [0xfa286adad207c7ea, 0x97d864ff2e89415e, 0xcf002b28585bd945, 0x95ec163fbdd0792e] },
|
||||
// TestCase { n: 2, digest: [0xe4b779622cbb574f, 0x1fe4b1bc9a0c9fc7, 0x40051ada5252de9b, 0xb351345b1894a59f] },
|
||||
// TestCase { n: 3, digest: [0x133a5a2fd0cae006, 0x072a7769ca9a550d, 0x92134dad95d394c6, 0x22234de7d7270aab] },
|
||||
// TestCase { n: 4, digest: [0x78269e830f2a824a, 0x76f8b00469a8fa81, 0x6793369b1d75ebf5, 0xfba1a89dc21d9b30] },
|
||||
// TestCase { n: 5, digest: [0x263994efd2cd5c57, 0x7c37a93fd48fc98b, 0xa081b26a68767d13, 0x16af92d6e1e4d7f8] },
|
||||
// TestCase { n: 6, digest: [0x0b0b0f1d64f8d58c, 0x2946089b2eb949fc, 0xf68bcf08b69a95e7, 0x814d6eb4b2df848c] },
|
||||
// TestCase { n: 7, digest: [0xae0c900a194ee051, 0x4555257fba7a500b, 0x1713fd448cc82c3a, 0xaf8f2e895e2136f3] },
|
||||
// TestCase { n: 8, digest: [0x100351f04fc470b7, 0x79d3c3c416087158, 0x113bb1c70a6e84ee, 0x3eab2507cdc254d3] },
|
||||
// TestCase { n: 9, digest: [0xbab284d7f11855d6, 0xe1b53d108f308a1c, 0x971fea7184337830, 0x6d674ae321cfb9ba] },
|
||||
// TestCase { n: 10, digest: [0x68c00dbe0ed03a8f, 0xab5ba3617eb6f76b, 0x5d735bb89418cc0b, 0xff4101076f3f3c70] },
|
||||
// TestCase { n: 11, digest: [0xaecce2fa7de4f97d, 0x07cee3dc720812e0, 0x4155bf667391a9e8, 0xbf8a49a12f40e746] },
|
||||
// TestCase { n: 12, digest: [0xd3f43f06fc7affd2, 0xee9a8ac5ef44071a, 0xe00ec9e7f468d0e2, 0x944e34913a974233] },
|
||||
// TestCase { n: 13, digest: [0xcd50fe6ab5e3de54, 0x9b2093adaeac949c, 0xa176a2a9e2c82787, 0xd35f0635a1ec333f] },
|
||||
// TestCase { n: 14, digest: [0x8f5188d26ca0368c, 0x0116bf587e5cc970, 0x30654ee52a3c66d8, 0xe8ded60382c44b04] },
|
||||
// TestCase { n: 15, digest: [0xc7f020f910327951, 0x13a468945463870d, 0xbcf8ca584edb30f3, 0x7e7234f0b8954e7e] },
|
||||
// TestCase { n: 16, digest: [0xf8a9aef7392048e7, 0x6124715a2c5343eb, 0x1b7f17ebec4a5b13, 0xdf61d868051dad75] },
|
||||
// TestCase { n: 17, digest: [0x44d1fb6822c7f3fa, 0x2623cc2240022e42, 0xc90ce9259c9e1160, 0x7a42bc611acacc12] },
|
||||
// TestCase { n: 18, digest: [0x85dab5b06ef2d176, 0x24a587b13a4e3b30, 0xf547a00373299873, 0xb298a6ef846d64a1] },
|
||||
// TestCase { n: 19, digest: [0x7cc060a3f2a74260, 0xa07dc76e73335eb0, 0xf8ed9acbcf8a242e, 0xd32eaf3150005e49] },
|
||||
// TestCase { n: 20, digest: [0x3e961c84e53106f9, 0x63d9a807f9cfd88c, 0x7031e8834a17821a, 0xf2e1c79698798fa9] },
|
||||
// TestCase { n: 21, digest: [0x8a0ab00081c9828f, 0xa5f7aadaf3af046e, 0xada8b4c6220b3420, 0x80ebc8c91a65518c] },
|
||||
// TestCase { n: 22, digest: [0x39505fc00f052122, 0xb13edc24a35665c7, 0xa7b164fffe37ec64, 0x8f7eeb42c068e19f] },
|
||||
// TestCase { n: 23, digest: [0x1f49d6f25f39522b, 0x879377d8df727784, 0x00f1461600d09cdd, 0xd2c7946a44e1aa66] },
|
||||
// TestCase { n: 24, digest: [0x1c6f7a68537f7dc7, 0x64e6e09714dc0854, 0x9abfed111e51bd96, 0x65061b2bc484ed8b] },
|
||||
// TestCase { n: 25, digest: [0x95fd5cc6bc02ab29, 0xe2e3c96d9b1b8b5d, 0xadcf491caa16549e, 0x97d91e370da3c0b4] },
|
||||
// TestCase { n: 26, digest: [0x7599c5052ba67767, 0x3fe4a05f44e96ed6, 0xbbfe6874aa53808c, 0xd6771e162cc9f0ff] },
|
||||
// TestCase { n: 27, digest: [0xdff28121d822093c, 0x7313ea03b57bb436, 0x10ed29b28a77d8c3, 0x6ee304be541fe36f] },
|
||||
// TestCase { n: 28, digest: [0xce2b7f232b504b48, 0x02c638c398c12cb0, 0x4f1d416215377a86, 0x2d43ff6c5dd88f8c] },
|
||||
// TestCase { n: 29, digest: [0xa60cb008de647e9a, 0x502e2e740f68e2d1, 0xe983eb54e4052013, 0xe76e59c5e5dbcca2] },
|
||||
// TestCase { n: 30, digest: [0x7735e3ac5e08fa00, 0x211a86449207c30d, 0x9d80ddd40e7760b2, 0xe60f32f28597a188] },
|
||||
// TestCase { n: 31, digest: [0x6fab3f12496f0691, 0x5116ad81bedd7d84, 0xaa8a7713a80b323b, 0xce6d94533fc40b88] },
|
||||
// TestCase { n: 32, digest: [0xce51cdbd641d57c0, 0xf638202a88ee7f9c, 0x26c291ecc5162b45, 0x04a0a62b949c236f] },
|
||||
// TestCase { n: 33, digest: [0x923391e4a4cde9e2, 0xdcb3acccba80597d, 0x247bb4b67044a0e1, 0x65bbac92e096d1ec] },
|
||||
// TestCase { n: 34, digest: [0x1550d0234ae35f05, 0x16f4d1708923d4f1, 0x232319cb4090ea4e, 0x8354e1aed093070c] },
|
||||
// TestCase { n: 35, digest: [0xc7dd24e6db4ea70f, 0x80bc3d2aac952cb1, 0xabbd1a878bc50565, 0xf1ebc3b8d513c591] },
|
||||
// TestCase { n: 36, digest: [0xba9c4b1ce906efb1, 0xa332d0daccc62979, 0xfb658fcad0b5fbbd, 0x62d21407f34a35ee] },
|
||||
// TestCase { n: 37, digest: [0xcb2973d44f2b589d, 0x01708b32c4556317, 0x3ad51597c12b8564, 0x28d3a5d7de72cfd5] },
|
||||
// TestCase { n: 38, digest: [0x1dcf1f4ab7338296, 0xb88c661141b5aabb, 0x7e546b6e9b31bc90, 0xf26f7e6ffabb4e69] },
|
||||
// TestCase { n: 39, digest: [0x2e139ff910c0f410, 0xba3d2c0a92ec3845, 0x2860e475933a7108, 0x8f2a6c6d13bedc7a] },
|
||||
// TestCase { n: 40, digest: [0xc18a53c17c360ef4, 0x5e56ea9228988c68, 0xee0bd138436e996d, 0x06afd46a753f8257] },
|
||||
// TestCase { n: 41, digest: [0x2c992403c5277dc5, 0xba8770bc3a54b043, 0x51b882882a7b7864, 0xf75e179a53e7948e] },
|
||||
// TestCase { n: 42, digest: [0xde855183965741c3, 0x93520eac77a8f98d, 0x6412ae8cf0522d78, 0x9db49c6b455a83b4] },
|
||||
// TestCase { n: 43, digest: [0x552e357ddb7e1ef6, 0x5fa779e9c7373b56, 0x18f7c445e27e5dcf, 0x2664ecee5e7bc6c2] },
|
||||
// TestCase { n: 44, digest: [0x37b8a716c87e5489, 0x1201fcd31e407152, 0x0979d7887c42e1ca, 0x902e8b2bf748b356] },
|
||||
// TestCase { n: 45, digest: [0xa48bdd1d464960ed, 0x8e92c1af0cf258bc, 0x7c5b447524b92ba9, 0xac63902e613e4ef0] },
|
||||
// TestCase { n: 46, digest: [0x542e62f9317fe11d, 0xc23ba113a3f3c810, 0x2bda30c42a89cc7e, 0x35616e9f1a00aa8f] },
|
||||
// TestCase { n: 47, digest: [0x1c9194a0acfa97d7, 0x60d536ac106dd774, 0x8855b4a40e110080, 0xc2c408114e8c20d6] },
|
||||
// TestCase { n: 48, digest: [0x0e90b1cc3ac49e0c, 0x1b73aa8e0decbf88, 0x0ca9ef7070e0513f, 0x25cfb975571b6139] },
|
||||
// TestCase { n: 49, digest: [0xba6d6f7aa664f2e7, 0x4b9af896093937b9, 0x115b9aeb6c5f563e, 0x41cb5f42c6d3b115] },
|
||||
// TestCase { n: 50, digest: [0xdc3bdc491154caf6, 0xb95159bae61b2035, 0x98bd384fb3d0100b, 0xd70226f2b71ea465] },
|
||||
// TestCase { n: 51, digest: [0x57f31da51bcd2eab, 0x4a3b3945a8662b5c, 0x44dffaa325530b19, 0x47f4e41c2c1474cf] },
|
||||
// TestCase { n: 52, digest: [0xc3f518f6cf3b43bf, 0x1214790ff48554e4, 0x99c1eabc61b218fd, 0xf90b03954d7937f8] },
|
||||
// TestCase { n: 53, digest: [0x6357b3cdcbc1283a, 0x6acc0c2d5aac9261, 0xdf11e7ad14d432d1, 0x2242b26bdcc8a380] },
|
||||
// TestCase { n: 54, digest: [0x1946dc4471f8c502, 0x6be7d72499e0b4a5, 0x6e11de349239ff90, 0xfca78044256b8b54] },
|
||||
// TestCase { n: 55, digest: [0x302b38fb3df623dd, 0x69b362f7932fd7af, 0x2b47156f9135508b, 0xfe6c574f0a102e92] },
|
||||
// TestCase { n: 56, digest: [0xfdc9bd08a0416122, 0x063ebf4767fc7914, 0x330f36279d94050e, 0x79c61f80746893ec] },
|
||||
// TestCase { n: 57, digest: [0x7b5d8384b67af5c0, 0xa705e0163fa4d839, 0x1e203432e872104e, 0xe0e7699f20a291f4] },
|
||||
// TestCase { n: 58, digest: [0xb0aa74a52fe04366, 0x194b0d4afcdc03d9, 0x5134dc604b5d9f2a, 0x53c6bf9d5a1d502b] },
|
||||
// TestCase { n: 59, digest: [0xd5c8258f6fc80e2b, 0x82bac373eb051b48, 0x5ef620241420462d, 0x58635db0134fb97a] },
|
||||
// TestCase { n: 60, digest: [0x42ebb974ac5dd0ef, 0x676d0c6b3dde78c3, 0x14ed5eda2c9cb9de, 0x0f78a26badaa447c] },
|
||||
// TestCase { n: 61, digest: [0x2b3ca7711db999d5, 0xb74bd29abcb6179a, 0x8ba196525e6adb25, 0x86cb9464ae269a43] },
|
||||
// TestCase { n: 62, digest: [0x3d0e61a2ca7a65a2, 0x31f77852d41a6c8d, 0x2e4ceaa39763a53d, 0x5232ff5a3d78755e] },
|
||||
// TestCase { n: 63, digest: [0xb2ed789e88c1f525, 0x1592c1a1eafd2a9b, 0x98700c512f8c9a5d, 0xf96837b5d99a4eb4] },
|
||||
// TestCase { n: 64, digest: [0xe4b7d14e11de2fa9, 0xe81afff2cee68e14, 0xc58abb080bf37dd3, 0x36ae8b2196b5ae88] },
|
||||
// TestCase { n: 65, digest: [0xa1df9ff199c41d63, 0xd02c067d3d12edc1, 0xc9b598130fa60794, 0x5afe82d34c3fc8fa] },
|
||||
// TestCase { n: 66, digest: [0x0bc0094a1f07256d, 0x33c5b4c2a171d5bd, 0x1f38f1b1dc92aa54, 0x4610d21f276faa11] },
|
||||
// TestCase { n: 67, digest: [0x8072f00df8f7e44f, 0x42f0c2b8fe81d8a0, 0x2b5caf9e7c0ff611, 0x92b0b3a4a4bebe1a] },
|
||||
// TestCase { n: 68, digest: [0x6539f06fab064b57, 0xdb298b91f6c4f44f, 0x5d8f8eec6b7e8c86, 0x848a447123f39006] },
|
||||
// TestCase { n: 69, digest: [0x87f32efc9eaa65f6, 0xc5699d4ab6443852, 0x61008286bc651f4a, 0xcbcf714354843da3] },
|
||||
// TestCase { n: 70, digest: [0xffb8ad2258107315, 0xf7d6a58eb54f2745, 0xaecf888211821114, 0x7e0ea33b4d56976e] },
|
||||
// TestCase { n: 71, digest: [0xa9e5b6d70f67db2b, 0x072fd05840040322, 0x40ffcc86e3909dec, 0x3d80f61616a9e6d7] },
|
||||
// TestCase { n: 72, digest: [0xa77dd95d9ff4d7b8, 0x3a0e0502f74c091a, 0x1fa83de1e7dc716d, 0xe01ae447cc3a0e40] },
|
||||
// TestCase { n: 73, digest: [0xc4a29dc875a308eb, 0xd2ed0da7aab24b0c, 0x4c2aaaed0bc4f059, 0xaea772c635ea901a] },
|
||||
// TestCase { n: 74, digest: [0xaad59bf06c151ecf, 0x5e0f45e55df36692, 0x4798afb8b944a01e, 0xd7152cd819bbd7f8] },
|
||||
// TestCase { n: 75, digest: [0x89ae5b2b35ba07c7, 0x129f4ff59afaa1a3, 0x4275f3f797112650, 0xea3b4baaf7190a19] },
|
||||
// TestCase { n: 76, digest: [0xab068e43be297604, 0x17bd1c3cf4afec96, 0xaa84a8098dba4516, 0xa6e487ceafb02c49] },
|
||||
// TestCase { n: 77, digest: [0x2c85080ef895bb4a, 0xbd280690a789c124, 0xca4f8423b50de8a5, 0xec809bb8c30de95b] },
|
||||
// TestCase { n: 78, digest: [0x51c3d13543e4922b, 0xff9c11d5b93268db, 0xd9cf911cc5326948, 0x4b7bb11eafe7fd44] },
|
||||
// TestCase { n: 79, digest: [0xb435274d75678586, 0x8600e7f2db687493, 0x282873a3600a38da, 0x727791507d1b600e] },
|
||||
// TestCase { n: 80, digest: [0x23ae45602324f628, 0x0dc16b33f43209c5, 0x2455376f83b1aeff, 0xd5470f22ec2113bc] },
|
||||
// ];
|
||||
//
|
||||
// for test_case in test_cases {
|
||||
// let n = test_case.n;
|
||||
// let expected_digest = test_case.digest;
|
||||
//
|
||||
// // Generate inputs
|
||||
// let inputs: Vec<F> = (0..n)
|
||||
// .map(|i| F::from_canonical_u64(i as u64 + 1))
|
||||
// .collect();
|
||||
//
|
||||
// // Call the sponge function
|
||||
// let output = hash_n_with_padding::<F,D,HF>(&inputs);
|
||||
//
|
||||
// // Compare the outputs
|
||||
// for (i, &out_elem) in output.elements.iter().enumerate() {
|
||||
// let expected_elem = F::from_canonical_u64(expected_digest[i]);
|
||||
// assert_eq!(
|
||||
// out_elem,
|
||||
// expected_elem,
|
||||
// "Mismatch at test case n={}, output element {}",
|
||||
// n,
|
||||
// i
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
@ -1,103 +0,0 @@
|
||||
use plonky2::hash::hash_types::{HashOut, RichField};
|
||||
use crate::params::HF;
|
||||
use plonky2::hash::hashing::PlonkyPermutation;
|
||||
use plonky2_field::types::HasExtension;
|
||||
use crate::sponge::hash_n_with_padding;
|
||||
use codex_plonky2_circuits::circuits::params::NUM_HASH_OUT_ELTS;
|
||||
|
||||
// --------- helper functions ---------
|
||||
|
||||
/// Converts an index to a vector of bits (LSB first) no padding.
|
||||
pub fn usize_to_bits_le(index: usize, bit_length: usize) -> Vec<bool> {
|
||||
// Assert that the index can fit within the given bit length.
|
||||
assert!(
|
||||
index < (1 << bit_length),
|
||||
"Index ({}) does not fit in {} bits",
|
||||
index,
|
||||
bit_length
|
||||
);
|
||||
|
||||
let mut bits = Vec::with_capacity(bit_length);
|
||||
for i in 0..bit_length {
|
||||
bits.push(((index >> i) & 1) == 1);
|
||||
}
|
||||
|
||||
// No padding
|
||||
bits
|
||||
}
|
||||
|
||||
/// returns the first bit_length bits of index
|
||||
pub fn low_bits(index: usize, bit_length: usize) -> Vec<bool> {
|
||||
|
||||
let mut bits = Vec::with_capacity(bit_length);
|
||||
|
||||
for i in 0..bit_length {
|
||||
// get the i-th bit and push its bool value
|
||||
bits.push(((index >> i) & 1) == 1);
|
||||
}
|
||||
|
||||
bits
|
||||
}
|
||||
|
||||
/// calculate the sampled cell index from entropy, slot root, and counter
|
||||
/// this is the non-circuit version for testing
|
||||
pub fn calculate_cell_index_bits<
|
||||
F: RichField + HasExtension<D>,
|
||||
const D: usize
|
||||
>(entropy: &Vec<F>, slot_root: HashOut<F, NUM_HASH_OUT_ELTS>, ctr: usize, depth: usize, mask_bits: Vec<bool>) -> Vec<bool> {
|
||||
let ctr_field = F::from_canonical_u64(ctr as u64);
|
||||
let mut ctr_as_digest = HashOut::<F, NUM_HASH_OUT_ELTS>::zero();
|
||||
ctr_as_digest.elements[0] = ctr_field;
|
||||
let mut hash_inputs = Vec::new();
|
||||
hash_inputs.extend_from_slice(&entropy);
|
||||
hash_inputs.extend_from_slice(&slot_root.elements);
|
||||
hash_inputs.extend_from_slice(&ctr_as_digest.elements);
|
||||
let hash_output = hash_n_with_padding::<F,D,HF>(&hash_inputs);
|
||||
let cell_index_bytes = hash_output.elements[0].as_canonical_u64();
|
||||
|
||||
let cell_index_bits = low_bits(cell_index_bytes as usize, depth);
|
||||
|
||||
let mut masked_cell_index_bits = vec![];
|
||||
|
||||
for i in 0..depth{
|
||||
masked_cell_index_bits.push(cell_index_bits[i] && mask_bits[i]);
|
||||
}
|
||||
|
||||
masked_cell_index_bits
|
||||
}
|
||||
|
||||
/// Converts a vector of bits (LSB first) into an index (usize).
|
||||
pub fn bits_le_padded_to_usize(bits: &[bool]) -> usize {
|
||||
bits.iter().enumerate().fold(0usize, |acc, (i, &bit)| {
|
||||
if bit {
|
||||
acc | (1 << i)
|
||||
} else {
|
||||
acc
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// computes the `last_index` (the binary decomposition of `inp-1`) and the `mask_bits`
|
||||
pub fn ceiling_log2(
|
||||
inp: usize,
|
||||
n: usize,
|
||||
) -> (Vec<bool>, Vec<bool>) {
|
||||
// Handle the case when inp is 0
|
||||
let last_index = if inp == 0 { panic!("input to ceiling_log2 is 0") } else { inp - 1 };
|
||||
let last_bits = usize_to_bits_le(last_index, n);
|
||||
|
||||
// Initialize aux, all false
|
||||
let mut aux = vec![false; n+1];
|
||||
aux[n] = true; // aux[n] = 1
|
||||
|
||||
// Initialize mask vector
|
||||
let mut mask = vec![false; n+1];
|
||||
|
||||
// Compute aux and mask bits
|
||||
for i in (0..n).rev() {
|
||||
aux[i] = aux[i + 1] && !last_bits[i];
|
||||
mask[i] = !aux[i];
|
||||
}
|
||||
|
||||
(last_bits, mask)
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
Proof Aggregation Experiments
|
||||
================================
|
||||
|
||||
In here, we archive the experiments done on various approaches to recursion before setting on the uniform approach.
|
||||
See [here](../codex-plonky2-circuits/src/recursion) for the best recursion circuit option for our use case that we settled on.
|
||||
@ -1,80 +0,0 @@
|
||||
use criterion::{Criterion, criterion_group, criterion_main};
|
||||
use plonky2::plonk::config::GenericConfig;
|
||||
use codex_plonky2_circuits::recursion::circuits::sampling_inner_circuit::SamplingRecursion;
|
||||
use codex_plonky2_circuits::recursion::cyclic::CyclicCircuit;
|
||||
use proof_input::params::{C, D, F,HF};
|
||||
use proof_input::gen_input::gen_testing_circuit_input;
|
||||
use proof_input::params::Params;
|
||||
|
||||
|
||||
/// Benchmark for building, proving, and verifying the Plonky2 tree recursion circuit.
|
||||
fn bench_cyclic_recursion<const N: usize>(c: &mut Criterion) -> anyhow::Result<()>{
|
||||
|
||||
let mut group = c.benchmark_group(format!("Cyclic Recursion Benchmark for N={}",N));
|
||||
|
||||
// number of samples in each proof
|
||||
let n_samples = 10;
|
||||
|
||||
let mut params = Params::default();
|
||||
let mut input_params = params.input_params;
|
||||
input_params.n_samples = n_samples;
|
||||
let mut circuit_params = params.circuit_params;
|
||||
circuit_params.n_samples = n_samples;
|
||||
let inner_sampling_circuit = SamplingRecursion::<F,D,HF,C>::new(circuit_params);
|
||||
let mut circ_inputs = vec![];
|
||||
for _i in 0..N {
|
||||
circ_inputs.push(gen_testing_circuit_input::<F, D>(&input_params));
|
||||
}
|
||||
|
||||
let mut cyclic_circ = CyclicCircuit::<F,D,_,C>::build_circuit::<HF>(inner_sampling_circuit.clone())?;
|
||||
|
||||
// Building Phase
|
||||
group.bench_function("build cyclic circuit", |b| {
|
||||
b.iter(|| {
|
||||
let _cyclic_circ = CyclicCircuit::<F,D,_,C>::build_circuit::<HF>(inner_sampling_circuit.clone());
|
||||
|
||||
})
|
||||
});
|
||||
println!("cyclic circuit size = {:?}", cyclic_circ.cyclic_circuit_data.common.degree_bits());
|
||||
|
||||
let proof = cyclic_circ.prove_n_layers(circ_inputs.clone())?;
|
||||
|
||||
// Proving Phase
|
||||
group.bench_function("prove cyclic circuit", |b| {
|
||||
b.iter(|| {
|
||||
let _proof = cyclic_circ.prove_n_layers(circ_inputs.clone());
|
||||
})
|
||||
});
|
||||
println!("Proof size: {} bytes", proof.to_bytes().len());
|
||||
println!("num of pi = {}", proof.public_inputs.len());
|
||||
|
||||
// Verifying Phase
|
||||
group.bench_function("verify cyclic circuit proof", |b| {
|
||||
b.iter(|| {
|
||||
cyclic_circ.verify_latest_proof();
|
||||
})
|
||||
});
|
||||
|
||||
assert!(
|
||||
cyclic_circ.verify_latest_proof().is_ok(),
|
||||
"proof verification failed"
|
||||
);
|
||||
|
||||
group.finish();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn bench_recursion(c: &mut Criterion){
|
||||
const N: usize = 2; // number of proofs to be aggregated
|
||||
bench_cyclic_recursion::<4>(c);
|
||||
bench_cyclic_recursion::<8>(c);
|
||||
bench_cyclic_recursion::<16>(c);
|
||||
}
|
||||
|
||||
/// Criterion benchmark group
|
||||
criterion_group!{
|
||||
name = recursion;
|
||||
config = Criterion::default().sample_size(10);
|
||||
targets = bench_recursion
|
||||
}
|
||||
criterion_main!(recursion);
|
||||
@ -1,100 +0,0 @@
|
||||
use criterion::{Criterion, criterion_group, criterion_main};
|
||||
use plonky2::iop::witness::PartialWitness;
|
||||
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
||||
use plonky2::plonk::circuit_data::{CircuitConfig};
|
||||
use plonky2::plonk::config::GenericConfig;
|
||||
use plonky2::plonk::proof::ProofWithPublicInputs;
|
||||
use codex_plonky2_circuits::circuits::sample_cells::{SampleCircuit};
|
||||
use codex_plonky2_circuits::recursion::circuits::leaf_circuit::LeafCircuit;
|
||||
use codex_plonky2_circuits::recursion::circuits::sampling_inner_circuit::SamplingRecursion;
|
||||
use codex_plonky2_circuits::recursion::hybrid::tree_circuit::HybridTreeRecursion;
|
||||
use proof_input::params::{C, D, F,HF};
|
||||
use proof_input::gen_input::gen_testing_circuit_input;
|
||||
use proof_input::params::Params;
|
||||
|
||||
/// Benchmark for building, proving, and verifying the Plonky2 tree recursion circuit.
|
||||
fn bench_hybrid_recursion<const N: usize, const M: usize, const K: usize>(c: &mut Criterion) -> anyhow::Result<()>{
|
||||
|
||||
let mut group = c.benchmark_group(format!("Tree Recursion - Approach 2 Benchmark for N={}, leaf ={}, Node ={}",K, M,N));
|
||||
|
||||
//------------ sampling inner circuit ----------------------
|
||||
// Circuit that does the sampling - default input
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut sampling_builder = CircuitBuilder::<F, D>::new(config);
|
||||
let mut params = Params::default();
|
||||
let one_circ_input = gen_testing_circuit_input::<F,D>(¶ms.input_params);
|
||||
let samp_circ = SampleCircuit::<F,D,HF>::new(params.circuit_params);
|
||||
let inner_tar = samp_circ.sample_slot_circuit_with_public_input(&mut sampling_builder)?;
|
||||
// get generate a sampling proof
|
||||
let mut pw = PartialWitness::<F>::new();
|
||||
samp_circ.sample_slot_assign_witness(&mut pw,&inner_tar,&one_circ_input);
|
||||
let inner_data = sampling_builder.build::<C>();
|
||||
let inner_proof = inner_data.prove(pw.clone())?;
|
||||
|
||||
// ------------------- leaf --------------------
|
||||
let inner_circ = SamplingRecursion::<F,D,HF,C>::new(Params::default().circuit_params);
|
||||
let leaf_circuit = LeafCircuit::<F,D,_,M>::new(inner_circ);
|
||||
|
||||
|
||||
// ------------- Node/tree circuit ------------------
|
||||
// node circuit that verifies leafs or itself
|
||||
|
||||
let mut tree = HybridTreeRecursion::<F,D,_,N,M>::new(leaf_circuit);
|
||||
|
||||
// prepare input
|
||||
let input_proofs: Vec<ProofWithPublicInputs<F, C, D>> = (0..K)
|
||||
.map(|_| {
|
||||
inner_proof.clone()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Building phase
|
||||
group.bench_function("prove tree", |b| {
|
||||
b.iter(|| {
|
||||
let _ = tree.prove_tree::<C,HF>(&input_proofs, inner_data.verifier_data());
|
||||
|
||||
})
|
||||
});
|
||||
|
||||
let (tree_root_proof, verifier_data) = tree.prove_tree::<C,HF>(&input_proofs, inner_data.verifier_data())?;
|
||||
|
||||
println!("tree circuit - num of public input = {}", tree_root_proof.public_inputs.len());
|
||||
println!("Proof size: {} bytes", tree_root_proof.to_bytes().len());
|
||||
|
||||
// Verifying Phase
|
||||
group.bench_function("verify tree circuit", |b| {
|
||||
b.iter(|| {
|
||||
verifier_data.verify(tree_root_proof.clone()).expect("verify fail");
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
group.finish();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn bench_tree_recursion_approach2(c: &mut Criterion){
|
||||
const N: usize = 2; // number of child nodes - binary here
|
||||
const M: usize = 64; // number of proofs aggregated in leaves
|
||||
const K: usize = 128; // number of proofs to be aggregated in the tree
|
||||
bench_hybrid_recursion::<N,4,4>(c);
|
||||
bench_hybrid_recursion::<N,8,8>(c);
|
||||
bench_hybrid_recursion::<N,4,16>(c);
|
||||
bench_hybrid_recursion::<N,8,16>(c);
|
||||
bench_hybrid_recursion::<N,16,16>(c);
|
||||
bench_hybrid_recursion::<N,8,32>(c);
|
||||
bench_hybrid_recursion::<N,16,32>(c);
|
||||
bench_hybrid_recursion::<N,32,32>(c);
|
||||
bench_hybrid_recursion::<N,16,64>(c);
|
||||
bench_hybrid_recursion::<N,32,64>(c);
|
||||
bench_hybrid_recursion::<N,16,128>(c);
|
||||
bench_hybrid_recursion::<4,16,128>(c);
|
||||
}
|
||||
|
||||
/// Criterion benchmark group
|
||||
criterion_group!{
|
||||
name = recursion;
|
||||
config = Criterion::default().sample_size(10);
|
||||
targets = bench_tree_recursion_approach2
|
||||
}
|
||||
criterion_main!(recursion);
|
||||
@ -1,112 +0,0 @@
|
||||
use anyhow::Result;
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use plonky2::hash::hash_types::HashOut;
|
||||
use plonky2::iop::witness::PartialWitness;
|
||||
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
||||
use plonky2::plonk::circuit_data::{CircuitConfig};
|
||||
use plonky2::plonk::config::GenericConfig;
|
||||
use plonky2_field::types::Field;
|
||||
use codex_plonky2_circuits::recursion::circuits::sampling_inner_circuit::SamplingRecursion;
|
||||
use codex_plonky2_circuits::recursion::simple::simple_recursion::{SimpleRecursionCircuit, SimpleRecursionInput};
|
||||
use proof_input::params::{D, C, F, HF, Params};
|
||||
use proof_input::gen_input::{build_circuit, prove_circuit};
|
||||
|
||||
/// Benchmark for building, proving, and verifying the Plonky2 recursion circuit.
|
||||
/// Simple recursion approach - verify N proofs in-circuit
|
||||
fn bench_simple_recursion<const N_INNER: usize>(c: &mut Criterion) -> Result<()>{
|
||||
let mut group = c.benchmark_group(format!("Simple Recursion Benchmark for N ={}", N_INNER));
|
||||
|
||||
// number of samples in each proof
|
||||
let n_samples = 10;
|
||||
// params
|
||||
let mut circ_params = Params::default().circuit_params;
|
||||
circ_params.n_samples = n_samples;
|
||||
|
||||
let (data, pw) = build_circuit(n_samples, 3)?;
|
||||
let proof = prove_circuit(&data, &pw)?;
|
||||
|
||||
// get proofs
|
||||
let mut proofs_with_pi = (0..N_INNER).map(|i| proof.clone()).collect::<Vec<_>>();
|
||||
|
||||
println!("inner circuit size = {:?}", data.common.degree_bits());
|
||||
|
||||
// careful here, the sampling recursion is the default so proofs should be for circuit
|
||||
// with default params
|
||||
let sampling_inner_circ = SamplingRecursion::<F,D,HF,C>::new(circ_params);
|
||||
let rec_circuit = SimpleRecursionCircuit::<F,D, _, N_INNER, C>::new(sampling_inner_circ);
|
||||
|
||||
group.bench_function("Build Circuit", |b| {
|
||||
b.iter(|| {
|
||||
// Create the circuit
|
||||
let local_config = CircuitConfig::standard_recursion_config();
|
||||
let mut local_builder = CircuitBuilder::<F, D>::new(local_config);
|
||||
// aggregate proofs
|
||||
let _loc_targets = rec_circuit.build_circuit(&mut local_builder).unwrap();
|
||||
let _agg_data = local_builder.build::<C>();
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
// Create the circuit
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut builder = CircuitBuilder::<F, D>::new(config);
|
||||
// Create a PartialWitness
|
||||
let mut pw = PartialWitness::new();
|
||||
|
||||
let targets = rec_circuit.build_circuit(&mut builder)?;
|
||||
let agg_data = builder.build::<C>();
|
||||
|
||||
println!("agg circuit size = {:?}", agg_data.common.degree_bits());
|
||||
|
||||
let mut default_entropy = HashOut::ZERO;
|
||||
default_entropy.elements[0] = F::from_canonical_u64(1234567);
|
||||
|
||||
let w = SimpleRecursionInput{
|
||||
proofs: proofs_with_pi,
|
||||
verifier_data: data.verifier_data(),
|
||||
entropy: default_entropy,
|
||||
};
|
||||
|
||||
rec_circuit.assign_witness(&mut pw,&targets,w)?;
|
||||
|
||||
group.bench_function("Prove Circuit", |b| {
|
||||
b.iter(|| {
|
||||
let local_pw = pw.clone();
|
||||
agg_data.prove(local_pw).expect("Failed to prove circuit")
|
||||
})
|
||||
});
|
||||
|
||||
let proof = agg_data.prove(pw)?;
|
||||
println!("Proof size: {} bytes", proof.to_bytes().len());
|
||||
println!("public input count = {:?}", proof.public_inputs.len());
|
||||
|
||||
// Verify the proof
|
||||
let verifier_data = agg_data.verifier_data();
|
||||
group.bench_function("Verify Proof", |b| {
|
||||
b.iter(|| {
|
||||
verifier_data.clone().verify(proof.clone()).expect("Failed to verify proof");
|
||||
})
|
||||
});
|
||||
|
||||
assert!(
|
||||
verifier_data.verify(proof).is_ok(),
|
||||
"proof verification failed"
|
||||
);
|
||||
|
||||
group.finish();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn bench_multiple_n(c: &mut Criterion){
|
||||
bench_simple_recursion::<32>(c);
|
||||
bench_simple_recursion::<64>(c);
|
||||
bench_simple_recursion::<128>(c);
|
||||
}
|
||||
|
||||
/// Criterion benchmark group
|
||||
criterion_group!{
|
||||
name = recursion;
|
||||
config = Criterion::default().sample_size(10);
|
||||
targets = bench_multiple_n
|
||||
}
|
||||
criterion_main!(recursion);
|
||||
@ -1,111 +0,0 @@
|
||||
use anyhow::Result;
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use plonky2::iop::witness::PartialWitness;
|
||||
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
||||
use plonky2::plonk::circuit_data::{CircuitConfig};
|
||||
use plonky2::plonk::config::GenericConfig;
|
||||
use codex_plonky2_circuits::recursion::circuits::sampling_inner_circuit::SamplingRecursion;
|
||||
use codex_plonky2_circuits::recursion::simple::simple_recursion_hashed_pi::{SimpleRecursionCircuitHashedPI, SimpleRecursionInputHashedPI};
|
||||
use proof_input::params::{D, C, F, HF, Params};
|
||||
use proof_input::gen_input::{build_circuit, prove_circuit};
|
||||
|
||||
/// Benchmark for building, proving, and verifying the Plonky2 recursion circuit.
|
||||
/// Simple recursion approach - verify N proofs in-circuit
|
||||
fn bench_recursion<const N_INNER: usize>(c: &mut Criterion) -> Result<()>{
|
||||
let mut group = c.benchmark_group(format!("Simple Recursion With Hashed Public Input Benchmark for N={}",N_INNER));
|
||||
|
||||
// number of samples in each proof
|
||||
let n_samples = 50;
|
||||
// params
|
||||
let mut circ_params = Params::default().circuit_params;
|
||||
circ_params.n_samples = n_samples;
|
||||
|
||||
let (data, pw) = build_circuit(n_samples, 3)?;
|
||||
let proof = prove_circuit(&data, &pw)?;
|
||||
|
||||
// get proofs
|
||||
let mut proofs_with_pi = (0..N_INNER).map(|i| proof.clone()).collect::<Vec<_>>();
|
||||
|
||||
println!("inner circuit size = {:?}", data.common.degree_bits());
|
||||
|
||||
// careful here, the sampling recursion is the default so proofs should be for circuit
|
||||
// with default params
|
||||
let sampling_inner_circ = SamplingRecursion::<F,D,HF,C>::new(circ_params);
|
||||
let rec_circuit = SimpleRecursionCircuitHashedPI::<F,D, _, N_INNER, C>::new(sampling_inner_circ);
|
||||
|
||||
group.bench_function("Build Circuit", |b| {
|
||||
b.iter(|| {
|
||||
// Create the circuit
|
||||
let local_config = CircuitConfig::standard_recursion_config();
|
||||
let mut local_builder = CircuitBuilder::<F, D>::new(local_config);
|
||||
// aggregate proofs
|
||||
let _loc_targets = rec_circuit.build_circuit::<HF>(&mut local_builder).unwrap();
|
||||
let _agg_data = local_builder.build::<C>();
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
// Create the circuit
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut builder = CircuitBuilder::<F, D>::new(config);
|
||||
// Create a PartialWitness
|
||||
let mut pw = PartialWitness::new();
|
||||
|
||||
let targets = rec_circuit.build_circuit::<HF>(&mut builder)?;
|
||||
let agg_data = builder.build::<C>();
|
||||
|
||||
println!("agg circuit size = {:?}", agg_data.common.degree_bits());
|
||||
|
||||
let w = SimpleRecursionInputHashedPI{
|
||||
proofs: proofs_with_pi,
|
||||
verifier_data: data.verifier_data(),
|
||||
};
|
||||
|
||||
rec_circuit.assign_witness(&mut pw,&targets,w)?;
|
||||
|
||||
group.bench_function("Prove Circuit", |b| {
|
||||
b.iter(|| {
|
||||
let local_pw = pw.clone();
|
||||
agg_data.prove(local_pw).expect("Failed to prove circuit")
|
||||
})
|
||||
});
|
||||
|
||||
let proof = agg_data.prove(pw)?;
|
||||
println!("Proof size: {} bytes", proof.to_bytes().len());
|
||||
println!("public input count = {:?}", proof.public_inputs.len());
|
||||
|
||||
// Verify the proof
|
||||
let verifier_data = agg_data.verifier_data();
|
||||
group.bench_function("Verify Proof", |b| {
|
||||
b.iter(|| {
|
||||
verifier_data.clone().verify(proof.clone()).expect("Failed to verify proof");
|
||||
})
|
||||
});
|
||||
|
||||
assert!(
|
||||
verifier_data.verify(proof).is_ok(),
|
||||
"proof verification failed"
|
||||
);
|
||||
|
||||
group.finish();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn bench_multiple_n(c: &mut Criterion){
|
||||
bench_recursion::<4>(c);
|
||||
bench_recursion::<8>(c);
|
||||
bench_recursion::<16>(c);
|
||||
bench_recursion::<32>(c);
|
||||
bench_recursion::<64>(c);
|
||||
bench_recursion::<128>(c);
|
||||
// bench_recursion::<256>(c);
|
||||
}
|
||||
|
||||
|
||||
/// Criterion benchmark group
|
||||
criterion_group!{
|
||||
name = recursion;
|
||||
config = Criterion::default().sample_size(10);
|
||||
targets = bench_multiple_n
|
||||
}
|
||||
criterion_main!(recursion);
|
||||
@ -1,68 +0,0 @@
|
||||
use criterion::{Criterion, criterion_group, criterion_main};
|
||||
use plonky2::plonk::circuit_data::VerifierCircuitData;
|
||||
use plonky2::plonk::config::GenericConfig;
|
||||
use plonky2::plonk::proof::ProofWithPublicInputs;
|
||||
use codex_plonky2_circuits::recursion::simple::simple_tree_recursion::aggregate_sampling_proofs_tree;
|
||||
use proof_input::params::{C, D, F, HF, Params};
|
||||
use proof_input::gen_input::{build_circuit, prove_circuit};
|
||||
|
||||
/// Benchmark for building, proving, and verifying the Plonky2 recursion circuit.
|
||||
fn bench_tree_recursion<const N_INNER:usize>(c: &mut Criterion) -> anyhow::Result<()>{
|
||||
|
||||
let mut group = c.benchmark_group(format!("Simple Tree Recursion Benchmark for N={}",N_INNER));
|
||||
|
||||
// number of samples in each proof
|
||||
let n_samples = 10;
|
||||
// params
|
||||
let mut circ_params = Params::default().circuit_params;
|
||||
circ_params.n_samples = n_samples;
|
||||
|
||||
let (data, pw) = build_circuit(n_samples, 3)?;
|
||||
let proof = prove_circuit(&data, &pw)?;
|
||||
|
||||
// get proofs
|
||||
let proofs_with_pi = (0..N_INNER).map(|i| proof.clone()).collect::<Vec<_>>();
|
||||
|
||||
println!("inner circuit size = {:?}", data.common.degree_bits());
|
||||
|
||||
|
||||
let mut agg_proof_with_pis: Option<ProofWithPublicInputs<F, C, D>> = None;
|
||||
let mut agg_vd: Option<VerifierCircuitData<F, C, D>> = None;
|
||||
|
||||
// Benchmark the Circuit Building Phase
|
||||
group.bench_function("build & prove Circuit", |b| {
|
||||
b.iter(|| {
|
||||
let (proof, vd_agg) = aggregate_sampling_proofs_tree::<F,D,C,HF>(&proofs_with_pi, data.verifier_data()).unwrap();
|
||||
agg_proof_with_pis = Some(proof);
|
||||
agg_vd = Some(vd_agg);
|
||||
})
|
||||
});
|
||||
|
||||
let proof = agg_proof_with_pis.unwrap();
|
||||
println!("Proof size: {} bytes", proof.to_bytes().len());
|
||||
|
||||
// Benchmark the Verifying Phase
|
||||
let loc_vd = agg_vd.unwrap();
|
||||
group.bench_function("Verify Proof", |b| {
|
||||
b.iter(|| {
|
||||
loc_vd.clone().verify(proof.clone()).expect("Failed to verify proof");
|
||||
})
|
||||
});
|
||||
|
||||
group.finish();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn bench_multiple_n(c: &mut Criterion){
|
||||
bench_tree_recursion::<4>(c);
|
||||
bench_tree_recursion::<8>(c);
|
||||
bench_tree_recursion::<16>(c);
|
||||
}
|
||||
|
||||
/// Criterion benchmark group
|
||||
criterion_group!{
|
||||
name = recursion;
|
||||
config = Criterion::default().sample_size(10);
|
||||
targets = bench_multiple_n
|
||||
}
|
||||
criterion_main!(recursion);
|
||||
@ -1,161 +0,0 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use criterion::{Criterion, criterion_group, criterion_main};
|
||||
use plonky2::plonk::config::GenericConfig;
|
||||
use plonky2::plonk::proof::ProofWithPublicInputs;
|
||||
use codex_plonky2_circuits::recursion::circuits::sampling_inner_circuit::SamplingRecursion;
|
||||
use codex_plonky2_circuits::recursion::tree1::{tree_circuit::TreeRecursion};
|
||||
use proof_input::params::{C, D, F, HF, Params};
|
||||
use proof_input::gen_input::{get_m_circ_input};
|
||||
|
||||
|
||||
/// Benchmark for building, proving, and verifying the approach1 Plonky2 tree recursion circuit.
|
||||
fn bench_node_recursion<const M: usize, const N: usize>(c: &mut Criterion) -> Result<()>{
|
||||
|
||||
let mut group = c.benchmark_group("Tree Recursion - Approach 1 Benchmark");
|
||||
|
||||
// number of samples in each proof
|
||||
let n_samples = 5;
|
||||
// params
|
||||
let mut circ_params = Params::default().circuit_params;
|
||||
circ_params.n_samples = n_samples;
|
||||
let mut input_params = Params::default().input_params;
|
||||
input_params.n_samples = n_samples;
|
||||
|
||||
let inner_sampling_circuit = SamplingRecursion::<F,D,HF,C>::new(circ_params);
|
||||
|
||||
let circ_input = get_m_circ_input::<M>(input_params);
|
||||
|
||||
// Building Phase
|
||||
group.bench_function("build", |b| {
|
||||
b.iter(|| {
|
||||
let _tree_circ = TreeRecursion::<F,D,_,M,N,C>::build::<HF>(inner_sampling_circuit.clone());
|
||||
})
|
||||
});
|
||||
|
||||
let mut tree_circ = TreeRecursion::<F,D,_,M,N,C>::build::<HF>(inner_sampling_circuit)?;
|
||||
println!("tree circuit size = {:?}", tree_circ.node_circ.cyclic_circuit_data.common.degree_bits());
|
||||
|
||||
// prove Phase
|
||||
group.bench_function("prove with leaf only", |b| {
|
||||
b.iter(|| {
|
||||
let _proof = tree_circ.prove(&circ_input,None, true);
|
||||
})
|
||||
});
|
||||
|
||||
let proof = tree_circ.prove(&circ_input,None, true)?;
|
||||
println!("Proof size: {} bytes", proof.to_bytes().len());
|
||||
println!("num of pi = {}", proof.public_inputs.len());
|
||||
|
||||
// make N node proofs
|
||||
let node_proofs: [ProofWithPublicInputs<F, C, D>; N] = (0..N)
|
||||
.map(|_| {
|
||||
proof.clone()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.try_into()
|
||||
.map_err(|_| anyhow!("Expected exactly M inner circuits")).unwrap();
|
||||
|
||||
// prove Phase for node leaf and node proofs
|
||||
group.bench_function("prove with leaf and node", |b| {
|
||||
b.iter(|| {
|
||||
let _proof = tree_circ.prove(&circ_input,Some(node_proofs.clone()), true);
|
||||
})
|
||||
});
|
||||
|
||||
// Verifying Phase for node leaf and node proofs
|
||||
group.bench_function("Verify Proof with leaf and node proofs", |b| {
|
||||
b.iter(|| {
|
||||
tree_circ.verify_proof(proof.clone()).expect("Failed to verify proof");
|
||||
})
|
||||
});
|
||||
|
||||
assert!(
|
||||
tree_circ.verify_proof(proof).is_ok(),
|
||||
"proof verification failed"
|
||||
);
|
||||
|
||||
group.finish();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn bench_tree_recursion<const M:usize, const N:usize, const DEPTH:usize, const TOTAL_INPUT:usize>(c: &mut Criterion) -> Result<()>{
|
||||
let mut group = c.benchmark_group(format!("bench tree recursion - approach 1 for N={}",TOTAL_INPUT));
|
||||
|
||||
// number of samples in each proof
|
||||
let n_samples = 5;
|
||||
// params
|
||||
let mut circ_params = Params::default().circuit_params;
|
||||
circ_params.n_samples = n_samples;
|
||||
let mut input_params = Params::default().input_params;
|
||||
input_params.n_samples = n_samples;
|
||||
|
||||
let inner_sampling_circuit = SamplingRecursion::<F,D,HF,C>::new(circ_params);
|
||||
|
||||
let circ_input = get_m_circ_input::<TOTAL_INPUT>(input_params).to_vec();
|
||||
|
||||
// Building Phase
|
||||
group.bench_function("build", |b| {
|
||||
b.iter(|| {
|
||||
let _tree_circ = TreeRecursion::<F,D,_,M,N,C>::build::<HF>(inner_sampling_circuit.clone()).unwrap();
|
||||
})
|
||||
});
|
||||
|
||||
let mut tree_circ = TreeRecursion::<F,D,_,M,N,C>::build::<HF>(inner_sampling_circuit)?;
|
||||
println!("tree circuit size = {:?}", tree_circ.node_circ.cyclic_circuit_data.common.degree_bits());
|
||||
|
||||
// prove Phase
|
||||
group.bench_function("prove tree", |b| {
|
||||
b.iter(|| {
|
||||
let _proof = tree_circ.prove_tree(circ_input.clone(),DEPTH).unwrap();
|
||||
})
|
||||
});
|
||||
|
||||
let proof = tree_circ.prove_tree(circ_input,DEPTH)?;
|
||||
println!("Proof size: {} bytes", proof.to_bytes().len());
|
||||
println!("num of pi = {}", proof.public_inputs.len());
|
||||
|
||||
// Verifying Phase for node leaf and node proofs
|
||||
group.bench_function("Verify final proof", |b| {
|
||||
b.iter(|| {
|
||||
tree_circ.verify_proof(proof.clone()).expect("Failed to verify proof");
|
||||
})
|
||||
});
|
||||
|
||||
assert!(
|
||||
tree_circ.verify_proof(proof).is_ok(),
|
||||
"proof verification failed"
|
||||
);
|
||||
|
||||
group.finish();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn bench_tree_recursion_approach1(c: &mut Criterion){
|
||||
const M: usize = 1;
|
||||
const N: usize = 2;
|
||||
bench_node_recursion::<M,N>(c);
|
||||
}
|
||||
|
||||
fn bench_multiple_params(c: &mut Criterion){
|
||||
const M: usize = 1;
|
||||
const N: usize = 2;
|
||||
const DEPTH1: usize = 2;
|
||||
const TOTAL_INPUT1: usize = (N.pow(DEPTH1 as u32) - 1) / (N - 1);
|
||||
const DEPTH2: usize = 3;
|
||||
const TOTAL_INPUT2: usize = (N.pow(DEPTH2 as u32) - 1) / (N - 1);
|
||||
const DEPTH3: usize = 4;
|
||||
const TOTAL_INPUT3: usize = (N.pow(DEPTH3 as u32) - 1) / (N - 1);
|
||||
|
||||
bench_tree_recursion::<M,N,DEPTH1,TOTAL_INPUT1>(c);
|
||||
bench_tree_recursion::<M,N,DEPTH2,TOTAL_INPUT2>(c);
|
||||
bench_tree_recursion::<M,N,DEPTH3,TOTAL_INPUT3>(c);
|
||||
}
|
||||
|
||||
|
||||
/// Criterion benchmark group
|
||||
criterion_group!{
|
||||
name = recursion;
|
||||
config = Criterion::default().sample_size(10);
|
||||
targets = bench_multiple_params
|
||||
}
|
||||
criterion_main!(recursion);
|
||||
@ -1,206 +0,0 @@
|
||||
use criterion::{Criterion, criterion_group, criterion_main};
|
||||
use plonky2::iop::witness::PartialWitness;
|
||||
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
||||
use plonky2::plonk::circuit_data::{CircuitConfig};
|
||||
use plonky2::plonk::config::GenericConfig;
|
||||
use plonky2::plonk::proof::ProofWithPublicInputs;
|
||||
use codex_plonky2_circuits::circuits::sample_cells::{SampleCircuit};
|
||||
use codex_plonky2_circuits::circuits::utils::vec_to_array;
|
||||
use codex_plonky2_circuits::recursion::circuits::leaf_circuit::{LeafCircuit, LeafInput};
|
||||
use codex_plonky2_circuits::recursion::circuits::sampling_inner_circuit::SamplingRecursion;
|
||||
use codex_plonky2_circuits::recursion::tree2::{tree_circuit::TreeRecursion};
|
||||
use proof_input::params::{C, D, F,HF};
|
||||
use proof_input::gen_input::gen_testing_circuit_input;
|
||||
use proof_input::params::Params;
|
||||
|
||||
|
||||
/// Benchmark for building, proving, and verifying the Plonky2 tree recursion circuit.
|
||||
fn bench_leaf<const M:usize, const N: usize, const K: usize>(c: &mut Criterion) -> anyhow::Result<()>{
|
||||
|
||||
let mut group = c.benchmark_group(format!("Tree Recursion - Approach 2 Benchmark for N={}",K));
|
||||
|
||||
//------------ sampling inner circuit ----------------------
|
||||
// Circuit that does the sampling - default input
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut sampling_builder = CircuitBuilder::<F, D>::new(config);
|
||||
let mut params = Params::default();
|
||||
let one_circ_input = gen_testing_circuit_input::<F,D>(¶ms.input_params);
|
||||
let samp_circ = SampleCircuit::<F,D,HF>::new(params.circuit_params);
|
||||
let inner_tar = samp_circ.sample_slot_circuit_with_public_input(&mut sampling_builder)?;
|
||||
// get generate a sampling proof
|
||||
let mut pw = PartialWitness::<F>::new();
|
||||
samp_circ.sample_slot_assign_witness(&mut pw,&inner_tar,&one_circ_input);
|
||||
let inner_data = sampling_builder.build::<C>();
|
||||
let inner_proof = inner_data.prove(pw.clone())?;
|
||||
|
||||
// Building Phase
|
||||
group.bench_function("build inner circuit", |b| {
|
||||
b.iter(|| {
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut sampling_builder = CircuitBuilder::<F, D>::new(config);
|
||||
let _inner_tar = samp_circ.sample_slot_circuit_with_public_input(&mut sampling_builder);
|
||||
sampling_builder.build::<C>();
|
||||
})
|
||||
});
|
||||
|
||||
// Proving Phase
|
||||
group.bench_function("prove inner circuit", |b| {
|
||||
b.iter(|| {
|
||||
let _inner_proof = inner_data.prove(pw.clone());
|
||||
})
|
||||
});
|
||||
|
||||
println!("inner circuit - Circuit size (degree bits): {:?}", inner_data.common.degree_bits() );
|
||||
println!("inner proof - num of public input = {}", inner_proof.public_inputs.len());
|
||||
|
||||
// ------------------- leaf --------------------
|
||||
// leaf circuit that verifies the sampling proof
|
||||
let inner_circ = SamplingRecursion::<F,D,HF,C>::new(Params::default().circuit_params);
|
||||
let leaf_circuit = LeafCircuit::<F,D,_, M>::new(inner_circ);
|
||||
|
||||
let leafs: Vec<ProofWithPublicInputs<F, C, D>> = (0..M).map(|i| inner_proof.clone()).collect::<Vec<_>>();
|
||||
let leafs_arr = vec_to_array::<M,ProofWithPublicInputs<F, C, D>>(leafs)?;
|
||||
|
||||
let leaf_in = LeafInput::<F,D,C, M>{
|
||||
inner_proof: leafs_arr,
|
||||
verifier_data: inner_data.verifier_data(),
|
||||
};
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut leaf_builder = CircuitBuilder::<F, D>::new(config);
|
||||
let leaf_targets = leaf_circuit.build::<C,HF>(&mut leaf_builder)?;
|
||||
let leaf_circ_data = leaf_builder.build::<C>();
|
||||
|
||||
// Building Phase
|
||||
group.bench_function("build leaf circuit", |b| {
|
||||
b.iter(|| {
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut leaf_builder = CircuitBuilder::<F, D>::new(config);
|
||||
let _leaf_targets = leaf_circuit.build::<C,HF>(&mut leaf_builder).unwrap();
|
||||
let _leaf_circ_data = leaf_builder.build::<C>();
|
||||
})
|
||||
});
|
||||
|
||||
let mut pw = PartialWitness::<F>::new();
|
||||
leaf_circuit.assign_targets::<C,HF>(&mut pw, &leaf_targets, &leaf_in)?;
|
||||
let leaf_proof = leaf_circ_data.prove(pw.clone())?;
|
||||
|
||||
// Proving Phase
|
||||
group.bench_function("prove leaf circuit", |b| {
|
||||
b.iter(|| {
|
||||
let _leaf_proof = leaf_circ_data.prove(pw.clone());
|
||||
})
|
||||
});
|
||||
|
||||
println!("leaf circuit - Circuit size (degree bits): {:?}", leaf_circ_data.common.degree_bits() );
|
||||
println!("leaf proof - num of public input = {}", leaf_proof.public_inputs.len());
|
||||
|
||||
|
||||
group.finish();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Benchmark for building, proving, and verifying the Plonky2 tree recursion circuit.
|
||||
fn bench_tree_recursion<const M: usize, const N: usize, const K: usize>(c: &mut Criterion) -> anyhow::Result<()>{
|
||||
|
||||
let mut group = c.benchmark_group(format!("Tree Recursion - Approach 2 Benchmark for N={}",K));
|
||||
|
||||
//------------ sampling inner circuit ----------------------
|
||||
// Circuit that does the sampling - default input
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut sampling_builder = CircuitBuilder::<F, D>::new(config);
|
||||
let mut params = Params::default();
|
||||
let one_circ_input = gen_testing_circuit_input::<F,D>(¶ms.input_params);
|
||||
let samp_circ = SampleCircuit::<F,D,HF>::new(params.circuit_params);
|
||||
let inner_tar = samp_circ.sample_slot_circuit_with_public_input(&mut sampling_builder)?;
|
||||
// get generate a sampling proof
|
||||
let mut pw = PartialWitness::<F>::new();
|
||||
samp_circ.sample_slot_assign_witness(&mut pw,&inner_tar,&one_circ_input);
|
||||
let inner_data = sampling_builder.build::<C>();
|
||||
let inner_proof = inner_data.prove(pw.clone())?;
|
||||
|
||||
// ------------------- leaf --------------------
|
||||
// leaf circuit that verifies the sampling proof
|
||||
let inner_circ = SamplingRecursion::<F,D,HF,C>::new(Params::default().circuit_params);
|
||||
let leaf_circuit = LeafCircuit::<F,D,_, M>::new(inner_circ);
|
||||
|
||||
let leafs: Vec<ProofWithPublicInputs<F, C, D>> = (0..M).map(|i| inner_proof.clone()).collect::<Vec<_>>();
|
||||
let leafs_arr = vec_to_array::<M,ProofWithPublicInputs<F, C, D>>(leafs)?;
|
||||
|
||||
let leaf_in = LeafInput::<F,D,C, M>{
|
||||
inner_proof: leafs_arr,
|
||||
verifier_data: inner_data.verifier_data(),
|
||||
};
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut leaf_builder = CircuitBuilder::<F, D>::new(config);
|
||||
let leaf_targets = leaf_circuit.build::<C,HF>(&mut leaf_builder)?;
|
||||
let leaf_circ_data = leaf_builder.build::<C>();
|
||||
|
||||
let mut pw = PartialWitness::<F>::new();
|
||||
leaf_circuit.assign_targets::<C,HF>(&mut pw, &leaf_targets, &leaf_in)?;
|
||||
let leaf_proof = leaf_circ_data.prove(pw.clone())?;
|
||||
|
||||
|
||||
// ------------- Node/tree circuit ------------------
|
||||
// node circuit that verifies leafs or itself
|
||||
|
||||
let mut tree = TreeRecursion::<F,D,C,N>::build::<_,HF, M>(leaf_circuit.clone())?;
|
||||
|
||||
// Building phase
|
||||
group.bench_function("build tree circuit", |b| {
|
||||
b.iter(|| {
|
||||
let _tree = TreeRecursion::<F,D,C,N>::build::<_,HF, M>(leaf_circuit.clone());
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
let leaf_proofs: Vec<ProofWithPublicInputs<F, C, D>> = (0..K)
|
||||
.map(|_| {
|
||||
leaf_proof.clone()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let tree_root_proof = tree.prove_tree(leaf_proofs.clone()).unwrap();
|
||||
|
||||
// Proving Phase
|
||||
group.bench_function("prove tree circuit", |b| {
|
||||
b.iter(|| {
|
||||
let _tree_root_proof = tree.prove_tree(leaf_proofs.clone());
|
||||
})
|
||||
});
|
||||
|
||||
println!("tree circuit - Circuit size (degree bits): {:?}", tree.node.node_data.node_circuit_data.common.degree_bits());
|
||||
println!("tree circuit - num of public input = {}", tree_root_proof.public_inputs.len());
|
||||
|
||||
assert!(
|
||||
tree.verify_proof(tree_root_proof.clone(), N==K).is_ok(),
|
||||
"proof verification failed"
|
||||
);
|
||||
|
||||
// Verifying Phase
|
||||
group.bench_function("verify tree circuit", |b| {
|
||||
b.iter(|| {
|
||||
tree.verify_proof(tree_root_proof.clone(), N==K).expect("verify fail");
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
group.finish();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn bench_tree_recursion_approach2(c: &mut Criterion){
|
||||
const M:usize = 1; // number of inner proofs in each leaf
|
||||
const N: usize = 2; // number of child nodes
|
||||
const K: usize = 4; // number of proofs to be aggregated in the tree
|
||||
bench_tree_recursion::<M,N,4>(c);
|
||||
// bench_tree_recursion::<M,N,8>(c);
|
||||
// bench_leaf::<2,N,8>(c);
|
||||
}
|
||||
|
||||
/// Criterion benchmark group
|
||||
criterion_group!{
|
||||
name = recursion;
|
||||
config = Criterion::default().sample_size(10);
|
||||
targets = bench_tree_recursion_approach2
|
||||
}
|
||||
criterion_main!(recursion);
|
||||
@ -1,89 +0,0 @@
|
||||
use criterion::{Criterion, criterion_group, criterion_main};
|
||||
use plonky2::iop::witness::PartialWitness;
|
||||
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
||||
use plonky2::plonk::circuit_data::{CircuitConfig};
|
||||
use plonky2::plonk::config::GenericConfig;
|
||||
use plonky2::plonk::proof::ProofWithPublicInputs;
|
||||
use codex_plonky2_circuits::circuits::sample_cells::{SampleCircuit};
|
||||
use codex_plonky2_circuits::recursion::uniform::tree::TreeRecursion;
|
||||
use proof_input::params::{C, D, F,HF};
|
||||
use proof_input::gen_input::gen_testing_circuit_input;
|
||||
use proof_input::params::Params;
|
||||
|
||||
/// Benchmark for building, proving, and verifying the Plonky2 tree recursion circuit.
|
||||
fn bench_uniform_recursion<const N: usize,>(c: &mut Criterion) -> anyhow::Result<()>{
|
||||
|
||||
let mut group = c.benchmark_group(format!("Uniform Tree Recursion Benchmark for N={}",N));
|
||||
|
||||
//------------ sampling inner circuit ----------------------
|
||||
// Circuit that does the sampling - 100 samples
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut sampling_builder = CircuitBuilder::<F, D>::new(config);
|
||||
let mut params = Params::default();
|
||||
params.input_params.n_samples = 100;
|
||||
params.circuit_params.n_samples = 100;
|
||||
let one_circ_input = gen_testing_circuit_input::<F,D>(¶ms.input_params);
|
||||
let samp_circ = SampleCircuit::<F,D,HF>::new(params.circuit_params);
|
||||
let inner_tar = samp_circ.sample_slot_circuit_with_public_input(&mut sampling_builder)?;
|
||||
// get generate a sampling proof
|
||||
let mut pw = PartialWitness::<F>::new();
|
||||
samp_circ.sample_slot_assign_witness(&mut pw,&inner_tar,&one_circ_input)?;
|
||||
let inner_data = sampling_builder.build::<C>();
|
||||
let inner_proof = inner_data.prove(pw.clone())?;
|
||||
|
||||
let proofs: Vec<ProofWithPublicInputs<F, C, D>> = (0..N).map(|i| inner_proof.clone()).collect();
|
||||
|
||||
// ------------------- tree --------------------
|
||||
|
||||
let mut tree : Option<TreeRecursion<F, D, C, HF>> = None;
|
||||
|
||||
// Building phase
|
||||
group.bench_function("build tree", |b| {
|
||||
b.iter(|| {
|
||||
tree = Some(TreeRecursion::<F,D,C,HF>::build(inner_data.common.clone()).unwrap());
|
||||
})
|
||||
});
|
||||
|
||||
let mut tree = tree.unwrap();
|
||||
|
||||
let mut proof: Option<ProofWithPublicInputs<F, C, D>> = None;
|
||||
|
||||
// Proving Phase
|
||||
group.bench_function("prove tree", |b| {
|
||||
b.iter(|| {
|
||||
proof = Some(tree.prove_tree(&proofs, &inner_data.verifier_only).unwrap());
|
||||
})
|
||||
});
|
||||
|
||||
let proof = proof.unwrap();
|
||||
|
||||
// Verifying Phase
|
||||
// group.bench_function("verify tree circuit", |b| {
|
||||
// b.iter(|| {
|
||||
// verifier_data.verify(proof.clone()).expect("verify fail");
|
||||
// })
|
||||
// });
|
||||
|
||||
group.finish();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn bench_uniform_tree_recursion(c: &mut Criterion){
|
||||
const N: usize = 2; // number of child nodes - binary here
|
||||
bench_uniform_recursion::<2>(c).expect("bench failed");
|
||||
bench_uniform_recursion::<4>(c).expect("bench failed");
|
||||
bench_uniform_recursion::<8>(c).expect("bench failed");
|
||||
bench_uniform_recursion::<16>(c).expect("bench failed");
|
||||
bench_uniform_recursion::<32>(c).expect("bench failed");
|
||||
bench_uniform_recursion::<64>(c).expect("bench failed");
|
||||
bench_uniform_recursion::<128>(c).expect("bench failed");
|
||||
bench_uniform_recursion::<256>(c).expect("bench failed");
|
||||
}
|
||||
|
||||
/// Criterion benchmark group
|
||||
criterion_group!{
|
||||
name = recursion;
|
||||
config = Criterion::default().sample_size(10);
|
||||
targets = bench_uniform_tree_recursion
|
||||
}
|
||||
criterion_main!(recursion);
|
||||
@ -1,45 +0,0 @@
|
||||
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;
|
||||
|
||||
/// InnerCircuit is the trait used to define the logic of the circuit and assign witnesses
|
||||
/// to that circuit instance.
|
||||
pub trait InnerCircuit<
|
||||
F: RichField + Extendable<D> + 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<F, D>,
|
||||
register_pi: bool
|
||||
) -> Result<Self::Targets>;
|
||||
|
||||
/// assign the actual witness values for the current instance of the circuit.
|
||||
fn assign_targets(
|
||||
&self,
|
||||
pw: &mut PartialWitness<F>,
|
||||
targets: &Self::Targets,
|
||||
input: &Self::Input,
|
||||
) -> 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,
|
||||
) -> Vec<Target>;
|
||||
|
||||
/// get the common data for the inner-circuit
|
||||
fn get_common_data(
|
||||
&self
|
||||
) -> Result<(CommonCircuitData<F, D>)>;
|
||||
}
|
||||
@ -1,155 +0,0 @@
|
||||
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, 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::circuits::inner_circuit::InnerCircuit;
|
||||
use crate::{error::CircuitError,Result};
|
||||
use crate::circuits::utils::vec_to_array;
|
||||
|
||||
/// recursion leaf circuit for the recursion tree circuit
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct LeafCircuit<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
I: InnerCircuit<F, D>,
|
||||
const M: usize,
|
||||
> {
|
||||
pub inner_circ: I,
|
||||
phantom_data: PhantomData<F>
|
||||
}
|
||||
|
||||
impl<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
I: InnerCircuit<F, D>,
|
||||
const M: usize,
|
||||
> LeafCircuit<F,D,I, M> {
|
||||
pub fn new(inner_circ: I) -> Self {
|
||||
Self{
|
||||
inner_circ,
|
||||
phantom_data:PhantomData::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct LeafTargets <
|
||||
const D: usize,
|
||||
const M: usize
|
||||
>{
|
||||
pub inner_proof: [ProofWithPublicInputsTarget<D>; M],
|
||||
pub verifier_data: VerifierCircuitTarget,
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct LeafInput<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
C: GenericConfig<D, F = F>,
|
||||
const M: usize,
|
||||
>{
|
||||
pub inner_proof: [ProofWithPublicInputs<F, C, D>; M],
|
||||
pub verifier_data: VerifierCircuitData<F, C, D>
|
||||
}
|
||||
|
||||
impl<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
I: InnerCircuit<F, D>,
|
||||
const M: usize,
|
||||
> LeafCircuit<F,D,I, M>{
|
||||
|
||||
/// build the leaf circuit
|
||||
pub fn build<
|
||||
C: GenericConfig<D, F = F>,
|
||||
H: AlgebraicHasher<F>,
|
||||
>(&self, builder: &mut CircuitBuilder<F, D>) -> Result<LeafTargets<D,M>>
|
||||
where
|
||||
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
|
||||
{
|
||||
|
||||
let common = self.inner_circ.get_common_data()?;
|
||||
|
||||
// the proof virtual targets - only one for now
|
||||
let mut vir_proofs = vec![];
|
||||
let mut pub_input = vec![];
|
||||
for _i in 0..M {
|
||||
let vir_proof = builder.add_virtual_proof_with_pis(&common);
|
||||
let inner_pub_input = vir_proof.public_inputs.clone();
|
||||
vir_proofs.push(vir_proof);
|
||||
pub_input.extend_from_slice(&inner_pub_input);
|
||||
}
|
||||
|
||||
// hash the public input & make it public
|
||||
let hash_inner_pub_input = builder.hash_n_to_hash_no_pad::<H>(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(common.config.fri_config.cap_height);
|
||||
|
||||
// verify the proofs in-circuit (only one now)
|
||||
for i in 0..M {
|
||||
builder.verify_proof::<C>(&vir_proofs[i], &inner_verifier_data, &common);
|
||||
}
|
||||
|
||||
let proofs = vec_to_array::<M, ProofWithPublicInputsTarget<D>>(vir_proofs)?;
|
||||
|
||||
// return targets
|
||||
let t = LeafTargets {
|
||||
inner_proof: proofs,
|
||||
verifier_data: inner_verifier_data,
|
||||
};
|
||||
Ok(t)
|
||||
|
||||
}
|
||||
|
||||
/// assign the leaf targets with given input
|
||||
pub fn assign_targets<
|
||||
C: GenericConfig<D, F = F>,
|
||||
H: AlgebraicHasher<F>,
|
||||
>(&self, pw: &mut PartialWitness<F>, targets: &LeafTargets<D,M>, input: &LeafInput<F, D, C, M>) -> Result<()>
|
||||
where
|
||||
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
|
||||
{
|
||||
// assign the proofs
|
||||
for i in 0..M {
|
||||
pw.set_proof_with_pis_target(&targets.inner_proof[i], &input.inner_proof[i])
|
||||
.map_err(|e| {
|
||||
CircuitError::ProofTargetAssignmentError("inner-proof".to_string(), e.to_string())
|
||||
})?;
|
||||
}
|
||||
|
||||
// assign the verifier data
|
||||
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<D, F = F>,
|
||||
H: AlgebraicHasher<F>,
|
||||
>(&self) -> Result<CircuitData<F, C, D>>
|
||||
where
|
||||
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
|
||||
{
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut builder = CircuitBuilder::<F, D>::new(config.clone());
|
||||
|
||||
self.build::<C,H>(&mut builder)?;
|
||||
|
||||
let circ_data = builder.build::<C>();
|
||||
|
||||
Ok(circ_data)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
pub mod inner_circuit;
|
||||
pub mod sampling_inner_circuit;
|
||||
pub mod leaf_circuit;
|
||||
@ -1,89 +0,0 @@
|
||||
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::recursion::circuits::inner_circuit::InnerCircuit;
|
||||
use crate::Result;
|
||||
|
||||
/// recursion Inner circuit for the sampling circuit
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SamplingRecursion<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
H: AlgebraicHasher<F>,
|
||||
C: GenericConfig<D, F = F>,
|
||||
> {
|
||||
pub sampling_circ: SampleCircuit<F,D,H>,
|
||||
phantom_data: PhantomData<C>,
|
||||
}
|
||||
|
||||
impl<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
H: AlgebraicHasher<F>,
|
||||
C: GenericConfig<D, F = F>,
|
||||
> SamplingRecursion<F, D, H, C> {
|
||||
pub fn new(circ_params:CircuitParams) -> Self {
|
||||
Self{
|
||||
sampling_circ: SampleCircuit::new(circ_params),
|
||||
phantom_data: PhantomData::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
H: AlgebraicHasher<F>,
|
||||
C: GenericConfig<D, F = F>,
|
||||
> InnerCircuit<F, D> for SamplingRecursion<F, D, H, C> {
|
||||
type Targets = SampleTargets;
|
||||
type Input = SampleCircuitInput<F, D>;
|
||||
|
||||
/// build the circuit
|
||||
fn build(&self, builder: &mut CircuitBuilder<F, D>, register_pi: bool) -> Result<Self::Targets> {
|
||||
if register_pi{
|
||||
self.sampling_circ.sample_slot_circuit_with_public_input(builder)
|
||||
}else {
|
||||
self.sampling_circ.sample_slot_circuit(builder)
|
||||
}
|
||||
}
|
||||
|
||||
fn assign_targets(&self, pw: &mut PartialWitness<F>, targets: &Self::Targets, input: &Self::Input) -> Result<()> {
|
||||
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) -> 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);
|
||||
|
||||
pub_targets
|
||||
}
|
||||
|
||||
/// 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<F, D>)> {
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut builder = CircuitBuilder::<F, D>::new(config);
|
||||
|
||||
// build the inner circuit
|
||||
self.sampling_circ.sample_slot_circuit_with_public_input(&mut builder)?;
|
||||
|
||||
let circ_data = builder.build::<C>();
|
||||
|
||||
Ok(circ_data.common)
|
||||
}
|
||||
}
|
||||
@ -1,265 +0,0 @@
|
||||
// Cyclic approach to recursion where at each cycle you verify previous proof
|
||||
// and run the inner circuit -> resulting in one proof that again can be fed
|
||||
// into another cyclic circle.
|
||||
|
||||
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};
|
||||
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::gates::noop::NoopGate;
|
||||
use plonky2_field::extension::Extendable;
|
||||
use crate::circuits::utils::select_hash;
|
||||
use crate::error::CircuitError;
|
||||
use crate::recursion::utils::conditional_verifier::{dummy_circuit};
|
||||
use crate::recursion::utils::dummy_gen::DummyProofGen;
|
||||
use crate::Result;
|
||||
|
||||
/// cyclic circuit struct
|
||||
/// contains necessary data
|
||||
/// note: only keeps track of latest proof not all proofs.
|
||||
pub struct CyclicCircuit<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
I: InnerCircuit<F, D>,
|
||||
C: GenericConfig<D, F = F>,
|
||||
>{
|
||||
pub layer: usize,
|
||||
pub circ: I,
|
||||
pub cyclic_target: CyclicCircuitTargets<F, D, I>,
|
||||
pub cyclic_circuit_data: CircuitData<F, C, D>,
|
||||
pub common_data: 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<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
I: InnerCircuit<F, D>,
|
||||
>{
|
||||
pub inner_targets: I::Targets,
|
||||
pub condition: BoolTarget,
|
||||
pub inner_cyclic_proof_with_pis: ProofWithPublicInputsTarget<D>,
|
||||
pub verifier_data: VerifierCircuitTarget,
|
||||
}
|
||||
|
||||
impl<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
I: InnerCircuit<F, D>,
|
||||
C: GenericConfig<D, F = F> + 'static,
|
||||
> CyclicCircuit<F, D, I, C> where
|
||||
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
|
||||
{
|
||||
|
||||
/// builds the cyclic recursion circuit using any inner circuit I
|
||||
/// return the circuit data
|
||||
pub fn build_circuit<
|
||||
H: AlgebraicHasher<F>,
|
||||
>(
|
||||
// &mut self,
|
||||
inner_circuit: I
|
||||
) -> Result<(Self)>{
|
||||
|
||||
// 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 = inner_circuit.build(& mut builder, false)?;
|
||||
|
||||
// common data for 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
|
||||
// TODO: make verifier data public
|
||||
// let verifier_data_target = builder.add_verifier_data_public_inputs();
|
||||
let verifier_data_target = builder.add_virtual_verifier_data(builder.config.fri_config.cap_height);
|
||||
// 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::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_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);
|
||||
|
||||
// verify proof in-circuit
|
||||
builder.verify_proof::<C>(&inner_cyclic_proof_with_pis, &verifier_data_target, &common_data);
|
||||
|
||||
// build the cyclic circuit
|
||||
let cyclic_circuit_data = builder.build::<C>();
|
||||
|
||||
// assign targets
|
||||
let cyc_t = CyclicCircuitTargets::<F,D,I>{
|
||||
inner_targets: inner_t,
|
||||
condition,
|
||||
inner_cyclic_proof_with_pis,
|
||||
verifier_data: verifier_data_target
|
||||
};
|
||||
|
||||
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
|
||||
/// takes circuit input
|
||||
pub fn prove_one_layer(
|
||||
&mut self,
|
||||
circ_input: &I::Input,
|
||||
) -> Result<ProofWithPublicInputs<F, C, D>>{
|
||||
|
||||
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)
|
||||
.map_err(|e|
|
||||
CircuitError::BoolTargetAssignmentError("condition".to_string(),e.to_string()),
|
||||
)?;
|
||||
pw.set_proof_with_pis_target::<C, D>(
|
||||
&cyc_targets.inner_cyclic_proof_with_pis,
|
||||
&DummyProofGen::<F, D, C>::get_dummy_node_proof(
|
||||
common_data,
|
||||
&circ_data.verifier_only,
|
||||
),
|
||||
).map_err(|e|
|
||||
CircuitError::ProofTargetAssignmentError("cyclic proof".to_string(),e.to_string()),
|
||||
)?;
|
||||
// assign verifier data
|
||||
let dummy_ver = dummy_circuit::<F, C, D>(common_data).verifier_only;
|
||||
pw.set_verifier_data_target(&cyc_targets.verifier_data, &dummy_ver)
|
||||
.map_err(|e| CircuitError::VerifierDataTargetAssignmentError(e.to_string()))?;
|
||||
}else{ // else add 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)
|
||||
.map_err(|e| CircuitError::VerifierDataTargetAssignmentError(e.to_string()))?;
|
||||
}
|
||||
|
||||
// prove
|
||||
let proof = circ_data.prove(pw).map_err(
|
||||
|e| CircuitError::InvalidProofError(e.to_string())
|
||||
)?;
|
||||
|
||||
self.latest_proof = Some(proof.clone());
|
||||
self.layer = self.layer + 1;
|
||||
Ok(proof)
|
||||
}
|
||||
|
||||
/// prove n recursive layers
|
||||
/// the function takes
|
||||
/// - circ_input: vector of n inputs
|
||||
pub fn prove_n_layers(
|
||||
&mut self,
|
||||
circ_input: Vec<I::Input>,
|
||||
) -> Result<ProofWithPublicInputs<F, C, D>>{
|
||||
|
||||
for i in 0..circ_input.len() {
|
||||
self.prove_one_layer(&circ_input[i])?;
|
||||
}
|
||||
|
||||
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<()>{
|
||||
|
||||
let proof = self.latest_proof
|
||||
.as_ref()
|
||||
.ok_or_else(|| CircuitError::OptionError("cyclic proof".to_string()))?
|
||||
.clone();
|
||||
|
||||
// TODO: check that the correct verifier data is consistent
|
||||
|
||||
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<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,2 +0,0 @@
|
||||
pub mod node_circuit;
|
||||
pub mod tree_circuit;
|
||||
@ -1,104 +0,0 @@
|
||||
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::{CommonCircuitData, VerifierCircuitData, VerifierCircuitTarget};
|
||||
use plonky2::plonk::config::{AlgebraicHasher, GenericConfig};
|
||||
use plonky2::plonk::proof::{ProofWithPublicInputs, ProofWithPublicInputsTarget};
|
||||
use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2;
|
||||
use plonky2_field::extension::Extendable;
|
||||
use crate::circuits::utils::{vec_to_array};
|
||||
use crate::{error::CircuitError, Result};
|
||||
|
||||
/// Node circuit struct
|
||||
/// contains necessary data
|
||||
/// N: number of proofs verified in-circuit (so num of child nodes)
|
||||
pub struct NodeCircuit<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
C: GenericConfig<D, F = F>,
|
||||
const N: usize,
|
||||
>{
|
||||
phantom_data: PhantomData<(F,C)>
|
||||
}
|
||||
|
||||
/// Node circuit targets
|
||||
/// assumes that all proofs use the same verifier data
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct NodeCircuitTargets<
|
||||
const D: usize,
|
||||
const N: usize,
|
||||
>{
|
||||
pub proof_targets: [ProofWithPublicInputsTarget<D>; N],
|
||||
pub verifier_data_target: VerifierCircuitTarget,
|
||||
}
|
||||
|
||||
impl<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
C: GenericConfig<D, F = F> + 'static,
|
||||
const N: usize,
|
||||
> NodeCircuit<F, D, C, N>
|
||||
where
|
||||
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
|
||||
{
|
||||
|
||||
/// builds the node circuit
|
||||
pub fn build_circuit<
|
||||
H: AlgebraicHasher<F>,
|
||||
>(
|
||||
builder: &mut CircuitBuilder<F, D>,
|
||||
common_data: &CommonCircuitData<F, D>,
|
||||
) -> Result<(NodeCircuitTargets<D, N>)>{
|
||||
|
||||
// the proof virtual targets
|
||||
let mut proof_targets = vec![];
|
||||
let mut inner_pub_input = vec![];
|
||||
for _i in 0..N {
|
||||
let vir_proof = builder.add_virtual_proof_with_pis(common_data);
|
||||
// collect the public input
|
||||
inner_pub_input.extend_from_slice(&vir_proof.public_inputs);
|
||||
// collect the proof targets
|
||||
proof_targets.push(vir_proof);
|
||||
}
|
||||
// hash the public input & make it public
|
||||
let hash_inner_pub_input = builder.hash_n_to_hash_no_pad::<H>(inner_pub_input);
|
||||
builder.register_public_inputs(&hash_inner_pub_input.elements);
|
||||
|
||||
// virtual target for the verifier data
|
||||
let inner_verifier_data = builder.add_virtual_verifier_data(common_data.config.fri_config.cap_height);
|
||||
|
||||
// verify the proofs in-circuit
|
||||
for i in 0..N {
|
||||
builder.verify_proof::<C>(&proof_targets[i],&inner_verifier_data,&common_data);
|
||||
}
|
||||
let proof_target_array = vec_to_array::<N,ProofWithPublicInputsTarget<D>>(proof_targets)?;
|
||||
|
||||
Ok(NodeCircuitTargets{
|
||||
proof_targets: proof_target_array,
|
||||
verifier_data_target: inner_verifier_data,
|
||||
})
|
||||
}
|
||||
|
||||
/// assigns the targets for the Node circuit
|
||||
pub fn assign_targets(
|
||||
node_targets: NodeCircuitTargets<D, N>,
|
||||
proofs_with_pi: &[ProofWithPublicInputs<F, C, D>; N],
|
||||
verifier_data: &VerifierCircuitData<F, C, D>,
|
||||
pw: &mut PartialWitness<F>,
|
||||
) -> Result<()>{
|
||||
for i in 0..N{
|
||||
pw.set_proof_with_pis_target(&node_targets.proof_targets[i],&proofs_with_pi[i])
|
||||
.map_err(|e| {
|
||||
CircuitError::ProofTargetAssignmentError(format!("proof {}", i), e.to_string())
|
||||
})?;
|
||||
}
|
||||
// assign the verifier data
|
||||
pw.set_verifier_data_target(&node_targets.verifier_data_target, &verifier_data.verifier_only)
|
||||
.map_err(|e| {
|
||||
CircuitError::VerifierDataTargetAssignmentError(e.to_string())
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -1,152 +0,0 @@
|
||||
use plonky2::hash::hash_types::RichField;
|
||||
use plonky2::iop::witness::PartialWitness;
|
||||
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
||||
use plonky2::plonk::circuit_data::{CircuitConfig, CircuitData, VerifierCircuitData};
|
||||
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_field::extension::Extendable;
|
||||
use crate::{error::CircuitError, Result};
|
||||
use crate::circuits::utils::vec_to_array;
|
||||
use crate::recursion::circuits::leaf_circuit::{LeafCircuit, LeafInput};
|
||||
use crate::recursion::hybrid::node_circuit::{NodeCircuit, NodeCircuitTargets};
|
||||
|
||||
/// Hybrid tree recursion - combines simple and tree recursion
|
||||
/// - N: number of leaf proofs to verify in the node circuit
|
||||
/// - M: number of inner proofs to verify in the leaf circuit
|
||||
pub struct HybridTreeRecursion<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
I: InnerCircuit<F, D>,
|
||||
const N: usize,
|
||||
const M: usize,
|
||||
> {
|
||||
pub leaf: LeafCircuit<F, D, I, M>,
|
||||
}
|
||||
|
||||
impl<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
I: InnerCircuit<F, D>,
|
||||
const N: usize,
|
||||
const M: usize,
|
||||
> HybridTreeRecursion<F, D, I, N, M>
|
||||
{
|
||||
|
||||
pub fn new(
|
||||
leaf: LeafCircuit<F, D, I, M>
|
||||
) -> Self {
|
||||
Self{
|
||||
leaf,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prove_tree<
|
||||
C: GenericConfig<D, F = F> + 'static,
|
||||
H: AlgebraicHasher<F>,
|
||||
>(
|
||||
&mut self,
|
||||
proofs_with_pi: &[ProofWithPublicInputs<F, C, D>],
|
||||
inner_verifier_data: VerifierCircuitData<F, C, D>,
|
||||
) -> Result<(ProofWithPublicInputs<F, C, D>, VerifierCircuitData<F, C, D>)> where
|
||||
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
|
||||
{
|
||||
// process leaves
|
||||
let (leaf_proofs, leaf_data) = self.get_leaf_proofs::<C,H>(
|
||||
proofs_with_pi,
|
||||
inner_verifier_data,
|
||||
)?;
|
||||
|
||||
// process nodes
|
||||
let (root_proof, last_verifier_data) =
|
||||
self.prove::<C,H>(&leaf_proofs,leaf_data.verifier_data(), None, None, 0)?;
|
||||
|
||||
Ok((root_proof, last_verifier_data))
|
||||
}
|
||||
|
||||
|
||||
fn get_leaf_proofs<
|
||||
C: GenericConfig<D, F = F> + 'static,
|
||||
H: AlgebraicHasher<F>,
|
||||
>(
|
||||
&mut self,
|
||||
proofs_with_pi: &[ProofWithPublicInputs<F, C, D>],
|
||||
inner_verifier_data: VerifierCircuitData<F, C, D>,
|
||||
) -> Result<(Vec<ProofWithPublicInputs<F, C, D>>, CircuitData<F, C, D>)> where
|
||||
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>{
|
||||
// builder with standard recursion config
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut builder = CircuitBuilder::<F, D>::new(config);
|
||||
|
||||
let leaf_targets = self.leaf.build::<C,H>(&mut builder)?;
|
||||
let leaf_data = builder.build::<C>();
|
||||
println!("leaf circuit size = {:?}", leaf_data.common.degree_bits());
|
||||
|
||||
let mut leaf_proofs = vec![];
|
||||
|
||||
for chunk in proofs_with_pi.chunks(M){
|
||||
let mut pw = PartialWitness::<F>::new();
|
||||
let chunk_arr = vec_to_array::<M,ProofWithPublicInputs<F, C, D>>(chunk.to_vec())?;
|
||||
let leaf_in = LeafInput{
|
||||
inner_proof: chunk_arr,
|
||||
verifier_data: inner_verifier_data.clone(),
|
||||
};
|
||||
self.leaf.assign_targets::<C,H>(&mut pw,&leaf_targets,&leaf_in)?;
|
||||
let proof = leaf_data.prove(pw).unwrap();
|
||||
leaf_proofs.push(proof);
|
||||
}
|
||||
|
||||
Ok((leaf_proofs, leaf_data))
|
||||
}
|
||||
|
||||
/// generates a proof - only one node
|
||||
/// takes N proofs
|
||||
fn prove<
|
||||
C: GenericConfig<D, F = F> + 'static,
|
||||
H: AlgebraicHasher<F>,
|
||||
>(
|
||||
&mut self,
|
||||
proofs_with_pi: &[ProofWithPublicInputs<F, C, D>],
|
||||
verifier_data: VerifierCircuitData<F, C, D>,
|
||||
node_target_options: Option<NodeCircuitTargets<D, N>>,
|
||||
node_data_option: Option<CircuitData<F, C, D>>,
|
||||
layer: usize,
|
||||
) -> Result<(ProofWithPublicInputs<F, C, D>, VerifierCircuitData<F, C, D>)> where
|
||||
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
|
||||
{
|
||||
|
||||
if proofs_with_pi.len() == 1 {
|
||||
return Ok((proofs_with_pi[0].clone(), verifier_data));
|
||||
}
|
||||
|
||||
let mut new_proofs = vec![];
|
||||
|
||||
let (node_data, node_targets) = if layer<2 {
|
||||
let node_config = CircuitConfig::standard_recursion_config();
|
||||
let mut node_builder = CircuitBuilder::<F, D>::new(node_config);
|
||||
let targets = NodeCircuit::<F, D, C, N>::build_circuit::<H>(&mut node_builder, &verifier_data.common)?;
|
||||
let data = node_builder.build::<C>();
|
||||
(data, targets)
|
||||
}else{
|
||||
(node_data_option.unwrap(), node_target_options.unwrap())
|
||||
};
|
||||
|
||||
for chunk in proofs_with_pi.chunks(N) {
|
||||
|
||||
let chunk_arr = vec_to_array::<N,ProofWithPublicInputs<F, C, D>>(chunk.to_vec())?;
|
||||
|
||||
let mut inner_pw = PartialWitness::new();
|
||||
|
||||
NodeCircuit::<F,D,C,N>::assign_targets(node_targets.clone(),&chunk_arr,&verifier_data, &mut inner_pw)?;
|
||||
|
||||
let proof = node_data.prove(inner_pw)
|
||||
.map_err(|e| CircuitError::ProofGenerationError(e.to_string()))?;
|
||||
new_proofs.push(proof);
|
||||
}
|
||||
|
||||
self.prove::<C,H>(&new_proofs, node_data.verifier_data(), Some(node_targets),Some(node_data), layer+1)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
pub mod cyclic;
|
||||
pub mod circuits;
|
||||
pub mod simple;
|
||||
pub mod tree1;
|
||||
pub mod tree2;
|
||||
pub mod hybrid;
|
||||
pub mod utils;
|
||||
pub mod uniform;
|
||||
@ -1,3 +0,0 @@
|
||||
pub mod simple_recursion;
|
||||
pub mod simple_recursion_hashed_pi;
|
||||
pub mod simple_tree_recursion;
|
||||
@ -1,156 +0,0 @@
|
||||
// the simple aggregation approach is verifying N proofs in-circuit and generating one final proof
|
||||
|
||||
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::{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 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<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
I: InnerCircuit<F, D>,
|
||||
const N: usize,
|
||||
C: GenericConfig<D, F = F>,
|
||||
> {
|
||||
pub inner_circuit: I,
|
||||
phantom_data: PhantomData<(F,C)>
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct 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,
|
||||
const D: usize,
|
||||
C: GenericConfig<D, F = F>,
|
||||
>{
|
||||
pub proofs: Vec<ProofWithPublicInputs<F, C, D>>,
|
||||
pub verifier_data: VerifierCircuitData<F, C, D>,
|
||||
pub entropy: HashOut<F>,
|
||||
}
|
||||
|
||||
impl<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
I: InnerCircuit<F, D>,
|
||||
const N: usize,
|
||||
C: GenericConfig<D, F = F>,
|
||||
> SimpleRecursionCircuit<F, D, I, N, C> where
|
||||
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>,
|
||||
{
|
||||
|
||||
pub fn new(
|
||||
inner_circuit: I,
|
||||
)->Self{
|
||||
Self{
|
||||
inner_circuit,
|
||||
phantom_data: PhantomData::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// contains the circuit logic and returns the witness & public input targets
|
||||
pub fn build_circuit<
|
||||
>(
|
||||
&self,
|
||||
builder: &mut CircuitBuilder<F, D>,
|
||||
) -> Result<SimpleRecursionTargets<D>>{
|
||||
// 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 _ 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);
|
||||
|
||||
// verify the proofs in-circuit
|
||||
for i in 0..N {
|
||||
builder.verify_proof::<C>(&proof_targets[i],&inner_verifier_data,&inner_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..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<D>,
|
||||
witnesses: SimpleRecursionInput<F, D, C>,
|
||||
) -> Result<()>{
|
||||
// assign the proofs with public input
|
||||
for i in 0..N{
|
||||
pw.set_proof_with_pis_target(&targets.proofs_with_pi[i],&witnesses.proofs[i])
|
||||
.map_err(|e| {
|
||||
CircuitError::ProofTargetAssignmentError(format!("proof {}", i), e.to_string())
|
||||
})?;
|
||||
}
|
||||
|
||||
// assign the verifier data
|
||||
pw.set_verifier_data_target(&targets.verifier_data, &witnesses.verifier_data.verifier_only)
|
||||
.map_err(|e| {
|
||||
CircuitError::VerifierDataTargetAssignmentError(e.to_string())
|
||||
})?;
|
||||
|
||||
// set the entropy hash target
|
||||
pw.set_hash_target(targets.entropy, witnesses.entropy)
|
||||
.map_err(|e| {
|
||||
CircuitError::HashTargetAssignmentError("entropy".to_string(), e.to_string())
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,132 +0,0 @@
|
||||
// the simple aggregation approach is verifying N proofs in-circuit and generating one final proof
|
||||
|
||||
use std::marker::PhantomData;
|
||||
use plonky2::hash::hash_types::RichField;
|
||||
use plonky2::iop::witness::{PartialWitness, WitnessWrite};
|
||||
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
||||
use plonky2::plonk::circuit_data::{VerifierCircuitData, VerifierCircuitTarget};
|
||||
use plonky2::plonk::config::{AlgebraicHasher, GenericConfig};
|
||||
use plonky2::plonk::proof::{ProofWithPublicInputs, ProofWithPublicInputsTarget};
|
||||
use plonky2_field::extension::Extendable;
|
||||
use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2;
|
||||
use crate::error::CircuitError;
|
||||
use crate::recursion::circuits::inner_circuit::InnerCircuit;
|
||||
use crate::Result;
|
||||
|
||||
// ---------------------- Simple recursion Approach 2 ---------------------------
|
||||
// The simple approach here separates the build (setting the targets) and assigning the witness.
|
||||
// ** the Hash of public input of the inner-proofs is the public input of the final proof **
|
||||
|
||||
pub struct SimpleRecursionCircuitHashedPI<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
I: InnerCircuit<F, D>,
|
||||
const N: usize,
|
||||
C: GenericConfig<D, F = F>,
|
||||
> {
|
||||
pub inner_circuit: I,
|
||||
phantom_data: PhantomData<(F,C)>
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SimpleRecursionTargetsHashedPI<
|
||||
const D: usize,
|
||||
> {
|
||||
pub proofs_with_pi: Vec<ProofWithPublicInputsTarget<D>>,
|
||||
pub verifier_data: VerifierCircuitTarget,
|
||||
}
|
||||
|
||||
pub struct SimpleRecursionInputHashedPI<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
C: GenericConfig<D, F = F>,
|
||||
>{
|
||||
pub proofs: Vec<ProofWithPublicInputs<F, C, D>>,
|
||||
pub verifier_data: VerifierCircuitData<F, C, D>,
|
||||
}
|
||||
|
||||
impl<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
I: InnerCircuit<F, D>,
|
||||
const N: usize,
|
||||
C: GenericConfig<D, F = F>,
|
||||
> SimpleRecursionCircuitHashedPI<F, D, I, N, C> where
|
||||
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>,
|
||||
{
|
||||
|
||||
pub fn new(
|
||||
inner_circuit: I,
|
||||
)->Self{
|
||||
Self{
|
||||
inner_circuit,
|
||||
phantom_data: PhantomData::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// contains the circuit logic and returns the witness & public input targets
|
||||
pub fn build_circuit<
|
||||
H: AlgebraicHasher<F>,
|
||||
>(
|
||||
&self,
|
||||
builder: &mut CircuitBuilder<F, D>,
|
||||
) -> Result<SimpleRecursionTargetsHashedPI<D>>{
|
||||
// the proof virtual targets
|
||||
let mut proof_targets = vec![];
|
||||
let mut inner_pub_input = vec![];
|
||||
let inner_common = self.inner_circuit.get_common_data()?;
|
||||
|
||||
for _i in 0..N {
|
||||
let vir_proof = builder.add_virtual_proof_with_pis(&inner_common);
|
||||
// collect the public input
|
||||
inner_pub_input.extend_from_slice(&vir_proof.public_inputs);
|
||||
// collect the proof targets
|
||||
proof_targets.push(vir_proof);
|
||||
}
|
||||
|
||||
// hash the public input & make it public
|
||||
let hash_inner_pub_input = builder.hash_n_to_hash_no_pad::<H>(inner_pub_input);
|
||||
builder.register_public_inputs(&hash_inner_pub_input.elements);
|
||||
|
||||
// virtual target for the verifier data
|
||||
let inner_verifier_data = builder.add_virtual_verifier_data(inner_common.config.fri_config.cap_height);
|
||||
|
||||
// verify the proofs in-circuit
|
||||
for i in 0..N {
|
||||
builder.verify_proof::<C>(&proof_targets[i],&inner_verifier_data,&inner_common);
|
||||
}
|
||||
|
||||
// return targets
|
||||
let srt = SimpleRecursionTargetsHashedPI {
|
||||
proofs_with_pi: proof_targets,
|
||||
verifier_data: inner_verifier_data,
|
||||
};
|
||||
Ok(srt)
|
||||
}
|
||||
|
||||
/// assign the targets
|
||||
pub fn assign_witness<
|
||||
>(
|
||||
&self,
|
||||
pw: &mut PartialWitness<F>,
|
||||
targets: &SimpleRecursionTargetsHashedPI<D>,
|
||||
witnesses: SimpleRecursionInputHashedPI<F, D, C>,
|
||||
) -> Result<()>{
|
||||
// assign the proofs with public input
|
||||
for i in 0..N{
|
||||
pw.set_proof_with_pis_target(&targets.proofs_with_pi[i],&witnesses.proofs[i])
|
||||
.map_err(|e| {
|
||||
CircuitError::ProofTargetAssignmentError(format!("proof {}", i), e.to_string())
|
||||
})?;
|
||||
}
|
||||
|
||||
// assign the verifier data
|
||||
pw.set_verifier_data_target(&targets.verifier_data, &witnesses.verifier_data.verifier_only)
|
||||
.map_err(|e| {
|
||||
CircuitError::VerifierDataTargetAssignmentError(e.to_string())
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,119 +0,0 @@
|
||||
use plonky2::hash::hash_types::RichField;
|
||||
use plonky2::plonk::proof::ProofWithPublicInputs;
|
||||
use plonky2::plonk::circuit_data::{CircuitConfig, VerifierCircuitData};
|
||||
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
||||
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<D> + Poseidon2,
|
||||
const D: usize,
|
||||
C: GenericConfig<D, F = F>,
|
||||
H: AlgebraicHasher<F>
|
||||
>(
|
||||
proofs_with_pi: &Vec<ProofWithPublicInputs<F, C, D>>,
|
||||
verifier_data: &VerifierCircuitData<F, C, D>,
|
||||
builder: &mut CircuitBuilder<F, D>,
|
||||
pw: &mut PartialWitness<F>,
|
||||
)-> Result<()>where
|
||||
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
|
||||
{
|
||||
// the proof virtual targets
|
||||
let mut proof_targets = vec![];
|
||||
let mut inner_pub_input = vec![];
|
||||
for _i in 0..proofs_with_pi.len() {
|
||||
let vir_proof = builder.add_virtual_proof_with_pis(&verifier_data.common);
|
||||
// collect the public input
|
||||
inner_pub_input.extend_from_slice(&vir_proof.public_inputs);
|
||||
// collect the proof targets
|
||||
proof_targets.push(vir_proof);
|
||||
}
|
||||
// hash the public input & make it public
|
||||
let hash_inner_pub_input = builder.hash_n_to_hash_no_pad::<H>(inner_pub_input);
|
||||
builder.register_public_inputs(&hash_inner_pub_input.elements);
|
||||
// assign the proofs with public input
|
||||
for i in 0..proofs_with_pi.len(){
|
||||
pw.set_proof_with_pis_target(&proof_targets[i],&proofs_with_pi[i])
|
||||
.map_err(|e| {
|
||||
CircuitError::ProofTargetAssignmentError(format!("proof {}", i), e.to_string())
|
||||
})?;
|
||||
}
|
||||
// virtual target for the verifier data
|
||||
let inner_verifier_data = builder.add_virtual_verifier_data(verifier_data.common.config.fri_config.cap_height);
|
||||
|
||||
// assign the verifier data
|
||||
pw.set_verifier_data_target(&inner_verifier_data, &verifier_data.verifier_only)
|
||||
.map_err(|e| {
|
||||
CircuitError::VerifierDataTargetAssignmentError(e.to_string())
|
||||
})?;
|
||||
|
||||
// verify the proofs in-circuit
|
||||
for i in 0..proofs_with_pi.len() {
|
||||
builder.verify_proof::<C>(&proof_targets[i],&inner_verifier_data,&verifier_data.common);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// aggregate sampling proofs in tree like structure
|
||||
/// uses the const params: `RECURSION_TREE_WIDTH`
|
||||
/// In this tree approach the building is done at each level -> very slow!
|
||||
/// takes `VerifierCircuitData`
|
||||
pub fn aggregate_sampling_proofs_tree
|
||||
<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
C: GenericConfig<D, F = F>,
|
||||
H: AlgebraicHasher<F>
|
||||
>(
|
||||
proofs_with_pi: &[ProofWithPublicInputs<F, C, D>],
|
||||
vd: VerifierCircuitData<F, C, D>
|
||||
) -> Result<(ProofWithPublicInputs<F, C, D>, VerifierCircuitData<F, C, D>)> where
|
||||
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
|
||||
{
|
||||
if proofs_with_pi.len() == 1 {
|
||||
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();
|
||||
|
||||
aggregate_sampling_proofs::<F,D,C,H>(
|
||||
&proofs_chunk,
|
||||
&vd,
|
||||
&mut inner_builder,
|
||||
&mut inner_pw,
|
||||
)?;
|
||||
|
||||
let inner_data = inner_builder.build::<C>();
|
||||
|
||||
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_tree::<F,D,C,H>(&new_proofs, new_circuit_data.unwrap())
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
pub mod tree_circuit;
|
||||
pub mod node_circuit;
|
||||
@ -1,259 +0,0 @@
|
||||
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::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;
|
||||
use crate::recursion::utils::conditional_verifier::dummy_circuit;
|
||||
use crate::recursion::utils::dummy_gen::DummyProofGen;
|
||||
|
||||
/// Node circuit struct
|
||||
/// contains necessary data
|
||||
/// M: number of inner-circuits to run
|
||||
/// N: number of proofs verified in-circuit (so num of child nodes)
|
||||
pub struct NodeCircuit<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
I: InnerCircuit<F, D>,
|
||||
const M: usize,
|
||||
const N: usize,
|
||||
C: GenericConfig<D, F = F>,
|
||||
>{
|
||||
pub circ: I,
|
||||
pub cyclic_target: NodeCircuitTargets<F, D, I,M,N>,
|
||||
pub cyclic_circuit_data: CircuitData<F, C, D>,
|
||||
pub common_data: CommonCircuitData<F, D>,
|
||||
}
|
||||
|
||||
/// Node circuit targets
|
||||
/// assumes that all inner proofs use the same verifier data
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct NodeCircuitTargets<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
I: InnerCircuit<F, D>,
|
||||
const M: usize,
|
||||
const N: usize,
|
||||
>{
|
||||
pub inner_targets: [I::Targets; M],
|
||||
pub condition: BoolTarget,
|
||||
pub inner_proofs_with_pis: [ProofWithPublicInputsTarget<D>; N],
|
||||
pub verifier_data: VerifierCircuitTarget,
|
||||
}
|
||||
|
||||
impl<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
I: InnerCircuit<F, D>,
|
||||
const M: usize,
|
||||
const N: usize,
|
||||
C: GenericConfig<D, F = F> + 'static,
|
||||
> NodeCircuit<F, D, I, M, N, C> where
|
||||
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
|
||||
{
|
||||
|
||||
/// builds the cyclic recursion circuit using any inner circuit I
|
||||
/// return the Node circuit
|
||||
/// TODO: make generic recursion config
|
||||
pub fn build_circuit<
|
||||
H: AlgebraicHasher<F>,
|
||||
>(
|
||||
inner_circ: I,
|
||||
) -> Result<(Self)>{
|
||||
|
||||
// builder with standard recursion config
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut builder = CircuitBuilder::<F, D>::new(config);
|
||||
|
||||
//build M inner circuits
|
||||
let inner_t: [I::Targets; M] =
|
||||
vec_to_array::<M, I::Targets>(
|
||||
(0..M)
|
||||
.map(|_| inner_circ.build(&mut builder, false))
|
||||
.collect::<Result<Vec<_>>>()?
|
||||
)?;
|
||||
|
||||
// common data for recursion
|
||||
let mut common_data = Self::common_data_for_node()?;
|
||||
|
||||
let pub_input_hash = builder.add_virtual_hash_public_input();
|
||||
// TODO: make verifier data public
|
||||
// let verifier_data_target = builder.add_verifier_data_public_inputs();
|
||||
let verifier_data_target = builder.add_virtual_verifier_data(builder.config.fri_config.cap_height);
|
||||
common_data.num_public_inputs = builder.num_public_inputs();
|
||||
|
||||
// condition
|
||||
let condition = builder.add_virtual_bool_target_safe();
|
||||
|
||||
// inner proofs targets - N proof targets
|
||||
let inner_cyclic_proof_with_pis: [ProofWithPublicInputsTarget<D>; N] =
|
||||
vec_to_array::<N,ProofWithPublicInputsTarget<D>>(
|
||||
(0..N)
|
||||
.map(|_| builder.add_virtual_proof_with_pis(&common_data))
|
||||
.collect::<Vec<_>>()
|
||||
)?;
|
||||
|
||||
// get the public input hash from all inner proof targets
|
||||
let mut inner_pub_input_hashes = vec![];
|
||||
for i in 0..N {
|
||||
let inner_cyclic_pis = &inner_cyclic_proof_with_pis[i].public_inputs;
|
||||
inner_pub_input_hashes.extend_from_slice(&inner_cyclic_pis[0..4]);
|
||||
}
|
||||
// hash all the inner public input h = H(h_1 | h_2 | ... | h_N)
|
||||
let inner_pub_input_hash = builder.hash_n_to_hash_no_pad::<H>(inner_pub_input_hashes);
|
||||
|
||||
// get the public input of the inner circuit
|
||||
let mut outer_pis = vec![];
|
||||
for i in 0..M {
|
||||
outer_pis.push( I::get_pub_input_targets(&inner_t[i]));
|
||||
}
|
||||
// hash all the public input -> generate one HashOut at the end
|
||||
let mut outer_pi_hashes = vec![];
|
||||
for i in 0..M {
|
||||
let hash_res = builder.hash_n_to_hash_no_pad::<H>(outer_pis[i].clone());
|
||||
outer_pi_hashes.extend_from_slice(&hash_res.elements)
|
||||
}
|
||||
// the final public input hash
|
||||
let outer_pi_hash = builder.hash_n_to_hash_no_pad::<H>(outer_pi_hashes);
|
||||
// zero hash for leaves
|
||||
let zero_hash = HashOutTarget::from_vec([builder.zero(); 4].to_vec());
|
||||
// if the inner proofs are dummy then use zero hash for public input
|
||||
let inner_pi_hash_or_zero_hash = select_hash(&mut builder, condition, inner_pub_input_hash, zero_hash);
|
||||
|
||||
// now hash the public input of the inner proofs and outer proof, so we have one public hash
|
||||
let mut hash_input = vec![];
|
||||
hash_input.extend_from_slice(&outer_pi_hash.elements);
|
||||
hash_input.extend_from_slice(&inner_pi_hash_or_zero_hash.elements);
|
||||
let outer_pi_hash = builder.hash_n_to_hash_no_pad::<H>(hash_input);
|
||||
// connect this up one to `pub_input_hash`
|
||||
builder.connect_hashes(pub_input_hash,outer_pi_hash);
|
||||
|
||||
// verify all N proofs in-circuit
|
||||
for i in 0..N {
|
||||
builder.verify_proof::<C>(&inner_cyclic_proof_with_pis[i], &verifier_data_target, &common_data);
|
||||
|
||||
}
|
||||
|
||||
// build the cyclic circuit
|
||||
let cyclic_circuit_data = builder.build::<C>();
|
||||
|
||||
// assign targets
|
||||
let cyc_t = NodeCircuitTargets::<F, D, I, M, N>{
|
||||
inner_targets: inner_t,
|
||||
condition,
|
||||
inner_proofs_with_pis: inner_cyclic_proof_with_pis,
|
||||
verifier_data: verifier_data_target
|
||||
};
|
||||
|
||||
// assign the data
|
||||
Ok(Self{
|
||||
circ: inner_circ,
|
||||
cyclic_target: cyc_t,
|
||||
cyclic_circuit_data,
|
||||
common_data,
|
||||
})
|
||||
}
|
||||
|
||||
/// assigns the targets for the Node circuit
|
||||
/// takes circuit input
|
||||
pub fn assign_targets(
|
||||
&mut self,
|
||||
circ_input: &[I::Input; M],
|
||||
proof_options: Option<[ProofWithPublicInputs<F, C, D>; N]>,
|
||||
pw: &mut PartialWitness<F>,
|
||||
is_leaf: bool,
|
||||
) -> Result<()>{
|
||||
|
||||
let circ_data = &self.cyclic_circuit_data;
|
||||
let cyc_targets = &self.cyclic_target;
|
||||
let common_data = &self.common_data;
|
||||
|
||||
for i in 0..M {
|
||||
self.circ.assign_targets(pw, &cyc_targets.inner_targets[i], &circ_input[i])?;
|
||||
}
|
||||
|
||||
if is_leaf == true {
|
||||
pw.set_bool_target(cyc_targets.condition, false)
|
||||
.map_err(|e| CircuitError::BoolTargetAssignmentError("condition".to_string(),e.to_string()))?;
|
||||
for i in 0..N {
|
||||
pw.set_proof_with_pis_target::<C, D>(
|
||||
&cyc_targets.inner_proofs_with_pis[i],
|
||||
&DummyProofGen::<F, D, C>::get_dummy_node_proof(
|
||||
common_data,
|
||||
&circ_data.verifier_only,
|
||||
),
|
||||
).map_err(|e| CircuitError::ProofTargetAssignmentError("inner proofs".to_string(),e.to_string()))?;
|
||||
}
|
||||
// assign verifier data
|
||||
let dummy_ver = dummy_circuit::<F, C, D>(common_data).verifier_only;
|
||||
pw.set_verifier_data_target(&cyc_targets.verifier_data, &dummy_ver)
|
||||
.map_err(|e| CircuitError::VerifierDataTargetAssignmentError(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()))?;
|
||||
}
|
||||
// assign verifier data
|
||||
pw.set_verifier_data_target(&cyc_targets.verifier_data, &circ_data.verifier_only)
|
||||
.map_err(|e| CircuitError::VerifierDataTargetAssignmentError(e.to_string()))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Generates `CommonCircuitData` usable for node recursion.
|
||||
/// the circuit being built here depends on M and N so must be re-generated
|
||||
/// if the params change
|
||||
pub fn common_data_for_node() -> Result<CommonCircuitData<F, D>>
|
||||
{
|
||||
// layer 1
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let builder = CircuitBuilder::<F, D>::new(config);
|
||||
let data = builder.build::<C>();
|
||||
|
||||
// layer 2
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut builder = CircuitBuilder::<F, D>::new(config.clone());
|
||||
let verifier_data = builder.add_virtual_verifier_data(data.common.config.fri_config.cap_height);
|
||||
// generate and verify N number of proofs
|
||||
for _ in 0..N {
|
||||
let proof = builder.add_virtual_proof_with_pis(&data.common);
|
||||
builder.verify_proof::<C>(&proof, &verifier_data, &data.common);
|
||||
}
|
||||
let data = builder.build::<C>();
|
||||
|
||||
// layer 3
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut builder = CircuitBuilder::<F, D>::new(config.clone());
|
||||
|
||||
// add a ConstantGate
|
||||
builder.add_gate(
|
||||
plonky2::gates::constant::ConstantGate::new(config.num_constants),
|
||||
vec![],
|
||||
);
|
||||
|
||||
// generate and verify N number of proofs
|
||||
let verifier_data = builder.add_verifier_data_public_inputs();
|
||||
for _ in 0..N {
|
||||
let proof = builder.add_virtual_proof_with_pis(&data.common);
|
||||
builder.verify_proof::<C>(&proof, &verifier_data, &data.common);
|
||||
}
|
||||
// pad - padding depends on the inner circuit size
|
||||
while builder.num_gates() < 1 << 13 {
|
||||
builder.add_gate(NoopGate, vec![]);
|
||||
}
|
||||
Ok(builder.build::<C>().common)
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,154 +0,0 @@
|
||||
use std::array::from_fn;
|
||||
use plonky2::hash::hash_types::RichField;
|
||||
use plonky2::iop::witness::{PartialWitness};
|
||||
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 crate::{error::CircuitError, Result};
|
||||
use crate::recursion::tree1::node_circuit::NodeCircuit;
|
||||
|
||||
/// the tree recursion struct simplifies the process
|
||||
/// of building, proving and verifying
|
||||
/// the two consts are:
|
||||
/// - M: number of inner circuits to run
|
||||
/// - N: number of inner proofs to verify
|
||||
pub struct TreeRecursion<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
I: InnerCircuit<F, D>,
|
||||
const M: usize,
|
||||
const N: usize,
|
||||
C: GenericConfig<D, F = F>,
|
||||
>{
|
||||
pub node_circ: NodeCircuit<F,D, I, M, N, C>
|
||||
}
|
||||
|
||||
impl<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
I: InnerCircuit<F, D>,
|
||||
const M: usize,
|
||||
const N: usize,
|
||||
C: GenericConfig<D, F = F> + 'static,
|
||||
> TreeRecursion<F, D, I, M, N, C> where
|
||||
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
|
||||
{
|
||||
|
||||
pub fn build<
|
||||
H: AlgebraicHasher<F>,
|
||||
>(
|
||||
inner_circuit: I,
|
||||
) -> Result<(Self)>{
|
||||
Ok(Self {
|
||||
node_circ: NodeCircuit:: < F,
|
||||
D,
|
||||
I,
|
||||
M,
|
||||
N,
|
||||
C>::build_circuit:: < H>(inner_circuit)?
|
||||
})
|
||||
}
|
||||
|
||||
/// generates a proof - only one node
|
||||
/// takes M circuit input and N proofs
|
||||
pub fn prove(
|
||||
&mut self,
|
||||
circ_input: &[I::Input; M],
|
||||
proofs_option: Option<[ProofWithPublicInputs<F, C, D>; N]>,
|
||||
is_leaf: bool,
|
||||
) -> Result<ProofWithPublicInputs<F, C, D>>{
|
||||
|
||||
let mut pw = PartialWitness::new();
|
||||
self.node_circ.assign_targets(
|
||||
circ_input,
|
||||
proofs_option,
|
||||
&mut pw,
|
||||
is_leaf,
|
||||
)?;
|
||||
|
||||
let circ_data = &self.node_circ.cyclic_circuit_data;
|
||||
|
||||
let proof = circ_data.prove(pw)
|
||||
.map_err(|e| CircuitError::InvalidProofError(e.to_string()))?;
|
||||
|
||||
Ok(proof)
|
||||
}
|
||||
|
||||
/// prove n in a tree structure recursively
|
||||
/// the function takes
|
||||
/// - circ_input: vector of circuit inputs
|
||||
pub fn prove_tree(
|
||||
&mut self,
|
||||
circ_input: Vec<I::Input>,
|
||||
depth: usize,
|
||||
) -> Result<ProofWithPublicInputs<F, C, D>>{
|
||||
// Total input size check
|
||||
let total_input = (N.pow(depth as u32) - 1) / (N - 1);
|
||||
|
||||
if circ_input.len() != total_input{
|
||||
return Err(CircuitError::RecursionTreeError(
|
||||
"Invalid input size for tree depth".to_string()
|
||||
));
|
||||
}
|
||||
|
||||
let mut cur_proofs: Vec<ProofWithPublicInputs<F, C, D>> = vec![];
|
||||
|
||||
// Iterate from leaf layer to root
|
||||
for layer in (0..depth).rev() {
|
||||
let layer_num_nodes = N.pow(layer as u32); // Number of nodes at this layer
|
||||
let mut next_proofs = Vec::new();
|
||||
|
||||
for node_idx in 0..layer_num_nodes {
|
||||
// Get the inputs for the current node
|
||||
let node_inputs: [I::Input; M] = from_fn(|i| {
|
||||
circ_input
|
||||
.get(node_idx * M + i)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| panic!("Index out of bounds at node {node_idx}, input {i}"))
|
||||
});
|
||||
|
||||
let proof = if layer == depth - 1 {
|
||||
// Leaf layer: no child proofs
|
||||
self.prove(&node_inputs, None, true)?
|
||||
} else {
|
||||
// Non-leaf layer: collect child proofs
|
||||
let proofs_array: [ProofWithPublicInputs<F, C, D>; N] = cur_proofs
|
||||
.drain(..N)
|
||||
.collect::<Vec<_>>()
|
||||
.try_into()
|
||||
.map_err(|_| CircuitError::ArrayLengthMismatchError("Incorrect number of proofs for node".to_string()))?;
|
||||
self.prove(&node_inputs, Some(proofs_array), false)?
|
||||
};
|
||||
next_proofs.push(proof);
|
||||
}
|
||||
cur_proofs = next_proofs;
|
||||
}
|
||||
|
||||
// Check that exactly one proof remains
|
||||
if cur_proofs.len() != 1 {
|
||||
return Err(CircuitError::RecursionTreeError(
|
||||
format!("Expected exactly 1 final proof, found {}",
|
||||
cur_proofs.len())
|
||||
));
|
||||
}
|
||||
|
||||
Ok(cur_proofs.remove(0))
|
||||
}
|
||||
|
||||
/// verifies the proof generated
|
||||
pub fn verify_proof(
|
||||
&self,
|
||||
proof: ProofWithPublicInputs<F, C, D>
|
||||
) -> Result<()>{
|
||||
|
||||
let circ_data = &self.node_circ.cyclic_circuit_data;
|
||||
|
||||
// TODO: check verifier_data
|
||||
|
||||
circ_data.verify(proof).map_err(|e|CircuitError::InvalidProofError(e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
pub mod node_circuit;
|
||||
pub mod tree_circuit;
|
||||
@ -1,289 +0,0 @@
|
||||
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_vec, vec_to_array};
|
||||
use crate::{error::CircuitError, Result};
|
||||
use crate::recursion::circuits::leaf_circuit::LeafCircuit;
|
||||
|
||||
/// Node circuit struct
|
||||
/// contains necessary data
|
||||
/// N: number of proofs verified in-circuit (so num of child nodes)
|
||||
pub struct NodeCircuit<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
C: GenericConfig<D, F = F>,
|
||||
const N: usize,
|
||||
>{
|
||||
pub node_targets: NodeCircuitTargets<D, N>,
|
||||
pub node_data: NodeData<F, D, C>,
|
||||
}
|
||||
|
||||
/// Node circuit targets
|
||||
/// assumes that all leaf proofs use the same verifier data
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct NodeCircuitTargets<
|
||||
const D: usize,
|
||||
const N: usize,
|
||||
>{
|
||||
pub leaf_proofs: [ProofWithPublicInputsTarget<D>; N],
|
||||
pub condition: BoolTarget,
|
||||
pub node_proofs: [ProofWithPublicInputsTarget<D>; N],
|
||||
pub leaf_verifier_data: VerifierCircuitTarget,
|
||||
pub node_verifier_data: VerifierCircuitTarget,
|
||||
}
|
||||
|
||||
/// Node common data and verifier data
|
||||
#[derive(Debug)]
|
||||
pub struct NodeData<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
C: GenericConfig<D, F = F>,
|
||||
>{
|
||||
pub node_circuit_data: CircuitData<F, C, D>,
|
||||
pub inner_node_common_data: CommonCircuitData<F, D>,
|
||||
pub leaf_circuit_data: CircuitData<F, C, D>,
|
||||
}
|
||||
|
||||
impl<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
C: GenericConfig<D, F = F> + 'static,
|
||||
const N: usize,
|
||||
> NodeCircuit<F, D, C, N>
|
||||
where
|
||||
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
|
||||
{
|
||||
|
||||
/// builds the node circuit
|
||||
/// the circuit data and targets are stored in the node struct
|
||||
/// TODO: make generic recursion config
|
||||
pub fn build_circuit<
|
||||
I: InnerCircuit<F, D>,
|
||||
H: AlgebraicHasher<F>,
|
||||
const M: usize,
|
||||
>(
|
||||
leaf_circuit: LeafCircuit<F, D, I, M>
|
||||
) -> Result<NodeCircuit<F, D, C, N>>{
|
||||
|
||||
// builder with standard recursion config
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut builder = CircuitBuilder::<F, D>::new(config);
|
||||
|
||||
// circuit data for leaf
|
||||
let leaf_circ_data = leaf_circuit.get_circuit_data::<C,H>()?;
|
||||
|
||||
// common data for leaf
|
||||
let leaf_common = leaf_circ_data.common.clone();
|
||||
|
||||
// virtual proofs for leaf proofs
|
||||
let mut leaf_proofs = vec![];
|
||||
for _i in 0..N {
|
||||
let vir_proof = builder.add_virtual_proof_with_pis(&leaf_common);
|
||||
leaf_proofs.push(vir_proof);
|
||||
}
|
||||
|
||||
// get the public input hash from all inner proof targets
|
||||
let mut leaf_pub_input_hashes = vec![];
|
||||
for i in 0..N {
|
||||
let inner_cyclic_pis = &leaf_proofs[i].public_inputs;
|
||||
leaf_pub_input_hashes.extend_from_slice(&inner_cyclic_pis[0..4]);
|
||||
}
|
||||
|
||||
// leaf verifier data
|
||||
// TODO: make verifier data public
|
||||
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.verify_proof::<C>(&leaf_proofs[i], &leaf_verifier_data, &leaf_common);
|
||||
|
||||
}
|
||||
|
||||
// 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();
|
||||
// TODO: make verifier data public
|
||||
// let _verifier_data_target = builder.add_verifier_data_public_inputs();
|
||||
let verifier_data_target = builder.add_virtual_verifier_data(builder.config.fri_config.cap_height);
|
||||
common_data.num_public_inputs = builder.num_public_inputs();
|
||||
|
||||
let inner_cyclic_proof_with_pis: [ProofWithPublicInputsTarget<D>; N] =
|
||||
vec_to_array::<N, ProofWithPublicInputsTarget<D>>(
|
||||
(0..N)
|
||||
.map(|_| builder.add_virtual_proof_with_pis(&common_data))
|
||||
.collect::<Vec<_>>()
|
||||
)?;
|
||||
|
||||
// get the public input hash from all inner proof targets
|
||||
let mut inner_pub_input_hashes = vec![];
|
||||
for i in 0..N {
|
||||
let inner_cyclic_pis = &inner_cyclic_proof_with_pis[i].public_inputs;
|
||||
inner_pub_input_hashes.extend_from_slice(&inner_cyclic_pis[0..4]);
|
||||
}
|
||||
|
||||
// select the public input - either leaf or node
|
||||
let pub_input_to_be_hashed = select_vec(&mut builder, condition, &leaf_pub_input_hashes ,&inner_pub_input_hashes);
|
||||
// hash all the node public input h = H(h_1 | h_2 | ... | h_N)
|
||||
let node_hash_or_leaf_hash= builder.hash_n_to_hash_no_pad::<H>(pub_input_to_be_hashed);
|
||||
|
||||
builder.connect_hashes(pub_input_hash,node_hash_or_leaf_hash);
|
||||
|
||||
// verify all N proofs in-circuit
|
||||
for i in 0..N {
|
||||
builder.verify_proof::<C>(&inner_cyclic_proof_with_pis[i], &verifier_data_target, &common_data);
|
||||
|
||||
}
|
||||
|
||||
// build the node circuit
|
||||
let node_circuit_data = builder.build::<C>();
|
||||
|
||||
// collect the leaf proofs
|
||||
let leaf_proofs: [ProofWithPublicInputsTarget<D>; N] =
|
||||
vec_to_array::<N, ProofWithPublicInputsTarget<D>>(
|
||||
(0..N).map(|i| {
|
||||
leaf_proofs[i].clone()
|
||||
}).collect::<Vec<_>>()
|
||||
)?;
|
||||
|
||||
// store targets
|
||||
let node_targets = NodeCircuitTargets::<D, N>{
|
||||
leaf_proofs,
|
||||
condition,
|
||||
node_proofs: inner_cyclic_proof_with_pis,
|
||||
leaf_verifier_data,
|
||||
node_verifier_data: verifier_data_target
|
||||
};
|
||||
|
||||
let node_data = NodeData{
|
||||
node_circuit_data,
|
||||
inner_node_common_data: common_data,
|
||||
leaf_circuit_data: leaf_circ_data,
|
||||
};
|
||||
|
||||
let node = NodeCircuit{
|
||||
node_targets,
|
||||
node_data,
|
||||
};
|
||||
|
||||
Ok(node)
|
||||
}
|
||||
|
||||
/// assigns the targets for the Node circuit - takes
|
||||
/// - either leaf or circuit proofs
|
||||
/// - leaf circuit data
|
||||
/// - partial witness
|
||||
/// - bool value, true if leaf node, otherwise false.
|
||||
pub fn assign_targets(
|
||||
node_targets: NodeCircuitTargets<D, N>,
|
||||
leaf_proofs: [ProofWithPublicInputs<F, C, D>; N],
|
||||
node_proofs: [ProofWithPublicInputs<F, C, D>; N],
|
||||
leaf_verifier_only_data: &VerifierOnlyCircuitData<C, D>,
|
||||
node_verifier_only_data: &VerifierOnlyCircuitData<C, D>,
|
||||
pw: &mut PartialWitness<F>,
|
||||
is_leaf: bool,
|
||||
) -> Result<()>{
|
||||
|
||||
if is_leaf == true {
|
||||
let dummy_node = node_proofs;
|
||||
// assign the condition
|
||||
pw.set_bool_target(node_targets.condition, true)
|
||||
.map_err(|e| CircuitError::BoolTargetAssignmentError("condition".to_string(),e.to_string()))?;
|
||||
for i in 0..N {
|
||||
// assign the node proofs with dummy
|
||||
pw.set_proof_with_pis_target::<C, D>(
|
||||
&node_targets.node_proofs[i],
|
||||
&dummy_node[i],
|
||||
).map_err(|e| CircuitError::ProofTargetAssignmentError("dummy node proofs".to_string(),e.to_string()))?;
|
||||
// assign the leaf proof with real proofs
|
||||
pw.set_proof_with_pis_target(
|
||||
&node_targets.leaf_proofs[i],
|
||||
&leaf_proofs[i]
|
||||
).map_err(|e| CircuitError::ProofTargetAssignmentError("leaf proofs".to_string(),e.to_string()))?;
|
||||
}
|
||||
}else{
|
||||
// assign the condition
|
||||
pw.set_bool_target(node_targets.condition, false)
|
||||
.map_err(|e| CircuitError::BoolTargetAssignmentError("condition".to_string(),e.to_string()))?;
|
||||
|
||||
// dummy leaf
|
||||
let dummy_leaf = leaf_proofs;
|
||||
for i in 0..N {
|
||||
// assign the node proofs
|
||||
pw.set_proof_with_pis_target(&node_targets.node_proofs[i], &node_proofs[i])
|
||||
.map_err(|e| CircuitError::ProofTargetAssignmentError("node proofs".to_string(),e.to_string()))?;
|
||||
|
||||
// assign leaf proofs with dummy
|
||||
pw.set_proof_with_pis_target::<C, D>(
|
||||
&node_targets.leaf_proofs[i],
|
||||
&dummy_leaf[i],
|
||||
).map_err(|e| CircuitError::ProofTargetAssignmentError("dummy leaf proofs".to_string(),e.to_string()))?;
|
||||
}
|
||||
}
|
||||
// assign the verifier data
|
||||
pw.set_verifier_data_target(&node_targets.leaf_verifier_data, leaf_verifier_only_data)
|
||||
.map_err(|e| CircuitError::VerifierDataTargetAssignmentError(e.to_string()))?;
|
||||
pw.set_verifier_data_target(&node_targets.node_verifier_data, node_verifier_only_data)
|
||||
.map_err(|e| CircuitError::VerifierDataTargetAssignmentError(e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Generates `CommonCircuitData` usable for node recursion.
|
||||
/// the circuit being built here depends on M and N so must be re-generated
|
||||
/// if the params change
|
||||
pub fn get_common_data_for_node() -> Result<CommonCircuitData<F, D>>
|
||||
{
|
||||
// layer 1
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let builder = CircuitBuilder::<F, D>::new(config);
|
||||
let data = builder.build::<C>();
|
||||
|
||||
// layer 2
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut builder = CircuitBuilder::<F, D>::new(config.clone());
|
||||
let verifier_data = builder.add_virtual_verifier_data(data.common.config.fri_config.cap_height);
|
||||
// generate and verify N number of proofs
|
||||
for _ in 0..1 {
|
||||
let proof = builder.add_virtual_proof_with_pis(&data.common);
|
||||
builder.verify_proof::<C>(&proof, &verifier_data, &data.common);
|
||||
}
|
||||
let data = builder.build::<C>();
|
||||
|
||||
// layer 3
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut builder = CircuitBuilder::<F, D>::new(config.clone());
|
||||
|
||||
// add a ConstantGate
|
||||
builder.add_gate(
|
||||
ConstantGate::new(config.num_constants),
|
||||
vec![],
|
||||
);
|
||||
|
||||
// generate and verify N number of proofs
|
||||
let verifier_data = builder.add_verifier_data_public_inputs();
|
||||
for _ in 0..N {
|
||||
let proof = builder.add_virtual_proof_with_pis(&data.common);
|
||||
builder.verify_proof::<C>(&proof, &verifier_data, &data.common);
|
||||
}
|
||||
// pad.
|
||||
while builder.num_gates() < 1 << 12 {
|
||||
builder.add_gate(NoopGate, vec![]);
|
||||
}
|
||||
Ok(builder.build::<C>().common)
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,168 +0,0 @@
|
||||
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_field::extension::Extendable;
|
||||
use crate::recursion::utils::dummy_gen::DummyProofGen;
|
||||
use crate::{error::CircuitError, Result};
|
||||
use crate::circuits::utils::vec_to_array;
|
||||
use crate::recursion::circuits::leaf_circuit::LeafCircuit;
|
||||
use crate::recursion::tree2::node_circuit::NodeCircuit;
|
||||
|
||||
/// the tree recursion struct simplifies the process
|
||||
/// of building, proving and verifying
|
||||
/// - N: number of inner proofs to verify in the node circuit
|
||||
pub struct TreeRecursion<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
C: GenericConfig<D, F = F> + 'static,
|
||||
const N: usize,
|
||||
> where
|
||||
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
|
||||
{
|
||||
pub node: NodeCircuit<F, D, C, N>
|
||||
}
|
||||
|
||||
impl<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
C: GenericConfig<D, F = F> + 'static,
|
||||
const N: usize,
|
||||
> TreeRecursion<F, D, C, N> where
|
||||
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
|
||||
{
|
||||
|
||||
|
||||
|
||||
pub fn build<
|
||||
I: InnerCircuit<F, D>,
|
||||
H: AlgebraicHasher<F>,
|
||||
const M: usize,
|
||||
>(
|
||||
leaf_circuit: LeafCircuit<F, D, I, M>
|
||||
) -> Result<Self>{
|
||||
Ok(
|
||||
Self{
|
||||
node: NodeCircuit::<F, D, C, N>::build_circuit::<I,H, M>(leaf_circuit)?,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/// generates a proof - only one node
|
||||
/// takes N proofs
|
||||
pub fn prove(
|
||||
&mut self,
|
||||
leaf_proofs: [ProofWithPublicInputs<F, C, D>; N],
|
||||
node_proofs: [ProofWithPublicInputs<F, C, D>; N],
|
||||
is_leaf: bool,
|
||||
) -> Result<ProofWithPublicInputs<F, C, D>>{
|
||||
|
||||
let mut pw = PartialWitness::new();
|
||||
|
||||
NodeCircuit::assign_targets(
|
||||
self.node.node_targets.clone(),
|
||||
leaf_proofs,
|
||||
node_proofs,
|
||||
&self.node.node_data.leaf_circuit_data.verifier_only,
|
||||
&self.node.node_data.node_circuit_data.verifier_only,
|
||||
&mut pw,
|
||||
is_leaf,
|
||||
)?;
|
||||
|
||||
let proof = self.node.node_data.node_circuit_data.prove(pw)
|
||||
.map_err(|e| CircuitError::ProofGenerationError(e.to_string()))?;
|
||||
Ok(proof)
|
||||
}
|
||||
|
||||
/// prove n leaf proofs in a tree structure
|
||||
/// the function uses circuit data from self takes
|
||||
/// - leaf_proofs: vector of circuit inputs
|
||||
/// NOTE: Expects the number of leaf proofs to be divisible by N, e.g. by 2 if binary tree
|
||||
pub fn prove_tree(
|
||||
&mut self,
|
||||
leaf_proofs: Vec<ProofWithPublicInputs<F, C, D>>,
|
||||
) -> Result<ProofWithPublicInputs<F, C, D>> {
|
||||
// 1. Check the total number of leaf_proofs is divisible by N
|
||||
if leaf_proofs.len() % N != 0 {
|
||||
return
|
||||
Err(CircuitError::RecursionTreeError(format!(
|
||||
"input proofs must be divisible by {}, got {}", N, leaf_proofs.len())
|
||||
))
|
||||
}
|
||||
|
||||
// 2. Prepare the dummy proofs
|
||||
let dummy_node_proofs = DummyProofGen::<F, D, C>::gen_n_dummy_node_proofs(
|
||||
&self.node.node_data.inner_node_common_data,
|
||||
&self.node.node_data.node_circuit_data.verifier_only,
|
||||
)?;
|
||||
|
||||
let dummy_leaf_proofs = DummyProofGen::<F, D, C>::gen_n_dummy_leaf_proofs(
|
||||
&self.node.node_data.leaf_circuit_data.common
|
||||
)?;
|
||||
|
||||
// 3. Work through levels of proofs until only one remains
|
||||
let mut current_level_proofs = leaf_proofs;
|
||||
|
||||
// Keep reducing until we’re left with 1 proof
|
||||
let mut level: usize = 0;
|
||||
while current_level_proofs.len() >= N {
|
||||
let mut next_level_proofs = Vec::new();
|
||||
|
||||
// Process in chunks of N
|
||||
for chunk in current_level_proofs.chunks_exact(N) {
|
||||
// Convert the chunk slice into a fixed-size array
|
||||
let chunk_array: [ProofWithPublicInputs<F, C, D>; N] =
|
||||
vec_to_array::<N,ProofWithPublicInputs<F, C, D>>(chunk.to_vec())?;
|
||||
|
||||
// Decide leaf or node based on level
|
||||
// assumes the first chunk is the leaf
|
||||
let (leaf_chunk, node_chunk, is_leaf) = if level == 0 {
|
||||
(chunk_array, dummy_node_proofs.clone(), true)
|
||||
} else {
|
||||
(dummy_leaf_proofs.clone(), chunk_array, false)
|
||||
};
|
||||
|
||||
let node = self.prove(
|
||||
leaf_chunk,
|
||||
node_chunk,
|
||||
is_leaf,
|
||||
)?;
|
||||
|
||||
next_level_proofs.push(node);
|
||||
}
|
||||
|
||||
current_level_proofs = next_level_proofs;
|
||||
level = level + 1;
|
||||
}
|
||||
|
||||
// 4. Check that exactly one proof remains
|
||||
if current_level_proofs.len() != 1 {
|
||||
return Err(CircuitError::RecursionTreeError(
|
||||
format!("Expected exactly 1 final proof, found {}",
|
||||
current_level_proofs.len())
|
||||
));
|
||||
}
|
||||
|
||||
// 5. Return the final root proof
|
||||
Ok(current_level_proofs.remove(0))
|
||||
}
|
||||
|
||||
/// verifies the proof generated
|
||||
/// TODO: separate prover from verifier.
|
||||
pub fn verify_proof(
|
||||
&self,
|
||||
proof: ProofWithPublicInputs<F, C, D>,
|
||||
is_leaf: bool,
|
||||
) -> Result<()>{
|
||||
|
||||
// TODO: if !is_leaf check verifier data
|
||||
|
||||
self.node.node_data.node_circuit_data.verify(proof)
|
||||
.map_err(|e| CircuitError::InvalidProofError(e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,139 +0,0 @@
|
||||
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, VerifierCircuitTarget, VerifierOnlyCircuitData};
|
||||
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::circuits::inner_circuit::InnerCircuit;
|
||||
use crate::{error::CircuitError,Result};
|
||||
|
||||
/// recursion leaf circuit - verifies N inner proof
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct LeafCircuit<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
C: GenericConfig<D, F = F>,
|
||||
H: AlgebraicHasher<F>,
|
||||
const N: usize,
|
||||
> where
|
||||
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
|
||||
{
|
||||
inner_common_data: CommonCircuitData<F, D>,
|
||||
phantom_data: PhantomData<(C,H)>
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct LeafTargets <
|
||||
const D: usize,
|
||||
>{
|
||||
pub inner_proof: Vec<ProofWithPublicInputsTarget<D>>,
|
||||
pub verifier_data: VerifierCircuitTarget,
|
||||
}
|
||||
|
||||
impl<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
C: GenericConfig<D, F = F>,
|
||||
H: AlgebraicHasher<F>,
|
||||
const N: usize,
|
||||
> LeafCircuit<F,D,C,H,N> where
|
||||
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
|
||||
{
|
||||
pub fn new(inner_common_data: CommonCircuitData<F,D>) -> Self {
|
||||
Self{
|
||||
inner_common_data,
|
||||
phantom_data:PhantomData::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// build the leaf circuit
|
||||
pub fn build(&self, builder: &mut CircuitBuilder<F, D>) -> Result<LeafTargets<D>> {
|
||||
|
||||
let inner_common = self.inner_common_data.clone();
|
||||
|
||||
// the proof virtual targets
|
||||
let mut pub_input = vec![];
|
||||
let mut vir_proofs = vec![];
|
||||
for _i in 0..N {
|
||||
let vir_proof = builder.add_virtual_proof_with_pis(&inner_common);
|
||||
let inner_pub_input = vir_proof.public_inputs.clone();
|
||||
vir_proofs.push(vir_proof);
|
||||
pub_input.extend_from_slice(&inner_pub_input);
|
||||
}
|
||||
|
||||
// hash the public input & make it public
|
||||
let hash_inner_pub_input = builder.hash_n_to_hash_no_pad::<H>(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);
|
||||
|
||||
// register verifier data hash as public input.
|
||||
let mut vd_pub_input = vec![];
|
||||
vd_pub_input.extend_from_slice(&inner_verifier_data.circuit_digest.elements);
|
||||
for i in 0..builder.config.fri_config.num_cap_elements() {
|
||||
vd_pub_input.extend_from_slice(&inner_verifier_data.constants_sigmas_cap.0[i].elements);
|
||||
}
|
||||
let hash_inner_vd_pub_input = builder.hash_n_to_hash_no_pad::<H>(vd_pub_input);
|
||||
builder.register_public_inputs(&hash_inner_vd_pub_input.elements);
|
||||
|
||||
// verify the proofs in-circuit
|
||||
for i in 0..N {
|
||||
builder.verify_proof::<C>(&vir_proofs[i], &inner_verifier_data, &inner_common);
|
||||
}
|
||||
|
||||
// return targets
|
||||
let t = LeafTargets {
|
||||
inner_proof: vir_proofs,
|
||||
verifier_data: inner_verifier_data,
|
||||
};
|
||||
Ok(t)
|
||||
|
||||
}
|
||||
|
||||
/// assign the leaf targets with given input
|
||||
pub fn assign_targets(
|
||||
&self, pw: &mut PartialWitness<F>,
|
||||
targets: &LeafTargets<D>,
|
||||
inner_proof: &[ProofWithPublicInputs<F, C, D>],
|
||||
verifier_only_data: &VerifierOnlyCircuitData<C, D>,
|
||||
) -> Result<()> {
|
||||
assert_eq!(inner_proof.len(), N);
|
||||
// assign the proofs
|
||||
for i in 0..N {
|
||||
pw.set_proof_with_pis_target(&targets.inner_proof[i], &inner_proof[i])
|
||||
.map_err(|e| {
|
||||
CircuitError::ProofTargetAssignmentError("inner-proof".to_string(), e.to_string())
|
||||
})?;
|
||||
}
|
||||
|
||||
// assign the verifier data
|
||||
pw.set_verifier_data_target(&targets.verifier_data, verifier_only_data)
|
||||
.map_err(|e| {
|
||||
CircuitError::VerifierDataTargetAssignmentError(e.to_string())
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// returns the leaf circuit data
|
||||
pub fn get_circuit_data (&self) -> Result<CircuitData<F, C, D>>
|
||||
where
|
||||
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
|
||||
{
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut builder = CircuitBuilder::<F, D>::new(config.clone());
|
||||
|
||||
self.build(&mut builder)?;
|
||||
|
||||
let circ_data = builder.build::<C>();
|
||||
|
||||
Ok(circ_data)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
pub mod leaf;
|
||||
pub mod node;
|
||||
pub mod tree;
|
||||
@ -1,152 +0,0 @@
|
||||
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, VerifierCircuitTarget, VerifierOnlyCircuitData};
|
||||
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::circuits::inner_circuit::InnerCircuit;
|
||||
use crate::{error::CircuitError,Result};
|
||||
use crate::circuits::utils::vec_to_array;
|
||||
|
||||
/// recursion node circuit - verifies 2 leaf proofs
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct NodeCircuit<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
C: GenericConfig<D, F = F>,
|
||||
H: AlgebraicHasher<F>,
|
||||
const M: usize,
|
||||
> where
|
||||
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
|
||||
{
|
||||
leaf_common_data: CommonCircuitData<F, D>,
|
||||
phantom_data: PhantomData<(C,H)>
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct NodeTargets<
|
||||
const D: usize,
|
||||
>{
|
||||
pub leaf_proofs: Vec<ProofWithPublicInputsTarget<D>>,
|
||||
pub verifier_data: VerifierCircuitTarget,
|
||||
}
|
||||
|
||||
impl<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
C: GenericConfig<D, F = F>,
|
||||
H: AlgebraicHasher<F>,
|
||||
const M: usize,
|
||||
> NodeCircuit<F,D,C,H,M> where
|
||||
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
|
||||
{
|
||||
|
||||
pub fn new(inner_common_data: CommonCircuitData<F,D>) -> Self {
|
||||
Self{
|
||||
leaf_common_data: inner_common_data,
|
||||
phantom_data:PhantomData::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// build the leaf circuit
|
||||
pub fn build(&self, builder: &mut CircuitBuilder<F, D>) -> Result<NodeTargets<D>> {
|
||||
|
||||
let inner_common = self.leaf_common_data.clone();
|
||||
|
||||
// assert public input is of size 8 - 2 hashout
|
||||
assert_eq!(inner_common.num_public_inputs, 8);
|
||||
|
||||
// the proof virtual targets - M proofs
|
||||
let mut vir_proofs = vec![];
|
||||
let mut pub_input = vec![];
|
||||
let mut inner_vd_hashes = vec![];
|
||||
for _i in 0..M {
|
||||
let vir_proof = builder.add_virtual_proof_with_pis(&inner_common);
|
||||
let inner_pub_input = vir_proof.public_inputs.clone();
|
||||
vir_proofs.push(vir_proof);
|
||||
pub_input.extend_from_slice(&inner_pub_input[0..4]);
|
||||
inner_vd_hashes.extend_from_slice(&inner_pub_input[4..8]);
|
||||
}
|
||||
|
||||
// hash the public input & make it public
|
||||
let hash_inner_pub_input = builder.hash_n_to_hash_no_pad::<H>(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);
|
||||
|
||||
// register verifier data hash as public input. H(H_l, H_l, H_n) -> public input
|
||||
let mut vd_pub_input = vec![];
|
||||
vd_pub_input.extend_from_slice(&inner_verifier_data.circuit_digest.elements);
|
||||
for i in 0..builder.config.fri_config.num_cap_elements() {
|
||||
vd_pub_input.extend_from_slice(&inner_verifier_data.constants_sigmas_cap.0[i].elements);
|
||||
}
|
||||
let vd_hash = builder.hash_n_to_hash_no_pad::<H>(vd_pub_input);
|
||||
inner_vd_hashes.extend_from_slice(&vd_hash.elements);
|
||||
let vd_hash_all = builder.hash_n_to_hash_no_pad::<H>(inner_vd_hashes);
|
||||
builder.register_public_inputs(&vd_hash_all.elements);
|
||||
|
||||
// verify the proofs in-circuit - M proofs
|
||||
for i in 0..M {
|
||||
builder.verify_proof::<C>(&vir_proofs[i], &inner_verifier_data, &inner_common);
|
||||
}
|
||||
|
||||
// let proofs = vec_to_array::<2, ProofWithPublicInputsTarget<D>>(vir_proofs)?;
|
||||
|
||||
// return targets
|
||||
let t = NodeTargets {
|
||||
leaf_proofs: vir_proofs,
|
||||
verifier_data: inner_verifier_data,
|
||||
};
|
||||
Ok(t)
|
||||
|
||||
}
|
||||
|
||||
/// assign the leaf targets with given input
|
||||
pub fn assign_targets(
|
||||
&self, pw: &mut PartialWitness<F>,
|
||||
targets: &NodeTargets<D>,
|
||||
node_proofs: &[ProofWithPublicInputs<F, C, D>],
|
||||
verifier_only_data: &VerifierOnlyCircuitData<C, D>,
|
||||
) -> Result<()> {
|
||||
// assert size of proofs vec
|
||||
assert_eq!(node_proofs.len(), M);
|
||||
|
||||
// assign the proofs
|
||||
for i in 0..M {
|
||||
pw.set_proof_with_pis_target(&targets.leaf_proofs[i], &node_proofs[i])
|
||||
.map_err(|e| {
|
||||
CircuitError::ProofTargetAssignmentError("inner-proof".to_string(), e.to_string())
|
||||
})?;
|
||||
}
|
||||
|
||||
// assign the verifier data
|
||||
pw.set_verifier_data_target(&targets.verifier_data, &verifier_only_data)
|
||||
.map_err(|e| {
|
||||
CircuitError::VerifierDataTargetAssignmentError(e.to_string())
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// returns the leaf circuit data
|
||||
pub fn get_circuit_data (&self) -> Result<CircuitData<F, C, D>>
|
||||
where
|
||||
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
|
||||
{
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut builder = CircuitBuilder::<F, D>::new(config.clone());
|
||||
|
||||
self.build(&mut builder)?;
|
||||
|
||||
let circ_data = builder.build::<C>();
|
||||
|
||||
Ok(circ_data)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -1,252 +0,0 @@
|
||||
use std::marker::PhantomData;
|
||||
use plonky2::hash::hash_types::{HashOut, RichField};
|
||||
use plonky2::iop::witness::PartialWitness;
|
||||
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
||||
use plonky2::plonk::circuit_data::{CircuitConfig, CircuitData, CommonCircuitData, VerifierCircuitData, VerifierOnlyCircuitData};
|
||||
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_field::extension::Extendable;
|
||||
use crate::{error::CircuitError, Result};
|
||||
use crate::recursion::uniform::{leaf::{LeafTargets,LeafCircuit},node::{NodeTargets,NodeCircuit}};
|
||||
|
||||
/// tree recursion
|
||||
pub struct TreeRecursion<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
C: GenericConfig<D, F = F>,
|
||||
H: AlgebraicHasher<F>,
|
||||
const N: usize,
|
||||
const M: usize,
|
||||
> where
|
||||
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
|
||||
{
|
||||
leaf: LeafCircuit<F, D, C, H, N>,
|
||||
node: NodeCircuit<F, D, C, H, M>,
|
||||
leaf_circ_data: CircuitData<F, C, D>,
|
||||
node_circ_data: CircuitData<F, C, D>,
|
||||
leaf_targets: LeafTargets<D>,
|
||||
node_targets: NodeTargets<D>,
|
||||
phantom_data: PhantomData<(H)>
|
||||
}
|
||||
|
||||
impl<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
C: GenericConfig<D, F = F>,
|
||||
H: AlgebraicHasher<F>,
|
||||
const N: usize,
|
||||
const M: usize,
|
||||
> TreeRecursion<F, D, C, H, N, M> where
|
||||
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
|
||||
{
|
||||
|
||||
pub fn build(
|
||||
inner_common_data: CommonCircuitData<F,D>
|
||||
) -> Result<Self> {
|
||||
// build leaf with standard recursion config
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut builder = CircuitBuilder::<F, D>::new(config);
|
||||
|
||||
let leaf = LeafCircuit::<_,D,_,_,N>::new(inner_common_data.clone());
|
||||
let leaf_targets = leaf.build(&mut builder)?;
|
||||
let leaf_circ_data = builder.build::<C>();
|
||||
// println!("leaf circuit size = {:?}", leaf_circ_data.common.degree_bits());
|
||||
|
||||
// build node with standard recursion config
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut builder = CircuitBuilder::<F, D>::new(config);
|
||||
|
||||
let node = NodeCircuit::<_,D,_,_,M>::new(leaf_circ_data.common.clone());
|
||||
let node_targets = node.build(&mut builder)?;
|
||||
let node_circ_data = builder.build::<C>();
|
||||
// println!("node circuit size = {:?}", node_circ_data.common.degree_bits());
|
||||
|
||||
Ok(Self{
|
||||
leaf,
|
||||
node,
|
||||
leaf_circ_data,
|
||||
node_circ_data,
|
||||
leaf_targets,
|
||||
node_targets,
|
||||
phantom_data: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_leaf_verifier_data(&self) -> VerifierCircuitData<F, C, D>{
|
||||
self.leaf_circ_data.verifier_data()
|
||||
}
|
||||
|
||||
pub fn get_node_verifier_data(&self) -> VerifierCircuitData<F, C, D>{
|
||||
self.node_circ_data.verifier_data()
|
||||
}
|
||||
|
||||
pub fn prove_tree
|
||||
(
|
||||
&mut self,
|
||||
proofs_with_pi: &[ProofWithPublicInputs<F, C, D>],
|
||||
inner_verifier_only_data: &VerifierOnlyCircuitData<C, D>,
|
||||
) -> Result<(ProofWithPublicInputs<F, C, D>)>
|
||||
{
|
||||
if proofs_with_pi.len() % 2 != 0 {
|
||||
return
|
||||
Err(CircuitError::RecursionTreeError(format!(
|
||||
"input proofs must be divisible by {}, got {}", 2, proofs_with_pi.len())
|
||||
))
|
||||
}
|
||||
// process leaves
|
||||
let leaf_proofs = self.get_leaf_proofs(
|
||||
proofs_with_pi,
|
||||
inner_verifier_only_data,
|
||||
)?;
|
||||
|
||||
// process nodes
|
||||
let (root_proof, vd) =
|
||||
self.prove(&leaf_proofs,&self.leaf_circ_data.verifier_only)?;
|
||||
|
||||
Ok(root_proof)
|
||||
}
|
||||
|
||||
fn get_leaf_proofs
|
||||
(
|
||||
&mut self,
|
||||
proofs_with_pi: &[ProofWithPublicInputs<F, C, D>],
|
||||
inner_verifier_only_data: &VerifierOnlyCircuitData<C, D>,
|
||||
) -> Result<(Vec<ProofWithPublicInputs<F, C, D>>)> {
|
||||
|
||||
let mut leaf_proofs = vec![];
|
||||
|
||||
for proof in proofs_with_pi.chunks(N){
|
||||
let mut pw = PartialWitness::<F>::new();
|
||||
|
||||
self.leaf.assign_targets(&mut pw,&self.leaf_targets,proof,inner_verifier_only_data)?;
|
||||
let proof = self.leaf_circ_data.prove(pw).unwrap();
|
||||
leaf_proofs.push(proof);
|
||||
}
|
||||
|
||||
Ok(leaf_proofs)
|
||||
}
|
||||
|
||||
/// generates a proof
|
||||
fn prove(
|
||||
&self,
|
||||
proofs_with_pi: &[ProofWithPublicInputs<F, C, D>],
|
||||
verifier_only_data: &VerifierOnlyCircuitData<C, D>,
|
||||
) -> Result<(ProofWithPublicInputs<F, C, D>, VerifierOnlyCircuitData<C, D>)> where
|
||||
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
|
||||
{
|
||||
|
||||
if proofs_with_pi.len() == 1 {
|
||||
return Ok((proofs_with_pi[0].clone(), verifier_only_data.clone()));
|
||||
}
|
||||
|
||||
let mut new_proofs = vec![];
|
||||
|
||||
for chunk in proofs_with_pi.chunks(M) {
|
||||
|
||||
let mut inner_pw = PartialWitness::new();
|
||||
|
||||
self.node.assign_targets(
|
||||
&mut inner_pw,
|
||||
&self.node_targets,
|
||||
chunk,
|
||||
verifier_only_data,
|
||||
)?;
|
||||
|
||||
let proof = self.node_circ_data.prove(inner_pw)
|
||||
.map_err(|e| CircuitError::ProofGenerationError(e.to_string()))?;
|
||||
new_proofs.push(proof);
|
||||
}
|
||||
|
||||
self.prove(&new_proofs, &self.node_circ_data.verifier_only)
|
||||
}
|
||||
|
||||
pub fn verify_proof_and_public_input(
|
||||
&self,
|
||||
proof: ProofWithPublicInputs<F, C, D>,
|
||||
inner_public_input: Vec<Vec<F>>,
|
||||
inner_verifier_data: &VerifierCircuitData<F, C, D>) -> Result<()>
|
||||
{
|
||||
let public_input = proof.public_inputs.clone();
|
||||
self.node_circ_data.verify(proof)
|
||||
.map_err(|e| CircuitError::InvalidProofError(e.to_string()))?;
|
||||
self.verify_public_input(public_input, inner_public_input, inner_verifier_data)
|
||||
}
|
||||
|
||||
pub fn verify_public_input(&self, public_input: Vec<F>, inner_public_input: Vec<Vec<F>>, inner_verifier_data: &VerifierCircuitData<F, C, D>) -> Result<()>{
|
||||
assert_eq!(public_input.len(), 8);
|
||||
|
||||
let given_input_hash = &public_input[0..4];
|
||||
let given_vd_hash = &public_input[4..8];
|
||||
|
||||
let inner_hash = Self::get_hash_of_verifier_data(&inner_verifier_data);
|
||||
|
||||
let leaf_hash = Self::get_hash_of_verifier_data(&self.leaf_circ_data.verifier_data());
|
||||
|
||||
let node_hash = Self::get_hash_of_verifier_data(&self.node_circ_data.verifier_data());
|
||||
|
||||
|
||||
let mut pub_in_hashes = vec![];
|
||||
let mut inner_vd_hashes = vec![];
|
||||
for pub_in in inner_public_input.chunks(N){
|
||||
let pub_in_flat: Vec<F> = pub_in
|
||||
.iter()
|
||||
.flat_map(|v| v.iter().cloned())
|
||||
.collect();
|
||||
let hash = H::hash_no_pad(&pub_in_flat);
|
||||
pub_in_hashes.push(hash);
|
||||
inner_vd_hashes.push(inner_hash.clone());
|
||||
}
|
||||
|
||||
let mut level = 0;
|
||||
while pub_in_hashes.len() > 1 {
|
||||
let mut next_level_pi_hashes = Vec::new();
|
||||
let mut next_level_vd_hashes = Vec::new();
|
||||
for (pi_chunk, vd_chunk) in pub_in_hashes.chunks(M).zip(inner_vd_hashes.chunks(M)) {
|
||||
// collect field elements
|
||||
let pi_chunk_f: Vec<F> = pi_chunk.iter()
|
||||
.flat_map(|h| h.elements.iter().cloned())
|
||||
.collect();
|
||||
let mut vd_chunk_f: Vec<F> = vd_chunk.iter()
|
||||
.flat_map(|h| h.elements.iter().cloned())
|
||||
.collect();
|
||||
let hash_n = if level == 0 {leaf_hash} else{node_hash};
|
||||
vd_chunk_f.extend_from_slice(&hash_n.elements);
|
||||
|
||||
// Compute Poseidon2 hash of the concatenated chunk
|
||||
let pi_hash = H::hash_no_pad(&pi_chunk_f);
|
||||
let vd_hash = H::hash_no_pad(&vd_chunk_f);
|
||||
next_level_pi_hashes.push(pi_hash);
|
||||
next_level_vd_hashes.push(vd_hash);
|
||||
}
|
||||
pub_in_hashes = next_level_pi_hashes;
|
||||
inner_vd_hashes = next_level_vd_hashes;
|
||||
level +=1;
|
||||
}
|
||||
|
||||
//check expected hash
|
||||
let expected_pi_hash = pub_in_hashes[0];
|
||||
let expected_vd_hash = inner_vd_hashes[0];
|
||||
|
||||
assert_eq!(given_input_hash, expected_pi_hash.elements);
|
||||
assert_eq!(given_vd_hash, expected_vd_hash.elements);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// helper fn to generate hash of verifier data
|
||||
fn get_hash_of_verifier_data(verifier_data: &VerifierCircuitData<F, C, D>) -> HashOut<F>{
|
||||
let mut vd = vec![];
|
||||
let digest: &HashOut<F> = &verifier_data.verifier_only.circuit_digest;
|
||||
let caps = &verifier_data.verifier_only.constants_sigmas_cap;
|
||||
vd.extend_from_slice(&digest.elements);
|
||||
for i in 0..verifier_data.common.config.fri_config.num_cap_elements() {
|
||||
let cap_hash = caps.0[i] as HashOut<F>;
|
||||
vd.extend_from_slice(&cap_hash.elements);
|
||||
}
|
||||
|
||||
H::hash_no_pad(&vd)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,121 +0,0 @@
|
||||
use plonky2::gates::noop::NoopGate;
|
||||
use plonky2::hash::hash_types::RichField;
|
||||
use plonky2::iop::target::BoolTarget;
|
||||
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
||||
use plonky2::plonk::circuit_data::{CircuitData, CommonCircuitData, VerifierCircuitTarget};
|
||||
use plonky2::plonk::config::{AlgebraicHasher, GenericConfig};
|
||||
use plonky2::plonk::proof::ProofWithPublicInputsTarget;
|
||||
use plonky2_field::extension::Extendable;
|
||||
use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2;
|
||||
|
||||
/// this takes verifier data (not public) and doesn't check the verifier data for consistency
|
||||
pub fn conditionally_verify_recursion_proof_or_dummy<F: RichField + Extendable<D> + Poseidon2, const D: usize ,C: GenericConfig<D, F = F> + 'static>(
|
||||
builder: &mut CircuitBuilder<F, D>,
|
||||
condition: BoolTarget,
|
||||
cyclic_proof_with_pis: &ProofWithPublicInputsTarget<D>,
|
||||
verifier_data: &VerifierCircuitTarget,
|
||||
common_data: &CommonCircuitData<F, D>,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
C::Hasher: AlgebraicHasher<F>,
|
||||
{
|
||||
let (dummy_proof_with_pis_target, dummy_verifier_data_target) =
|
||||
dummy_proof_and_vk_no_generator::<F, D, C>(builder, common_data)?;
|
||||
|
||||
// TODO: make verifier data public
|
||||
// // Connect previous verifier data to current one. This guarantees that every proof in the cycle uses the same verifier data.
|
||||
// self.connect_hashes(
|
||||
// inner_cyclic_pis.circuit_digest,
|
||||
// verifier_data.circuit_digest,
|
||||
// );
|
||||
// self.connect_merkle_caps(
|
||||
// &inner_cyclic_pis.constants_sigmas_cap,
|
||||
// &verifier_data.constants_sigmas_cap,
|
||||
// );
|
||||
|
||||
// Verify the cyclic proof if `condition` is set to true, otherwise verify the other proof.
|
||||
builder.conditionally_verify_proof::<C>(
|
||||
condition,
|
||||
cyclic_proof_with_pis,
|
||||
verifier_data,
|
||||
&dummy_proof_with_pis_target,
|
||||
&dummy_verifier_data_target,
|
||||
common_data,
|
||||
);
|
||||
|
||||
// Make sure we have every gate to match `common_data`.
|
||||
for g in &common_data.gates {
|
||||
builder.add_gate_to_gate_set(g.clone());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Conditionally verify a proof with a new generated dummy proof.
|
||||
pub fn conditionally_verify_proof_or_dummy<F: RichField + Extendable<D> + Poseidon2, const D: usize ,C: GenericConfig<D, F = F> + 'static>(
|
||||
builder: &mut CircuitBuilder<F, D>,
|
||||
condition: BoolTarget,
|
||||
proof_with_pis: &ProofWithPublicInputsTarget<D>,
|
||||
inner_verifier_data: &VerifierCircuitTarget,
|
||||
inner_common_data: &CommonCircuitData<F, D>,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
C::Hasher: AlgebraicHasher<F>,
|
||||
{
|
||||
let (dummy_proof_with_pis_target, dummy_verifier_data_target) =
|
||||
dummy_proof_and_vk_no_generator::<F, D, C>(builder, inner_common_data)?;
|
||||
builder.conditionally_verify_proof::<C>(
|
||||
condition,
|
||||
proof_with_pis,
|
||||
inner_verifier_data,
|
||||
&dummy_proof_with_pis_target,
|
||||
&dummy_verifier_data_target,
|
||||
inner_common_data,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Generate a circuit matching a given `CommonCircuitData`.
|
||||
pub(crate) fn dummy_circuit<F: RichField + Extendable<D> + Poseidon2, C: GenericConfig<D, F = F>, const D: usize>(
|
||||
common_data: &CommonCircuitData<F, D>,
|
||||
) -> CircuitData<F, C, D> {
|
||||
let config = common_data.config.clone();
|
||||
assert!(
|
||||
!common_data.config.zero_knowledge,
|
||||
"Degree calculation can be off if zero-knowledge is on."
|
||||
);
|
||||
|
||||
// Number of `NoopGate`s to add to get a circuit of size `degree` in the end.
|
||||
// Need to account for public input hashing, a `PublicInputGate` and a `ConstantGate`.
|
||||
let degree = common_data.degree();
|
||||
let num_noop_gate = degree - common_data.num_public_inputs.div_ceil(8) - 2;
|
||||
|
||||
let mut builder = CircuitBuilder::<F, D>::new(config);
|
||||
for _ in 0..num_noop_gate {
|
||||
builder.add_gate(NoopGate, vec![]);
|
||||
}
|
||||
for gate in &common_data.gates {
|
||||
builder.add_gate_to_gate_set(gate.clone());
|
||||
}
|
||||
for _ in 0..common_data.num_public_inputs {
|
||||
builder.add_virtual_public_input();
|
||||
}
|
||||
|
||||
let circuit = builder.build::<C>();
|
||||
assert_eq!(&circuit.common, common_data);
|
||||
circuit
|
||||
}
|
||||
|
||||
pub(crate) fn dummy_proof_and_vk_no_generator<F: RichField + Extendable<D> + Poseidon2, const D: usize ,C: GenericConfig<D, F = F> + 'static> (
|
||||
builder: &mut CircuitBuilder<F, D>,
|
||||
common_data: &CommonCircuitData<F, D>,
|
||||
) -> anyhow::Result<(ProofWithPublicInputsTarget<D>, VerifierCircuitTarget)>
|
||||
where
|
||||
C::Hasher: AlgebraicHasher<F>,
|
||||
{
|
||||
let dummy_circuit = dummy_circuit::<F, C, D>(common_data);
|
||||
let dummy_proof_with_pis_target = builder.add_virtual_proof_with_pis(common_data);
|
||||
let dummy_verifier_data_target = builder.constant_verifier_data(&dummy_circuit.verifier_only);
|
||||
|
||||
Ok((dummy_proof_with_pis_target, dummy_verifier_data_target))
|
||||
}
|
||||
@ -1,79 +0,0 @@
|
||||
use std::marker::PhantomData;
|
||||
use plonky2::plonk::circuit_data::{CommonCircuitData, VerifierOnlyCircuitData};
|
||||
use plonky2::plonk::proof::{ProofWithPublicInputs};
|
||||
use plonky2::recursion::dummy_circuit::{dummy_proof};
|
||||
use crate::recursion::utils::conditional_verifier::dummy_circuit;
|
||||
use hashbrown::HashMap;
|
||||
use plonky2::hash::hash_types::{ RichField};
|
||||
use plonky2::plonk::config::{AlgebraicHasher, GenericConfig};
|
||||
use plonky2_field::extension::Extendable;
|
||||
use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2;
|
||||
use crate::{error::CircuitError, Result};
|
||||
use crate::circuits::utils::vec_to_array;
|
||||
|
||||
/// A generator for creating dummy proofs.
|
||||
pub struct DummyProofGen<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
C: GenericConfig<D, F = F>,
|
||||
> where
|
||||
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
|
||||
{
|
||||
phantom_data: PhantomData<(F,C)>,
|
||||
}
|
||||
|
||||
impl<
|
||||
F: RichField + Extendable<D> + Poseidon2,
|
||||
const D: usize,
|
||||
C: GenericConfig<D, F = F>,
|
||||
> DummyProofGen<F, D, C>
|
||||
where
|
||||
<C as GenericConfig<D>>::Hasher: AlgebraicHasher<F>
|
||||
{
|
||||
|
||||
/// Generates a single dummy leaf proof.
|
||||
pub fn gen_dummy_leaf_proof(
|
||||
common_data: &CommonCircuitData<F, D>,
|
||||
) -> Result<ProofWithPublicInputs<F, C, D>> {
|
||||
dummy_proof::<F, C, D>(&dummy_circuit::<F, C, D>(common_data), HashMap::new())
|
||||
.map_err(|e| CircuitError::DummyProofGenerationError(e.to_string()))
|
||||
}
|
||||
|
||||
/// Generates a single dummy node proof.
|
||||
pub fn get_dummy_node_proof(
|
||||
node_common: &CommonCircuitData<F, D>,
|
||||
node_verifier_only_data: &VerifierOnlyCircuitData<C, D>,
|
||||
) -> ProofWithPublicInputs<F, C, D> {
|
||||
Self::recursion_base_proof(node_common, HashMap::new())
|
||||
}
|
||||
|
||||
fn recursion_base_proof(
|
||||
common_data: &CommonCircuitData<F, D>,
|
||||
mut nonzero_public_inputs: HashMap<usize, F>
|
||||
) -> ProofWithPublicInputs<F, C, D>{
|
||||
dummy_proof::<F, C, D>(
|
||||
&dummy_circuit::<F, C, D>(common_data),
|
||||
nonzero_public_inputs,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Generates an array of `N` dummy leaf proofs.
|
||||
pub fn gen_n_dummy_leaf_proofs<const N: usize>(
|
||||
common_data: &CommonCircuitData<F, D>,
|
||||
) -> Result<[ProofWithPublicInputs<F, C, D>; N]> {
|
||||
let dummy_proof = Self::gen_dummy_leaf_proof(common_data)?;
|
||||
let n_dummy_vec = (0..N).map(|_| dummy_proof.clone()).collect::<Vec<_>>();
|
||||
vec_to_array::<N, ProofWithPublicInputs<F, C, D>>(n_dummy_vec)
|
||||
}
|
||||
|
||||
/// Generates an array of `N` dummy node proofs.
|
||||
pub fn gen_n_dummy_node_proofs<const N: usize>(
|
||||
node_common: &CommonCircuitData<F, D>,
|
||||
node_verifier_only_data: &VerifierOnlyCircuitData<C, D>,
|
||||
) -> Result<[ProofWithPublicInputs<F, C, D>; N]> {
|
||||
let dummy_proof = Self::get_dummy_node_proof(node_common, node_verifier_only_data);
|
||||
let n_dummy_vec = (0..N).map(|_| dummy_proof.clone()).collect::<Vec<_>>();
|
||||
vec_to_array::<N, ProofWithPublicInputs<F, C, D>>(n_dummy_vec)
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
pub mod dummy_gen;
|
||||
pub mod conditional_verifier;
|
||||
@ -1,104 +0,0 @@
|
||||
// some tests for cyclic recursion
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::time::Instant;
|
||||
use anyhow::Result;
|
||||
use plonky2::hash::hash_types::HashOut;
|
||||
use plonky2::hash::hashing::hash_n_to_hash_no_pad;
|
||||
use plonky2::hash::poseidon::PoseidonPermutation;
|
||||
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
||||
use plonky2::plonk::circuit_data::CircuitConfig;
|
||||
use plonky2::plonk::config::GenericConfig;
|
||||
use crate::params::{F, D, C, HF};
|
||||
use codex_plonky2_circuits::recursion::circuits::sampling_inner_circuit::SamplingRecursion;
|
||||
use crate::gen_input::gen_testing_circuit_input;
|
||||
use crate::params::Params;
|
||||
use codex_plonky2_circuits::recursion::cyclic::CyclicCircuit;
|
||||
|
||||
|
||||
/// Uses cyclic recursion to sample the dataset
|
||||
#[test]
|
||||
fn test_cyclic_recursion() -> Result<()> {
|
||||
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut builder = CircuitBuilder::<F, D>::new(config);
|
||||
let one = builder.one();
|
||||
|
||||
|
||||
let mut params = Params::default();
|
||||
let inner_sampling_circuit = SamplingRecursion::<F,D,HF,C>::new(params.circuit_params);
|
||||
let circ_input = gen_testing_circuit_input::<F,D>(¶ms.input_params);
|
||||
|
||||
let s = Instant::now();
|
||||
let mut cyclic_circ = CyclicCircuit::<F,D,_,C>::build_circuit::<HF>(inner_sampling_circuit)?;
|
||||
println!("build = {:?}", s.elapsed());
|
||||
let s = Instant::now();
|
||||
let proof = cyclic_circ.prove_one_layer(&circ_input)?;
|
||||
println!("prove = {:?}", s.elapsed());
|
||||
println!("num of pi = {}", proof.public_inputs.len());
|
||||
println!("pub input: {:?}", proof.public_inputs);
|
||||
let s = Instant::now();
|
||||
assert!(
|
||||
cyclic_circ.verify_latest_proof().is_ok(),
|
||||
"proof verification failed"
|
||||
);
|
||||
println!("verify = {:?}", s.elapsed());
|
||||
|
||||
// check public input hash is correct
|
||||
let mut hash_input = vec![];
|
||||
hash_input.push(circ_input.slot_index);
|
||||
hash_input.extend_from_slice(&circ_input.dataset_root.elements);
|
||||
hash_input.extend_from_slice(&circ_input.entropy.elements);
|
||||
|
||||
let hash_res = hash_n_to_hash_no_pad::<F, PoseidonPermutation<F>>(&hash_input);
|
||||
let zero_hash = HashOut::<F>::ZERO;
|
||||
let mut hash_input2 = vec![];
|
||||
hash_input2.extend_from_slice(&hash_res.elements);
|
||||
hash_input2.extend_from_slice(&zero_hash.elements);
|
||||
let hash_res = hash_n_to_hash_no_pad::<F, PoseidonPermutation<F>>(&hash_input2);
|
||||
|
||||
println!("hash input = {:?}", hash_res.elements);
|
||||
assert_eq!(
|
||||
proof.public_inputs[0..4].to_vec(),
|
||||
hash_res.elements.to_vec(),
|
||||
"public input hash incorrect"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Uses cyclic recursion to sample the dataset n times
|
||||
#[test]
|
||||
fn test_cyclic_recursion_n_layers() -> Result<()> {
|
||||
const N : usize = 2;
|
||||
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut builder = CircuitBuilder::<F, D>::new(config);
|
||||
let one = builder.one();
|
||||
|
||||
let mut params = Params::default();
|
||||
let inner_sampling_circuit = SamplingRecursion::<F,D,HF,C>::new(params.circuit_params);
|
||||
let mut circ_inputs = vec![];
|
||||
for i in 0..N {
|
||||
circ_inputs.push(gen_testing_circuit_input::<F, D>(¶ms.input_params));
|
||||
}
|
||||
|
||||
let s = Instant::now();
|
||||
let mut cyclic_circ = CyclicCircuit::<F,D,_,C>::build_circuit::<HF>(inner_sampling_circuit)?;
|
||||
println!("build = {:?}", s.elapsed());
|
||||
let s = Instant::now();
|
||||
let proof = cyclic_circ.prove_n_layers(circ_inputs)?;
|
||||
println!("prove = {:?}", s.elapsed());
|
||||
println!("num of pi = {}", proof.public_inputs.len());
|
||||
println!("pub input: {:?}", proof.public_inputs);
|
||||
let s = Instant::now();
|
||||
assert!(
|
||||
cyclic_circ.verify_latest_proof().is_ok(),
|
||||
"proof verification failed"
|
||||
);
|
||||
println!("verify = {:?}", s.elapsed());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -1,124 +0,0 @@
|
||||
// some tests for approach 2 of the tree recursion
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::time::Instant;
|
||||
use plonky2::iop::witness::PartialWitness;
|
||||
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
||||
use plonky2::plonk::circuit_data::CircuitConfig;
|
||||
use plonky2::plonk::config::{GenericConfig, Hasher};
|
||||
use plonky2::plonk::proof::{ProofWithPublicInputs};
|
||||
use codex_plonky2_circuits::circuits::sample_cells::SampleCircuit;
|
||||
use crate::params::{F, D, C, HF};
|
||||
use codex_plonky2_circuits::recursion::circuits::sampling_inner_circuit::SamplingRecursion;
|
||||
use codex_plonky2_circuits::recursion::circuits::inner_circuit::InnerCircuit;
|
||||
use codex_plonky2_circuits::recursion::circuits::leaf_circuit::{LeafCircuit};
|
||||
// use plonky2_poseidon2::poseidon2_hash::poseidon2::{Poseidon2, Poseidon2Hash};
|
||||
use crate::gen_input::gen_testing_circuit_input;
|
||||
use crate::params::Params;
|
||||
use codex_plonky2_circuits::recursion::hybrid::tree_circuit::HybridTreeRecursion;
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_hybrid_recursion() -> anyhow::Result<()> {
|
||||
const N: usize = 2; // binary tree
|
||||
const M: usize = 1; // number of proofs in leaves
|
||||
const K: usize = 8;
|
||||
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut sampling_builder = CircuitBuilder::<F, D>::new(config);
|
||||
|
||||
//------------ sampling inner circuit ----------------------
|
||||
// Circuit that does the sampling - default input
|
||||
let mut params = Params::default();
|
||||
let one_circ_input = gen_testing_circuit_input::<F,D>(¶ms.input_params);
|
||||
let samp_circ = SampleCircuit::<F,D,HF>::new(params.circuit_params);
|
||||
let inner_tar = samp_circ.sample_slot_circuit_with_public_input(&mut sampling_builder)?;
|
||||
// get generate a sampling proof
|
||||
let mut pw = PartialWitness::<F>::new();
|
||||
samp_circ.sample_slot_assign_witness(&mut pw,&inner_tar,&one_circ_input)?;
|
||||
let inner_data = sampling_builder.build::<C>();
|
||||
println!("sampling circuit degree bits = {:?}", inner_data.common.degree_bits());
|
||||
let inner_proof = inner_data.prove(pw)?;
|
||||
|
||||
// ------------------- leaf --------------------
|
||||
// leaf circuit that verifies the sampling proof
|
||||
let inner_circ = SamplingRecursion::<F,D,HF,C>::new(Params::default().circuit_params);
|
||||
let leaf_circuit = LeafCircuit::<F,D,_,M>::new(inner_circ);
|
||||
|
||||
// ------------- tree circuit ------------------
|
||||
|
||||
let mut tree = HybridTreeRecursion::<F,D,_,N,M>::new(leaf_circuit);
|
||||
|
||||
// prepare input
|
||||
let input_proofs: Vec<ProofWithPublicInputs<F, C, D>> = (0..K)
|
||||
.map(|_| {
|
||||
inner_proof.clone()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// prove tree
|
||||
|
||||
let s = Instant::now();
|
||||
let (tree_root_proof, verifier_data) = tree.prove_tree::<C,HF>(&input_proofs, inner_data.verifier_data())?;
|
||||
println!("prove = {:?}", s.elapsed());
|
||||
println!("num of pi = {}", tree_root_proof.public_inputs.len());
|
||||
let s = Instant::now();
|
||||
assert!(
|
||||
verifier_data.verify(tree_root_proof.clone()).is_ok(),
|
||||
"proof verification failed"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
tree_root_proof.public_inputs[0..4].to_vec(),
|
||||
get_expected_tree_root_pi_hash::<M, N>(input_proofs),
|
||||
"Public input of tree_root_proof does not match the expected root hash"
|
||||
);
|
||||
println!("verify = {:?}", s.elapsed());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ------------ Public Input Verification ------------
|
||||
/// Recompute the expected root public input hash outside the circuit
|
||||
fn get_expected_tree_root_pi_hash<const M: usize, const N:usize>(input_proofs: Vec<ProofWithPublicInputs<F, C, D>>) -> Vec<F>{
|
||||
// Compute the leaf hashes
|
||||
|
||||
let mut current_hashes = vec![];
|
||||
for chunk in input_proofs.chunks(M){
|
||||
let chunk_f: Vec<F> = chunk.iter()
|
||||
.flat_map(|p| p.public_inputs.iter().cloned())
|
||||
.collect();
|
||||
|
||||
let hash = HF::hash_no_pad(&chunk_f);
|
||||
current_hashes.push(hash);
|
||||
}
|
||||
|
||||
// compute parent hashes until one root hash remains
|
||||
while current_hashes.len() > 1 {
|
||||
let mut next_level_hashes = Vec::new();
|
||||
|
||||
for chunk in current_hashes.chunks(N) {
|
||||
// Ensure each chunk has exactly N elements
|
||||
assert!(
|
||||
chunk.len() == N,
|
||||
"Number of proofs is not divisible by N"
|
||||
);
|
||||
|
||||
// collect field elements
|
||||
let chunk_f: Vec<F> = chunk.iter()
|
||||
.flat_map(|h| h.elements.iter().cloned())
|
||||
.collect();
|
||||
|
||||
// Compute Poseidon2 hash of the concatenated chunk
|
||||
let hash = HF::hash_no_pad(&chunk_f);
|
||||
next_level_hashes.push(hash);
|
||||
}
|
||||
|
||||
current_hashes = next_level_hashes;
|
||||
}
|
||||
|
||||
//the expected root hash
|
||||
current_hashes[0].elements.to_vec()
|
||||
}
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
pub mod simple_recursion;
|
||||
pub mod simple_tree;
|
||||
pub mod cyclic_recursion;
|
||||
pub mod tree1;
|
||||
pub mod tree2;
|
||||
mod hybrid;
|
||||
mod uniform;
|
||||
@ -1,162 +0,0 @@
|
||||
// tests for simple recursion approaches
|
||||
|
||||
use std::time::Instant;
|
||||
use plonky2::hash::hash_types::{HashOut, NUM_HASH_OUT_ELTS};
|
||||
use plonky2::iop::witness::PartialWitness;
|
||||
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
||||
use plonky2::plonk::circuit_data::{CircuitConfig, CircuitData};
|
||||
use plonky2::plonk::config::AlgebraicHasher;
|
||||
use plonky2_field::types::Field;
|
||||
use codex_plonky2_circuits::recursion::circuits::sampling_inner_circuit::SamplingRecursion;
|
||||
use codex_plonky2_circuits::recursion::simple::simple_recursion::{SimpleRecursionCircuit, SimpleRecursionInput};
|
||||
use codex_plonky2_circuits::recursion::simple::simple_recursion_hashed_pi::{SimpleRecursionCircuitHashedPI, SimpleRecursionInputHashedPI};
|
||||
use crate::gen_input::{build_circuit, prove_circuit};
|
||||
use crate::params::{C, D, F, HF, Params};
|
||||
|
||||
|
||||
// test the simple recursion approach
|
||||
#[test]
|
||||
pub fn test_simple_recursion()-> anyhow::Result<()>{
|
||||
// number of samples in each proof
|
||||
let n_samples = 5;
|
||||
// number of inner proofs:
|
||||
const N_INNER: usize = 4;
|
||||
let mut data: Option<CircuitData<F, C, D>> = None;
|
||||
|
||||
// get proofs
|
||||
let mut proofs_with_pi = vec![];
|
||||
for i in 0..N_INNER {
|
||||
// build the circuit
|
||||
let (data_i, pw) = build_circuit(n_samples, i)?;
|
||||
proofs_with_pi.push(prove_circuit(&data_i, &pw)?);
|
||||
data = Some(data_i);
|
||||
}
|
||||
let data = data.unwrap();
|
||||
println!("inner circuit size = {:?}", data.common.degree_bits());
|
||||
|
||||
// careful here, the sampling recursion is the default so proofs should be for circuit
|
||||
// with default params
|
||||
let sampling_inner_circ = SamplingRecursion::<F,D,HF,C>::new(Params::default().circuit_params);
|
||||
let rec_circuit = SimpleRecursionCircuit::<F,D, _, N_INNER, C>::new(sampling_inner_circ);
|
||||
|
||||
// Create the circuit
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut builder = CircuitBuilder::<F, D>::new(config);
|
||||
// Create a PartialWitness
|
||||
let mut pw = PartialWitness::new();
|
||||
|
||||
let targets = rec_circuit.build_circuit(&mut builder)?;
|
||||
|
||||
let start = Instant::now();
|
||||
let agg_data = builder.build::<C>();
|
||||
println!("build time = {:?}", start.elapsed());
|
||||
println!("agg circuit size = {:?}", agg_data.common.degree_bits());
|
||||
|
||||
let mut default_entropy = HashOut::ZERO;
|
||||
default_entropy.elements[0] = F::from_canonical_u64(1234567);
|
||||
|
||||
let w = SimpleRecursionInput{
|
||||
proofs: proofs_with_pi,
|
||||
verifier_data: data.verifier_data(),
|
||||
entropy: default_entropy,
|
||||
};
|
||||
|
||||
rec_circuit.assign_witness(&mut pw,&targets,w)?;
|
||||
|
||||
let start = Instant::now();
|
||||
let proof = agg_data.prove(pw)?;
|
||||
println!("prove time = {:?}", start.elapsed());
|
||||
println!("public input count = {:?}", proof.public_inputs.len());
|
||||
|
||||
// Verify the proof
|
||||
let verifier_data = agg_data.verifier_data();
|
||||
assert!(
|
||||
verifier_data.verify(proof).is_ok(),
|
||||
"proof verification failed"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// test the simple recursion approach with hashed public input
|
||||
#[test]
|
||||
pub fn test_simple_recursion_with_hashed_pi()-> anyhow::Result<()>{
|
||||
// number of samples in each proof
|
||||
let n_samples = 5;
|
||||
// number of inner proofs:
|
||||
const N_INNER: usize = 4;
|
||||
let mut data: Option<CircuitData<F, C, D>> = None;
|
||||
|
||||
// get proofs
|
||||
let mut proofs_with_pi = vec![];
|
||||
for i in 0..N_INNER {
|
||||
// build the circuit
|
||||
let (data_i, pw) = build_circuit(n_samples, i)?;
|
||||
proofs_with_pi.push(prove_circuit(&data_i, &pw)?);
|
||||
data = Some(data_i);
|
||||
}
|
||||
let data = data.unwrap();
|
||||
println!("inner circuit size = {:?}", data.common.degree_bits());
|
||||
|
||||
// careful here, the sampling recursion is the default so proofs should be for circuit
|
||||
// with default params
|
||||
let sampling_inner_circ = SamplingRecursion::<F,D,HF,C>::new(Params::default().circuit_params);
|
||||
let rec_circuit = SimpleRecursionCircuitHashedPI::<F,D, _, N_INNER, C>::new(sampling_inner_circ);
|
||||
|
||||
// Create the circuit
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut builder = CircuitBuilder::<F, D>::new(config);
|
||||
// Create a PartialWitness
|
||||
let mut pw = PartialWitness::new();
|
||||
|
||||
let targets = rec_circuit.build_circuit::<HF>(&mut builder)?;
|
||||
|
||||
let start = Instant::now();
|
||||
let agg_data = builder.build::<C>();
|
||||
println!("build time = {:?}", start.elapsed());
|
||||
println!("agg circuit size = {:?}", agg_data.common.degree_bits());
|
||||
|
||||
let mut default_entropy = HashOut::ZERO;
|
||||
default_entropy.elements[0] = F::from_canonical_u64(1234567);
|
||||
|
||||
let w = SimpleRecursionInputHashedPI{
|
||||
proofs: proofs_with_pi.clone(),
|
||||
verifier_data: data.verifier_data(),
|
||||
};
|
||||
|
||||
rec_circuit.assign_witness(&mut pw,&targets,w)?;
|
||||
|
||||
let start = Instant::now();
|
||||
let proof = agg_data.prove(pw)?;
|
||||
println!("prove time = {:?}", start.elapsed());
|
||||
println!("public input count = {:?}", proof.public_inputs.len());
|
||||
|
||||
// Verify the proof
|
||||
let verifier_data = agg_data.verifier_data();
|
||||
assert!(
|
||||
verifier_data.verify(proof.clone()).is_ok(),
|
||||
"proof verification failed"
|
||||
);
|
||||
|
||||
let inner_pi: Vec<F> = proofs_with_pi.iter()
|
||||
.flat_map(|p| p.public_inputs.iter())
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
assert!(
|
||||
check_agg_proof_hash::<HF>(inner_pi, proof.public_inputs),
|
||||
"public input verification failed"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn check_agg_proof_hash<H: AlgebraicHasher<F>>(inner_pi: Vec<F>, agg_pi: Vec<F>) -> bool{
|
||||
|
||||
if agg_pi.len() != NUM_HASH_OUT_ELTS {
|
||||
return false;
|
||||
}
|
||||
let expected = H::hash_no_pad(&inner_pi);
|
||||
|
||||
expected == HashOut::from_vec(agg_pi)
|
||||
}
|
||||
@ -1,64 +0,0 @@
|
||||
// tests for simple recursion approaches
|
||||
|
||||
use std::time::Instant;
|
||||
use plonky2::plonk::circuit_data::{CircuitData};
|
||||
use crate::gen_input::{build_circuit, prove_circuit};
|
||||
use codex_plonky2_circuits::recursion::simple::simple_tree_recursion::aggregate_sampling_proofs_tree;
|
||||
use crate::params::{C, D, F,HF};
|
||||
|
||||
// Test simple tree recursion
|
||||
#[test]
|
||||
fn test_simple_tree_recursion() -> anyhow::Result<()> {
|
||||
// number of samples in each proof
|
||||
let n_samples = 5;
|
||||
// number of inner proofs:
|
||||
let n_inner = 4;
|
||||
let mut data: Option<CircuitData<F, C, D>> = None;
|
||||
|
||||
// get proofs
|
||||
let mut proofs_with_pi = vec![];
|
||||
for i in 0..n_inner{
|
||||
// build the circuit
|
||||
let (data_i, pw) = build_circuit(n_samples, i)?;
|
||||
proofs_with_pi.push(prove_circuit(&data_i, &pw)?);
|
||||
data = Some(data_i);
|
||||
}
|
||||
|
||||
let data = data.unwrap();
|
||||
println!("inner circuit size = {:?}", data.common.degree_bits());
|
||||
// serialization
|
||||
// let gate_serializer = DefaultGateSerializer;
|
||||
// let generator_serializer =DefaultGeneratorSerializer::<C, D>::default();
|
||||
// let data_bytes = data.to_bytes(&gate_serializer, &generator_serializer).unwrap();
|
||||
// println!("inner proof circuit data size = {} bytes", data_bytes.len());
|
||||
// let file_path = "inner_circ_data.bin";
|
||||
// // Write data to the file
|
||||
// write_bytes_to_file(data_bytes, file_path).unwrap();
|
||||
// println!("Data written to {}", file_path);
|
||||
|
||||
let start_time = Instant::now();
|
||||
let (proof, vd_agg) = aggregate_sampling_proofs_tree::<F,D,C,HF>(&proofs_with_pi, data.verifier_data())?;
|
||||
println!("prove_time = {:?}", start_time.elapsed());
|
||||
println!("num of public inputs = {}", proof.public_inputs.len());
|
||||
println!("agg pub input = {:?}", proof.public_inputs);
|
||||
println!("outer circuit size = {:?}", vd_agg.common.degree_bits());
|
||||
|
||||
// serialization
|
||||
// // let gate_serializer = DefaultGateSerializer;
|
||||
// // let generator_serializer =DefaultGeneratorSerializer::<C, D>::default();
|
||||
// let outer_data_bytes = vd_agg.to_bytes(&gate_serializer, &generator_serializer).unwrap();
|
||||
// println!("outer proof circuit data size = {} bytes", outer_data_bytes.len());
|
||||
// let file_path = "outer_circ_data.bin";
|
||||
// // Write data to the file
|
||||
// write_bytes_to_file(outer_data_bytes, file_path).unwrap();
|
||||
// println!("Data written to {}", file_path);
|
||||
|
||||
// Verify the proof
|
||||
let verifier_data = vd_agg;//.verifier_data();
|
||||
assert!(
|
||||
verifier_data.verify(proof).is_ok(),
|
||||
"Merkle proof verification failed"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -1,165 +0,0 @@
|
||||
// some tests for approach 1 of the tree recursion
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::time::Instant;
|
||||
use plonky2::plonk::config::{GenericConfig, GenericHashOut, Hasher};
|
||||
use plonky2_field::types::Field;
|
||||
use codex_plonky2_circuits::circuits::sample_cells::SampleCircuitInput;
|
||||
use crate::params::{C, D, F, HF, Params};
|
||||
use codex_plonky2_circuits::recursion::circuits::sampling_inner_circuit::SamplingRecursion;
|
||||
use codex_plonky2_circuits::recursion::circuits::inner_circuit::InnerCircuit;
|
||||
use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2;
|
||||
use crate::gen_input::get_m_default_circ_input;
|
||||
use codex_plonky2_circuits::recursion::tree1::tree_circuit::TreeRecursion;
|
||||
|
||||
/// Uses node recursion to sample the dataset
|
||||
#[test]
|
||||
fn test_tree1_node_recursion() -> anyhow::Result<()> {
|
||||
const M: usize = 1;
|
||||
const N: usize = 2;
|
||||
|
||||
let default_params = Params::default().circuit_params;
|
||||
let inner_sampling_circuit = SamplingRecursion::<F,D,HF,C>::new(default_params);
|
||||
|
||||
let circ_input = get_m_default_circ_input::<M>();
|
||||
|
||||
let s = Instant::now();
|
||||
let mut tree_circ = TreeRecursion::<F,D,_,M,N,C>::build::<HF>(inner_sampling_circuit)?;
|
||||
println!("build = {:?}", s.elapsed());
|
||||
println!("tree circuit size = {:?}", tree_circ.node_circ.cyclic_circuit_data.common.degree_bits());
|
||||
let s = Instant::now();
|
||||
let proof = tree_circ.prove(&circ_input,None, true)?;
|
||||
println!("prove = {:?}", s.elapsed());
|
||||
println!("num of pi = {}", proof.public_inputs.len());
|
||||
let s = Instant::now();
|
||||
assert!(
|
||||
tree_circ.verify_proof(proof).is_ok(),
|
||||
"proof verification failed"
|
||||
);
|
||||
println!("verify = {:?}", s.elapsed());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Uses node recursion to sample the dataset
|
||||
#[test]
|
||||
fn test_tree_recursion_approach1() -> anyhow::Result<()> {
|
||||
const M: usize = 1;
|
||||
const N: usize = 2;
|
||||
|
||||
const DEPTH: usize = 3;
|
||||
const TOTAL_INPUT: usize = (N.pow(DEPTH as u32) - 1) / (N - 1);
|
||||
|
||||
let default_params = Params::default().circuit_params;
|
||||
let inner_sampling_circuit = SamplingRecursion::<F,D,HF,C>::new(default_params);
|
||||
|
||||
let all_circ_input = get_m_default_circ_input::<TOTAL_INPUT>().to_vec();
|
||||
|
||||
let s = Instant::now();
|
||||
let mut tree_circ = TreeRecursion::<F,D,_,M,N,C>::build::<HF>(inner_sampling_circuit)?;
|
||||
println!("build = {:?}", s.elapsed());
|
||||
println!("tree circuit size = {:?}", tree_circ.node_circ.cyclic_circuit_data.common.degree_bits());
|
||||
let s = Instant::now();
|
||||
let proof = tree_circ.prove_tree(all_circ_input.clone(),DEPTH)?;
|
||||
println!("prove = {:?}", s.elapsed());
|
||||
println!("num of pi = {}", proof.public_inputs.len());
|
||||
|
||||
// Extract the final public input hash from the proof
|
||||
let final_proof_hash = &proof.public_inputs[0..4];
|
||||
|
||||
// Recompute the expected final public input hash (outside the circuit)
|
||||
let expected_hash = compute_expected_pub_input_hash(
|
||||
&all_circ_input,
|
||||
DEPTH,
|
||||
M,
|
||||
N
|
||||
)?;
|
||||
|
||||
// Check that the final hash in the proof matches the expected hash
|
||||
assert_eq!(final_proof_hash, expected_hash.as_slice(), "Public input hash mismatch");
|
||||
|
||||
let s = Instant::now();
|
||||
assert!(
|
||||
tree_circ.verify_proof(proof).is_ok(),
|
||||
"proof verification failed"
|
||||
);
|
||||
println!("verify = {:?}", s.elapsed());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Recursively compute the final public input hash for a single node in the recursion tree.
|
||||
/// This is the same logic from `NodeCircuit::build_circuit`
|
||||
/// TODO: optimize this
|
||||
fn compute_node_hash(
|
||||
all_circ_inputs: &[SampleCircuitInput<F, D>],
|
||||
depth: usize,
|
||||
current_depth: usize,
|
||||
node_idx: usize,
|
||||
M: usize,
|
||||
N: usize,
|
||||
) -> [F; 4] {
|
||||
// Calculate the index in all_circ_inputs for this node's M inputs.
|
||||
// Total inputs per layer: sum_{k=0}^{current_depth-1} M*N^k = M * ((N^current_depth - 1)/(N-1))
|
||||
let offset_for_layer = ((N.pow(current_depth as u32) - 1) / (N - 1)) * M;
|
||||
let node_start = offset_for_layer + node_idx * M;
|
||||
let node_inputs = &all_circ_inputs[node_start..node_start + M];
|
||||
|
||||
// Compute the outer public input hash:
|
||||
// public inputs are [slot_index, dataset_root.elements, entropy.elements].
|
||||
let mut outer_pi_hashes = vec![];
|
||||
for inp in node_inputs {
|
||||
let mut pi_vec = vec![inp.slot_index];
|
||||
pi_vec.extend_from_slice(&inp.dataset_root.elements);
|
||||
pi_vec.extend_from_slice(&inp.entropy.elements);
|
||||
let hash_res = HF::hash_no_pad(&pi_vec);
|
||||
outer_pi_hashes.extend_from_slice(&hash_res.elements);
|
||||
}
|
||||
|
||||
// hash all these M hashes into one
|
||||
let outer_pi_hash = HF::hash_no_pad(&outer_pi_hashes);
|
||||
|
||||
let is_leaf = current_depth == depth - 1;
|
||||
|
||||
// Compute the inner proof hash (or zero hash if leaf)
|
||||
let inner_pi_hash_or_zero = if is_leaf {
|
||||
// condition = false at leaf, so inner proofs = zero hash
|
||||
[F::ZERO; 4]
|
||||
} else {
|
||||
// condition = true at non-leaf node -> recursively compute child hashes
|
||||
let next_depth = current_depth + 1;
|
||||
let child_start = node_idx * N;
|
||||
|
||||
let mut inner_pub_input_hashes = vec![];
|
||||
for i in child_start..child_start + N {
|
||||
let child_hash = compute_node_hash(all_circ_inputs, depth, next_depth, i, M, N);
|
||||
inner_pub_input_hashes.extend_from_slice(&child_hash);
|
||||
}
|
||||
|
||||
let inner_pub_input_hash = HF::hash_no_pad(&inner_pub_input_hashes);
|
||||
inner_pub_input_hash.elements
|
||||
};
|
||||
|
||||
// Combine outer_pi_hash and inner_pi_hash_or_zero
|
||||
let mut final_input = vec![];
|
||||
final_input.extend_from_slice(&outer_pi_hash.elements);
|
||||
final_input.extend_from_slice(&inner_pi_hash_or_zero);
|
||||
|
||||
let final_hash = HF::hash_no_pad(&final_input);
|
||||
final_hash.elements
|
||||
}
|
||||
|
||||
/// Compute the expected public input hash for the entire recursion tree.
|
||||
/// This function calls `compute_node_hash` starting from the root (layer 0, node 0).
|
||||
pub fn compute_expected_pub_input_hash(
|
||||
all_circ_inputs: &[SampleCircuitInput<F, D>],
|
||||
depth: usize,
|
||||
M: usize,
|
||||
N: usize,
|
||||
) -> anyhow::Result<Vec<F>> {
|
||||
// The root node is at layer = 0 and node_idx = 0
|
||||
let final_hash = compute_node_hash(all_circ_inputs, depth, 0, 0, M, N);
|
||||
Ok(final_hash.to_vec())
|
||||
}
|
||||
}
|
||||
@ -1,198 +0,0 @@
|
||||
// some tests for approach 2 of the tree recursion
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::time::Instant;
|
||||
use plonky2::hash::hash_types::HashOut;
|
||||
use plonky2::iop::witness::PartialWitness;
|
||||
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
||||
use plonky2::plonk::circuit_data::CircuitConfig;
|
||||
use plonky2::plonk::config::{GenericConfig, Hasher};
|
||||
use plonky2::plonk::proof::{ProofWithPublicInputs};
|
||||
use codex_plonky2_circuits::circuits::sample_cells::SampleCircuit;
|
||||
use crate::params::{F, D, C, HF};
|
||||
use codex_plonky2_circuits::recursion::circuits::sampling_inner_circuit::SamplingRecursion;
|
||||
use codex_plonky2_circuits::recursion::circuits::inner_circuit::InnerCircuit;
|
||||
use codex_plonky2_circuits::recursion::circuits::leaf_circuit::{LeafCircuit, LeafInput};
|
||||
// use plonky2_poseidon2::poseidon2_hash::poseidon2::{Poseidon2, Poseidon2Hash};
|
||||
use crate::gen_input::gen_testing_circuit_input;
|
||||
use crate::params::Params;
|
||||
use codex_plonky2_circuits::recursion::tree2::{tree_circuit::TreeRecursion};
|
||||
|
||||
|
||||
/// Uses node recursion to sample the dataset
|
||||
#[test]
|
||||
fn test_leaf_circuit() -> anyhow::Result<()> {
|
||||
const M: usize = 1;
|
||||
const N: usize = 2;
|
||||
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut builder = CircuitBuilder::<F, D>::new(config);
|
||||
let params = Params::default();
|
||||
|
||||
let one_circ_input = gen_testing_circuit_input::<F,D>(¶ms.input_params);
|
||||
let samp_circ = SampleCircuit::<F,D, HF>::new(params.circuit_params);
|
||||
let inner_tar = samp_circ.sample_slot_circuit_with_public_input(&mut builder)?;
|
||||
let mut pw = PartialWitness::<F>::new();
|
||||
samp_circ.sample_slot_assign_witness(&mut pw,&inner_tar,&one_circ_input);
|
||||
let inner_d = builder.build::<C>();
|
||||
let inner_prf = inner_d.prove(pw)?;
|
||||
|
||||
let leaf_in = LeafInput::<F,D,C,M>{
|
||||
inner_proof:[inner_prf; M],
|
||||
verifier_data: inner_d.verifier_data(),
|
||||
};
|
||||
let config2 = CircuitConfig::standard_recursion_config();
|
||||
let mut builder = CircuitBuilder::<F, D>::new(config2);
|
||||
|
||||
let inner_circ = SamplingRecursion::<F,D,HF,C>::new(Params::default().circuit_params);
|
||||
let leaf_circuit = LeafCircuit::<F,D,_, M>::new(inner_circ);
|
||||
|
||||
let s = Instant::now();
|
||||
let leaf_tar = leaf_circuit.build::<C,HF>(&mut builder)?;
|
||||
let circ_data = builder.build::<C>();
|
||||
println!("build = {:?}", s.elapsed());
|
||||
println!("sampling circuit size = {:?}", circ_data.common.degree_bits());
|
||||
let s = Instant::now();
|
||||
let mut pw = PartialWitness::<F>::new();
|
||||
leaf_circuit.assign_targets::<C,HF>(&mut pw, &leaf_tar, &leaf_in)?;
|
||||
let proof = circ_data.prove(pw)?;
|
||||
println!("prove = {:?}", s.elapsed());
|
||||
println!("num of pi = {}", proof.public_inputs.len());
|
||||
let s = Instant::now();
|
||||
assert!(
|
||||
circ_data.verify(proof).is_ok(),
|
||||
"proof verification failed"
|
||||
);
|
||||
println!("verify = {:?}", s.elapsed());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tree_recursion_approach2() -> anyhow::Result<()> {
|
||||
const M: usize = 1;
|
||||
const N: usize = 2; // binary tree
|
||||
const K: usize = 2; // number of leaves/slots sampled - should be power of 2
|
||||
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut sampling_builder = CircuitBuilder::<F, D>::new(config);
|
||||
|
||||
//------------ sampling inner circuit ----------------------
|
||||
// Circuit that does the sampling - default input
|
||||
let mut params = Params::default();
|
||||
let one_circ_input = gen_testing_circuit_input::<F,D>(¶ms.input_params);
|
||||
let samp_circ = SampleCircuit::<F,D,HF>::new(params.circuit_params);
|
||||
let inner_tar = samp_circ.sample_slot_circuit_with_public_input(&mut sampling_builder)?;
|
||||
// get generate a sampling proof
|
||||
let mut pw = PartialWitness::<F>::new();
|
||||
samp_circ.sample_slot_assign_witness(&mut pw,&inner_tar,&one_circ_input)?;
|
||||
let inner_data = sampling_builder.build::<C>();
|
||||
println!("sampling circuit degree bits = {:?}", inner_data.common.degree_bits());
|
||||
let inner_proof = inner_data.prove(pw)?;
|
||||
|
||||
// ------------------- leaf --------------------
|
||||
// leaf circuit that verifies the sampling proof
|
||||
let inner_circ = SamplingRecursion::<F,D,HF,C>::new(Params::default().circuit_params);
|
||||
let leaf_circuit = LeafCircuit::<F,D,_, M>::new(inner_circ);
|
||||
|
||||
let leaf_in = LeafInput::<F,D,C, M>{
|
||||
inner_proof:[inner_proof; M],
|
||||
verifier_data: inner_data.verifier_data(),
|
||||
};
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut leaf_builder = CircuitBuilder::<F, D>::new(config);
|
||||
// build
|
||||
let s = Instant::now();
|
||||
let leaf_targets = leaf_circuit.build::<C,HF>(&mut leaf_builder)?;
|
||||
let leaf_circ_data = leaf_builder.build::<C>();
|
||||
println!("build = {:?}", s.elapsed());
|
||||
println!("leaf circuit size = {:?}", leaf_circ_data.common.degree_bits());
|
||||
// prove
|
||||
let s = Instant::now();
|
||||
let mut pw = PartialWitness::<F>::new();
|
||||
leaf_circuit.assign_targets::<C,HF>(&mut pw, &leaf_targets, &leaf_in)?;
|
||||
let leaf_proof = leaf_circ_data.prove(pw)?;
|
||||
println!("prove = {:?}", s.elapsed());
|
||||
println!("num of pi = {}", leaf_proof.public_inputs.len());
|
||||
// verify
|
||||
let s = Instant::now();
|
||||
assert!(
|
||||
leaf_circ_data.verify(leaf_proof.clone()).is_ok(),
|
||||
"proof verification failed"
|
||||
);
|
||||
println!("verify = {:?}", s.elapsed());
|
||||
|
||||
// ------------- tree circuit ------------------
|
||||
// node circuit that verifies leafs or itself
|
||||
// build
|
||||
let s = Instant::now();
|
||||
let mut tree = TreeRecursion::<F,D,C,N>::build::<_,HF, M>(leaf_circuit)?;
|
||||
println!("build = {:?}", s.elapsed());
|
||||
println!("node circuit degree bits = {:?}", tree.node.node_data.node_circuit_data.common.degree_bits());
|
||||
|
||||
// prove leaf
|
||||
let s = Instant::now();
|
||||
let leaf_proofs: Vec<ProofWithPublicInputs<F, C, D>> = (0..K)
|
||||
.map(|_| {
|
||||
leaf_proof.clone()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let tree_root_proof = tree.prove_tree(leaf_proofs.clone())?;
|
||||
println!("prove = {:?}", s.elapsed());
|
||||
println!("num of pi = {}", tree_root_proof.public_inputs.len());
|
||||
let s = Instant::now();
|
||||
assert!(
|
||||
tree.verify_proof(tree_root_proof.clone(),false).is_ok(),
|
||||
"proof verification failed"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
tree_root_proof.public_inputs[0..4].to_vec(),
|
||||
get_expected_tree_root_pi_hash::<N>(leaf_proofs),
|
||||
"Public input of tree_root_proof does not match the expected root hash"
|
||||
);
|
||||
println!("verify = {:?}", s.elapsed());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ------------ Public Input Verification ------------
|
||||
/// Recompute the expected root public input hash outside the circuit
|
||||
fn get_expected_tree_root_pi_hash<const N:usize>(leaf_proofs: Vec<ProofWithPublicInputs<F, C, D>>)
|
||||
-> Vec<F>{
|
||||
// Step 1: Extract relevant public inputs from each leaf proof
|
||||
let mut current_hashes: Vec<HashOut<F>> = leaf_proofs
|
||||
.iter()
|
||||
.map(|p|HashOut::from_vec(p.public_inputs.clone())) // Adjust index if different
|
||||
.collect();
|
||||
|
||||
// Step 2: Iteratively compute parent hashes until one root hash remains
|
||||
while current_hashes.len() > 1 {
|
||||
let mut next_level_hashes = Vec::new();
|
||||
|
||||
for chunk in current_hashes.chunks(N) {
|
||||
// Ensure each chunk has exactly N elements
|
||||
assert!(
|
||||
chunk.len() == N,
|
||||
"Number of proofs is not divisible by N"
|
||||
);
|
||||
|
||||
// collect field elements
|
||||
let chunk_f: Vec<F> = chunk.iter()
|
||||
.flat_map(|h| h.elements.iter().cloned())
|
||||
.collect();
|
||||
|
||||
// Compute Poseidon2 hash of the concatenated chunk
|
||||
let hash = HF::hash_no_pad(&chunk_f);
|
||||
next_level_hashes.push(hash);
|
||||
}
|
||||
|
||||
current_hashes = next_level_hashes;
|
||||
}
|
||||
|
||||
// The final hash is the expected root hash
|
||||
current_hashes[0].elements.to_vec()
|
||||
}
|
||||
}
|
||||
@ -1,67 +0,0 @@
|
||||
// some tests for approach 2 of the tree recursion
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::fs;
|
||||
use plonky2::iop::witness::{PartialWitness};
|
||||
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
||||
use plonky2::plonk::circuit_data::{CircuitConfig};
|
||||
use plonky2::plonk::config::{ GenericConfig};
|
||||
use plonky2::plonk::proof::{ProofWithPublicInputs};
|
||||
use codex_plonky2_circuits::circuits::sample_cells::SampleCircuit;
|
||||
use crate::params::{F, D, C, HF};
|
||||
use crate::gen_input::gen_testing_circuit_input;
|
||||
use crate::params::Params;
|
||||
use codex_plonky2_circuits::recursion::uniform::{tree::TreeRecursion};
|
||||
|
||||
#[test]
|
||||
fn test_uniform_recursion() -> anyhow::Result<()> {
|
||||
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut sampling_builder = CircuitBuilder::<F, D>::new(config);
|
||||
|
||||
//------------ sampling inner circuit ----------------------
|
||||
// Circuit that does the sampling - 100 samples
|
||||
let mut params = Params::default();
|
||||
params.input_params.n_samples = 100;
|
||||
params.circuit_params.n_samples = 100;
|
||||
let one_circ_input = gen_testing_circuit_input::<F,D>(¶ms.input_params);
|
||||
let samp_circ = SampleCircuit::<F,D,HF>::new(params.circuit_params);
|
||||
let inner_tar = samp_circ.sample_slot_circuit_with_public_input(&mut sampling_builder)?;
|
||||
// get generate a sampling proof
|
||||
let mut pw = PartialWitness::<F>::new();
|
||||
samp_circ.sample_slot_assign_witness(&mut pw,&inner_tar,&one_circ_input)?;
|
||||
let inner_data = sampling_builder.build::<C>();
|
||||
println!("sampling circuit degree bits = {:?}", inner_data.common.degree_bits());
|
||||
let inner_proof = inner_data.prove(pw)?;
|
||||
|
||||
let proofs: Vec<ProofWithPublicInputs<F, C, D>> = (0..16).map(|i| inner_proof.clone()).collect();
|
||||
|
||||
// ------------------- tree --------------------
|
||||
const N: usize = 1;
|
||||
const M: usize = 4;
|
||||
|
||||
let mut tree = TreeRecursion::<F,D,C,HF, N, M>::build(inner_data.common.clone())?;
|
||||
|
||||
// serialize circuit into JSON
|
||||
let common_circuit_data_serialized = serde_json::to_string(&tree.get_leaf_verifier_data().common ).unwrap();
|
||||
fs::write("leaf_common.json" , common_circuit_data_serialized) .expect("Unable to write file");
|
||||
|
||||
// serialize circuit into JSON
|
||||
let common_circuit_data_serialized = serde_json::to_string(&tree.get_node_verifier_data().common ).unwrap();
|
||||
fs::write("node_common.json" , common_circuit_data_serialized) .expect("Unable to write file");
|
||||
|
||||
|
||||
let root = tree.prove_tree(&proofs, &inner_data.verifier_only)?;
|
||||
println!("pub input size = {}", root.public_inputs.len());
|
||||
|
||||
let inner_pi: Vec<Vec<F>> = proofs.iter().map(|p| p.public_inputs.clone()).collect();
|
||||
|
||||
assert!(
|
||||
tree.verify_proof_and_public_input(root,inner_pi,&inner_data.verifier_data()).is_ok(),
|
||||
"proof verification failed"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -1,395 +0,0 @@
|
||||
// some tests for approach 2 of the tree recursion
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::fs;
|
||||
use std::time::Instant;
|
||||
use plonky2::gates::constant::ConstantGate;
|
||||
use plonky2::iop::witness::{PartialWitness, WitnessWrite};
|
||||
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
||||
use plonky2::plonk::circuit_data::{CircuitConfig, CommonCircuitData, VerifierCircuitTarget};
|
||||
use plonky2::plonk::config::{AlgebraicHasher, GenericConfig, Hasher};
|
||||
use plonky2::plonk::proof::{ProofWithPublicInputs, ProofWithPublicInputsTarget};
|
||||
use codex_plonky2_circuits::circuits::sample_cells::SampleCircuit;
|
||||
use crate::params::{F, D, C, HF};
|
||||
use codex_plonky2_circuits::recursion::circuits::sampling_inner_circuit::SamplingRecursion;
|
||||
// use codex_plonky2_circuits::recursion::circuits::inner_circuit::InnerCircuit;
|
||||
// use codex_plonky2_circuits::recursion::circuits::leaf_circuit::{LeafCircuit};
|
||||
// use plonky2_poseidon2::poseidon2_hash::poseidon2::{Poseidon2, Poseidon2Hash};
|
||||
use crate::gen_input::gen_testing_circuit_input;
|
||||
use crate::params::Params;
|
||||
use codex_plonky2_circuits::recursion::uniform::{leaf::{LeafCircuit,LeafInput,LeafTargets},node::{NodeCircuit,NodeInput,NodeTargets}, tree::TreeRecursion};
|
||||
|
||||
#[test]
|
||||
fn test_treeuniform() -> anyhow::Result<()> {
|
||||
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut sampling_builder = CircuitBuilder::<F, D>::new(config);
|
||||
|
||||
//------------ sampling inner circuit ----------------------
|
||||
// Circuit that does the sampling - default input
|
||||
let mut params = Params::default();
|
||||
params.input_params.n_samples = 100;
|
||||
params.circuit_params.n_samples = 100;
|
||||
let one_circ_input = gen_testing_circuit_input::<F,D>(¶ms.input_params);
|
||||
let samp_circ = SampleCircuit::<F,D,HF>::new(params.circuit_params);
|
||||
let inner_tar = samp_circ.sample_slot_circuit_with_public_input(&mut sampling_builder)?;
|
||||
// get generate a sampling proof
|
||||
let mut pw = PartialWitness::<F>::new();
|
||||
samp_circ.sample_slot_assign_witness(&mut pw,&inner_tar,&one_circ_input)?;
|
||||
let inner_data = sampling_builder.build::<C>();
|
||||
println!("sampling circuit degree bits = {:?}", inner_data.common.degree_bits());
|
||||
let inner_proof = inner_data.prove(pw)?;
|
||||
|
||||
let proofs: Vec<ProofWithPublicInputs<F, C, D>> = (0..4).map(|i| inner_proof.clone()).collect();
|
||||
|
||||
// ------------------- tree --------------------
|
||||
|
||||
let mut tree = TreeRecursion::<F,D,C,HF>::build(inner_data.common.clone())?;
|
||||
|
||||
let root = tree.prove_tree(&proofs, inner_data.verifier_data())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_2uniform() -> anyhow::Result<()> {
|
||||
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut sampling_builder = CircuitBuilder::<F, D>::new(config);
|
||||
|
||||
//------------ sampling inner circuit ----------------------
|
||||
// Circuit that does the sampling - default input
|
||||
let mut params = Params::default();
|
||||
params.input_params.n_samples = 100;
|
||||
params.circuit_params.n_samples = 100;
|
||||
let one_circ_input = gen_testing_circuit_input::<F,D>(¶ms.input_params);
|
||||
let samp_circ = SampleCircuit::<F,D,HF>::new(params.circuit_params);
|
||||
let inner_tar = samp_circ.sample_slot_circuit_with_public_input(&mut sampling_builder)?;
|
||||
// get generate a sampling proof
|
||||
let mut pw = PartialWitness::<F>::new();
|
||||
samp_circ.sample_slot_assign_witness(&mut pw,&inner_tar,&one_circ_input)?;
|
||||
let inner_data = sampling_builder.build::<C>();
|
||||
println!("sampling circuit degree bits = {:?}", inner_data.common.degree_bits());
|
||||
let inner_proof = inner_data.prove(pw)?;
|
||||
|
||||
// serialize circuit into JSON
|
||||
let common_circuit_data_serialized = serde_json::to_string(&inner_data.common ).unwrap();
|
||||
fs::write("circ_common.json" , common_circuit_data_serialized) .expect("Unable to write file");
|
||||
|
||||
|
||||
// ------------------- leaf --------------------
|
||||
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut builder = CircuitBuilder::<F, D>::new(config.clone());
|
||||
|
||||
let leaf_circ = LeafCircuit::<F,D,C,HF>::new(inner_data.common.clone());
|
||||
let leaf_targ = leaf_circ.build(&mut builder)?;
|
||||
|
||||
// let (proof_targ, vd_targ) = build_proof_ver_circuit::<HF>(&mut builder,&inner_data.common).unwrap();
|
||||
// let leaf_targets = leaf_circuit.build::<C,HF>(&mut builder)?;
|
||||
|
||||
// // add a ConstantGate
|
||||
// builder.add_gate(
|
||||
// ConstantGate::new(config.num_constants),
|
||||
// vec![],
|
||||
// );
|
||||
|
||||
let leaf_data = builder.build::<C>();
|
||||
println!("leaf circuit size = {:?}", leaf_data.common.degree_bits());
|
||||
|
||||
// serialize circuit into JSON
|
||||
let common_circuit_data_serialized = serde_json::to_string(&leaf_data.common ).unwrap();
|
||||
fs::write("leaf_common.json" , common_circuit_data_serialized) .expect("Unable to write file");
|
||||
|
||||
// prove
|
||||
let mut pw = PartialWitness::<F>::new();
|
||||
// pw.set_proof_with_pis_target(&proof_targ, &inner_proof)?;
|
||||
// pw.set_verifier_data_target(&vd_targ,&inner_data.verifier_only)?;
|
||||
let leaf_in = LeafInput{
|
||||
inner_proof: inner_proof.clone(),
|
||||
verifier_data: inner_data.verifier_data().clone(),
|
||||
};
|
||||
|
||||
leaf_circ.assign_targets(&mut pw, &leaf_targ, &leaf_in)?;
|
||||
|
||||
let leaf_proof = leaf_data.prove(pw)?;
|
||||
|
||||
leaf_data.verify(leaf_proof.clone())?;
|
||||
|
||||
// ------------- node1 circuit ------------------
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut builder = CircuitBuilder::<F, D>::new(config);
|
||||
|
||||
let node_circ = NodeCircuit::<F,D,C,HF>::new(leaf_data.common.clone());
|
||||
let node_targ = node_circ.build(&mut builder)?;
|
||||
|
||||
// let (proof_targ, vd_targ) = build_node_proof_circuit::<HF>(&mut builder,&leaf_data.common).unwrap();
|
||||
// let leaf_targets = leaf_circuit.build::<C,HF>(&mut builder)?;
|
||||
|
||||
let node_data = builder.build::<C>();
|
||||
println!("node circuit size = {:?}", node_data.common.degree_bits());
|
||||
|
||||
// serialize circuit into JSON
|
||||
let common_circuit_data_serialized = serde_json::to_string(&node_data.common ).unwrap();
|
||||
fs::write("node_common.json" , common_circuit_data_serialized) .expect("Unable to write file");
|
||||
|
||||
// prove
|
||||
let mut pw = PartialWitness::<F>::new();
|
||||
// pw.set_proof_with_pis_target(&proof_targ[0], &leaf_proof)?;
|
||||
// pw.set_proof_with_pis_target(&proof_targ[1], &leaf_proof)?;
|
||||
// pw.set_verifier_data_target(&vd_targ,&leaf_data.verifier_only)?;
|
||||
|
||||
let node_in = NodeInput{
|
||||
node_proofs: [leaf_proof.clone(),leaf_proof.clone()],
|
||||
verifier_data: leaf_data.verifier_data(),
|
||||
};
|
||||
|
||||
node_circ.assign_targets(&mut pw, &node_targ,&node_in)?;
|
||||
|
||||
let node_proof = node_data.prove(pw)?;
|
||||
|
||||
node_data.verify(node_proof.clone())?;
|
||||
|
||||
|
||||
// ------------- check ----------------
|
||||
// prove
|
||||
let mut pw = PartialWitness::<F>::new();
|
||||
|
||||
let node_in = NodeInput{
|
||||
node_proofs: [node_proof.clone(),node_proof.clone()],
|
||||
verifier_data: node_data.verifier_data(),
|
||||
};
|
||||
|
||||
node_circ.assign_targets(&mut pw, &node_targ,&node_in)?;
|
||||
|
||||
let node2_proof = node_data.prove(pw)?;
|
||||
|
||||
node_data.verify(node2_proof.clone())?;
|
||||
|
||||
|
||||
// ------------- node2 circuit ------------------
|
||||
// let config = CircuitConfig::standard_recursion_config();
|
||||
// let mut builder = CircuitBuilder::<F, D>::new(config);
|
||||
//
|
||||
// let (proof_targ, vd_targ) = build_node_proof_circuit::<HF>(&mut builder,&node_data.common).unwrap();
|
||||
// // let leaf_targets = leaf_circuit.build::<C,HF>(&mut builder)?;
|
||||
//
|
||||
// let node2_data = builder.build::<C>();
|
||||
// println!("node2 circuit size = {:?}", node2_data.common.degree_bits());
|
||||
//
|
||||
// // serialize circuit into JSON
|
||||
// let common_circuit_data_serialized = serde_json::to_string(&node2_data.common ).unwrap();
|
||||
// fs::write("node2_common.json" , common_circuit_data_serialized) .expect("Unable to write file");
|
||||
//
|
||||
// // prove
|
||||
// let mut pw = PartialWitness::<F>::new();
|
||||
// pw.set_proof_with_pis_target(&proof_targ[0], &leaf_proof)?;
|
||||
// pw.set_proof_with_pis_target(&proof_targ[1], &leaf_proof)?;
|
||||
// pw.set_verifier_data_target(&vd_targ,&leaf_data.verifier_only)?;
|
||||
//
|
||||
// let node_proof = node2_data.prove(pw)?;
|
||||
|
||||
|
||||
// prove node
|
||||
// let mut pw = PartialWitness::<F>::new();
|
||||
// pw.set_proof_with_pis_target(&proof_targ[0], &node_proof)?;
|
||||
// pw.set_proof_with_pis_target(&proof_targ[1], &node_proof)?;
|
||||
// pw.set_verifier_data_target(&vd_targ,&node_data.verifier_only)?;
|
||||
//
|
||||
// let node2_proof = node_data.prove(pw)?;
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_uniform_recursion() -> anyhow::Result<()> {
|
||||
// const N: usize = 2; // binary tree
|
||||
// const M: usize = 1; // number of proofs in leaves
|
||||
// const K: usize = 8;
|
||||
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut sampling_builder = CircuitBuilder::<F, D>::new(config);
|
||||
|
||||
//------------ sampling inner circuit ----------------------
|
||||
// Circuit that does the sampling - default input
|
||||
let mut params = Params::default();
|
||||
params.input_params.n_samples = 100;
|
||||
params.circuit_params.n_samples = 100;
|
||||
let one_circ_input = gen_testing_circuit_input::<F,D>(¶ms.input_params);
|
||||
let samp_circ = SampleCircuit::<F,D,HF>::new(params.circuit_params);
|
||||
let inner_tar = samp_circ.sample_slot_circuit_with_public_input(&mut sampling_builder)?;
|
||||
// get generate a sampling proof
|
||||
let mut pw = PartialWitness::<F>::new();
|
||||
samp_circ.sample_slot_assign_witness(&mut pw,&inner_tar,&one_circ_input)?;
|
||||
let inner_data = sampling_builder.build::<C>();
|
||||
println!("sampling circuit degree bits = {:?}", inner_data.common.degree_bits());
|
||||
let inner_proof = inner_data.prove(pw)?;
|
||||
|
||||
// serialize circuit into JSON
|
||||
let common_circuit_data_serialized = serde_json::to_string(&inner_data.common ).unwrap();
|
||||
fs::write("circ_common.json" , common_circuit_data_serialized) .expect("Unable to write file");
|
||||
|
||||
|
||||
// ------------------- leaf --------------------
|
||||
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut builder = CircuitBuilder::<F, D>::new(config.clone());
|
||||
|
||||
let (proof_targ, vd_targ) = build_proof_ver_circuit::<HF>(&mut builder,&inner_data.common).unwrap();
|
||||
// let leaf_targets = leaf_circuit.build::<C,HF>(&mut builder)?;
|
||||
|
||||
// // add a ConstantGate
|
||||
// builder.add_gate(
|
||||
// ConstantGate::new(config.num_constants),
|
||||
// vec![],
|
||||
// );
|
||||
|
||||
let leaf_data = builder.build::<C>();
|
||||
println!("leaf circuit size = {:?}", leaf_data.common.degree_bits());
|
||||
|
||||
// serialize circuit into JSON
|
||||
let common_circuit_data_serialized = serde_json::to_string(&leaf_data.common ).unwrap();
|
||||
fs::write("leaf_common.json" , common_circuit_data_serialized) .expect("Unable to write file");
|
||||
|
||||
// prove
|
||||
let mut pw = PartialWitness::<F>::new();
|
||||
pw.set_proof_with_pis_target(&proof_targ, &inner_proof)?;
|
||||
pw.set_verifier_data_target(&vd_targ,&inner_data.verifier_only)?;
|
||||
|
||||
let leaf_proof = leaf_data.prove(pw)?;
|
||||
|
||||
// ------------- node1 circuit ------------------
|
||||
let config = CircuitConfig::standard_recursion_config();
|
||||
let mut builder = CircuitBuilder::<F, D>::new(config);
|
||||
|
||||
let (proof_targ, vd_targ) = build_node_proof_circuit::<HF>(&mut builder,&leaf_data.common).unwrap();
|
||||
// let leaf_targets = leaf_circuit.build::<C,HF>(&mut builder)?;
|
||||
|
||||
let node_data = builder.build::<C>();
|
||||
println!("node circuit size = {:?}", node_data.common.degree_bits());
|
||||
|
||||
// serialize circuit into JSON
|
||||
let common_circuit_data_serialized = serde_json::to_string(&node_data.common ).unwrap();
|
||||
fs::write("node_common.json" , common_circuit_data_serialized) .expect("Unable to write file");
|
||||
|
||||
// prove
|
||||
let mut pw = PartialWitness::<F>::new();
|
||||
pw.set_proof_with_pis_target(&proof_targ[0], &leaf_proof)?;
|
||||
pw.set_proof_with_pis_target(&proof_targ[1], &leaf_proof)?;
|
||||
pw.set_verifier_data_target(&vd_targ,&leaf_data.verifier_only)?;
|
||||
|
||||
let node_proof = node_data.prove(pw)?;
|
||||
|
||||
|
||||
// ------------- node2 circuit ------------------
|
||||
// let config = CircuitConfig::standard_recursion_config();
|
||||
// let mut builder = CircuitBuilder::<F, D>::new(config);
|
||||
//
|
||||
// let (proof_targ, vd_targ) = build_node_proof_circuit::<HF>(&mut builder,&node_data.common).unwrap();
|
||||
// // let leaf_targets = leaf_circuit.build::<C,HF>(&mut builder)?;
|
||||
//
|
||||
// let node2_data = builder.build::<C>();
|
||||
// println!("node2 circuit size = {:?}", node2_data.common.degree_bits());
|
||||
//
|
||||
// // serialize circuit into JSON
|
||||
// let common_circuit_data_serialized = serde_json::to_string(&node2_data.common ).unwrap();
|
||||
// fs::write("node2_common.json" , common_circuit_data_serialized) .expect("Unable to write file");
|
||||
//
|
||||
// // prove
|
||||
// let mut pw = PartialWitness::<F>::new();
|
||||
// pw.set_proof_with_pis_target(&proof_targ[0], &leaf_proof)?;
|
||||
// pw.set_proof_with_pis_target(&proof_targ[1], &leaf_proof)?;
|
||||
// pw.set_verifier_data_target(&vd_targ,&leaf_data.verifier_only)?;
|
||||
//
|
||||
// let node_proof = node2_data.prove(pw)?;
|
||||
|
||||
|
||||
// prove node
|
||||
let mut pw = PartialWitness::<F>::new();
|
||||
pw.set_proof_with_pis_target(&proof_targ[0], &node_proof)?;
|
||||
pw.set_proof_with_pis_target(&proof_targ[1], &node_proof)?;
|
||||
pw.set_verifier_data_target(&vd_targ,&node_data.verifier_only)?;
|
||||
|
||||
let node2_proof = node_data.prove(pw)?;
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// builds the node circuit
|
||||
pub fn build_proof_ver_circuit<
|
||||
H: AlgebraicHasher<F>,
|
||||
>(
|
||||
builder: &mut CircuitBuilder<F, D>,
|
||||
common_data: &CommonCircuitData<F, D>,
|
||||
) -> anyhow::Result<(ProofWithPublicInputsTarget<D>, VerifierCircuitTarget)>{
|
||||
|
||||
// the proof virtual targets
|
||||
// let mut proof_targets = vec![];
|
||||
let mut inner_pub_input = vec![];
|
||||
// for _i in 0..N {
|
||||
let vir_proof = builder.add_virtual_proof_with_pis(common_data);
|
||||
// collect the public input
|
||||
inner_pub_input.extend_from_slice(&vir_proof.public_inputs);
|
||||
// collect the proof targets
|
||||
// proof_targets.push(vir_proof);
|
||||
// }
|
||||
// hash the public input & make it public
|
||||
let hash_inner_pub_input = builder.hash_n_to_hash_no_pad::<H>(inner_pub_input);
|
||||
builder.register_public_inputs(&hash_inner_pub_input.elements);
|
||||
|
||||
// virtual target for the verifier data
|
||||
let inner_verifier_data = builder.add_virtual_verifier_data(common_data.config.fri_config.cap_height);
|
||||
|
||||
// verify the proofs in-circuit
|
||||
// for i in 0..N {
|
||||
builder.verify_proof::<C>(&vir_proof,&inner_verifier_data,&common_data);
|
||||
// }
|
||||
// let proof_target_array = vec_to_array::<N,ProofWithPublicInputsTarget<D>>(proof_targets)?;
|
||||
|
||||
Ok(
|
||||
(vir_proof,
|
||||
inner_verifier_data)
|
||||
)
|
||||
}
|
||||
|
||||
/// builds the node circuit
|
||||
pub fn build_node_proof_circuit<
|
||||
H: AlgebraicHasher<F>,
|
||||
>(
|
||||
builder: &mut CircuitBuilder<F, D>,
|
||||
common_data: &CommonCircuitData<F, D>,
|
||||
) -> anyhow::Result<(Vec<ProofWithPublicInputsTarget<D>>, VerifierCircuitTarget)>{
|
||||
|
||||
// the proof virtual targets
|
||||
let mut proof_targets = vec![];
|
||||
let mut inner_pub_input = vec![];
|
||||
for _i in 0..2 {
|
||||
let vir_proof = builder.add_virtual_proof_with_pis(common_data);
|
||||
// collect the public input
|
||||
inner_pub_input.extend_from_slice(&vir_proof.public_inputs);
|
||||
// collect the proof targets
|
||||
proof_targets.push(vir_proof);
|
||||
}
|
||||
// hash the public input & make it public
|
||||
let hash_inner_pub_input = builder.hash_n_to_hash_no_pad::<H>(inner_pub_input);
|
||||
builder.register_public_inputs(&hash_inner_pub_input.elements);
|
||||
|
||||
// virtual target for the verifier data
|
||||
let inner_verifier_data = builder.add_virtual_verifier_data(common_data.config.fri_config.cap_height);
|
||||
|
||||
// verify the proofs in-circuit
|
||||
for i in 0..2 {
|
||||
builder.verify_proof::<C>(&proof_targets[i],&inner_verifier_data,&common_data);
|
||||
}
|
||||
// let proof_target_array = vec_to_array::<N,ProofWithPublicInputsTarget<D>>(proof_targets)?;
|
||||
|
||||
Ok(
|
||||
(proof_targets,
|
||||
inner_verifier_data)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user