From cc57393251c2a67362da25b34e7dafa774f14c5d Mon Sep 17 00:00:00 2001 From: Youngjoon Lee <5462944+youngjoon-lee@users.noreply.github.com> Date: Tue, 18 Aug 2026 06:53:25 +0000 Subject: [PATCH] test(zk): add benchmark for ZkSig batch verification (#3306) --- zk/proofs/zksign/Cargo.toml | 4 ++ zk/proofs/zksign/benches/verify.rs | 67 ++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 zk/proofs/zksign/benches/verify.rs diff --git a/zk/proofs/zksign/Cargo.toml b/zk/proofs/zksign/Cargo.toml index 834cbd03b..f9ac8843d 100644 --- a/zk/proofs/zksign/Cargo.toml +++ b/zk/proofs/zksign/Cargo.toml @@ -34,5 +34,9 @@ rand = { workspace = true } harness = false name = "prove" +[[bench]] +harness = false +name = "verify" + [lints] workspace = true diff --git a/zk/proofs/zksign/benches/verify.rs b/zk/proofs/zksign/benches/verify.rs new file mode 100644 index 000000000..8c8b37eeb --- /dev/null +++ b/zk/proofs/zksign/benches/verify.rs @@ -0,0 +1,67 @@ +//! `ZkSign` verification benchmarks. +//! +//! Run with `cargo bench -p logos-blockchain-zksign --bench verify`. +//! +//! These measure the payoff of verifying a block's `ZkSign` proofs in a single +//! batch instead of one at a time. + +use std::sync::LazyLock; + +use divan::counter::ItemsCount; +use lb_groth16::Fr; +use lb_poseidon2::{Digest as _, Poseidon2Bn254Hasher}; +use logos_blockchain_zksign::{ + ZkSignPrivateKeysData, ZkSignProof, ZkSignVerifierInputs, ZkSignWitnessInputs, batch_verify, + prove, verify, +}; +use num_bigint::BigUint; + +/// Batch sizes exercised by the batched and sequential verification +/// benchmarks. +/// +/// The largest batch size (1024) is the number of transactions a block can hold +/// because most txs usually contain at least one `Transfer` operation to pay +/// fees. +const BATCH_SIZES: [usize; 6] = [1, 4, 16, 64, 256, 1024]; + +/// Generate 1024 distinct proofs to be used for all the benchmarks. +static PROOFS: LazyLock> = LazyLock::new(|| { + let n_proofs = BATCH_SIZES[BATCH_SIZES.len() - 1]; + (0..n_proofs as u64) + .map(|index| { + let secret_keys: [Fr; 32] = + core::array::from_fn(|key| BigUint::from(index * 32 + key as u64 + 1).into()); + let message_hash = Poseidon2Bn254Hasher::digest(&[BigUint::from(index).into()]); + prove(ZkSignWitnessInputs::from_witness_data_and_message_hash( + ZkSignPrivateKeysData::from(secret_keys), + message_hash, + )) + .unwrap() + }) + .collect() +}); + +fn main() { + divan::main(); +} + +/// Verifies a batch of `batch_size` proofs. +#[divan::bench(args = BATCH_SIZES)] +fn bench_batch_verify(bencher: divan::Bencher, batch_size: usize) { + let batch = &PROOFS[..batch_size]; + bencher + .counter(ItemsCount::new(batch_size)) + .bench(|| assert!(divan::black_box(batch_verify(batch)).unwrap())); +} + +/// Verifies `batch_size` proofs one at a time, as the baseline +/// [`bench_batch_verify`] is compared against. +#[divan::bench(args = BATCH_SIZES)] +fn bench_sequential_verify(bencher: divan::Bencher, batch_size: usize) { + let batch = &PROOFS[..batch_size]; + bencher.counter(ItemsCount::new(batch_size)).bench(|| { + for (proof, inputs) in batch { + assert!(divan::black_box(verify(proof, inputs)).unwrap()); + } + }); +}