diff --git a/w3f-plonk-common/Cargo.toml b/w3f-plonk-common/Cargo.toml index 2a436b2..3ff5b49 100644 --- a/w3f-plonk-common/Cargo.toml +++ b/w3f-plonk-common/Cargo.toml @@ -21,6 +21,11 @@ rand_core = "0.6" [dev-dependencies] ark-ed-on-bls12-381-bandersnatch = { version = "0.5", default-features = false } +criterion = { version = "0.5", features = ["html_reports"] } + +[[bench]] +name = "plonk_common" +harness = false [features] default = ["std"] diff --git a/w3f-plonk-common/benches/SUMMARY.md b/w3f-plonk-common/benches/SUMMARY.md new file mode 100644 index 0000000..4391e77 --- /dev/null +++ b/w3f-plonk-common/benches/SUMMARY.md @@ -0,0 +1,60 @@ +# w3f-plonk-common Benchmark Results + +Measured with `cargo bench --bench plonk_common -p w3f-plonk-common -- --quick`. + +Curve: Bandersnatch (on BLS12-381). Single-threaded, release profile. + +Machine: AMD Ryzen Threadripper 3970X (64 logical cores), 62 GiB RAM, Arch Linux 6.18.9, rustc 1.93.0. + +## Domain Creation + +| Domain Size | Hiding | Non-Hiding | +|-------------|-----------|------------| +| 512 | 884 us | 865 us | +| 1024 | 1.90 ms | 1.89 ms | +| 4096 | 8.79 ms | 8.85 ms | +| 16384 | 44.2 ms | 44.1 ms | + +Hiding vs non-hiding makes no measurable difference. Scales roughly linearly with domain size. + +## Field Column Construction + +| Domain Size | private_column | public_column | shifted_4x | +|-------------|----------------|---------------|------------| +| 512 | 455 us | 445 us | 2.31 us | +| 1024 | 982 us | 981 us | 4.62 us | +| 4096 | 4.76 ms | 4.68 ms | 22.8 us | + +Column construction is dominated by FFT (interpolation + 4x evaluation). `shifted_4x` is a cheap rotate+copy. + +## Booleanity Gadget + +Constraint evaluation in 4x domain. + +| Domain Size | constraints | +|-------------|-------------| +| 512 | 45.1 us | +| 1024 | 90.9 us | +| 4096 | 384 us | + +Single constraint `b(1-b)`. Linear scaling. + +## Inner Product Gadget + +| Domain Size | init | constraints | constraints_linearized | +|-------------|---------|-------------|------------------------| +| 512 | 1.65 ms | 100 us | 9.73 us | +| 1024 | 3.20 ms | 210 us | 19.6 us | +| 4096 | 13.8 ms | 942 us | 94.2 us | + +Init includes column construction (2 FFTs). Constraints are evaluated pointwise in 4x domain. Linearization is a single polynomial scalar multiplication. + +## TE Conditional Addition Gadget + +| Domain Size | init | constraints | constraints_linearized | +|-------------|----------|-------------|------------------------| +| 512 | 3.78 ms | 857 us | 75.9 us | +| 1024 | 8.03 ms | 1.72 ms | 162 us | +| 4096 | 35.2 ms | 13.9 ms | 669 us | + +Init includes EC conditional additions (sequential scan) plus column construction. Constraint evaluation is the most expensive gadget due to the degree-4 EC addition formulas. Linearization remains cheap. diff --git a/w3f-plonk-common/benches/plonk_common.rs b/w3f-plonk-common/benches/plonk_common.rs new file mode 100644 index 0000000..dcb494f --- /dev/null +++ b/w3f-plonk-common/benches/plonk_common.rs @@ -0,0 +1,181 @@ +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; + +use ark_ec::AffineRepr; +use ark_ed_on_bls12_381_bandersnatch::{EdwardsAffine, Fq}; +use ark_std::{test_rng, UniformRand}; + +use w3f_plonk_common::domain::Domain; +use w3f_plonk_common::gadgets::booleanity::{BitColumn, Booleanity}; +use w3f_plonk_common::gadgets::ec::{AffineColumn, CondAdd}; +use w3f_plonk_common::gadgets::inner_prod::InnerProd; +use w3f_plonk_common::gadgets::ProverGadget; +use w3f_plonk_common::test_helpers::{random_bitvec, random_vec}; + +fn bench_domain_creation(c: &mut Criterion) { + let mut group = c.benchmark_group("domain_creation"); + for log_n in [9, 10, 12, 14] { + let n = 1usize << log_n; + group.bench_with_input(BenchmarkId::new("hiding", n), &n, |b, &n| { + b.iter(|| Domain::::new(n, true)); + }); + group.bench_with_input(BenchmarkId::new("non_hiding", n), &n, |b, &n| { + b.iter(|| Domain::::new(n, false)); + }); + } + group.finish(); +} + +fn bench_field_column(c: &mut Criterion) { + let rng = &mut test_rng(); + let mut group = c.benchmark_group("field_column"); + for log_n in [9, 10, 12] { + let n = 1usize << log_n; + let domain = Domain::::new(n, true); + let vals: Vec = random_vec(domain.capacity - 1, rng); + + group.bench_with_input( + BenchmarkId::new("private_column", n), + &(vals.clone(), &domain), + |b, (vals, domain)| { + b.iter(|| domain.private_column(vals.clone())); + }, + ); + + group.bench_with_input( + BenchmarkId::new("public_column", n), + &(vals.clone(), &domain), + |b, (vals, domain)| { + b.iter(|| domain.public_column(vals.clone())); + }, + ); + + let col = domain.private_column(vals); + group.bench_with_input(BenchmarkId::new("shifted_4x", n), &col, |b, col| { + b.iter(|| col.shifted_4x()); + }); + } + group.finish(); +} + +fn bench_booleanity_gadget(c: &mut Criterion) { + let rng = &mut test_rng(); + let mut group = c.benchmark_group("booleanity"); + for log_n in [9, 10, 12] { + let n = 1usize << log_n; + let domain = Domain::::new(n, true); + let bits = random_bitvec(domain.capacity - 1, 0.5, rng); + let bit_col = BitColumn::init(bits, &domain); + + group.bench_with_input( + BenchmarkId::new("constraints", n), + &bit_col, + |b, bit_col| { + let gadget = Booleanity::init(bit_col.clone()); + b.iter(|| gadget.constraints()); + }, + ); + } + group.finish(); +} + +fn bench_inner_prod_gadget(c: &mut Criterion) { + let rng = &mut test_rng(); + let mut group = c.benchmark_group("inner_prod"); + for log_n in [9, 10, 12] { + let n = 1usize << log_n; + let domain = Domain::::new(n, true); + let a: Vec = random_vec(domain.capacity - 1, rng); + let b_vals: Vec = random_vec(domain.capacity - 1, rng); + + group.bench_with_input( + BenchmarkId::new("init", n), + &(a.clone(), b_vals.clone(), &domain), + |bench, (a, b_vals, domain)| { + bench.iter(|| { + let a_col = domain.private_column(a.clone()); + let b_col = domain.private_column(b_vals.clone()); + InnerProd::::init(a_col, b_col, domain); + }); + }, + ); + + let a_col = domain.private_column(a); + let b_col = domain.private_column(b_vals); + let gadget = InnerProd::::init(a_col, b_col, &domain); + + group.bench_with_input( + BenchmarkId::new("constraints", n), + &gadget, + |bench, gadget| { + bench.iter(|| gadget.constraints()); + }, + ); + + let zeta = Fq::rand(rng); + group.bench_with_input( + BenchmarkId::new("constraints_linearized", n), + &(gadget, zeta), + |bench, (gadget, zeta)| { + bench.iter(|| gadget.constraints_linearized(zeta)); + }, + ); + } + group.finish(); +} + +fn bench_te_cond_add_gadget(c: &mut Criterion) { + let rng = &mut test_rng(); + let mut group = c.benchmark_group("te_cond_add"); + for log_n in [9, 10, 12] { + let n = 1usize << log_n; + let domain = Domain::::new(n, true); + let seed = EdwardsAffine::generator(); + + let bitmask = random_bitvec(domain.capacity - 1, 0.5, rng); + let points = random_vec::(domain.capacity - 1, rng); + + group.bench_with_input( + BenchmarkId::new("init", n), + &(bitmask.clone(), points.clone(), &domain), + |bench, (bitmask, points, domain)| { + bench.iter(|| { + let bitmask_col = BitColumn::init(bitmask.clone(), domain); + let points_col = AffineColumn::private_column(points.clone(), domain); + CondAdd::init(bitmask_col, points_col, seed, domain); + }); + }, + ); + + let bitmask_col = BitColumn::init(bitmask, &domain); + let points_col = AffineColumn::private_column(points, &domain); + let gadget = CondAdd::init(bitmask_col, points_col, seed, &domain); + + group.bench_with_input( + BenchmarkId::new("constraints", n), + &gadget, + |bench, gadget| { + bench.iter(|| gadget.constraints()); + }, + ); + + let zeta = Fq::rand(rng); + group.bench_with_input( + BenchmarkId::new("constraints_linearized", n), + &(gadget, zeta), + |bench, (gadget, zeta)| { + bench.iter(|| gadget.constraints_linearized(zeta)); + }, + ); + } + group.finish(); +} + +criterion_group!( + benches, + bench_domain_creation, + bench_field_column, + bench_booleanity_gadget, + bench_inner_prod_gadget, + bench_te_cond_add_gadget, +); +criterion_main!(benches); diff --git a/w3f-ring-proof/Cargo.toml b/w3f-ring-proof/Cargo.toml index 928c2f8..727883a 100644 --- a/w3f-ring-proof/Cargo.toml +++ b/w3f-ring-proof/Cargo.toml @@ -22,6 +22,11 @@ ark-transcript = { version = "0.0.3", default-features = false } [dev-dependencies] ark-bls12-381 = { version = "0.5", default-features = false, features = ["curve"] } ark-ed-on-bls12-381-bandersnatch = { version = "0.5", default-features = false } +criterion = { version = "0.5", features = ["html_reports"] } + +[[bench]] +name = "ring_proof" +harness = false [features] default = [ "std" ] diff --git a/w3f-ring-proof/benches/SUMMARY.md b/w3f-ring-proof/benches/SUMMARY.md new file mode 100644 index 0000000..9a41a2c --- /dev/null +++ b/w3f-ring-proof/benches/SUMMARY.md @@ -0,0 +1,62 @@ +# w3f-ring-proof Benchmark Results + +Measured with `cargo bench --bench ring_proof -p w3f-ring-proof -- --quick`. + +Curve: Bandersnatch on BLS12-381 with KZG. Single-threaded, release profile. + +Machine: AMD Ryzen Threadripper 3970X (64 logical cores), 62 GiB RAM, Arch Linux 6.18.9, rustc 1.93.0. + +## Setup (PCS + PIOP Parameters) + +| Domain Size | Time | +|-------------|----------| +| 512 | 61.9 ms | +| 1024 | 86.1 ms | + +Includes KZG trusted setup (`3 * domain_size` degree) and domain/PIOP parameter construction. + +## Indexing (Fixed Column Commitments) + +| Domain Size | Time | +|-------------|----------| +| 512 | 48.0 ms | +| 1024 | 92.0 ms | + +Commits the ring key columns and selector polynomial using KZG. Full keyset (max capacity). + +## Proving + +| Domain Size | Time | +|-------------|-----------| +| 512 | 159 ms | +| 1024 | 289 ms | + +Single proof generation. Includes witness generation (conditional additions, inner product accumulation) and PLONK prover (constraint evaluation, quotient polynomial, KZG commitments and openings). + +## Single Verification + +| Domain Size | Time | +|-------------|----------| +| 512 | 3.63 ms | +| 1024 | 3.36 ms | + +Single proof verification. Dominated by pairing checks. Near-constant with domain size as the verifier works with evaluations, not full polynomials. + +## Batch Verification (domain_size = 1024) + +| Batch Size | Sequential | KZG Accumulator | Speedup | +|------------|------------|-----------------|---------| +| 1 | 3.10 ms | 3.10 ms | 1.0x | +| 4 | 14.0 ms | 5.29 ms | 2.6x | +| 16 | 49.8 ms | 11.3 ms | 4.4x | +| 32 | 99.6 ms | 19.8 ms | 5.0x | + +Sequential verification scales linearly (one pairing check per proof). KZG accumulator batches all pairing equations into a single check via MSM, giving sub-linear scaling. + +## Proof Size + +| Format | Size | +|------------|---------| +| Compressed | 592 bytes | + +Serialization time: ~771 ns. diff --git a/w3f-ring-proof/benches/ring_proof.rs b/w3f-ring-proof/benches/ring_proof.rs new file mode 100644 index 0000000..b0ab568 --- /dev/null +++ b/w3f-ring-proof/benches/ring_proof.rs @@ -0,0 +1,261 @@ +use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion}; + +use ark_bls12_381::Bls12_381; +use ark_ec::CurveGroup; +use ark_ed_on_bls12_381_bandersnatch::{BandersnatchConfig, EdwardsAffine, Fq, Fr}; +use ark_serialize::CanonicalSerialize; +use ark_std::ops::Mul; +use ark_std::rand::Rng; +use ark_std::{test_rng, UniformRand}; +use w3f_pcs::pcs::kzg::KZG; +use w3f_pcs::pcs::PCS; + +use w3f_plonk_common::domain::Domain; +use w3f_plonk_common::test_helpers::random_vec; +use w3f_ring_proof::ring_prover::RingProver; +use w3f_ring_proof::ring_verifier::RingVerifier; +use w3f_ring_proof::{index, ArkTranscript, PiopParams, RingProof}; + +type CS = KZG; + +fn setup( + rng: &mut impl Rng, + domain_size: usize, +) -> (>::Params, PiopParams) { + let setup_degree = 3 * domain_size; + let pcs_params = CS::setup(setup_degree, rng); + let domain = Domain::new(domain_size, true); + let h = EdwardsAffine::rand(rng); + let seed = EdwardsAffine::rand(rng); + let padding = EdwardsAffine::rand(rng); + let piop_params = PiopParams::setup(domain, h, seed, padding); + (pcs_params, piop_params) +} + +fn make_transcript() -> ArkTranscript { + ArkTranscript::new(b"w3f-ring-proof-bench") +} + +/// Get the Pedersen blinding base H from the PIOP params (first element of the power-of-2 multiples). +fn get_h(piop_params: &PiopParams) -> EdwardsAffine { + piop_params.power_of_2_multiples_of_h()[0] +} + +/// Generate a proof and its corresponding blinded public key. +fn generate_proof( + piop_params: &PiopParams, + pcs_params: &>::Params, + pks: &[EdwardsAffine], + rng: &mut impl Rng, +) -> (EdwardsAffine, RingProof) { + let h = get_h(piop_params); + let prover_idx = rng.gen_range(0..pks.len()); + let (prover_key, _) = index::<_, CS, _>(pcs_params, piop_params, pks); + let prover = RingProver::init( + prover_key, + piop_params.clone(), + prover_idx, + make_transcript(), + ); + let blinding_factor = Fr::rand(rng); + let blinded_pk = (pks[prover_idx] + h.mul(blinding_factor)).into_affine(); + let proof = prover.prove(blinding_factor); + (blinded_pk, proof) +} + +fn bench_setup(c: &mut Criterion) { + let rng = &mut test_rng(); + let mut group = c.benchmark_group("ring_proof/setup"); + group.sample_size(10); + + for log_n in [9, 10] { + let n = 1usize << log_n; + group.bench_with_input(BenchmarkId::new("pcs_and_piop", n), &n, |b, &n| { + b.iter(|| setup(rng, n)); + }); + } + group.finish(); +} + +fn bench_index(c: &mut Criterion) { + let rng = &mut test_rng(); + let mut group = c.benchmark_group("ring_proof/index"); + group.sample_size(10); + + for log_n in [9, 10] { + let n = 1usize << log_n; + let (pcs_params, piop_params) = setup(rng, n); + let keyset_size = piop_params.keyset_part_size; + let pks = random_vec::(keyset_size, rng); + + group.bench_with_input(BenchmarkId::new("full_keyset", n), &n, |b, _| { + b.iter(|| index::<_, CS, _>(&pcs_params, &piop_params, &pks)); + }); + } + group.finish(); +} + +fn bench_prove(c: &mut Criterion) { + let rng = &mut test_rng(); + let mut group = c.benchmark_group("ring_proof/prove"); + group.sample_size(10); + + for log_n in [9, 10] { + let n = 1usize << log_n; + let (pcs_params, piop_params) = setup(rng, n); + let keyset_size = piop_params.keyset_part_size; + let pks = random_vec::(keyset_size, rng); + let (prover_key, _) = index::<_, CS, _>(&pcs_params, &piop_params, &pks); + + let prover_idx = rng.gen_range(0..keyset_size); + let prover = RingProver::init( + prover_key, + piop_params.clone(), + prover_idx, + make_transcript(), + ); + + group.bench_with_input(BenchmarkId::new("single", n), &n, |b, _| { + let blinding_factor = Fr::rand(rng); + b.iter(|| prover.prove(blinding_factor)); + }); + } + group.finish(); +} + +fn bench_verify(c: &mut Criterion) { + let rng = &mut test_rng(); + let mut group = c.benchmark_group("ring_proof/verify"); + group.sample_size(10); + + for log_n in [9, 10] { + let n = 1usize << log_n; + let (pcs_params, piop_params) = setup(rng, n); + let keyset_size = piop_params.keyset_part_size; + let pks = random_vec::(keyset_size, rng); + + let (blinded_pk, proof) = generate_proof(&piop_params, &pcs_params, &pks, rng); + let (_, verifier_key) = index::<_, CS, _>(&pcs_params, &piop_params, &pks); + let verifier = RingVerifier::init(verifier_key, piop_params, make_transcript()); + + group.bench_with_input(BenchmarkId::new("single", n), &n, |b, _| { + b.iter(|| verifier.verify(proof.clone(), blinded_pk)); + }); + } + group.finish(); +} + +fn bench_verify_batch_sequential(c: &mut Criterion) { + let rng = &mut test_rng(); + let mut group = c.benchmark_group("ring_proof/verify_batch_sequential"); + group.sample_size(10); + + let log_n = 10; + let n = 1usize << log_n; + let (pcs_params, piop_params) = setup(rng, n); + let keyset_size = piop_params.keyset_part_size; + let pks = random_vec::(keyset_size, rng); + + // Pre-generate proofs for the largest batch. + let max_batch = 32; + let claims: Vec<(EdwardsAffine, RingProof)> = (0..max_batch) + .map(|_| generate_proof(&piop_params, &pcs_params, &pks, rng)) + .collect(); + + let (_, verifier_key) = index::<_, CS, _>(&pcs_params, &piop_params, &pks); + let verifier = RingVerifier::init(verifier_key, piop_params, make_transcript()); + + for batch_size in [1, 4, 16, 32] { + let (results, proofs): (Vec<_>, Vec<_>) = claims[..batch_size].iter().cloned().unzip(); + + group.bench_with_input( + BenchmarkId::new("sequential", batch_size), + &batch_size, + |b, _| { + b.iter(|| verifier.verify_batch(proofs.clone(), results.clone())); + }, + ); + } + group.finish(); +} + +fn bench_verify_batch_kzg(c: &mut Criterion) { + let rng = &mut test_rng(); + let mut group = c.benchmark_group("ring_proof/verify_batch_kzg"); + group.sample_size(10); + + let log_n = 10; + let n = 1usize << log_n; + let (pcs_params, piop_params) = setup(rng, n); + let keyset_size = piop_params.keyset_part_size; + let pks = random_vec::(keyset_size, rng); + + let max_batch = 32; + let claims: Vec<(EdwardsAffine, RingProof)> = (0..max_batch) + .map(|_| generate_proof(&piop_params, &pcs_params, &pks, rng)) + .collect(); + + for batch_size in [1, 4, 16, 32] { + let (results, proofs): (Vec<_>, Vec<_>) = claims[..batch_size].iter().cloned().unzip(); + + group.bench_with_input( + BenchmarkId::new("kzg_accumulator", batch_size), + &batch_size, + |b, _| { + // Recreate verifier each iteration since verify_batch_kzg consumes self. + b.iter_batched( + || { + let (_, vk) = index::<_, CS, _>(&pcs_params, &piop_params, &pks); + let verifier = + RingVerifier::init(vk, piop_params.clone(), make_transcript()); + (verifier, proofs.clone(), results.clone()) + }, + |(verifier, proofs, results)| verifier.verify_batch_kzg(proofs, results), + BatchSize::LargeInput, + ); + }, + ); + } + group.finish(); +} + +fn bench_proof_size(c: &mut Criterion) { + let rng = &mut test_rng(); + let mut group = c.benchmark_group("ring_proof/serialization"); + group.sample_size(10); + + let n = 1usize << 10; + let (pcs_params, piop_params) = setup(rng, n); + let keyset_size = piop_params.keyset_part_size; + let pks = random_vec::(keyset_size, rng); + + let (_, proof) = generate_proof(&piop_params, &pcs_params, &pks, rng); + + let mut buf = Vec::new(); + proof.serialize_compressed(&mut buf).unwrap(); + let proof_size = buf.len(); + + group.bench_function( + BenchmarkId::new("serialize_compressed", format!("{proof_size}_bytes")), + |b| { + b.iter(|| { + let mut buf = Vec::with_capacity(proof_size); + proof.serialize_compressed(&mut buf).unwrap(); + buf + }); + }, + ); + group.finish(); +} + +criterion_group!( + benches, + bench_setup, + bench_index, + bench_prove, + bench_verify, + bench_verify_batch_sequential, + bench_verify_batch_kzg, + bench_proof_size, +); +criterion_main!(benches);