diff --git a/Cargo.lock b/Cargo.lock index 56d597ac..92b34879 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1226,6 +1226,18 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +[[package]] +name = "light-poseidon" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3d87542063daaccbfecd78b60f988079b6ec4e089249658b9455075c78d42" +dependencies = [ + "ark-bn254", + "ark-ff 0.5.0", + "num-bigint", + "thiserror 1.0.69", +] + [[package]] name = "litrs" version = "0.4.1" @@ -1415,7 +1427,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6" dependencies = [ "memchr", - "thiserror", + "thiserror 2.0.12", "ucd-trie", ] @@ -1564,13 +1576,12 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", - "zerocopy 0.8.24", ] [[package]] @@ -1706,7 +1717,7 @@ dependencies = [ "serde", "serde_json", "sled", - "thiserror", + "thiserror 2.0.12", "tiny-keccak", "zerokit_utils", ] @@ -1770,7 +1781,7 @@ dependencies = [ "primitive-types", "proptest", "rand 0.8.5", - "rand 0.9.0", + "rand 0.9.1", "rlp", "ruint-macro", "serde", @@ -2008,13 +2019,33 @@ version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", ] [[package]] @@ -2537,10 +2568,24 @@ dependencies = [ "hex", "hex-literal", "lazy_static", + "light-poseidon", "num-bigint", "num-traits", + "rand 0.9.1", + "rand_chacha 0.9.0", + "rand_core 0.9.3", + "rln", "serde", "sled", "tiny-keccak", "vacp2p_pmtree", + "zk-kit-lean-imt", +] + +[[package]] +name = "zk-kit-lean-imt" +version = "0.1.1" +source = "git+https://github.com/privacy-scaling-explorations/zk-kit.rust#1014994e0505befc33d51e854c73ab8717b64d90" +dependencies = [ + "thiserror 2.0.12", ] diff --git a/Makefile b/Makefile index b3c74ee9..893149d3 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,7 @@ endif [ -s "$$NVM_DIR/nvm.sh" ] && \. "$$NVM_DIR/nvm.sh" && \ nvm install 22.14.0 && \ nvm use 22.14.0' - @curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + @cargo install wasm-pack @echo "\033[1;32m>>> Now run this command to activate Node.js 22.14.0: \033[1;33msource $$HOME/.nvm/nvm.sh && nvm use 22.14.0\033[0m" build: .pre-build diff --git a/flake.lock b/flake.lock index 46a1cbf8..d0ab16ca 100644 --- a/flake.lock +++ b/flake.lock @@ -18,7 +18,28 @@ }, "root": { "inputs": { - "nixpkgs": "nixpkgs" + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1745289264, + "narHash": "sha256-7nt+UJ7qaIUe2J7BdnEEph9n2eKEwxUwKS/QIr091uA=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "3b7171858c20d5293360042936058fb0c4cb93a9", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" } } }, diff --git a/flake.nix b/flake.nix index 5aa94d5c..f49614cc 100644 --- a/flake.nix +++ b/flake.nix @@ -4,9 +4,13 @@ inputs = { # Version 24.11 nixpkgs.url = "github:NixOS/nixpkgs?rev=f44bd8ca21e026135061a0a57dcf3d0775b67a49"; + rust-overlay = { + url = "github:oxalica/rust-overlay"; + inputs.nixpkgs.follows = "nixpkgs"; + }; }; - outputs = { self, nixpkgs }: + outputs = { self, nixpkgs, rust-overlay }: let stableSystems = [ "x86_64-linux" "aarch64-linux" @@ -15,7 +19,8 @@ "i686-windows" ]; forAllSystems = nixpkgs.lib.genAttrs stableSystems; - pkgsFor = forAllSystems (system: import nixpkgs { inherit system; }); + overlays = [ (import rust-overlay) ]; + pkgsFor = forAllSystems (system: import nixpkgs { inherit system overlays; }); in rec { packages = forAllSystems (system: let @@ -29,9 +34,21 @@ pkgs = pkgsFor.${system}; in { default = pkgs.mkShell { - inputsFrom = [ - packages.${system}.default + buildInputs = with pkgs; [ + git + cmake + cargo-make + gnuplot + rustup + xz + wasm-pack + rust-bin.stable.latest.default ]; + # Shared library liblzma.so.5 used by wasm-pack + shellHook = '' + xz_lib=$(nix-store -q --references $(which xz) | grep xz) + export LD_LIBRARY_PATH=$xz_lib/lib:$LD_LIBRARY_PATH + ''; }; }); }; diff --git a/utils/Cargo.toml b/utils/Cargo.toml index f13bc9a7..216c0599 100644 --- a/utils/Cargo.toml +++ b/utils/Cargo.toml @@ -25,12 +25,22 @@ serde = "1.0" lazy_static = "1.5.0" hex = "0.4" + + [dev-dependencies] ark-bn254 = { version = "0.5.0", features = ["std"] } num-traits = "0.2.19" hex-literal = "1.0.0" tiny-keccak = { version = "2.0.2", features = ["keccak"] } criterion = { version = "0.4.0", features = ["html_reports"] } +rand = "0.9.1" +rand_chacha = "0.9.0" +rand_core = "0.9.3" +# dev artifact for benching zk-kit lean-imt +light-poseidon = "0.3.0" +zk-kit-lean-imt = { git = "https://github.com/privacy-scaling-explorations/zk-kit.rust", package = "zk-kit-lean-imt" } +rln = { path = "../rln", default-features = false } +lazy_static = "1.5.0" [features] default = [] @@ -43,3 +53,6 @@ harness = false [[bench]] name = "poseidon_benchmark" harness = false +[[bench]] +name = "imt_benchy" +harness = false diff --git a/utils/benches/imt_benchy.rs b/utils/benches/imt_benchy.rs new file mode 100644 index 00000000..2ba48934 --- /dev/null +++ b/utils/benches/imt_benchy.rs @@ -0,0 +1,767 @@ +use std::{hint::black_box, str::FromStr, time::Duration}; + +use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion}; +use light_poseidon::{ + Poseidon as LtPoseidon, PoseidonBytesHasher as LtPoseidonBytesHasher, PoseidonHasher as _, +}; +use rand::RngCore; +use rand_chacha::ChaCha8Rng; +use rand_core::SeedableRng; +use rln::{ + circuit::Fr, + hashers::PoseidonHash, + utils::{bytes_le_to_fr, fr_to_bytes_le}, +}; +use zerokit_utils::{ + FullMerkleConfig, FullMerkleTree, Hasher as ZKitUtilsHasher, OptimalMerkleConfig, + OptimalMerkleTree, ZerokitMerkleTree, +}; +use zk_kit_lean_imt::{hashed_tree::LeanIMTHasher, lean_imt::LeanIMT}; + +// ChaCha8Rng is chosen for its portable determinism +struct HashMockStream { + rng: ChaCha8Rng, +} +impl HashMockStream { + fn seeded_stream(seed: u64) -> Self { + let rng = ChaCha8Rng::seed_from_u64(seed); + Self { rng } + } +} + +impl Iterator for HashMockStream { + type Item = [u8; 32]; + + fn next(&mut self) -> Option { + let mut res = [0; 32]; + self.rng.fill_bytes(&mut res); + Some(res) + } +} + +#[derive(Debug)] +/// To benchmark the data structure abscent of the hashing overhead +struct BenchyNoOpHasher; +struct BenchyIFTHasher; +struct BenchyLightPosHasher; + +// ===== +// Ships to IFT Hasher interface +// IFT poseidon hasher doesn't need +// ===== + +impl ZKitUtilsHasher for BenchyNoOpHasher { + type Fr = Fr; + + fn default_leaf() -> Self::Fr { + Fr::default() + } + + fn hash(input: &[Self::Fr]) -> Self::Fr { + *input.first().unwrap_or(&Self::default_leaf()) + } +} + +impl ZKitUtilsHasher for BenchyLightPosHasher { + type Fr = Fr; + + fn default_leaf() -> Self::Fr { + Self::Fr::default() + } + + fn hash(input: &[Self::Fr]) -> Self::Fr { + let mut hasher = LtPoseidon::::new_circom(input.len()).unwrap(); + hasher.hash(input).unwrap() + } +} + +// ===== +// shims for lean imt interface +// ===== + +impl LeanIMTHasher for BenchyNoOpHasher { + fn hash(input: &[u8]) -> [u8; N] { + input[0..32].try_into().unwrap() + } +} +impl LeanIMTHasher<32> for BenchyLightPosHasher { + fn hash(input: &[u8]) -> [u8; 32] { + let chunks: Vec<&[u8]> = input.chunks(32).collect(); + let mut hasher = LtPoseidon::::new_circom(chunks.len()).unwrap(); + hasher.hash_bytes_le(&chunks).unwrap() + } +} +impl LeanIMTHasher<32> for BenchyIFTHasher { + fn hash(input: &[u8]) -> [u8; 32] { + let chunks: Vec<&[u8]> = input.chunks(32).collect(); + let mut lt_hasher = LtPoseidon::::new_circom(chunks.len()).unwrap(); + lt_hasher.hash_bytes_le(&chunks).unwrap() + } +} + +/// We start with the data to be hashed, and make the changes needed for them +/// to be valid Fr bytes, just needing raw reinterpretation. +/// Needed for LeanIMT because it processes &[u8], not &[Fr] +/// and we want to do away with that mapping as a performance variable +fn lean_data_prep(raw_vec: &[[u8; 32]]) -> Vec<[u8; 32]> { + raw_vec + .iter() + .cloned() + // take raw bytes and Fr-ize it + .map(|chunk| bytes_le_to_fr(&chunk).0) + // turn it back into a byte collection + .map(|bytes| fr_to_bytes_le(&bytes)) + // coorce it into the [u8; 32]s needed for light-imt hash signature + .map(|bytes| std::convert::TryInto::<[u8; 32]>::try_into(bytes).unwrap()) + .collect() +} + +fn spawn_inputs(size_group: &[u32]) -> (Vec<[u8; 32]>, Vec) { + let max = *size_group.iter().max().unwrap() as usize; + let data_table: Vec<[u8; 32]> = HashMockStream::seeded_stream(42).take(max).collect(); + + let fr_table = HashMockStream::seeded_stream(42) + .take(max) + .map(|bytes: [u8; 32]| { + Fr::from_str( + bytes + .iter() + .map(|b| format!("{}", b % 10)) + .collect::() + .as_str(), + ) + }) + .collect::, _>>() + .unwrap(); + (data_table, fr_table) +} + +pub fn hashless_setup_iterative(c: &mut Criterion) { + let mut group = c.benchmark_group("hashless tree iterative setup"); + group.measurement_time(Duration::from_secs(10)); + let size_group = [7u32, 13, 17, 40]; + let (data_table, fr_table) = spawn_inputs(&size_group); + + for size in size_group { + let data_source = &data_table[0..size as usize]; + let fr_source = &fr_table[0..size as usize]; + group.bench_function(BenchmarkId::new("Lean IMT iterative", size), |b| { + b.iter_batched( + // Setup: create values for each benchmark iteration + || LeanIMT::<32>::new(&[], >::hash).unwrap(), + // Actual benchmark + |mut tree| { + for d in data_source.iter() { + #[allow(clippy::unit_arg)] + black_box(tree.insert(d, >::hash)) + } + }, + BatchSize::SmallInput, + ) + }); + + group.bench_function( + BenchmarkId::new("IFT optimal iterative", size), + |b| { + b.iter_batched( + // Setup: create values for each benchmark iteration + || { + OptimalMerkleTree::::new( + (size.ilog2() + 1) as usize, + Fr::default(), + OptimalMerkleConfig::default(), + ) + .unwrap() + }, + // Actual benchmark + |mut tree| { + for (i, d) in fr_source.iter().enumerate() { + black_box(tree.set(i, *d)).unwrap(); + } + }, + BatchSize::SmallInput, + ) + }, + ); + group.bench_function( + BenchmarkId::new("IFT full iterative", size), + |b| { + b.iter_batched( + // Setup: create values for each benchmark iteration + || { + FullMerkleTree::::new( + (size.ilog2() + 1) as usize, + Fr::default(), + FullMerkleConfig::default(), + ) + .unwrap() + }, + // Actual benchmark + |mut tree| { + for (i, d) in fr_source.iter().enumerate() { + black_box(tree.set(i, *d)).unwrap(); + } + }, + BatchSize::SmallInput, + ) + }, + ); + } +} +pub fn hashless_setup_batch(c: &mut Criterion) { + let mut group = c.benchmark_group("hashless tree batch setup"); + group.measurement_time(Duration::from_secs(10)); + let size_group = [7u32, 13, 17, 40]; + let (data_table, fr_table) = spawn_inputs(&size_group); + for size in size_group { + let data_source = &data_table[0..size as usize]; + group.bench_function(BenchmarkId::new("Lean IMT batch", size), |b| { + b.iter_batched( + // Setup: create values for each benchmark iteration + || LeanIMT::<32>::new(&[], >::hash).unwrap(), + // Actual benchmark + |mut tree| { + black_box(tree.insert_many( + data_source, + black_box(>::hash), + )) + }, + BatchSize::SmallInput, + ) + }); + group.bench_function( + BenchmarkId::new("IFT optimal batch", size), + |b| { + b.iter_batched( + // Setup: create values for each benchmark iteration + || { + let data_source = &fr_table[0..size as usize]; + let data_source = data_source.iter().copied(); + let tree = OptimalMerkleTree::::new( + (size.ilog2() + 1) as usize, + Fr::default(), + OptimalMerkleConfig::default(), + ) + .unwrap(); + (tree, data_source) + }, + // Actual benchmark + |(mut tree, data_source)| black_box(tree.set_range(0, data_source)), + BatchSize::SmallInput, + ) + }, + ); + group.bench_function( + BenchmarkId::new("IFT full batch", size), + |b| { + b.iter_batched( + // Setup: create values for each benchmark iteration + || { + let data_source = &fr_table[0..size as usize]; + let data_source = data_source.iter().copied(); + let tree = FullMerkleTree::::new( + (size.ilog2() + 1) as usize, + Fr::default(), + FullMerkleConfig::default(), + ) + .unwrap(); + (tree, data_source) + }, + // Actual benchmark + |(mut tree, data_source)| black_box(tree.set_range(0, data_source)), + BatchSize::SmallInput, + ) + }, + ); + } + group.finish(); +} + +fn tree_hash_batch_setup_shootout(c: &mut Criterion) { + let mut group = c.benchmark_group("hash+tree batch shootout"); + group.measurement_time(Duration::from_secs(10)); + let size_group = [7u32, 13, 17, 40]; + let (data_table, fr_table) = spawn_inputs(&size_group); + for size in size_group { + let data_source = &data_table[0..size as usize]; + group.bench_function(BenchmarkId::new("Lean IMT batch poseidon", size), |b| { + b.iter_batched( + // Setup: create values for each benchmark iteration + || { + let byte_form = lean_data_prep(data_source); + let tree = + LeanIMT::<32>::new(&[], >::hash) + .unwrap(); + (tree, byte_form) + }, + // Actual benchmark + |(mut tree, byte_form)| { + black_box(tree.insert_many( + &byte_form, + black_box(>::hash), + )) + }, + BatchSize::SmallInput, + ) + }); + group.bench_function( + BenchmarkId::new("Lean IMT batch light-poseidon", size), + |b| { + b.iter_batched( + // Setup: create values for each benchmark iteration + || { + let byte_form = lean_data_prep(data_source); + let tree = LeanIMT::<32>::new( + &[], + >::hash, + ) + .unwrap(); + (tree, byte_form) + }, + // Actual benchmark + |(mut tree, data_source)| { + black_box(tree.insert_many( + &data_source, + black_box(>::hash), + )) + }, + BatchSize::SmallInput, + ) + }, + ); + group.bench_function( + BenchmarkId::new("IFT optimal batch poseidon", size), + |b| { + b.iter_batched( + // Setup: create values for each benchmark iteration + || { + let fr_slice = &fr_table[0..size as usize]; + let fr_iter = fr_slice.iter().copied(); + let tree = OptimalMerkleTree::::new( + (size.ilog2() + 1) as usize, + Fr::default(), + OptimalMerkleConfig::default(), + ) + .unwrap(); + (tree, fr_iter) + }, + // Actual benchmark + |(mut tree, data_source)| black_box(tree.set_range(0, data_source)), + BatchSize::SmallInput, + ) + }, + ); + group.bench_function( + BenchmarkId::new("IFT optimal batch light-poseidon", size), + |b| { + b.iter_batched( + // Setup: create values for each benchmark iteration + || { + let fr_slice = &fr_table[0..size as usize]; + let fr_iter = fr_slice.iter().copied(); + let tree = OptimalMerkleTree::::new( + (size.ilog2() + 1) as usize, + Fr::default(), + OptimalMerkleConfig::default(), + ) + .unwrap(); + (tree, fr_iter) + }, + // Actual benchmark + |(mut tree, fr_iter)| black_box(tree.set_range(0, fr_iter)), + BatchSize::SmallInput, + ) + }, + ); + group.bench_function( + BenchmarkId::new("IFT full batch poseidon", size), + |b| { + b.iter_batched( + // Setup: create values for each benchmark iteration + || { + let fr_slice = &fr_table[0..size as usize]; + let fr_iter = fr_slice.iter().copied(); + let tree = FullMerkleTree::::new( + (size.ilog2() + 1) as usize, + Fr::default(), + FullMerkleConfig::default(), + ) + .unwrap(); + (tree, fr_iter) + }, + // Actual benchmark + |(mut tree, fr_iter)| black_box(tree.set_range(0, fr_iter)), + BatchSize::SmallInput, + ) + }, + ); + group.bench_function( + BenchmarkId::new("IFT full batch light-poseidon", size), + |b| { + b.iter_batched( + // Setup: create values for each benchmark iteration + || { + let fr_slice = &fr_table[0..size as usize]; + let fr_iter = fr_slice.iter().copied(); + let tree = FullMerkleTree::::new( + (size.ilog2() + 1) as usize, + Fr::default(), + FullMerkleConfig::default(), + ) + .unwrap(); + (tree, fr_iter) + }, + // Actual benchmark + |(mut tree, fr_iter)| black_box(tree.set_range(0, fr_iter)), + BatchSize::SmallInput, + ) + }, + ); + } + group.finish(); +} + +pub fn proof_gen_shootout(c: &mut Criterion) { + let mut group = c.benchmark_group("MTree proof-gen shootout"); + group.measurement_time(Duration::from_secs(15)); + let size_group = [7u32, 13, 17, 40]; + let (data_table, fr_table) = spawn_inputs(&size_group); + for size in size_group { + let data_source = &data_table[0..size as usize]; + // let data_stream = HashMockStream::seeded_stream(size as u64); + // let chunk_vec = data_stream.take(size as usize).collect::>(); + group.bench_function( + BenchmarkId::new("Lean IMT + ift_pos proof generation", size), + |b,| { + b.iter_batched( + // Setup: create values for each benchmark iteration + || { + let frd_byte_chunks = lean_data_prep(data_source); + LeanIMT::<32>::new( + &frd_byte_chunks, + >::hash, + ) + .unwrap() + }, + // Actual benchmark + |tree| black_box(tree.generate_proof(0)), + BatchSize::SmallInput, + ) + }, + ); + group.bench_function( + BenchmarkId::new("Lean IMT + light-pos proof generation", size), + |b,| { + b.iter_batched( + // Setup: create values for each benchmark iteration + || { + let frd_byte_chunks = lean_data_prep(data_source); + LeanIMT::<32>::new( + &frd_byte_chunks, + >::hash, + ) + .unwrap() + }, + // Actual benchmark + |tree| black_box(tree.generate_proof(0)), + BatchSize::SmallInput, + ) + }, + ); + group.bench_function( + BenchmarkId::new("IFT full + ift-pos proof generation", size), + |b| { + b.iter_batched( + // Setup: create values for each benchmark iteration + || { + let fr_form: Vec = data_source + .iter() + .cloned() + // take raw bytes and Fr-ize it + .map(|chunk| bytes_le_to_fr(&chunk).0) + .collect(); + let mut tree = FullMerkleTree::::new( + size.ilog2() as usize + 1, + Fr::default(), + FullMerkleConfig::default(), + ) + .unwrap(); + tree.set_range(0, fr_form.into_iter()).unwrap(); + tree + }, + // Actual benchmark + |tree| black_box(tree.proof(0)), + BatchSize::SmallInput, + ) + }, + ); + group.bench_function( + BenchmarkId::new("IFT full + light-pos proof generation", size), + |b,| { + b.iter_batched( + // Setup: create values for each benchmark iteration + || { + let fr_form: Vec = data_source + .iter() + .cloned() + // take raw bytes and Fr-ize it + .map(|chunk| bytes_le_to_fr(&chunk).0) + .collect(); + let mut tree = FullMerkleTree::::new( + size.ilog2() as usize + 1, + Fr::default(), + FullMerkleConfig::default(), + ) + .unwrap(); + tree.set_range(0, fr_form.into_iter()).unwrap(); + tree + }, + // Actual benchmark + |tree| black_box(tree.proof(0)), + BatchSize::SmallInput, + ) + }, + ); + group.bench_function( + BenchmarkId::new("IFT optimal + ift-pos proof generation", size), + |b,| { + b.iter_batched( + // Setup: create values for each benchmark iteration + || { + let fr_slice = &fr_table[0..size as usize]; + let fr_iter = fr_slice.iter().copied(); + let mut tree = OptimalMerkleTree::::new( + size.ilog2() as usize + 1, + Fr::default(), + OptimalMerkleConfig::default(), + ) + .unwrap(); + tree.set_range(0, fr_iter).unwrap(); + tree + }, + // Actual benchmark + |tree| black_box(tree.proof(0)), + BatchSize::SmallInput, + ) + }, + ); + group.bench_function( + BenchmarkId::new("IFT optimal + light-pos proof generation", size), + |b,| { + b.iter_batched( + // Setup: create values for each benchmark iteration + || { + let fr_slice = &fr_table[0..size as usize]; + let fr_iter = fr_slice.iter().copied(); + let mut tree = OptimalMerkleTree::::new( + size.ilog2() as usize + 1, + Fr::default(), + OptimalMerkleConfig::default(), + ) + .unwrap(); + tree.set_range(0, fr_iter).unwrap(); + tree + }, + // Actual benchmark + |tree| black_box(tree.proof(0)), + BatchSize::SmallInput, + ) + }, + ); + } +} + +pub fn verification_shootout(c: &mut Criterion) { + let mut group = c.benchmark_group("MTree verification shootout"); + group.measurement_time(Duration::from_secs(15)); + let size_group = [7u32, 17, 40]; + for size in size_group { + let data_stream = HashMockStream::seeded_stream(size as u64); + let data_source = data_stream.take(size as usize).collect::>(); + group.bench_function( + BenchmarkId::new("Lean IMT + ift-pos verification", size), + |b| { + b.iter_batched( + // Setup: create values for each benchmark iteration + || { + let frd_bytes = lean_data_prep(&data_source); + let tree = LeanIMT::<32>::new( + &frd_bytes, + >::hash, + ) + .unwrap(); + let proof = tree.generate_proof(0).unwrap(); + // assert!(LeanIMT::verify_proof(&proof, >::hash)); + proof + }, + // Actual benchmark + |proof| { + black_box(LeanIMT::verify_proof( + &proof, + >::hash, + )) + }, + BatchSize::SmallInput, + ) + }, + ); + group.bench_function( + BenchmarkId::new("Lean IMT + light-pos verification", size), + |b| { + b.iter_batched( + // Setup: create values for each benchmark iteration + || { + let frd_bytes = lean_data_prep(&data_source); + let tree = LeanIMT::<32>::new( + &frd_bytes, + >::hash, + ) + .unwrap(); + let proof = tree.generate_proof(0).unwrap(); + // assert!(LeanIMT::verify_proof(&proof, >::hash)); + proof + }, + // Actual benchmark + |proof| { + black_box(LeanIMT::verify_proof( + &proof, + >::hash, + )) + }, + BatchSize::SmallInput, + ) + }, + ); + group.bench_function( + BenchmarkId::new("IFT full + light-pos verification", size), + |b| { + b.iter_batched( + // Setup: create values for each benchmark iteration + || { + let fr_form: Vec = data_source + .iter() + .cloned() + // take raw bytes and Fr-ize it + .map(|chunk| bytes_le_to_fr(&chunk).0) + .collect(); + let mut tree = FullMerkleTree::::new( + size.ilog2() as usize + 1, + Fr::default(), + FullMerkleConfig::default(), + ) + .unwrap(); + let first_leaf = *fr_form.first().unwrap(); + tree.set_range(0, fr_form.into_iter()).unwrap(); + let proof = tree.proof(0).unwrap(); + // assert!(tree.verify(&first_leaf, &proof).unwrap()); + (first_leaf, tree, proof) + }, + // Actual benchmark + |(first_leaf, tree, proof)| black_box(tree.verify(&first_leaf, &proof)), + BatchSize::SmallInput, + ) + }, + ); + group.bench_function( + BenchmarkId::new("IFT full + ift-pos verification", size), + |b| { + b.iter_batched( + // Setup: create values for each benchmark iteration + || { + let fr_form: Vec = data_source + .iter() + .cloned() + // take raw bytes and Fr-ize it + .map(|chunk| bytes_le_to_fr(&chunk).0) + .collect(); + let mut tree = FullMerkleTree::::new( + size.ilog2() as usize + 1, + Fr::default(), + FullMerkleConfig::default(), + ) + .unwrap(); + let first_leaf = *fr_form.first().unwrap(); + tree.set_range(0, fr_form.into_iter()).unwrap(); + let proof = tree.proof(0).unwrap(); + // assert!(tree.verify(&first_leaf, &proof).unwrap()); + (first_leaf, tree, proof) + }, + // Actual benchmark + |(first_leaf, tree, proof)| black_box(tree.verify(&first_leaf, &proof)), + BatchSize::SmallInput, + ) + }, + ); + group.bench_function( + BenchmarkId::new("IFT optimal + light-pos verification", size), + |b| { + b.iter_batched( + // Setup: create values for each benchmark iteration + || { + let fr_form: Vec = data_source + .iter() + .cloned() + // take raw bytes and Fr-ize it + .map(|chunk| bytes_le_to_fr(&chunk).0) + .collect(); + let mut tree = OptimalMerkleTree::::new( + size.ilog2() as usize + 1, + Fr::default(), + OptimalMerkleConfig::default(), + ) + .unwrap(); + let first_leaf = *fr_form.first().unwrap(); + tree.set_range(0, fr_form.into_iter()).unwrap(); + let proof = tree.proof(0).unwrap(); + // assert!(tree.verify(&first_leaf, &proof).unwrap()); + (first_leaf, tree, proof) + }, + // Actual benchmark + |(first_leaf, tree, proof)| black_box(tree.verify(&first_leaf, &proof)), + BatchSize::SmallInput, + ) + }, + ); + group.bench_function( + BenchmarkId::new("IFT optimal + ift-pos verification", size), + |b| { + b.iter_batched( + // Setup: create values for each benchmark iteration + || { + let fr_form: Vec = data_source + .iter() + .cloned() + // take raw bytes and Fr-ize it + .map(|chunk| bytes_le_to_fr(&chunk).0) + .collect(); + let mut tree = OptimalMerkleTree::::new( + size.ilog2() as usize + 1, + Fr::default(), + OptimalMerkleConfig::default(), + ) + .unwrap(); + let first_leaf = *fr_form.first().unwrap(); + tree.set_range(0, fr_form.into_iter()).unwrap(); + let proof = tree.proof(0).unwrap(); + // assert!(tree.verify(&first_leaf, &proof).unwrap()); + (first_leaf, tree, proof) + }, + // Actual benchmark + |(first_leaf, tree, proof)| black_box(tree.verify(&first_leaf, &proof)), + BatchSize::SmallInput, + ) + }, + ); + } +} +criterion_main!(tree_benchies); +criterion_group! { + name = tree_benchies; + config = Criterion::default(); + targets = + hashless_setup_batch, + hashless_setup_iterative, + tree_hash_batch_setup_shootout, + proof_gen_shootout, + verification_shootout +} diff --git a/utils/benches/merkle_tree_benchmark.rs b/utils/benches/merkle_tree_benchmark.rs index 89ebbe62..c4102c43 100644 --- a/utils/benches/merkle_tree_benchmark.rs +++ b/utils/benches/merkle_tree_benchmark.rs @@ -1,6 +1,8 @@ use criterion::{criterion_group, criterion_main, Criterion}; -use hex_literal::hex; use lazy_static::lazy_static; +use rand::RngCore; +use rand_chacha::ChaCha8Rng; +use rand_core::SeedableRng; use std::{fmt::Display, str::FromStr}; use tiny_keccak::{Hasher as _, Keccak}; use zerokit_utils::{ @@ -14,6 +16,28 @@ struct Keccak256; #[derive(Clone, Copy, Eq, PartialEq, Debug, Default)] struct TestFr([u8; 32]); +// ChaCha8Rng is chosen for its portable determinism +struct FrRngStream { + rng: ChaCha8Rng, +} + +impl FrRngStream { + fn seeded_stream(seed: u64) -> Self { + let rng = ChaCha8Rng::seed_from_u64(seed); + Self { rng } + } +} + +impl Iterator for FrRngStream { + type Item = TestFr; + + fn next(&mut self) -> Option { + let mut res = [0; 32]; + self.rng.fill_bytes(&mut res); + Some(TestFr(res)) + } +} + impl Hasher for Keccak256 { type Fr = TestFr; @@ -47,13 +71,11 @@ impl FromStr for TestFr { } lazy_static! { - static ref LEAVES: [TestFr; 4] = [ - hex!("0000000000000000000000000000000000000000000000000000000000000001"), - hex!("0000000000000000000000000000000000000000000000000000000000000002"), - hex!("0000000000000000000000000000000000000000000000000000000000000003"), - hex!("0000000000000000000000000000000000000000000000000000000000000004"), - ] - .map(TestFr); + static ref LEAVES: [TestFr; 40] = FrRngStream::seeded_stream(42) + .take(40) + .collect::>() + .try_into() + .unwrap(); } pub fn optimal_merkle_tree_benchmark(c: &mut Criterion) { @@ -75,7 +97,7 @@ pub fn optimal_merkle_tree_benchmark(c: &mut Criterion) { c.bench_function("OptimalMerkleTree::override_range", |b| { b.iter(|| { - tree.override_range(0, LEAVES.into_iter(), [0, 1, 2, 3].into_iter()) + tree.override_range(0, LEAVES.into_iter().take(4), [0, 1, 2, 3].into_iter()) .unwrap(); }) }); @@ -124,7 +146,7 @@ pub fn full_merkle_tree_benchmark(c: &mut Criterion) { c.bench_function("FullMerkleTree::override_range", |b| { b.iter(|| { - tree.override_range(0, LEAVES.into_iter(), [0, 1, 2, 3].into_iter()) + tree.override_range(0, LEAVES.into_iter().take(4), [0, 1, 2, 3].into_iter()) .unwrap(); }) });