fix benchmarks

This commit is contained in:
M Alghazwi 2024-11-14 12:26:37 +01:00
parent e843d68a4d
commit 31d8cb1447
14 changed files with 2306 additions and 357 deletions

View File

@ -10,4 +10,12 @@ Repository organization
- [`codex-plonky2-circuits`](./codex-plonky2-circuits) contains the codex proof circuits tailored specifically for plonky2. These circuits have the functionality as those in [**here**](https://github.com/codex-storage/codex-storage-proofs-circuits) - [`codex-plonky2-circuits`](./codex-plonky2-circuits) contains the codex proof circuits tailored specifically for plonky2. These circuits have the functionality as those in [**here**](https://github.com/codex-storage/codex-storage-proofs-circuits)
- [`proof-input`](./proof-input) contains the lib code to generate proof input for the circuit from fake dataset.
- [`workflow`](./workflow) contains the scripts and example code to generate input, run the circuits, generate a proof, and verify the proof.
Documentation
-----------------
To be added soon.
**WARNING**: This repository contains work-in-progress prototypes, and has not received careful code review. It is NOT ready for production use. **WARNING**: This repository contains work-in-progress prototypes, and has not received careful code review. It is NOT ready for production use.

View File

@ -26,4 +26,4 @@ writeup coming soon...
see [`workflow`](../workflow) for how to use the circuits and run them. see [`workflow`](../workflow) for how to use the circuits and run them.
## Benchmarks ## Benchmarks
see [`BENCHMARKS.md`](../proof-input/BENCHMARKS.md) see [`BENCHMARKS.md`](../workflow/BENCHMARKS.md)

View File

@ -21,6 +21,26 @@ pub struct CircuitParams{
pub n_samples: usize, pub n_samples: usize,
} }
// hardcoded default constants
const DEFAULT_MAX_DEPTH:usize = 32;
const DEFAULT_MAX_LOG2_N_SLOTS:usize = 8;
const DEFAULT_BLOCK_TREE_DEPTH:usize = 5;
const DEFAULT_N_FIELD_ELEMS_PER_CELL:usize = 272;
const DEFAULT_N_SAMPLES:usize = 5;
/// Implement the Default trait for Params using the hardcoded constants
impl Default for CircuitParams {
fn default() -> Self {
Self{
max_depth: DEFAULT_MAX_DEPTH,
max_log2_n_slots: DEFAULT_MAX_LOG2_N_SLOTS,
block_tree_depth: DEFAULT_BLOCK_TREE_DEPTH,
n_field_elems_per_cell: DEFAULT_N_FIELD_ELEMS_PER_CELL,
n_samples: DEFAULT_N_SAMPLES,
}
}
}
impl CircuitParams { impl CircuitParams {
/// Creates a new `CircuitParams` struct from environment. /// Creates a new `CircuitParams` struct from environment.
/// ///
@ -32,12 +52,12 @@ impl CircuitParams {
/// ///
/// Returns an error if any environment variable is missing or fails to parse. /// Returns an error if any environment variable is missing or fails to parse.
pub fn from_env() -> Result<Self> { pub fn from_env() -> Result<Self> {
let max_depth = env::var("MAX_DEPTH") let MAX_DEPTH = env::var("MAX_DEPTH")
.context("MAX_DEPTH is not set")? .context("MAX_DEPTH is not set")?
.parse::<usize>() .parse::<usize>()
.context("MAX_DEPTH must be a valid usize")?; .context("MAX_DEPTH must be a valid usize")?;
let max_log2_n_slots = env::var("MAX_LOG2_N_SLOTS") let MAX_LOG2_N_SLOTS = env::var("MAX_LOG2_N_SLOTS")
.context("MAX_LOG2_N_SLOTS is not set")? .context("MAX_LOG2_N_SLOTS is not set")?
.parse::<usize>() .parse::<usize>()
.context("MAX_LOG2_N_SLOTS must be a valid usize")?; .context("MAX_LOG2_N_SLOTS must be a valid usize")?;
@ -58,8 +78,8 @@ impl CircuitParams {
.context("N_SAMPLES must be a valid usize")?; .context("N_SAMPLES must be a valid usize")?;
Ok(CircuitParams { Ok(CircuitParams {
max_depth, max_depth: MAX_DEPTH,
max_log2_n_slots, max_log2_n_slots: MAX_LOG2_N_SLOTS,
block_tree_depth, block_tree_depth,
n_field_elems_per_cell, n_field_elems_per_cell,
n_samples, n_samples,

View File

@ -14,15 +14,3 @@ plonky2 = { version = "0.2.2" }
plonky2_field = { version = "0.2.2", default-features = false } plonky2_field = { version = "0.2.2", default-features = false }
plonky2_poseidon2 = { path = "../plonky2_poseidon2" } plonky2_poseidon2 = { path = "../plonky2_poseidon2" }
codex-plonky2-circuits = { path = "../codex-plonky2-circuits" } codex-plonky2-circuits = { path = "../codex-plonky2-circuits" }
[[bench]]
name = "safe_circuit"
harness = false
[[bench]]
name = "prove_cells"
harness = false
[[bench]]
name = "sample_cells"
harness = false

View File

@ -20,4 +20,4 @@ the [`plonky2 codex proof circuits`](../codex-plonky2-circuits). Currently only
see [`workflow`](../workflow) for how to generate proof input. see [`workflow`](../workflow) for how to generate proof input.
## Benchmarks ## Benchmarks
see [`BENCHMARKS.md`](../proof-input/BENCHMARKS.md) see [`BENCHMARKS.md`](../workflow/BENCHMARKS.md)

View File

@ -1,152 +0,0 @@
use criterion::{criterion_group, criterion_main, Criterion};
use anyhow::Result;
use std::time::{Duration, Instant};
use codex_plonky2_circuits::{
merkle_tree::merkle_safe::MerkleProof,
circuits::merkle_circuit::MerkleTreeCircuit,
};
use plonky2::plonk::circuit_data::{CircuitConfig, CircuitData};
use plonky2::plonk::config::{AlgebraicHasher, GenericConfig, Hasher, PoseidonGoldilocksConfig};
use plonky2::iop::witness::PartialWitness;
use plonky2::hash::poseidon::PoseidonHash;
use plonky2::field::extension::Extendable;
use plonky2::hash::hash_types::RichField;
use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2;
use std::marker::PhantomData;
use plonky2::plonk::circuit_builder::CircuitBuilder;
use codex_plonky2_circuits::circuits::prove_single_cell::SlotTreeCircuit;
macro_rules! pretty_print {
($($arg:tt)*) => {
print!("\x1b[0;36mINFO ===========>\x1b[0m ");
println!($($arg)*);
}
}
// Hash function used
type HF = PoseidonHash;
fn prepare_data<
F: RichField + Extendable<D> + Poseidon2,
C: GenericConfig<D, F = F>,
const D: usize,
H: Hasher<F> + AlgebraicHasher<F>,
>(N: usize) -> Result<(
SlotTreeCircuit<F, C, D, H>,
Vec<usize>,
Vec<MerkleProof<F, H>>,
)> {
// Initialize the slot tree with default data
let slot_tree = SlotTreeCircuit::<F, C,D, H>::default();
// Select N leaf indices to prove
let leaf_indices: Vec<usize> = (0..N).collect();
// Get the Merkle proofs for the selected leaves
let proofs: Vec<_> = leaf_indices
.iter()
.map(|&leaf_index| slot_tree.get_proof(leaf_index))
.collect();
Ok((slot_tree, leaf_indices, proofs))
}
fn build_circuit<
F: RichField + Extendable<D> + Poseidon2,
C: GenericConfig<D, F = F>,
const D: usize,
H: Hasher<F> + AlgebraicHasher<F>,
>(
slot_tree: &SlotTreeCircuit<F, C, D, H>,
leaf_indices: &[usize],
proofs: &[MerkleProof<F, H>],
) -> Result<(CircuitData<F, C, D>, PartialWitness<F>)>
{
// 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();
// For each proof, create targets, add constraints, and assign witnesses
for (i, &leaf_index) in leaf_indices.iter().enumerate() {
// Build the circuit for each proof
let mut targets = SlotTreeCircuit::<F,C,D,H>::prove_single_cell(&mut builder);
// Assign witnesses for each proof
slot_tree.single_cell_assign_witness(
&mut pw,
&mut targets,
leaf_index,
&slot_tree.cell_data[leaf_index],
proofs[i].clone(),
)?;
}
// Build the circuit
let data = builder.build::<C>();
Ok((data, pw))
}
fn single_cell_proof_benchmark(c: &mut Criterion) {
let mut group = c.benchmark_group("Single Cell Proof Benchmark");
// Circuit parameters
const D: usize = 2;
type C = PoseidonGoldilocksConfig;
type F = <C as GenericConfig<D>>::F;
type H = PoseidonHash;
// Prepare the data that will be used in all steps
let N = 5; // Number of leaves to prove
let (slot_tree, leaf_indices, proofs) = prepare_data::<F, C, D, H>(N).unwrap();
// Benchmark the circuit building
group.bench_function("Single Cell Proof Build", |b| {
b.iter(|| {
build_circuit::<F, C, D, H>(&slot_tree, &leaf_indices, &proofs).unwrap();
})
});
// Build the circuit
let (data, pw) = build_circuit::<F, C, D, H>(&slot_tree, &leaf_indices, &proofs).unwrap();
pretty_print!(
"Circuit size: 2^{} gates",
data.common.degree_bits()
);
let start_time = Instant::now();
let proof_with_pis = data.prove(pw.clone()).unwrap();
println!("prove_time = {:?}", start_time.elapsed());
// Benchmark the proving time
group.bench_function("Single Cell Proof Prove", |b| {
b.iter(|| {
let _proof_with_pis = data.prove(pw.clone()).unwrap();
})
});
// Generate the proof
let proof_with_pis = data.prove(pw.clone()).unwrap();
let verifier_data = data.verifier_data();
pretty_print!("Proof size: {} bytes", proof_with_pis.to_bytes().len());
// Benchmark the verification time
group.bench_function("Single Cell Proof Verify", |b| {
b.iter(|| {
verifier_data.verify(proof_with_pis.clone()).unwrap();
})
});
group.finish();
}
criterion_group!(name = benches;
config = Criterion::default().sample_size(10);
targets = single_cell_proof_benchmark);
criterion_main!(benches);

View File

@ -1,129 +0,0 @@
use criterion::{criterion_group, criterion_main, Criterion};
use anyhow::Result;
use std::time::{Duration, Instant};
use plonky2::plonk::circuit_data::{CircuitConfig, CircuitData};
use plonky2::plonk::config::{AlgebraicHasher, GenericConfig, Hasher, PoseidonGoldilocksConfig};
use plonky2::iop::witness::PartialWitness;
use plonky2::hash::poseidon::PoseidonHash;
use plonky2::field::extension::Extendable;
use plonky2::hash::hash_types::RichField;
use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2;
use plonky2::plonk::circuit_builder::CircuitBuilder;
use codex_plonky2_circuits::circuits::params::TESTING_SLOT_INDEX;
use codex_plonky2_circuits::circuits::sample_cells::SampleCircuit;
macro_rules! pretty_print {
($($arg:tt)*) => {
print!("\x1b[0;36mINFO ===========>\x1b[0m ");
println!($($arg)*);
}
}
// Hash function used
type HF = PoseidonHash;
fn prepare_data<
F: RichField + Extendable<D> + Poseidon2,
C: GenericConfig<D, F = F>,
const D: usize,
H: Hasher<F> + AlgebraicHasher<F>,
>() -> Result<(
SampleCircuit<F, C, D, H>,
usize,
usize,
)> {
// Initialize the dataset tree with testing data
let mut dataset_t = SampleCircuit::<F,C,D,H>::new_for_testing();
let slot_index = TESTING_SLOT_INDEX;
let entropy = 123;
Ok((dataset_t, slot_index, entropy))
}
fn build_circuit<
F: RichField + Extendable<D> + Poseidon2,
C: GenericConfig<D, F = F>,
const D: usize,
H: Hasher<F> + AlgebraicHasher<F>,
>(
dataset_tree: &mut SampleCircuit<F, C, D, H>,
slot_index: usize,
entropy: usize,
// proofs: &[MerkleProof<F, H>],
) -> Result<(CircuitData<F, C, D>, PartialWitness<F>)>
{
// Create the circuit
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
let mut targets = dataset_tree.sample_slot_circuit(&mut builder);
// Create a PartialWitness
let mut pw = PartialWitness::new();
dataset_tree.sample_slot_assign_witness(&mut pw, &mut targets,slot_index,entropy);
// Build the circuit
let data = builder.build::<C>();
Ok((data, pw))
}
fn sampling_benchmark(c: &mut Criterion) {
let mut group = c.benchmark_group("Sampling Benchmark");
// Circuit parameters
const D: usize = 2;
type C = PoseidonGoldilocksConfig;
type F = <C as GenericConfig<D>>::F;
type H = PoseidonHash;
// Prepare the data that will be used in all steps
let (mut dataset_tree, slot_index, entropy) = prepare_data::<F, C, D, H>().unwrap();
// Benchmark the circuit building
group.bench_function("Single Cell Proof Build", |b| {
b.iter(|| {
build_circuit::<F, C, D, H>(&mut dataset_tree, slot_index, entropy).unwrap();
})
});
// Build the circuit
let (data, pw) = build_circuit::<F, C, D, H>(&mut dataset_tree, slot_index, entropy).unwrap();
pretty_print!(
"Circuit size: 2^{} gates",
data.common.degree_bits()
);
let start_time = Instant::now();
let proof_with_pis = data.prove(pw.clone()).unwrap();
println!("prove_time = {:?}", start_time.elapsed());
// Benchmark the proving time
group.bench_function("Single Cell Proof Prove", |b| {
b.iter(|| {
let _proof_with_pis = data.prove(pw.clone()).unwrap();
})
});
// Generate the proof
let proof_with_pis = data.prove(pw.clone()).unwrap();
let verifier_data = data.verifier_data();
pretty_print!("Proof size: {} bytes", proof_with_pis.to_bytes().len());
// Benchmark the verification time
group.bench_function("Single Cell Proof Verify", |b| {
b.iter(|| {
verifier_data.verify(proof_with_pis.clone()).unwrap();
})
});
group.finish();
}
criterion_group!(name = benches;
config = Criterion::default().sample_size(10);
targets = sampling_benchmark);
criterion_main!(benches);

2081
proof-input/input.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -326,7 +326,7 @@ mod tests {
assert_eq!(original_circ_input, imported_circ_input, "circuit input are not equal"); assert_eq!(original_circ_input, imported_circ_input, "circuit input are not equal");
// cleanup: Remove the generated JSON file // cleanup: Remove the generated JSON file
fs::remove_file("input.json")?; // fs::remove_file("input.json")?;
println!("Test passed: Original and imported circuit input are equal."); println!("Test passed: Original and imported circuit input are equal.");
@ -383,7 +383,7 @@ mod tests {
// reads the json input and verify (non-circuit) // reads the json input and verify (non-circuit)
// NOTE: expects that the json input proof uses the default params // NOTE: expects that the json input proof uses the default params
#[test] #[test]
fn test_read_json_and_verify() -> anyhow::Result<()> { fn test_read_json_and_verify() -> Result<()> {
let params = TestParams::default(); let params = TestParams::default();
// Import the circuit input from JSON // Import the circuit input from JSON

View File

@ -13,7 +13,7 @@ use crate::sponge::hash_n_with_padding;
// --------- helper functions --------- // --------- helper functions ---------
/// Converts an index to a vector of bits (LSB first) no padding. /// Converts an index to a vector of bits (LSB first) no padding.
pub(crate) fn usize_to_bits_le(index: usize, bit_length: usize) -> Vec<bool> { 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 that the index can fit within the given bit length.
assert!( assert!(
index < (1 << bit_length), index < (1 << bit_length),
@ -32,7 +32,7 @@ pub(crate) fn usize_to_bits_le(index: usize, bit_length: usize) -> Vec<bool> {
} }
/// returns the first bit_length bits of index /// returns the first bit_length bits of index
pub(crate) fn low_bits(index: usize, bit_length: usize) -> Vec<bool> { pub fn low_bits(index: usize, bit_length: usize) -> Vec<bool> {
let mut bits = Vec::with_capacity(bit_length); let mut bits = Vec::with_capacity(bit_length);
@ -46,7 +46,7 @@ pub(crate) fn low_bits(index: usize, bit_length: usize) -> Vec<bool> {
/// calculate the sampled cell index from entropy, slot root, and counter /// calculate the sampled cell index from entropy, slot root, and counter
/// this is the non-circuit version for testing /// this is the non-circuit version for testing
pub(crate) fn calculate_cell_index_bits< pub fn calculate_cell_index_bits<
F: RichField + Extendable<D> + Poseidon2, F: RichField + Extendable<D> + Poseidon2,
const D: usize const D: usize
>(entropy: &Vec<F>, slot_root: HashOut<F>, ctr: usize, depth: usize, mask_bits: Vec<bool>) -> Vec<bool> { >(entropy: &Vec<F>, slot_root: HashOut<F>, ctr: usize, depth: usize, mask_bits: Vec<bool>) -> Vec<bool> {
@ -72,7 +72,7 @@ pub(crate) fn calculate_cell_index_bits<
} }
/// Converts a vector of bits (LSB first) into an index (usize). /// Converts a vector of bits (LSB first) into an index (usize).
pub(crate) fn bits_le_padded_to_usize(bits: &[bool]) -> usize { pub fn bits_le_padded_to_usize(bits: &[bool]) -> usize {
bits.iter().enumerate().fold(0usize, |acc, (i, &bit)| { bits.iter().enumerate().fold(0usize, |acc, (i, &bit)| {
if bit { if bit {
acc | (1 << i) acc | (1 << i)

View File

@ -16,6 +16,10 @@ plonky2_poseidon2 = { path = "../plonky2_poseidon2" }
codex-plonky2-circuits = { path = "../codex-plonky2-circuits" } codex-plonky2-circuits = { path = "../codex-plonky2-circuits" }
proof-input = { path = "../proof-input" } proof-input = { path = "../proof-input" }
[dev-dependencies]
criterion = { version = "0.5.1", default-features = false }
tynm = { version = "0.1.6", default-features = false }
[[bin]] [[bin]]
name = "prove_and_verify" name = "prove_and_verify"
path = "src/bin/prove_and_verify.rs" path = "src/bin/prove_and_verify.rs"
@ -31,3 +35,11 @@ path = "src/bin/build_circ.rs"
[[bin]] [[bin]]
name = "prove" name = "prove"
path = "src/bin/prove.rs" path = "src/bin/prove.rs"
[[bench]]
name = "safe_circuit"
harness = false
[[bench]]
name = "sample_cells"
harness = false

View File

@ -5,15 +5,19 @@ use codex_plonky2_circuits::{merkle_tree::merkle_safe::MerkleTree, circuits::mer
use plonky2::field::types::Field; use plonky2::field::types::Field;
use plonky2::plonk::circuit_data::{CircuitConfig, CircuitData}; use plonky2::plonk::circuit_data::{CircuitConfig, CircuitData};
use plonky2::plonk::config::{AlgebraicHasher, GenericConfig, Hasher, PoseidonGoldilocksConfig}; use plonky2::plonk::config::{AlgebraicHasher, GenericConfig, Hasher, PoseidonGoldilocksConfig};
use plonky2::iop::witness::PartialWitness; use plonky2::iop::witness::{PartialWitness, WitnessWrite};
use plonky2::hash::hash_types::HashOut; use plonky2::hash::hash_types::{HashOut, NUM_HASH_OUT_ELTS};
use plonky2::hash::poseidon::PoseidonHash; use plonky2::hash::poseidon::PoseidonHash;
use plonky2::field::extension::Extendable; use plonky2::field::extension::Extendable;
use plonky2::hash::hash_types::RichField; use plonky2::hash::hash_types::RichField;
use plonky2_poseidon2::poseidon2_hash::poseidon2::Poseidon2; use plonky2_poseidon2::poseidon2_hash::poseidon2::{Poseidon2, Poseidon2Hash};
use std::marker::PhantomData; use std::marker::PhantomData;
use plonky2::plonk::circuit_builder::CircuitBuilder; use plonky2::plonk::circuit_builder::CircuitBuilder;
use codex_plonky2_circuits::merkle_tree::merkle_safe::MerkleProof; use codex_plonky2_circuits::merkle_tree::merkle_safe::MerkleProof;
use plonky2_field::goldilocks_field::GoldilocksField;
use proof_input::tests::merkle_circuit;
use proof_input::tests::merkle_circuit::{assign_witness, MerkleTreeCircuitInput};
use proof_input::utils::usize_to_bits_le;
macro_rules! pretty_print { macro_rules! pretty_print {
($($arg:tt)*) => { ($($arg:tt)*) => {
@ -22,37 +26,34 @@ macro_rules! pretty_print {
} }
} }
fn prepare_data<F, H>(N: usize) -> Result<( fn prepare_data<
MerkleTree<F, H>, F: RichField + Extendable<D> + Poseidon2,
Vec<HashOut<F>>, const D: usize,
Vec<usize>, C: GenericConfig<D, F = F>,
Vec<MerkleProof<F, H>>, H: Hasher<F> + AlgebraicHasher<F>,
>(N: usize, max_depth: usize) -> Result<(
Vec<MerkleTreeCircuitInput<F, D>>,
HashOut<F>, HashOut<F>,
)> )> {
where // Generate random leaf data
F: RichField + Extendable<2> + Poseidon2, let nleaves = 16; // Number of leaves
H: Hasher<F> + AlgebraicHasher<F> + Hasher<F>,
{
// Total number of leaves in the Merkle tree
let nleaves = 1u64 << 16;
// Generate leaf data
let data = (0..nleaves) let data = (0..nleaves)
.map(|i| F::from_canonical_u64(i as u64)) .map(|i| F::from_canonical_u64(i))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
// Hash the data to obtain leaf hashes // Hash the data to obtain leaf hashes
let leaves: Vec<HashOut<F>> = data let leaves: Vec<HashOut<F>> = data
.iter() .iter()
.map(|&element| { .map(|&element| {
// Hash each field element to get the leaf hash
PoseidonHash::hash_no_pad(&[element]) PoseidonHash::hash_no_pad(&[element])
}) })
.collect(); .collect();
//initialize the Merkle tree
let zero_hash = HashOut { let zero_hash = HashOut {
elements: [F::ZERO; 4], elements: [F::ZERO; 4],
}; };
let tree = MerkleTree::<F, H>::new(&leaves, zero_hash)?; let tree = MerkleTree::<F, D>::new(&leaves, zero_hash)?;
// Select N leaf indices to prove // Select N leaf indices to prove
let leaf_indices: Vec<usize> = (0..N).collect(); let leaf_indices: Vec<usize> = (0..N).collect();
@ -63,20 +64,42 @@ fn prepare_data<F, H>(N: usize) -> Result<(
.map(|&leaf_index| tree.get_proof(leaf_index)) .map(|&leaf_index| tree.get_proof(leaf_index))
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
let mut circ_inputs = vec![];
for i in 0..N{
let path_bits = usize_to_bits_le(leaf_indices[i], max_depth);
let last_index = (nleaves - 1) as usize;
let last_bits = usize_to_bits_le(last_index, max_depth);
let mask_bits = usize_to_bits_le(last_index, max_depth+1);
// circuit input
let circuit_input = MerkleTreeCircuitInput::<F, D>{
leaf: tree.layers[0][leaf_indices[i]],
path_bits,
last_bits,
mask_bits,
merkle_path: proofs[i].path.clone(),
};
circ_inputs.push(circuit_input);
}
// Expected Merkle root // Expected Merkle root
let expected_root = tree.root()?; let expected_root = tree.root()?;
Ok((tree, leaves, leaf_indices, proofs, expected_root)) Ok((circ_inputs, expected_root))
} }
fn build_circuit<F, C, const D: usize, H>( fn build_circuit<
tree: &MerkleTree<F, H>,
leaf_indices: &[usize],
) -> Result<(CircuitData<F, C, D>, PartialWitness<F>)>
where
F: RichField + Extendable<D> + Poseidon2, F: RichField + Extendable<D> + Poseidon2,
const D: usize,
C: GenericConfig<D, F = F>, C: GenericConfig<D, F = F>,
H: Hasher<F> + AlgebraicHasher<F> + Hasher<F>, H: Hasher<F> + AlgebraicHasher<F>,
>(
circ_inputs: Vec<MerkleTreeCircuitInput<F, D>>,
expected_root: HashOut<F>,
max_depth: usize,
) -> Result<(CircuitData<F, C, D>, PartialWitness<F>)>
{ {
// Create the circuit // Create the circuit
let config = CircuitConfig::standard_recursion_config(); let config = CircuitConfig::standard_recursion_config();
@ -85,19 +108,20 @@ fn build_circuit<F, C, const D: usize, H>(
// Create a PartialWitness // Create a PartialWitness
let mut pw = PartialWitness::new(); let mut pw = PartialWitness::new();
// Initialize the circuit instance for i in 0..circ_inputs.len() {
let mut circuit_instance = MerkleTreeCircuit::<F, C, D, H> { let (mut targets, reconstructed_root_target) = merkle_circuit::build_circuit(&mut builder, max_depth);
tree: tree.clone(),
_phantom: PhantomData,
};
// For each proof, create targets, add constraints, and assign witnesses // expected Merkle root
for &leaf_index in leaf_indices.iter() { let expected_root_target = builder.add_virtual_hash();
// Build the circuit for each proof
let (mut targets, _root) = circuit_instance.build_circuit(&mut builder);
// Assign witnesses for each proof // check equality with expected root
circuit_instance.assign_witness(&mut pw, &mut targets, leaf_index)?; for i in 0..NUM_HASH_OUT_ELTS {
builder.connect(expected_root_target.elements[i], reconstructed_root_target.elements[i]);
}
//assign input
assign_witness(&mut pw, &mut targets, circ_inputs[i].clone())?;
pw.set_hash_target(expected_root_target, expected_root);
} }
// Build the circuit // Build the circuit
@ -106,28 +130,28 @@ fn build_circuit<F, C, const D: usize, H>(
Ok((data, pw)) Ok((data, pw))
} }
fn merkle_proof_benchmark(c: &mut Criterion) { fn merkle_proof_benchmark<
F: RichField + Extendable<D> + Poseidon2,
const D: usize,
C: GenericConfig<D, F = F>,
H: Hasher<F> + AlgebraicHasher<F>,
>(c: &mut Criterion) {
let mut group = c.benchmark_group("Merkle Proof Benchmark"); let mut group = c.benchmark_group("Merkle Proof Benchmark");
// Circuit parameters
const D: usize = 2;
type C = PoseidonGoldilocksConfig;
type F = <C as GenericConfig<D>>::F;
type H = PoseidonHash;
// Prepare the data that will be used in all steps // Prepare the data that will be used in all steps
let N = 5; // Number of leaves to prove let N = 5; // Number of leaves to prove
let (tree, _leaves, leaf_indices, _proofs, _expected_root) = prepare_data::<F, H>(N).unwrap(); let max_depth = 4;
let (circ_input, expected_root) = prepare_data::<F, D,C,H>(N, max_depth).unwrap();
// Benchmark the circuit building // Benchmark the circuit building
group.bench_function("Merkle Proof Build", |b| { group.bench_function("Merkle Proof Build", |b| {
b.iter(|| { b.iter(|| {
build_circuit::<F, C, D, H>(&tree, &leaf_indices).unwrap(); build_circuit::<F, D, C, H>(circ_input.clone(), expected_root.clone(), max_depth).unwrap();
}) })
}); });
// Build the circuit once to get the data for the proving and verifying steps // Build the circuit once to get the data for the proving and verifying steps
let (data, pw) = build_circuit::<F, C, D, H>(&tree, &leaf_indices).unwrap(); let (data, pw) = build_circuit::<F, D, C, H>(circ_input.clone(), expected_root.clone(), max_depth).unwrap();
pretty_print!( pretty_print!(
"circuit size: 2^{} gates", "circuit size: 2^{} gates",
@ -157,8 +181,18 @@ fn merkle_proof_benchmark(c: &mut Criterion) {
group.finish(); group.finish();
} }
fn run_bench(c: &mut Criterion){
// Circuit types
const D: usize = 2;
type C = PoseidonGoldilocksConfig;
type F = <C as GenericConfig<D>>::F;
type H = Poseidon2Hash;
merkle_proof_benchmark::<F,D,C,H>(c);
}
// criterion_group!(benches, merkle_proof_benchmark); // criterion_group!(benches, merkle_proof_benchmark);
criterion_group!(name = benches; criterion_group!(name = benches;
config = Criterion::default().sample_size(10); config = Criterion::default().sample_size(10);
targets = merkle_proof_benchmark); targets = run_bench);
criterion_main!(benches); criterion_main!(benches);

View File

@ -0,0 +1,87 @@
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 proof_input::json::import_circ_input_from_json;
use codex_plonky2_circuits::circuits::sample_cells::{SampleCircuit, SampleCircuitInput};
use codex_plonky2_circuits::circuits::params::CircuitParams;
use proof_input::params::{D, C, F, Params};
/// Benchmark for building, proving, and verifying the Plonky2 circuit.
fn bench_prove_verify(c: &mut Criterion) {
// get default parameters
let circuit_params = CircuitParams::default();
// Import the circuit input from a JSON file
let circ_input: SampleCircuitInput<F, D> = import_circ_input_from_json("input.json").expect("Failed to import circuit input from JSON");
println!("Witness imported from input.json");
// Create the circuit configuration
let config = CircuitConfig::standard_recursion_config();
let mut builder = CircuitBuilder::<F, D>::new(config);
// Initialize the SampleCircuit with the parameters
let circ = SampleCircuit::new(circuit_params.clone());
let mut targets = circ.sample_slot_circuit(&mut builder);
// Create a PartialWitness and assign the circuit input
let mut pw = PartialWitness::new();
circ.sample_slot_assign_witness(&mut pw, &mut targets, circ_input.clone());
// Benchmark Group: Separate benchmarks for building, proving, and verifying
let mut group = c.benchmark_group("Prove and Verify");
// Benchmark the Circuit Building Phase
group.bench_function("Build Circuit", |b| {
b.iter(|| {
let config = CircuitConfig::standard_recursion_config();
let mut local_builder = CircuitBuilder::<F, D>::new(config);
let local_circ = SampleCircuit::new(circuit_params.clone());
let mut local_targets = local_circ.sample_slot_circuit(&mut local_builder);
let mut local_pw = PartialWitness::new();
local_circ.sample_slot_assign_witness(&mut local_pw, &mut local_targets, circ_input.clone());
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());
// 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 proof_with_pis = data.prove(pw.clone()).expect("Failed to prove circuit");
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();
}
/// Criterion benchmark group
criterion_group!{
name = prove_verify_benches;
config = Criterion::default().sample_size(10);
targets = bench_prove_verify
}
criterion_main!(prove_verify_benches);