From 9a5ab32d0dcf38b1db71bc27e312b1ad8c3e8ace Mon Sep 17 00:00:00 2001 From: markoburcul Date: Wed, 23 Apr 2025 11:54:35 +0200 Subject: [PATCH 01/11] flake: add rust overlay and shell dependencies --- flake.lock | 23 ++++++++++++++++++++++- flake.nix | 23 +++++++++++++++++++---- 2 files changed, 41 insertions(+), 5 deletions(-) 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..f7029a12 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,19 @@ pkgs = pkgsFor.${system}; in { default = pkgs.mkShell { - inputsFrom = [ - packages.${system}.default + buildInputs = with pkgs; [ + git + cmake + 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 + ''; }; }); }; From f9bf41b69dd89aa3a450fdf10e69dd8a83100a75 Mon Sep 17 00:00:00 2001 From: markoburcul Date: Wed, 23 Apr 2025 11:54:55 +0200 Subject: [PATCH 02/11] makefile: install wasm-pack with cargo --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 602c923296ec8a86752a98ea9842777987bbcc9f Mon Sep 17 00:00:00 2001 From: Ben-PH Date: Thu, 1 May 2025 15:14:54 +0200 Subject: [PATCH 03/11] build(nix devshell): Add gnuplot and cargo-make --- flake.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flake.nix b/flake.nix index f7029a12..1af2e27e 100644 --- a/flake.nix +++ b/flake.nix @@ -38,6 +38,8 @@ git cmake rustup + cargo-make + gnuplot xz wasm-pack rust-bin.stable.latest.default From 4d93b592bcd13e7243f11b6f6fdbd0e119ca42b6 Mon Sep 17 00:00:00 2001 From: Ben-PH Date: Mon, 5 May 2025 12:43:12 +0200 Subject: [PATCH 04/11] feat: Add light-poseidon crate dep --- Cargo.lock | 39 ++++++++++++++++++++++++++--- utils/Cargo.toml | 1 + utils/benches/poseidon_benchmark.rs | 1 + 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 56d597ac..cebc6141 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", ] @@ -1706,7 +1718,7 @@ dependencies = [ "serde", "serde_json", "sled", - "thiserror", + "thiserror 2.0.12", "tiny-keccak", "zerokit_utils", ] @@ -2008,13 +2020,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,6 +2569,7 @@ dependencies = [ "hex", "hex-literal", "lazy_static", + "light-poseidon", "num-bigint", "num-traits", "serde", diff --git a/utils/Cargo.toml b/utils/Cargo.toml index f13bc9a7..504cf6ee 100644 --- a/utils/Cargo.toml +++ b/utils/Cargo.toml @@ -24,6 +24,7 @@ sled = "0.34.7" serde = "1.0" lazy_static = "1.5.0" hex = "0.4" +light-poseidon = "0.3.0" [dev-dependencies] ark-bn254 = { version = "0.5.0", features = ["std"] } diff --git a/utils/benches/poseidon_benchmark.rs b/utils/benches/poseidon_benchmark.rs index b48485cd..120b7483 100644 --- a/utils/benches/poseidon_benchmark.rs +++ b/utils/benches/poseidon_benchmark.rs @@ -2,6 +2,7 @@ use ark_bn254::Fr; use criterion::{ black_box, criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion, Throughput, }; +use light_poseidon::PoseidonParameters as LPoseidonParameters; use zerokit_utils::Poseidon; const ROUND_PARAMS: [(usize, usize, usize, usize); 8] = [ From 80b393568fc8b56673d9869464b1dc4b7205585a Mon Sep 17 00:00:00 2001 From: Ben-PH Date: Mon, 5 May 2025 18:21:00 +0200 Subject: [PATCH 05/11] style: Make poseidon details easier to grok --- utils/src/poseidon/poseidon_constants.rs | 54 +++++---- utils/src/poseidon/poseidon_hash.rs | 140 ++++++++++++++++------- utils/tests/poseidon_constants.rs | 4 +- 3 files changed, 130 insertions(+), 68 deletions(-) diff --git a/utils/src/poseidon/poseidon_constants.rs b/utils/src/poseidon/poseidon_constants.rs index b6cca338..a601f400 100644 --- a/utils/src/poseidon/poseidon_constants.rs +++ b/utils/src/poseidon/poseidon_constants.rs @@ -225,37 +225,43 @@ pub fn find_poseidon_ark_and_mds( partial_rounds, ); - let mut ark = Vec::::with_capacity((full_rounds + partial_rounds) as usize); - for _ in 0..(full_rounds + partial_rounds) { - let values = lfsr.get_field_elements_rejection_sampling::(rate); - for el in values { - ark.push(el); + let ark = { + let mut res = Vec::::with_capacity((full_rounds + partial_rounds) as usize); + for _ in 0..(full_rounds + partial_rounds) { + let values = lfsr.get_field_elements_rejection_sampling::(rate); + for el in values { + res.push(el); + } } - } + res + }; - let mut mds = Vec::>::with_capacity(rate); - mds.resize(rate, vec![F::zero(); rate]); + let mds = { + let mut res = Vec::>::with_capacity(rate); + res.resize(rate, vec![F::zero(); rate]); - // Note that we build the MDS matrix generating 2*rate elements. If the matrix built is not secure (see checks with algorithm 1, 2, 3 in reference implementation) - // it has to be skipped. Since here we do not implement such algorithm we allow to pass a parameter to skip generations of elements giving unsecure matrixes. - // At the moment, the skip_matrices parameter has to be generated from the reference implementation and passed to this function - for _ in 0..skip_matrices { - let _ = lfsr.get_field_elements_mod_p::(2 * (rate)); - } + // Note that we build the MDS matrix generating 2*rate elements. If the matrix built is not secure (see checks with algorithm 1, 2, 3 in reference implementation) + // it has to be skipped. Since here we do not implement such algorithm we allow to pass a parameter to skip generations of elements giving unsecure matrixes. + // At the moment, the skip_matrices parameter has to be generated from the reference implementation and passed to this function + for _ in 0..skip_matrices { + let _ = lfsr.get_field_elements_mod_p::(2 * (rate)); + } - // a qualifying matrix must satisfy the following requirements - // - there is no duplication among the elements in x or y - // - there is no i and j such that x[i] + y[j] = p - // - the resultant MDS passes all the three tests + // a qualifying matrix must satisfy the following requirements + // - there is no duplication among the elements in x or y + // - there is no i and j such that x[i] + y[j] = p + // - the resultant MDS passes all the three tests - let xs = lfsr.get_field_elements_mod_p::(rate); - let ys = lfsr.get_field_elements_mod_p::(rate); + let xs = lfsr.get_field_elements_mod_p::(rate); + let ys = lfsr.get_field_elements_mod_p::(rate); - for i in 0..(rate) { - for (j, ys_item) in ys.iter().enumerate().take(rate) { - mds[i][j] = (xs[i] + ys_item).inverse().unwrap(); + for i in 0..(rate) { + for (j, ys_item) in ys.iter().enumerate().take(rate) { + res[i][j] = (xs[i] + ys_item).inverse().unwrap(); + } } - } + res + }; (ark, mds) } diff --git a/utils/src/poseidon/poseidon_hash.rs b/utils/src/poseidon/poseidon_hash.rs index 742567af..7c744933 100644 --- a/utils/src/poseidon/poseidon_hash.rs +++ b/utils/src/poseidon/poseidon_hash.rs @@ -8,49 +8,80 @@ use ark_ff::PrimeField; #[derive(Debug, Clone, PartialEq, Eq)] pub struct RoundParameters { + // confirm: Is this "rate"? does this correlate with light-poseidon "width" parameter? pub t: usize, - pub n_rounds_f: usize, - pub n_rounds_p: usize, + pub n_rounds_full: usize, + pub n_rounds_partial: usize, pub skip_matrices: usize, - pub c: Vec, - pub m: Vec>, + pub ark_consts: Vec, + pub mds: Vec>, +} +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RoundParameVec { + pub inner: Vec>, } +// Dev artifact: helps grok internal params against light-poseidon approach to params +// /// Parameters for the Poseidon hash algorithm. +// pub struct PoseidonParameters { +// /// Round constants. +// pub ark: Vec, +// /// MDS matrix. +// pub mds: Vec>, +// /// Number of full rounds (where S-box is applied to all elements of the +// /// state). +// pub full_rounds: usize, +// /// Number of partial rounds (where S-box is applied only to the first +// /// element of the state). +// pub partial_rounds: usize, +// /// Number of prime fields in the state. +// pub width: usize, +// /// Exponential used in S-box to power elements of the state. +// pub alpha: u64, +// } + pub struct Poseidon { round_params: Vec>, } -impl Poseidon { - // Loads round parameters and generates round constants - // poseidon_params is a vector containing tuples (t, RF, RP, skip_matrices) - // where: t is the rate (input length + 1), RF is the number of full rounds, RP is the number of partial rounds - // and skip_matrices is a (temporary) parameter used to generate secure MDS matrices (see comments in the description of find_poseidon_ark_and_mds) - // TODO: implement automatic generation of round parameters - pub fn from(poseidon_params: &[(usize, usize, usize, usize)]) -> Self { + +impl RoundParameVec { + fn make_param_vec(poseidon_params: &[(usize, usize, usize, usize)]) -> Self { let mut read_params = Vec::>::with_capacity(poseidon_params.len()); - for &(t, n_rounds_f, n_rounds_p, skip_matrices) in poseidon_params { + for &(t, n_rounds_full, n_rounds_partial, skip_matrices) in poseidon_params { let (ark, mds) = find_poseidon_ark_and_mds::( 1, // is_field = 1 0, // is_sbox_inverse = 0 F::MODULUS_BIT_SIZE as u64, t, - n_rounds_f as u64, - n_rounds_p as u64, + n_rounds_full as u64, + n_rounds_partial as u64, skip_matrices, ); let rp = RoundParameters { t, - n_rounds_p, - n_rounds_f, + n_rounds_partial, + n_rounds_full, skip_matrices, - c: ark, - m: mds, + ark_consts: ark, + mds, }; read_params.push(rp); } - + Self { inner: read_params } + } +} +impl Poseidon { + // Loads round parameters and generates round constants + // poseidon_params is a vector containing tuples (t, n_rounds_full, n_rounds_partial, skip_matrices) + // where t is the rate (input length + 1) + // and skip_matrices is a (temporary) parameter used to generate secure MDS matrices (see comments in the description of find_poseidon_ark_and_mds) + // TODO: implement automatic generation of round parameters + pub fn from(poseidon_params: &[(usize, usize, usize, usize)]) -> Self { + let param_vec = RoundParameVec::make_param_vec(poseidon_params); + // dbg!(¶m_vec.inner); Poseidon { - round_params: read_params, + round_params: param_vec.inner, } } @@ -93,37 +124,24 @@ impl Poseidon { } pub fn hash(&self, inp: &[F]) -> Result { + if inp.is_empty() { + return Err("Attempt to hash empty data input".to_string()); + } // Note that the rate t becomes input length + 1; hence for length N we pick parameters with T = N + 1 let t = inp.len() + 1; - // We seek the index (Poseidon's round_params is an ordered vector) for the parameters corresponding to t - let param_index = self.round_params.iter().position(|el| el.t == t); - - if inp.is_empty() || param_index.is_none() { + let Some(params) = self.round_params.iter().find(|el| el.t == t) else { return Err("No parameters found for inputs length".to_string()); - } - - let param_index = param_index.unwrap(); + }; let mut state = vec![F::ZERO; t]; let mut state_2 = state.clone(); state[1..].clone_from_slice(inp); - for i in 0..(self.round_params[param_index].n_rounds_f - + self.round_params[param_index].n_rounds_p) - { - self.ark( - &mut state, - &self.round_params[param_index].c, - i * self.round_params[param_index].t, - ); - self.sbox( - self.round_params[param_index].n_rounds_f, - self.round_params[param_index].n_rounds_p, - &mut state, - i, - ); - self.mix_2(&state, &self.round_params[param_index].m, &mut state_2); + for i in 0..(params.n_rounds_full + params.n_rounds_partial) { + self.ark(&mut state, ¶ms.ark_consts, i * params.t); + self.sbox(params.n_rounds_full, params.n_rounds_partial, &mut state, i); + self.mix_2(&state, ¶ms.mds, &mut state_2); std::mem::swap(&mut state, &mut state_2); } @@ -140,3 +158,41 @@ where Self::from(&[]) } } + +// WIP artifact +#[cfg(test)] +mod test { + use ark_bn254::Fr; + + use super::*; + const ROUND_PARAMS: [(usize, usize, usize, usize); 8] = [ + (2, 8, 56, 0), + (3, 8, 57, 0), + (4, 8, 56, 0), + (5, 8, 60, 0), + (6, 8, 60, 0), + (7, 8, 63, 0), + (8, 8, 64, 0), + (9, 8, 63, 0), + ]; + // #[test] + // fn see_params() { + // let mut param_vec = RoundParameVec::::make_param_vec(&ROUND_PARAMS); + // let stats /* (rate, fulls, partual, sm, ark_n, mds_n) */ = param_vec.inner.into_iter().map(|RoundParameters { rate, n_rounds_full, n_rounds_partial, skip_matrices, ark_consts, mds }| (rate, n_rounds_full, n_rounds_partial, skip_matrices, ark_consts.len(), mds.len())).collect::>(); + // println!("r f p s cl ml"); + // for s in stats.iter() { + // println!("{:?}", s); + // } + // panic!(); + // } + #[test] + fn see_data() { + let size = 10; + let mut param_vec = RoundParameVec::::make_param_vec(&ROUND_PARAMS); + let mut values = Vec::with_capacity(size as usize); + for i in 0..size { + values.push([Fr::from(u128::MAX - i)]); + } + panic!("{:?}", values); + } +} diff --git a/utils/tests/poseidon_constants.rs b/utils/tests/poseidon_constants.rs index 02bb0aec..43c7562a 100644 --- a/utils/tests/poseidon_constants.rs +++ b/utils/tests/poseidon_constants.rs @@ -3533,8 +3533,8 @@ mod test { let poseidon_hasher = Poseidon::::from(&ROUND_PARAMS); let poseidon_parameters = poseidon_hasher.get_parameters(); for i in 0..poseidon_parameters.len() { - assert_eq!(loaded_c[i], poseidon_parameters[i].c); - assert_eq!(loaded_m[i], poseidon_parameters[i].m); + assert_eq!(loaded_c[i], poseidon_parameters[i].ark_consts); + assert_eq!(loaded_m[i], poseidon_parameters[i].mds); } } else { unreachable!(); From b65433ba99fdcd17724b4177eac843a7fe40d434 Mon Sep 17 00:00:00 2001 From: Ben-PH Date: Mon, 5 May 2025 19:32:00 +0200 Subject: [PATCH 06/11] (ugly as sin)test: generate objects needed to bench_cmp poseidons --- utils/benches/poseidon_benchmark.rs | 57 ++++++++++++++++++++++++----- utils/src/poseidon/poseidon_hash.rs | 40 +++++++++++--------- 2 files changed, 70 insertions(+), 27 deletions(-) diff --git a/utils/benches/poseidon_benchmark.rs b/utils/benches/poseidon_benchmark.rs index 120b7483..82c402db 100644 --- a/utils/benches/poseidon_benchmark.rs +++ b/utils/benches/poseidon_benchmark.rs @@ -2,8 +2,10 @@ use ark_bn254::Fr; use criterion::{ black_box, criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion, Throughput, }; -use light_poseidon::PoseidonParameters as LPoseidonParameters; -use zerokit_utils::Poseidon; +use light_poseidon::{ + PoseidonHasher as LPoseidonHasher, PoseidonParameters as LPoseidonParameters, +}; +use zerokit_utils::{Poseidon, RoundParameters}; const ROUND_PARAMS: [(usize, usize, usize, usize); 8] = [ (2, 8, 56, 0), @@ -16,6 +18,14 @@ const ROUND_PARAMS: [(usize, usize, usize, usize); 8] = [ (9, 8, 63, 0), ]; +fn make_values(size: u32) -> Vec<[Fr; 1]> { + let mut values = Vec::with_capacity(size as usize); + for i in 0..size { + values.push([Fr::from(i)]); + } + values +} + pub fn poseidon_benchmark(c: &mut Criterion) { let hasher = Poseidon::::from(&ROUND_PARAMS); let mut group = c.benchmark_group("poseidon Fr"); @@ -26,13 +36,7 @@ pub fn poseidon_benchmark(c: &mut Criterion) { group.bench_with_input(BenchmarkId::new("Array hash", size), size, |b, &size| { b.iter_batched( // Setup: create values for each benchmark iteration - || { - let mut values = Vec::with_capacity(size as usize); - for i in 0..size { - values.push([Fr::from(i)]); - } - values - }, + || make_values(size), // Actual benchmark |values| { for v in values.iter() { @@ -40,6 +44,41 @@ pub fn poseidon_benchmark(c: &mut Criterion) { } }, BatchSize::SmallInput, + ); + b.iter_batched( + // Setup: create values for each benchmark iteration + || { + // first, we need to pull out the parameters that the internal hasher is + // using... + let RoundParameters { + t, + n_rounds_full, + n_rounds_partial, + skip_matrices: _, + ark_consts, + mds, + } = hasher.select_params(&[Fr::from(1)]).unwrap(); + // then we need to translate it to the light-poseidon paramater + let l_params = LPoseidonParameters { + ark: ark_consts.clone(), + mds: mds.clone(), + full_rounds: *n_rounds_full, + partial_rounds: *n_rounds_partial, + width: *t, + alpha: 1, + }; + + let vals = make_values(size); + let light_hasher = light_poseidon::Poseidon::::new(l_params); + (vals, light_hasher) + }, + // Actual benchmark + |(values, mut light_hasher)| { + for v in values.iter() { + let _ = light_hasher.hash(black_box(&v[..])); + } + }, + BatchSize::SmallInput, ) }); } diff --git a/utils/src/poseidon/poseidon_hash.rs b/utils/src/poseidon/poseidon_hash.rs index 7c744933..ea27f845 100644 --- a/utils/src/poseidon/poseidon_hash.rs +++ b/utils/src/poseidon/poseidon_hash.rs @@ -123,20 +123,24 @@ impl Poseidon { } } - pub fn hash(&self, inp: &[F]) -> Result { + pub fn select_params(&self, inp: &[F]) -> Result<&RoundParameters, String> { if inp.is_empty() { return Err("Attempt to hash empty data input".to_string()); } // Note that the rate t becomes input length + 1; hence for length N we pick parameters with T = N + 1 let t = inp.len() + 1; + self.round_params + .iter() + .find(|el| el.t == t) + .ok_or("No parameters found for inputs length".to_string()) + } - let Some(params) = self.round_params.iter().find(|el| el.t == t) else { - return Err("No parameters found for inputs length".to_string()); - }; - - let mut state = vec![F::ZERO; t]; - let mut state_2 = state.clone(); - state[1..].clone_from_slice(inp); + pub fn hash(&self, inp: &[F]) -> Result { + let params = self.select_params(inp)?; + let mut state = Vec::with_capacity(inp.len() + 1); + state.push(F::ZERO); + state.extend_from_slice(inp); + let mut state_2 = vec![F::ZERO; inp.len() + 1]; for i in 0..(params.n_rounds_full + params.n_rounds_partial) { self.ark(&mut state, ¶ms.ark_consts, i * params.t); @@ -185,14 +189,14 @@ mod test { // } // panic!(); // } - #[test] - fn see_data() { - let size = 10; - let mut param_vec = RoundParameVec::::make_param_vec(&ROUND_PARAMS); - let mut values = Vec::with_capacity(size as usize); - for i in 0..size { - values.push([Fr::from(u128::MAX - i)]); - } - panic!("{:?}", values); - } + // #[test] + // fn see_data() { + // let size = 10; + // let mut param_vec = RoundParameVec::::make_param_vec(&ROUND_PARAMS); + // let mut values = Vec::with_capacity(size as usize); + // for i in 0..size { + // values.push([Fr::from(u128::MAX - i)]); + // } + // panic!("{:?}", values); + // } } From 63a0472466258dc3d4fef90b7b9be5b56722dedf Mon Sep 17 00:00:00 2001 From: Ben-PH Date: Tue, 6 May 2025 11:49:35 +0200 Subject: [PATCH 07/11] fix: make benchmarks actually comparative --- utils/benches/poseidon_benchmark.rs | 114 +++++++++++++++------------- 1 file changed, 61 insertions(+), 53 deletions(-) diff --git a/utils/benches/poseidon_benchmark.rs b/utils/benches/poseidon_benchmark.rs index 82c402db..77effd99 100644 --- a/utils/benches/poseidon_benchmark.rs +++ b/utils/benches/poseidon_benchmark.rs @@ -19,68 +19,76 @@ const ROUND_PARAMS: [(usize, usize, usize, usize); 8] = [ ]; fn make_values(size: u32) -> Vec<[Fr; 1]> { - let mut values = Vec::with_capacity(size as usize); - for i in 0..size { - values.push([Fr::from(i)]); - } - values + (0..size).map(|i| [Fr::from(i)]).collect() } pub fn poseidon_benchmark(c: &mut Criterion) { let hasher = Poseidon::::from(&ROUND_PARAMS); let mut group = c.benchmark_group("poseidon Fr"); + // pull out the _actual_ parameters used by the benchmark. + // This is a potential smell: It doesn't feel "clean" to do it this way, however, I can't see a + // clear way to get an equivilent parameter set without non-trivial work to the ift poseidon + // implimentation + let RoundParameters { + t, + n_rounds_full, + n_rounds_partial, + skip_matrices: _, + ark_consts, + mds, + } = hasher.select_params(&[Fr::from(1)]).unwrap(); + for size in [10u32, 100, 1000].iter() { group.throughput(Throughput::Elements(*size as u64)); - group.bench_with_input(BenchmarkId::new("Array hash", size), size, |b, &size| { - b.iter_batched( - // Setup: create values for each benchmark iteration - || make_values(size), - // Actual benchmark - |values| { - for v in values.iter() { - let _ = hasher.hash(black_box(&v[..])); - } - }, - BatchSize::SmallInput, - ); - b.iter_batched( - // Setup: create values for each benchmark iteration - || { - // first, we need to pull out the parameters that the internal hasher is - // using... - let RoundParameters { - t, - n_rounds_full, - n_rounds_partial, - skip_matrices: _, - ark_consts, - mds, - } = hasher.select_params(&[Fr::from(1)]).unwrap(); - // then we need to translate it to the light-poseidon paramater - let l_params = LPoseidonParameters { - ark: ark_consts.clone(), - mds: mds.clone(), - full_rounds: *n_rounds_full, - partial_rounds: *n_rounds_partial, - width: *t, - alpha: 1, - }; - - let vals = make_values(size); - let light_hasher = light_poseidon::Poseidon::::new(l_params); - (vals, light_hasher) - }, - // Actual benchmark - |(values, mut light_hasher)| { - for v in values.iter() { - let _ = light_hasher.hash(black_box(&v[..])); - } - }, - BatchSize::SmallInput, - ) - }); + group.bench_with_input( + BenchmarkId::new("Array hash ift", size), + size, + |b, &size| { + b.iter_batched( + // setup + || { + // this needs to be done here due to move/copy/etc issues. + let l_params = LPoseidonParameters { + ark: ark_consts.clone(), + mds: mds.clone(), + full_rounds: *n_rounds_full, + partial_rounds: *n_rounds_partial, + width: *t, + alpha: 1, + }; + let vals = make_values(size); + let light_hasher = light_poseidon::Poseidon::::new(l_params); + (vals, light_hasher) + }, + // Actual benchmark + |(values, mut light_hasher)| { + for v in values.iter() { + let _ = light_hasher.hash(black_box(&v[..])); + } + }, + BatchSize::SmallInput, + ) + }, + ); + group.bench_with_input( + BenchmarkId::new("Array hash light", size), + size, + |b, &size| { + b.iter_batched( + // setup + || make_values(size), + // Actual benchmark + |values| { + for v in values.iter() { + let _ = hasher.hash(black_box(&v[..])); + } + }, + BatchSize::SmallInput, + ) + }, + ); } // Benchmark single hash operation separately From a0cefd50bde2941e7b18bad3e6bdb526a476d69c Mon Sep 17 00:00:00 2001 From: Ben-PH Date: Tue, 6 May 2025 15:13:06 +0200 Subject: [PATCH 08/11] fix: correct benchmark mislabelling --- utils/benches/poseidon_benchmark.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/benches/poseidon_benchmark.rs b/utils/benches/poseidon_benchmark.rs index 77effd99..eb167a0c 100644 --- a/utils/benches/poseidon_benchmark.rs +++ b/utils/benches/poseidon_benchmark.rs @@ -43,7 +43,7 @@ pub fn poseidon_benchmark(c: &mut Criterion) { group.throughput(Throughput::Elements(*size as u64)); group.bench_with_input( - BenchmarkId::new("Array hash ift", size), + BenchmarkId::new("Array hash light", size), size, |b, &size| { b.iter_batched( @@ -73,7 +73,7 @@ pub fn poseidon_benchmark(c: &mut Criterion) { }, ); group.bench_with_input( - BenchmarkId::new("Array hash light", size), + BenchmarkId::new("Array hash ift", size), size, |b, &size| { b.iter_batched( From 96bc791047ed2dfeb4603b38536db1c0fc0e1ded Mon Sep 17 00:00:00 2001 From: Ben-PH Date: Tue, 6 May 2025 15:18:13 +0200 Subject: [PATCH 09/11] fix: correct alpha to the default '5' --- utils/benches/poseidon_benchmark.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/benches/poseidon_benchmark.rs b/utils/benches/poseidon_benchmark.rs index eb167a0c..ca21d5a9 100644 --- a/utils/benches/poseidon_benchmark.rs +++ b/utils/benches/poseidon_benchmark.rs @@ -56,7 +56,7 @@ pub fn poseidon_benchmark(c: &mut Criterion) { full_rounds: *n_rounds_full, partial_rounds: *n_rounds_partial, width: *t, - alpha: 1, + alpha: 5, }; let vals = make_values(size); let light_hasher = light_poseidon::Poseidon::::new(l_params); From 9bb8246b6aadc5fdcf6d2f5b01a7f1f0d35a87dc Mon Sep 17 00:00:00 2001 From: Ben-PH Date: Tue, 6 May 2025 15:22:54 +0200 Subject: [PATCH 10/11] test: Inc. `new_circom(1)` constructed hash in bench --- utils/benches/poseidon_benchmark.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/utils/benches/poseidon_benchmark.rs b/utils/benches/poseidon_benchmark.rs index ca21d5a9..b2ee6ad2 100644 --- a/utils/benches/poseidon_benchmark.rs +++ b/utils/benches/poseidon_benchmark.rs @@ -39,6 +39,7 @@ pub fn poseidon_benchmark(c: &mut Criterion) { mds, } = hasher.select_params(&[Fr::from(1)]).unwrap(); + // group.measurement_time(std::time::Duration::from_secs(30)); for size in [10u32, 100, 1000].iter() { group.throughput(Throughput::Elements(*size as u64)); @@ -89,6 +90,27 @@ pub fn poseidon_benchmark(c: &mut Criterion) { ) }, ); + group.bench_with_input( + BenchmarkId::new("Array hash light_circom", size), + size, + |b, &size| { + b.iter_batched( + // setup + || { + let light_hasher_circom = + light_poseidon::Poseidon::::new_circom(1).unwrap(); + (light_hasher_circom, make_values(size)) + }, + // Actual benchmark + |(mut light_hasher_circom, values)| { + for v in values.iter() { + let _ = light_hasher_circom.hash(black_box(&v[..])); + } + }, + BatchSize::SmallInput, + ) + }, + ); } // Benchmark single hash operation separately From f0649e22d0d290683570f8d12910c158b3e900b3 Mon Sep 17 00:00:00 2001 From: Ben-PH Date: Thu, 15 May 2025 15:58:33 +0200 Subject: [PATCH 11/11] Use seedable and clean up the setups --- Cargo.lock | 11 +- utils/Cargo.toml | 4 + utils/benches/poseidon_benchmark.rs | 160 ++++++++++++---------------- 3 files changed, 79 insertions(+), 96 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cebc6141..a74e9b1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1576,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]] @@ -1782,7 +1781,7 @@ dependencies = [ "primitive-types", "proptest", "rand 0.8.5", - "rand 0.9.0", + "rand 0.9.1", "rlp", "ruint-macro", "serde", @@ -2572,6 +2571,10 @@ dependencies = [ "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", diff --git a/utils/Cargo.toml b/utils/Cargo.toml index 504cf6ee..0441dbfb 100644 --- a/utils/Cargo.toml +++ b/utils/Cargo.toml @@ -32,6 +32,10 @@ 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" +rln = { path = "../rln", default-features = false } [features] default = [] diff --git a/utils/benches/poseidon_benchmark.rs b/utils/benches/poseidon_benchmark.rs index b2ee6ad2..46040a4a 100644 --- a/utils/benches/poseidon_benchmark.rs +++ b/utils/benches/poseidon_benchmark.rs @@ -5,6 +5,10 @@ use criterion::{ use light_poseidon::{ PoseidonHasher as LPoseidonHasher, PoseidonParameters as LPoseidonParameters, }; +use rand::RngCore; +use rand_chacha::ChaCha8Rng; +use rand_core::SeedableRng; +use rln::utils::bytes_le_to_fr; use zerokit_utils::{Poseidon, RoundParameters}; const ROUND_PARAMS: [(usize, usize, usize, usize); 8] = [ @@ -18,108 +22,80 @@ const ROUND_PARAMS: [(usize, usize, usize, usize); 8] = [ (9, 8, 63, 0), ]; -fn make_values(size: u32) -> Vec<[Fr; 1]> { - (0..size).map(|i| [Fr::from(i)]).collect() +struct U256Stream { + rng: ChaCha8Rng, +} +impl U256Stream { + fn seeded_stream(seed: u64) -> Self { + let rng = ChaCha8Rng::seed_from_u64(seed); + Self { rng } + } +} + +impl Iterator for U256Stream { + type Item = [u8; 32]; + + fn next(&mut self) -> Option { + let mut res = [0; 32]; + self.rng.fill_bytes(&mut res); + Some(res) + } } pub fn poseidon_benchmark(c: &mut Criterion) { let hasher = Poseidon::::from(&ROUND_PARAMS); let mut group = c.benchmark_group("poseidon Fr"); - // pull out the _actual_ parameters used by the benchmark. - // This is a potential smell: It doesn't feel "clean" to do it this way, however, I can't see a - // clear way to get an equivilent parameter set without non-trivial work to the ift poseidon - // implimentation - let RoundParameters { - t, - n_rounds_full, - n_rounds_partial, - skip_matrices: _, - ark_consts, - mds, - } = hasher.select_params(&[Fr::from(1)]).unwrap(); - // group.measurement_time(std::time::Duration::from_secs(30)); - for size in [10u32, 100, 1000].iter() { + for size in [1u32, 2].iter() { group.throughput(Throughput::Elements(*size as u64)); + let vals = U256Stream::seeded_stream(*size as u64) + .take(*size as usize) + .map(|b| bytes_le_to_fr(&b).0) + .collect::>(); + let RoundParameters { + t, + n_rounds_full, + n_rounds_partial, + skip_matrices: _, + ark_consts, + mds, + } = hasher.select_params(&vals).unwrap(); - group.bench_with_input( - BenchmarkId::new("Array hash light", size), - size, - |b, &size| { - b.iter_batched( - // setup - || { - // this needs to be done here due to move/copy/etc issues. - let l_params = LPoseidonParameters { - ark: ark_consts.clone(), - mds: mds.clone(), - full_rounds: *n_rounds_full, - partial_rounds: *n_rounds_partial, - width: *t, - alpha: 5, - }; - let vals = make_values(size); - let light_hasher = light_poseidon::Poseidon::::new(l_params); - (vals, light_hasher) - }, - // Actual benchmark - |(values, mut light_hasher)| { - for v in values.iter() { - let _ = light_hasher.hash(black_box(&v[..])); - } - }, - BatchSize::SmallInput, - ) - }, - ); - group.bench_with_input( - BenchmarkId::new("Array hash ift", size), - size, - |b, &size| { - b.iter_batched( - // setup - || make_values(size), - // Actual benchmark - |values| { - for v in values.iter() { - let _ = hasher.hash(black_box(&v[..])); - } - }, - BatchSize::SmallInput, - ) - }, - ); - group.bench_with_input( - BenchmarkId::new("Array hash light_circom", size), - size, - |b, &size| { - b.iter_batched( - // setup - || { - let light_hasher_circom = - light_poseidon::Poseidon::::new_circom(1).unwrap(); - (light_hasher_circom, make_values(size)) - }, - // Actual benchmark - |(mut light_hasher_circom, values)| { - for v in values.iter() { - let _ = light_hasher_circom.hash(black_box(&v[..])); - } - }, - BatchSize::SmallInput, - ) - }, - ); - } + group.bench_function(BenchmarkId::new("Array hash light", size), |b| { + b.iter_batched( + // setup + || { + // this needs to be done here due to move/copy/etc issues. + let l_params = LPoseidonParameters { + ark: ark_consts.clone(), + mds: mds.clone(), + full_rounds: *n_rounds_full, + partial_rounds: *n_rounds_partial, + width: *t, + alpha: 5, + }; - // Benchmark single hash operation separately - group.bench_function("Single hash", |b| { - let input = [Fr::from(u64::MAX)]; - b.iter(|| { - let _ = hasher.hash(black_box(&input[..])); - }) - }); + light_poseidon::Poseidon::::new(l_params) + }, + // Actual benchmark + |mut light_hasher| black_box(light_hasher.hash(&vals)), + BatchSize::SmallInput, + ) + }); + group.bench_function(BenchmarkId::new("Array hash ift", size), |b| { + b.iter(|| black_box(hasher.hash(&vals))) + }); + group.bench_function(BenchmarkId::new("Array hash light_circom", size), |b| { + b.iter_batched( + // setup + || light_poseidon::Poseidon::::new_circom(*size as usize).unwrap(), + // Actual benchmark + |mut light_hasher_circom| black_box(light_hasher_circom.hash(&vals)), + BatchSize::SmallInput, + ) + }); + } group.finish(); }