From a6e9ab66038c60fb6bb978c702ea68454dcc408d Mon Sep 17 00:00:00 2001 From: vinhtc27 Date: Mon, 26 Jan 2026 12:15:19 +0700 Subject: [PATCH 01/16] feat(rln): integrate GPU acceleration using ICICLE for proof generation --- Cargo.lock | 58 +++++++++ rln/Cargo.toml | 8 +- rln/src/circuit/icicle.rs | 33 ++++++ rln/src/circuit/icicle/convert.rs | 191 ++++++++++++++++++++++++++++++ rln/src/circuit/icicle/msm.rs | 75 ++++++++++++ rln/src/circuit/icicle/proof.rs | 118 ++++++++++++++++++ rln/src/circuit/mod.rs | 3 + rln/src/error.rs | 5 + rln/src/protocol/mod.rs | 2 + rln/src/protocol/proof.rs | 84 +++++++++++++ rln/tests/protocol.rs | 26 ++++ 11 files changed, 602 insertions(+), 1 deletion(-) create mode 100644 rln/src/circuit/icicle.rs create mode 100644 rln/src/circuit/icicle/convert.rs create mode 100644 rln/src/circuit/icicle/msm.rs create mode 100644 rln/src/circuit/icicle/proof.rs diff --git a/Cargo.lock b/Cargo.lock index bc9b96a5..800c3d83 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -413,6 +413,15 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -735,6 +744,52 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1" +[[package]] +name = "icicle-bn254" +version = "4.0.0" +source = "git+https://github.com/ingonyama-zk/icicle.git?branch=main#625532a624e5aaa6e9d31a1c92587f1fcc30dc76" +dependencies = [ + "cmake", + "icicle-core", + "icicle-hash", + "icicle-runtime", + "serde", +] + +[[package]] +name = "icicle-core" +version = "4.0.0" +source = "git+https://github.com/ingonyama-zk/icicle.git?branch=main#625532a624e5aaa6e9d31a1c92587f1fcc30dc76" +dependencies = [ + "cmake", + "hex", + "icicle-runtime", + "once_cell", + "rand 0.8.5", + "rayon", + "serde", +] + +[[package]] +name = "icicle-hash" +version = "4.0.0" +source = "git+https://github.com/ingonyama-zk/icicle.git?branch=main#625532a624e5aaa6e9d31a1c92587f1fcc30dc76" +dependencies = [ + "cmake", + "icicle-core", + "icicle-runtime", + "rand 0.8.5", +] + +[[package]] +name = "icicle-runtime" +version = "4.0.0" +source = "git+https://github.com/ingonyama-zk/icicle.git?branch=main#625532a624e5aaa6e9d31a1c92587f1fcc30dc76" +dependencies = [ + "cmake", + "once_cell", +] + [[package]] name = "indexmap" version = "2.12.1" @@ -1232,6 +1287,9 @@ dependencies = [ "cfg-if", "criterion", "document-features", + "icicle-bn254", + "icicle-core", + "icicle-runtime", "num-bigint", "num-traits", "once_cell", diff --git a/rln/Cargo.toml b/rln/Cargo.toml index 5bc828be..d4c55a28 100644 --- a/rln/Cargo.toml +++ b/rln/Cargo.toml @@ -59,11 +59,16 @@ serde = { version = "1.0.228", features = ["derive"] } # Documentation document-features = { version = "0.2.12", optional = true } +# GPU Acceleration (optional) +icicle-runtime = { git = "https://github.com/ingonyama-zk/icicle.git", branch = "main", optional = true } +icicle-core = { git = "https://github.com/ingonyama-zk/icicle.git", branch = "main", optional = true } +icicle-bn254 = { git = "https://github.com/ingonyama-zk/icicle.git", branch = "main", optional = true } + [dev-dependencies] criterion = { version = "0.8.0", features = ["html_reports"] } [features] -default = ["parallel", "pmtree-ft"] +default = ["parallel", "pmtree-ft", "icicle"] stateless = [] parallel = [ "rayon", @@ -79,6 +84,7 @@ fullmerkletree = [] # Pre-allocated tree, fastest access optimalmerkletree = [] # Sparse storage, memory efficient pmtree-ft = ["zerokit_utils/pmtree-ft"] # Persistent storage, disk-based headers = ["safer-ffi/headers"] # Generate C header file with safer-ffi +icicle = ["icicle-runtime", "icicle-core", "icicle-bn254"] # GPU acceleration [[bench]] name = "pmtree_benchmark" diff --git a/rln/src/circuit/icicle.rs b/rln/src/circuit/icicle.rs new file mode 100644 index 00000000..ed2718c7 --- /dev/null +++ b/rln/src/circuit/icicle.rs @@ -0,0 +1,33 @@ +// This crate is based on the code by Ingonyama. Its preimage can be found here: +// https://github.com/ingonyama-zk/icicle + +use ark_relations::r1cs::SynthesisError; + +use crate::error::ProtocolError; + +mod convert; +mod msm; +pub(crate) mod proof; + +/// Error wrapper for ICICLE operations +fn icicle_err(_msg: &str) -> ProtocolError { + ProtocolError::Synthesis(SynthesisError::PolynomialDegreeTooLarge) +} + +/// Initialize ICICLE runtime and select device +pub fn init_icicle() -> Result<(), ProtocolError> { + // Try to load CUDA backend, fall back to CPU + let _ = icicle_runtime::runtime::load_backend_from_env_or_default(); + + // Get device - try CUDA first, then CPU + let device = icicle_runtime::Device::new("CUDA", 0); + let device_result = icicle_runtime::set_device(&device); + + // If CUDA fails, try CPU + if device_result.is_err() { + let cpu_device = icicle_runtime::Device::new("CPU", 0); + icicle_runtime::set_device(&cpu_device).map_err(|_| icicle_err("No device available"))?; + } + + Ok(()) +} diff --git a/rln/src/circuit/icicle/convert.rs b/rln/src/circuit/icicle/convert.rs new file mode 100644 index 00000000..43a0cc6a --- /dev/null +++ b/rln/src/circuit/icicle/convert.rs @@ -0,0 +1,191 @@ +// This crate is based on the code by Ingonyama. Its preimage can be found here: +// https://github.com/ingonyama-zk/icicle/blob/main/examples/rust/arkworks-icicle-conversions + +use ark_bn254::{Fr as ArkFr, G1Affine as ArkG1Affine, G2Affine as ArkG2Affine}; +use ark_ec::AffineRepr; +use ark_ff::{BigInteger, Field as ArkField, PrimeField}; +use icicle_bn254::curve::{ + BaseField as IcicleBaseField, G1Affine as IcicleG1Affine, G1Projective as IcicleG1Projective, + G2Affine as IcicleG2Affine, G2BaseField as IcicleG2BaseField, + G2Projective as IcicleG2Projective, ScalarField as IcicleScalar, +}; +use icicle_core::{ + affine::Affine as IcicleAffineTrait, bignum::BigNum, projective::Projective as IcicleProjective, +}; + +#[inline] +fn from_ark(ark: &T) -> I +where + T: ArkField, + I: BigNum, +{ + let mut ark_bytes = vec![]; + for base_elem in ark.to_base_prime_field_elements() { + ark_bytes.extend_from_slice(&base_elem.into_bigint().to_bytes_le()); + } + I::from_bytes_le(&ark_bytes) +} + +/// Convert arkworks Fr scalar to ICICLE scalar +#[inline] +pub(crate) fn ark_fr_to_icicle(scalar: &ArkFr) -> IcicleScalar { + from_ark(scalar) +} + +/// Convert slice of arkworks Fr scalars to ICICLE scalars +pub(crate) fn ark_frs_to_icicle(scalars: &[ArkFr]) -> Vec { + scalars.iter().map(ark_fr_to_icicle).collect() +} + +/// Convert ICICLE scalar to arkworks Fr +#[inline] +pub(crate) fn icicle_to_ark_fr(scalar: &IcicleScalar) -> ArkFr { + let bytes = scalar.to_bytes_le(); + ArkFr::from_le_bytes_mod_order(&bytes) +} + +/// Convert slice of ICICLE scalars to arkworks Fr +#[allow(dead_code)] +pub(crate) fn icicle_to_ark_frs(scalars: &[IcicleScalar]) -> Vec { + scalars.iter().map(icicle_to_ark_fr).collect() +} + +/// Convert arkworks G1Affine point to ICICLE G1Affine +#[inline] +pub(crate) fn ark_g1_to_icicle(point: &ArkG1Affine) -> IcicleG1Affine { + if point.is_zero() { + return IcicleG1Affine::zero(); + } + + IcicleG1Affine { + x: from_ark::<_, IcicleBaseField>(&point.x().unwrap()), + y: from_ark::<_, IcicleBaseField>(&point.y().unwrap()), + } +} + +/// Convert slice of arkworks G1Affine points to ICICLE G1Affine +pub(crate) fn ark_g1s_to_icicle(points: &[ArkG1Affine]) -> Vec { + points.iter().map(ark_g1_to_icicle).collect() +} + +/// Convert ICICLE G1Projective to arkworks G1Affine +#[inline] +pub(crate) fn icicle_g1_proj_to_ark(point: &IcicleG1Projective) -> ark_bn254::G1Affine { + use ark_bn254::{Fq, G1Affine}; + + // Convert to affine in ICICLE + let affine: IcicleG1Affine = point.to_affine(); + + // Check for zero point + if affine == IcicleG1Affine::zero() { + return G1Affine::zero(); + } + + let x_bytes = affine.x.to_bytes_le(); + let y_bytes = affine.y.to_bytes_le(); + + let x = Fq::from_le_bytes_mod_order(&x_bytes); + let y = Fq::from_le_bytes_mod_order(&y_bytes); + + G1Affine::new_unchecked(x, y) +} + +/// Convert arkworks G2Affine point to ICICLE G2Affine +#[inline] +pub(crate) fn ark_g2_to_icicle(point: &ArkG2Affine) -> IcicleG2Affine { + if point.is_zero() { + return IcicleG2Affine::zero(); + } + + // G2 coordinates are in Fq2 (extension field) + let x = point.x().unwrap(); + let y = point.y().unwrap(); + + // Fq2 has c0 and c1 components - combine into extension field format + let x_c0_bytes = x.c0.into_bigint().to_bytes_le(); + let x_c1_bytes = x.c1.into_bigint().to_bytes_le(); + let y_c0_bytes = y.c0.into_bigint().to_bytes_le(); + let y_c1_bytes = y.c1.into_bigint().to_bytes_le(); + + // Combine into extension field format for ICICLE + let mut x_bytes = [0u8; 64]; + let mut y_bytes = [0u8; 64]; + + x_bytes[..32].copy_from_slice(&x_c0_bytes); + x_bytes[32..].copy_from_slice(&x_c1_bytes); + y_bytes[..32].copy_from_slice(&y_c0_bytes); + y_bytes[32..].copy_from_slice(&y_c1_bytes); + + let x_field = IcicleG2BaseField::from_bytes_le(&x_bytes); + let y_field = IcicleG2BaseField::from_bytes_le(&y_bytes); + + IcicleG2Affine { + x: x_field, + y: y_field, + } +} + +/// Convert slice of arkworks G2Affine points to ICICLE G2Affine +pub(crate) fn ark_g2s_to_icicle(points: &[ArkG2Affine]) -> Vec { + points.iter().map(ark_g2_to_icicle).collect() +} + +/// Convert ICICLE G2Projective to arkworks G2Affine +#[inline] +pub(crate) fn icicle_g2_proj_to_ark(point: &IcicleG2Projective) -> ArkG2Affine { + use ark_bn254::{Fq, Fq2, G2Affine}; + + let affine: IcicleG2Affine = point.to_affine(); + + if affine == IcicleG2Affine::zero() { + return G2Affine::zero(); + } + + let x_bytes = affine.x.to_bytes_le(); + let y_bytes = affine.y.to_bytes_le(); + + // Split back into c0, c1 components + let x_c0 = Fq::from_le_bytes_mod_order(&x_bytes[..32]); + let x_c1 = Fq::from_le_bytes_mod_order(&x_bytes[32..64]); + let y_c0 = Fq::from_le_bytes_mod_order(&y_bytes[..32]); + let y_c1 = Fq::from_le_bytes_mod_order(&y_bytes[32..64]); + + let x = Fq2::new(x_c0, x_c1); + let y = Fq2::new(y_c0, y_c1); + + G2Affine::new_unchecked(x, y) +} + +#[cfg(test)] +mod tests { + use ark_std::UniformRand; + + use super::*; + + #[test] + fn test_scalar_roundtrip() { + let mut rng = ark_std::test_rng(); + for _ in 0..100 { + let ark_scalar = ArkFr::rand(&mut rng); + let icicle_scalar = ark_fr_to_icicle(&ark_scalar); + let back = icicle_to_ark_fr(&icicle_scalar); + assert_eq!(ark_scalar, back); + } + } + + #[test] + fn test_g1_conversion() { + use ark_bn254::G1Projective; + use ark_ec::CurveGroup; + + let mut rng = ark_std::test_rng(); + for _ in 0..10 { + let ark_point = G1Projective::rand(&mut rng).into_affine(); + let icicle_point = ark_g1_to_icicle(&ark_point); + // Basic sanity check - point should not be zero unless input was zero + if !ark_point.is_zero() { + assert!(icicle_point != IcicleG1Affine::zero()); + } + } + } +} diff --git a/rln/src/circuit/icicle/msm.rs b/rln/src/circuit/icicle/msm.rs new file mode 100644 index 00000000..0f156b1c --- /dev/null +++ b/rln/src/circuit/icicle/msm.rs @@ -0,0 +1,75 @@ +use ark_bn254::{Fr as ArkFr, G1Affine as ArkG1Affine, G2Affine as ArkG2Affine}; +use ark_ec::AffineRepr; +use ark_ff::Zero; +use icicle_bn254::curve::{G1Projective as IcicleG1Projective, G2Projective as IcicleG2Projective}; +use icicle_core::{ + msm::{msm, MSMConfig}, + projective::Projective as IcicleProjective, +}; +use icicle_runtime::memory::HostSlice; + +use super::convert::{ + ark_frs_to_icicle, ark_g1s_to_icicle, ark_g2s_to_icicle, icicle_g1_proj_to_ark, + icicle_g2_proj_to_ark, +}; +use crate::error::ProtocolError; + +/// Perform MSM on G1 using ICICLE +pub fn icicle_msm_g1( + scalars: &[ArkFr], + points: &[ArkG1Affine], +) -> Result { + if scalars.is_empty() || points.is_empty() { + return Ok(ark_bn254::G1Projective::zero()); + } + + // Convert to ICICLE types + let icicle_scalars = ark_frs_to_icicle(scalars); + let icicle_points = ark_g1s_to_icicle(points); + + // Prepare result + let mut result = vec![IcicleG1Projective::zero()]; + + // Perform MSM + let cfg = MSMConfig::default(); + msm( + HostSlice::from_slice(&icicle_scalars), + HostSlice::from_slice(&icicle_points), + &cfg, + HostSlice::from_mut_slice(&mut result), + )?; + + // Convert back to arkworks + let affine = icicle_g1_proj_to_ark(&result[0]); + Ok(affine.into_group()) +} + +/// Perform MSM on G2 using ICICLE +pub fn icicle_msm_g2( + scalars: &[ArkFr], + points: &[ArkG2Affine], +) -> Result { + if scalars.is_empty() || points.is_empty() { + return Ok(ark_bn254::G2Projective::zero()); + } + + // Convert to ICICLE types + let icicle_scalars = ark_frs_to_icicle(scalars); + let icicle_points = ark_g2s_to_icicle(points); + + // Prepare result + let mut result = vec![IcicleG2Projective::zero()]; + + // Perform MSM + let cfg = MSMConfig::default(); + msm( + HostSlice::from_slice(&icicle_scalars), + HostSlice::from_slice(&icicle_points), + &cfg, + HostSlice::from_mut_slice(&mut result), + )?; + + // Convert back to arkworks + let affine = icicle_g2_proj_to_ark(&result[0]); + Ok(affine.into_group()) +} diff --git a/rln/src/circuit/icicle/proof.rs b/rln/src/circuit/icicle/proof.rs new file mode 100644 index 00000000..e52e01d6 --- /dev/null +++ b/rln/src/circuit/icicle/proof.rs @@ -0,0 +1,118 @@ +use std::ops::Mul; + +use ark_ec::{AffineRepr, CurveGroup}; +use ark_ff::Zero; + +use crate::{ + circuit::{ + icicle::msm::{icicle_msm_g1, icicle_msm_g2}, + ArkG2Affine, Fr, G1Affine, G1Projective, G2Projective, Proof, ProvingKey, + }, + error::ProtocolError, +}; + +/// Calculate coefficient for proof element using ICICLE MSM +fn calculate_coeff_g1_icicle( + initial: G1Projective, + query: &[G1Affine], + vk_param: G1Affine, + assignment: &[Fr], +) -> Result { + let el = query[0]; + + // MSM for query[1..] with assignment + let msm_result = if query.len() > 1 && !assignment.is_empty() { + icicle_msm_g1(assignment, &query[1..])? + } else { + G1Projective::zero() + }; + + let mut res = initial; + res += el.into_group(); + res += msm_result; + res += vk_param.into_group(); + + Ok(res) +} + +/// Calculate coefficient for G2 proof element using ICICLE MSM +fn calculate_coeff_g2_icicle( + initial: G2Projective, + query: &[ArkG2Affine], + vk_param: ArkG2Affine, + assignment: &[Fr], +) -> Result { + let el = query[0]; + + // MSM for query[1..] with assignment + let msm_result = if query.len() > 1 && !assignment.is_empty() { + icicle_msm_g2(assignment, &query[1..])? + } else { + G2Projective::zero() + }; + + let mut res = initial; + res += el.into_group(); + res += msm_result; + res += vk_param.into_group(); + + Ok(res) +} + +/// Generates a zkSNARK proof using ICICLE GPU acceleration. +pub fn create_proof_with_icicle_msm( + pk: &ProvingKey, + r: Fr, + s: Fr, + h: &[Fr], + input_assignment: &[Fr], + aux_assignment: &[Fr], +) -> Result { + // h_acc = MSM(pk.h_query, h) + let h_acc = icicle_msm_g1(h, &pk.h_query)?; + + // l_aux_acc = MSM(pk.l_query, aux_assignment) + let l_aux_acc = icicle_msm_g1(aux_assignment, &pk.l_query)?; + + // r_s_delta_g1 = pk.delta_g1 * (r * s) + let r_s_delta_g1 = pk.delta_g1.mul(r * s); + + // Combine assignments + let assignment: Vec = input_assignment + .iter() + .chain(aux_assignment.iter()) + .copied() + .collect(); + + // Compute A + // g_a = calculate_coeff(r * delta_g1, pk.a_query, pk.vk.alpha_g1, assignment) + let r_g1 = pk.delta_g1.mul(r); + let g_a = calculate_coeff_g1_icicle(r_g1, &pk.a_query, pk.vk.alpha_g1, &assignment)?; + let s_g_a = g_a.mul(s); + + // Compute B in G1 (only if r != 0) + let g1_b = if !r.is_zero() { + let s_g1 = pk.delta_g1.mul(s); + calculate_coeff_g1_icicle(s_g1, &pk.b_g1_query, pk.beta_g1, &assignment)? + } else { + ark_bn254::G1Projective::zero() + }; + + // Compute B in G2 + let s_g2 = pk.vk.delta_g2.mul(s); + let g2_b = calculate_coeff_g2_icicle(s_g2, &pk.b_g2_query, pk.vk.beta_g2, &assignment)?; + let r_g1_b = g1_b.mul(r); + + // Compute C + let mut g_c = s_g_a; + g_c += r_g1_b; + g_c -= r_s_delta_g1; + g_c += l_aux_acc; + g_c += h_acc; + + Ok(Proof { + a: g_a.into_affine(), + b: g2_b.into_affine(), + c: g_c.into_affine(), + }) +} diff --git a/rln/src/circuit/mod.rs b/rln/src/circuit/mod.rs index c4733678..d5f3be98 100644 --- a/rln/src/circuit/mod.rs +++ b/rln/src/circuit/mod.rs @@ -4,6 +4,9 @@ pub(crate) mod error; pub(crate) mod iden3calc; pub(crate) mod qap; +#[cfg(feature = "icicle")] +pub(crate) mod icicle; + #[cfg(not(target_arch = "wasm32"))] use std::sync::LazyLock; diff --git a/rln/src/error.rs b/rln/src/error.rs index a99b09d7..e44d7439 100644 --- a/rln/src/error.rs +++ b/rln/src/error.rs @@ -1,6 +1,8 @@ use std::{array::TryFromSliceError, num::TryFromIntError}; use ark_relations::r1cs::SynthesisError; +#[cfg(feature = "icicle")] +use icicle_runtime::IcicleError; use num_bigint::{BigInt, ParseBigIntError}; use zerokit_utils::error::{FromConfigError, HashError, ZerokitMerkleTreeError}; @@ -27,6 +29,9 @@ pub enum UtilsError { /// Errors that can occur during RLN protocol operations (proof generation, verification, etc.) #[derive(Debug, thiserror::Error)] pub enum ProtocolError { + #[cfg(feature = "icicle")] + #[error("Icicle error: {0}")] + Icicle(#[from] IcicleError), #[error("Error producing proof: {0}")] Synthesis(#[from] SynthesisError), #[error("RLN utility error: {0}")] diff --git a/rln/src/protocol/mod.rs b/rln/src/protocol/mod.rs index b35dec1f..b5ac8c16 100644 --- a/rln/src/protocol/mod.rs +++ b/rln/src/protocol/mod.rs @@ -12,6 +12,8 @@ pub use proof::{ rln_proof_to_bytes_be, rln_proof_to_bytes_le, rln_proof_values_to_bytes_be, rln_proof_values_to_bytes_le, verify_zk_proof, RLNProof, RLNProofValues, }; +#[cfg(feature = "icicle")] +pub use proof::{generate_zk_proof_icicle, generate_zk_proof_with_witness_icicle}; pub use slashing::recover_id_secret; pub use witness::{ bytes_be_to_rln_witness, bytes_le_to_rln_witness, compute_tree_root, proof_values_from_witness, diff --git a/rln/src/protocol/proof.rs b/rln/src/protocol/proof.rs index 0a966e09..80ab7204 100644 --- a/rln/src/protocol/proof.rs +++ b/rln/src/protocol/proof.rs @@ -4,6 +4,12 @@ use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::{rand::thread_rng, UniformRand}; use num_bigint::BigInt; use num_traits::Signed; +#[cfg(feature = "icicle")] +use { + crate::circuit::icicle::{init_icicle, proof::create_proof_with_icicle_msm}, + ark_groth16::r1cs_to_qap::R1CSToQAP, + ark_poly::GeneralEvaluationDomain, +}; use super::witness::{inputs_for_witness_calculation, RLNWitnessInput}; use crate::{ @@ -343,3 +349,81 @@ pub fn verify_zk_proof( Ok(verified) } + +/// Generates a zkSNARK proof from pre-calculated witness using ICICLE GPU acceleration. +/// +/// This is a drop-in replacement for `generate_zk_proof_with_witness` that uses +/// GPU-accelerated MSM operations via the ICICLE library. +/// Falls back to CPU if GPU is not available. +#[cfg(feature = "icicle")] +pub fn generate_zk_proof_with_witness_icicle( + calculated_witness: Vec, + zkey: &Zkey, +) -> Result { + // Initialize ICICLE + init_icicle()?; + + let full_assignment = calculated_witness_to_field_elements::(calculated_witness)?; + + // Compute witness polynomial h using CircomReduction (same as before) + let h = CircomReduction::witness_map_from_matrices::>( + &zkey.1, + zkey.1.num_instance_variables, + zkey.1.num_constraints, + &full_assignment, + )?; + + // Random Values + let mut rng = thread_rng(); + let r = Fr::rand(&mut rng); + let s = Fr::rand(&mut rng); + + // Split assignment into input and auxiliary + let input_assignment = &full_assignment[1..zkey.1.num_instance_variables]; + let aux_assignment = &full_assignment[zkey.1.num_instance_variables..]; + + // Generate proof using ICICLE + create_proof_with_icicle_msm(&zkey.0, r, s, &h, input_assignment, aux_assignment) +} + +/// Generates a zkSNARK proof using ICICLE GPU acceleration. +/// +/// This is a drop-in replacement for `generate_zk_proof` that uses +/// GPU-accelerated MSM operations via the ICICLE library. +/// Falls back to CPU if GPU is not available. +#[cfg(feature = "icicle")] +pub fn generate_zk_proof_icicle( + zkey: &Zkey, + witness: &RLNWitnessInput, + graph: &Graph, +) -> Result { + // Initialize ICICLE + init_icicle()?; + + // Calculate witness using the standard method + let inputs = inputs_for_witness_calculation(witness)? + .into_iter() + .map(|(name, values)| (name.to_string(), values)); + + let full_assignment = calc_witness(inputs, graph)?; + + // Compute witness polynomial h using CircomReduction (same as before) + let h = CircomReduction::witness_map_from_matrices::>( + &zkey.1, + zkey.1.num_instance_variables, + zkey.1.num_constraints, + &full_assignment, + )?; + + // Random Values + let mut rng = thread_rng(); + let r = Fr::rand(&mut rng); + let s = Fr::rand(&mut rng); + + // Split assignment into input and auxiliary + let input_assignment = &full_assignment[1..zkey.1.num_instance_variables]; + let aux_assignment = &full_assignment[zkey.1.num_instance_variables..]; + + // Generate proof using ICICLE + create_proof_with_icicle_msm(&zkey.0, r, s, &h, input_assignment, aux_assignment) +} diff --git a/rln/tests/protocol.rs b/rln/tests/protocol.rs index 10d62182..0c97d02f 100644 --- a/rln/tests/protocol.rs +++ b/rln/tests/protocol.rs @@ -4,6 +4,8 @@ mod test { use ark_ff::BigInt; use rln::prelude::*; + #[cfg(feature = "icicle")] + use rln::protocol::generate_zk_proof_icicle; use zerokit_utils::merkle_tree::{ZerokitMerkleProof, ZerokitMerkleTree}; type ConfigOf = ::Config; @@ -290,4 +292,28 @@ mod test { assert_eq!(identity_secret, expected_identity_secret_seed_phrase); assert_eq!(id_commitment, expected_id_commitment_seed_phrase); } + + #[test] + #[cfg(feature = "icicle")] + // We test that normal and ICICLE proofs both verify correctly + fn test_end_to_end_icicle() { + let witness = get_test_witness(); + + let proving_key = zkey_from_folder(); + let graph_data = graph_from_folder(); + + let normal_proof = generate_zk_proof(proving_key, &witness, graph_data).unwrap(); + + let icicle_proof = generate_zk_proof_icicle(proving_key, &witness, graph_data).unwrap(); + + let proof_values = proof_values_from_witness(&witness).unwrap(); + + let normal_success = + verify_zk_proof(&proving_key.0.vk, &normal_proof, &proof_values).unwrap(); + let icicle_success = + verify_zk_proof(&proving_key.0.vk, &icicle_proof, &proof_values).unwrap(); + + assert!(normal_success, "Normal proof should verify"); + assert!(icicle_success, "ICICLE proof should verify"); + } } From de7eb47b88c775e1731be6786df0b362b1e7e836 Mon Sep 17 00:00:00 2001 From: vinhtc27 Date: Mon, 26 Jan 2026 12:19:54 +0700 Subject: [PATCH 02/16] chore(rln): cleanup ICICLE device initialization --- rln/src/circuit/icicle.rs | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/rln/src/circuit/icicle.rs b/rln/src/circuit/icicle.rs index ed2718c7..0be7dab0 100644 --- a/rln/src/circuit/icicle.rs +++ b/rln/src/circuit/icicle.rs @@ -1,7 +1,7 @@ // This crate is based on the code by Ingonyama. Its preimage can be found here: // https://github.com/ingonyama-zk/icicle -use ark_relations::r1cs::SynthesisError; +use icicle_runtime::{runtime, set_device, Device}; use crate::error::ProtocolError; @@ -9,24 +9,16 @@ mod convert; mod msm; pub(crate) mod proof; -/// Error wrapper for ICICLE operations -fn icicle_err(_msg: &str) -> ProtocolError { - ProtocolError::Synthesis(SynthesisError::PolynomialDegreeTooLarge) -} - /// Initialize ICICLE runtime and select device pub fn init_icicle() -> Result<(), ProtocolError> { // Try to load CUDA backend, fall back to CPU - let _ = icicle_runtime::runtime::load_backend_from_env_or_default(); + let _ = runtime::load_backend_from_env_or_default(); - // Get device - try CUDA first, then CPU - let device = icicle_runtime::Device::new("CUDA", 0); - let device_result = icicle_runtime::set_device(&device); + let cuda = Device::new("CUDA", 0); - // If CUDA fails, try CPU - if device_result.is_err() { - let cpu_device = icicle_runtime::Device::new("CPU", 0); - icicle_runtime::set_device(&cpu_device).map_err(|_| icicle_err("No device available"))?; + if set_device(&cuda).is_err() { + let cpu = Device::new("CPU", 0); + set_device(&cpu)?; } Ok(()) From e6c4f8dbef32967548d886de07356cb60f6e4d84 Mon Sep 17 00:00:00 2001 From: vinhtc27 Date: Mon, 26 Jan 2026 13:57:45 +0700 Subject: [PATCH 03/16] refactor(rln): replace Ark types with local circuit types for consistency --- rln/src/circuit/icicle/convert.rs | 29 +++++++++++++++-------------- rln/src/circuit/icicle/msm.rs | 14 ++++++++------ rln/src/circuit/icicle/proof.rs | 6 +++--- 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/rln/src/circuit/icicle/convert.rs b/rln/src/circuit/icicle/convert.rs index 43a0cc6a..c31bd459 100644 --- a/rln/src/circuit/icicle/convert.rs +++ b/rln/src/circuit/icicle/convert.rs @@ -1,9 +1,8 @@ // This crate is based on the code by Ingonyama. Its preimage can be found here: // https://github.com/ingonyama-zk/icicle/blob/main/examples/rust/arkworks-icicle-conversions -use ark_bn254::{Fr as ArkFr, G1Affine as ArkG1Affine, G2Affine as ArkG2Affine}; use ark_ec::AffineRepr; -use ark_ff::{BigInteger, Field as ArkField, PrimeField}; +use ark_ff::{BigInteger, Field, PrimeField}; use icicle_bn254::curve::{ BaseField as IcicleBaseField, G1Affine as IcicleG1Affine, G1Projective as IcicleG1Projective, G2Affine as IcicleG2Affine, G2BaseField as IcicleG2BaseField, @@ -13,10 +12,12 @@ use icicle_core::{ affine::Affine as IcicleAffineTrait, bignum::BigNum, projective::Projective as IcicleProjective, }; +use crate::circuit::{Fr, G1Affine, G2Affine}; + #[inline] fn from_ark(ark: &T) -> I where - T: ArkField, + T: Field, I: BigNum, { let mut ark_bytes = vec![]; @@ -28,31 +29,31 @@ where /// Convert arkworks Fr scalar to ICICLE scalar #[inline] -pub(crate) fn ark_fr_to_icicle(scalar: &ArkFr) -> IcicleScalar { +pub(crate) fn ark_fr_to_icicle(scalar: &Fr) -> IcicleScalar { from_ark(scalar) } /// Convert slice of arkworks Fr scalars to ICICLE scalars -pub(crate) fn ark_frs_to_icicle(scalars: &[ArkFr]) -> Vec { +pub(crate) fn ark_frs_to_icicle(scalars: &[Fr]) -> Vec { scalars.iter().map(ark_fr_to_icicle).collect() } /// Convert ICICLE scalar to arkworks Fr #[inline] -pub(crate) fn icicle_to_ark_fr(scalar: &IcicleScalar) -> ArkFr { +pub(crate) fn icicle_to_ark_fr(scalar: &IcicleScalar) -> Fr { let bytes = scalar.to_bytes_le(); - ArkFr::from_le_bytes_mod_order(&bytes) + Fr::from_le_bytes_mod_order(&bytes) } /// Convert slice of ICICLE scalars to arkworks Fr #[allow(dead_code)] -pub(crate) fn icicle_to_ark_frs(scalars: &[IcicleScalar]) -> Vec { +pub(crate) fn icicle_to_ark_frs(scalars: &[IcicleScalar]) -> Vec { scalars.iter().map(icicle_to_ark_fr).collect() } /// Convert arkworks G1Affine point to ICICLE G1Affine #[inline] -pub(crate) fn ark_g1_to_icicle(point: &ArkG1Affine) -> IcicleG1Affine { +pub(crate) fn ark_g1_to_icicle(point: &G1Affine) -> IcicleG1Affine { if point.is_zero() { return IcicleG1Affine::zero(); } @@ -64,7 +65,7 @@ pub(crate) fn ark_g1_to_icicle(point: &ArkG1Affine) -> IcicleG1Affine { } /// Convert slice of arkworks G1Affine points to ICICLE G1Affine -pub(crate) fn ark_g1s_to_icicle(points: &[ArkG1Affine]) -> Vec { +pub(crate) fn ark_g1s_to_icicle(points: &[G1Affine]) -> Vec { points.iter().map(ark_g1_to_icicle).collect() } @@ -92,7 +93,7 @@ pub(crate) fn icicle_g1_proj_to_ark(point: &IcicleG1Projective) -> ark_bn254::G1 /// Convert arkworks G2Affine point to ICICLE G2Affine #[inline] -pub(crate) fn ark_g2_to_icicle(point: &ArkG2Affine) -> IcicleG2Affine { +pub(crate) fn ark_g2_to_icicle(point: &G2Affine) -> IcicleG2Affine { if point.is_zero() { return IcicleG2Affine::zero(); } @@ -126,13 +127,13 @@ pub(crate) fn ark_g2_to_icicle(point: &ArkG2Affine) -> IcicleG2Affine { } /// Convert slice of arkworks G2Affine points to ICICLE G2Affine -pub(crate) fn ark_g2s_to_icicle(points: &[ArkG2Affine]) -> Vec { +pub(crate) fn ark_g2s_to_icicle(points: &[G2Affine]) -> Vec { points.iter().map(ark_g2_to_icicle).collect() } /// Convert ICICLE G2Projective to arkworks G2Affine #[inline] -pub(crate) fn icicle_g2_proj_to_ark(point: &IcicleG2Projective) -> ArkG2Affine { +pub(crate) fn icicle_g2_proj_to_ark(point: &IcicleG2Projective) -> G2Affine { use ark_bn254::{Fq, Fq2, G2Affine}; let affine: IcicleG2Affine = point.to_affine(); @@ -166,7 +167,7 @@ mod tests { fn test_scalar_roundtrip() { let mut rng = ark_std::test_rng(); for _ in 0..100 { - let ark_scalar = ArkFr::rand(&mut rng); + let ark_scalar = Fr::rand(&mut rng); let icicle_scalar = ark_fr_to_icicle(&ark_scalar); let back = icicle_to_ark_fr(&icicle_scalar); assert_eq!(ark_scalar, back); diff --git a/rln/src/circuit/icicle/msm.rs b/rln/src/circuit/icicle/msm.rs index 0f156b1c..8b70da8d 100644 --- a/rln/src/circuit/icicle/msm.rs +++ b/rln/src/circuit/icicle/msm.rs @@ -1,4 +1,3 @@ -use ark_bn254::{Fr as ArkFr, G1Affine as ArkG1Affine, G2Affine as ArkG2Affine}; use ark_ec::AffineRepr; use ark_ff::Zero; use icicle_bn254::curve::{G1Projective as IcicleG1Projective, G2Projective as IcicleG2Projective}; @@ -12,12 +11,15 @@ use super::convert::{ ark_frs_to_icicle, ark_g1s_to_icicle, ark_g2s_to_icicle, icicle_g1_proj_to_ark, icicle_g2_proj_to_ark, }; -use crate::error::ProtocolError; +use crate::{ + circuit::{Fr, G1Affine, G2Affine}, + error::ProtocolError, +}; /// Perform MSM on G1 using ICICLE pub fn icicle_msm_g1( - scalars: &[ArkFr], - points: &[ArkG1Affine], + scalars: &[Fr], + points: &[G1Affine], ) -> Result { if scalars.is_empty() || points.is_empty() { return Ok(ark_bn254::G1Projective::zero()); @@ -46,8 +48,8 @@ pub fn icicle_msm_g1( /// Perform MSM on G2 using ICICLE pub fn icicle_msm_g2( - scalars: &[ArkFr], - points: &[ArkG2Affine], + scalars: &[Fr], + points: &[G2Affine], ) -> Result { if scalars.is_empty() || points.is_empty() { return Ok(ark_bn254::G2Projective::zero()); diff --git a/rln/src/circuit/icicle/proof.rs b/rln/src/circuit/icicle/proof.rs index e52e01d6..5c1c2275 100644 --- a/rln/src/circuit/icicle/proof.rs +++ b/rln/src/circuit/icicle/proof.rs @@ -6,7 +6,7 @@ use ark_ff::Zero; use crate::{ circuit::{ icicle::msm::{icicle_msm_g1, icicle_msm_g2}, - ArkG2Affine, Fr, G1Affine, G1Projective, G2Projective, Proof, ProvingKey, + Fr, G1Affine, G1Projective, G2Affine, G2Projective, Proof, ProvingKey, }, error::ProtocolError, }; @@ -38,8 +38,8 @@ fn calculate_coeff_g1_icicle( /// Calculate coefficient for G2 proof element using ICICLE MSM fn calculate_coeff_g2_icicle( initial: G2Projective, - query: &[ArkG2Affine], - vk_param: ArkG2Affine, + query: &[G2Affine], + vk_param: G2Affine, assignment: &[Fr], ) -> Result { let el = query[0]; From 94e6174c61c8f11473d3e00ae07ce6e767e1278e Mon Sep 17 00:00:00 2001 From: vinhtc27 Date: Mon, 26 Jan 2026 15:31:25 +0700 Subject: [PATCH 04/16] refactor(rln): remove ICICLE runtime initialization and use default --- rln/src/circuit/icicle.rs | 19 ------------------- rln/src/protocol/proof.rs | 11 ++--------- 2 files changed, 2 insertions(+), 28 deletions(-) diff --git a/rln/src/circuit/icicle.rs b/rln/src/circuit/icicle.rs index 0be7dab0..51fa1a60 100644 --- a/rln/src/circuit/icicle.rs +++ b/rln/src/circuit/icicle.rs @@ -1,25 +1,6 @@ // This crate is based on the code by Ingonyama. Its preimage can be found here: // https://github.com/ingonyama-zk/icicle -use icicle_runtime::{runtime, set_device, Device}; - -use crate::error::ProtocolError; - mod convert; mod msm; pub(crate) mod proof; - -/// Initialize ICICLE runtime and select device -pub fn init_icicle() -> Result<(), ProtocolError> { - // Try to load CUDA backend, fall back to CPU - let _ = runtime::load_backend_from_env_or_default(); - - let cuda = Device::new("CUDA", 0); - - if set_device(&cuda).is_err() { - let cpu = Device::new("CPU", 0); - set_device(&cpu)?; - } - - Ok(()) -} diff --git a/rln/src/protocol/proof.rs b/rln/src/protocol/proof.rs index 80ab7204..0910756b 100644 --- a/rln/src/protocol/proof.rs +++ b/rln/src/protocol/proof.rs @@ -6,9 +6,8 @@ use num_bigint::BigInt; use num_traits::Signed; #[cfg(feature = "icicle")] use { - crate::circuit::icicle::{init_icicle, proof::create_proof_with_icicle_msm}, - ark_groth16::r1cs_to_qap::R1CSToQAP, - ark_poly::GeneralEvaluationDomain, + crate::circuit::icicle::proof::create_proof_with_icicle_msm, + ark_groth16::r1cs_to_qap::R1CSToQAP, ark_poly::GeneralEvaluationDomain, }; use super::witness::{inputs_for_witness_calculation, RLNWitnessInput}; @@ -360,9 +359,6 @@ pub fn generate_zk_proof_with_witness_icicle( calculated_witness: Vec, zkey: &Zkey, ) -> Result { - // Initialize ICICLE - init_icicle()?; - let full_assignment = calculated_witness_to_field_elements::(calculated_witness)?; // Compute witness polynomial h using CircomReduction (same as before) @@ -397,9 +393,6 @@ pub fn generate_zk_proof_icicle( witness: &RLNWitnessInput, graph: &Graph, ) -> Result { - // Initialize ICICLE - init_icicle()?; - // Calculate witness using the standard method let inputs = inputs_for_witness_calculation(witness)? .into_iter() From a5534fb65191fa453ae10311d2f364f36a93a510 Mon Sep 17 00:00:00 2001 From: vinhtc27 Date: Mon, 26 Jan 2026 16:19:29 +0700 Subject: [PATCH 05/16] feat(rln): update ICICLE dependencies to version 4.0.0 and implement GPU backend initialization --- Cargo.lock | 8 +-- rln/Cargo.toml | 10 ++- rln/benches/proof_benchmark.rs | 107 +++++++++++++++++++++++++++++++++ rln/src/circuit/icicle.rs | 33 ++++++++++ rln/src/circuit/mod.rs | 2 +- rln/src/prelude.rs | 2 + 6 files changed, 154 insertions(+), 8 deletions(-) create mode 100644 rln/benches/proof_benchmark.rs diff --git a/Cargo.lock b/Cargo.lock index 800c3d83..75fb34ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -747,7 +747,7 @@ checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1" [[package]] name = "icicle-bn254" version = "4.0.0" -source = "git+https://github.com/ingonyama-zk/icicle.git?branch=main#625532a624e5aaa6e9d31a1c92587f1fcc30dc76" +source = "git+https://github.com/ingonyama-zk/icicle.git?tag=v4.0.0#6c77775961b0e4dc2be359feec1ac11536c18043" dependencies = [ "cmake", "icicle-core", @@ -759,7 +759,7 @@ dependencies = [ [[package]] name = "icicle-core" version = "4.0.0" -source = "git+https://github.com/ingonyama-zk/icicle.git?branch=main#625532a624e5aaa6e9d31a1c92587f1fcc30dc76" +source = "git+https://github.com/ingonyama-zk/icicle.git?tag=v4.0.0#6c77775961b0e4dc2be359feec1ac11536c18043" dependencies = [ "cmake", "hex", @@ -773,7 +773,7 @@ dependencies = [ [[package]] name = "icicle-hash" version = "4.0.0" -source = "git+https://github.com/ingonyama-zk/icicle.git?branch=main#625532a624e5aaa6e9d31a1c92587f1fcc30dc76" +source = "git+https://github.com/ingonyama-zk/icicle.git?tag=v4.0.0#6c77775961b0e4dc2be359feec1ac11536c18043" dependencies = [ "cmake", "icicle-core", @@ -784,7 +784,7 @@ dependencies = [ [[package]] name = "icicle-runtime" version = "4.0.0" -source = "git+https://github.com/ingonyama-zk/icicle.git?branch=main#625532a624e5aaa6e9d31a1c92587f1fcc30dc76" +source = "git+https://github.com/ingonyama-zk/icicle.git?tag=v4.0.0#6c77775961b0e4dc2be359feec1ac11536c18043" dependencies = [ "cmake", "once_cell", diff --git a/rln/Cargo.toml b/rln/Cargo.toml index d4c55a28..0bac7726 100644 --- a/rln/Cargo.toml +++ b/rln/Cargo.toml @@ -60,9 +60,9 @@ serde = { version = "1.0.228", features = ["derive"] } document-features = { version = "0.2.12", optional = true } # GPU Acceleration (optional) -icicle-runtime = { git = "https://github.com/ingonyama-zk/icicle.git", branch = "main", optional = true } -icicle-core = { git = "https://github.com/ingonyama-zk/icicle.git", branch = "main", optional = true } -icicle-bn254 = { git = "https://github.com/ingonyama-zk/icicle.git", branch = "main", optional = true } +icicle-runtime = { git = "https://github.com/ingonyama-zk/icicle.git", tag = "v4.0.0", optional = true } +icicle-core = { git = "https://github.com/ingonyama-zk/icicle.git", tag = "v4.0.0", optional = true } +icicle-bn254 = { git = "https://github.com/ingonyama-zk/icicle.git", tag = "v4.0.0", optional = true } [dev-dependencies] criterion = { version = "0.8.0", features = ["html_reports"] } @@ -95,6 +95,10 @@ required-features = ["pmtree-ft"] name = "poseidon_tree_benchmark" harness = false +[[bench]] +name = "proof_benchmark" +harness = false + [package.metadata.docs.rs] all-features = true diff --git a/rln/benches/proof_benchmark.rs b/rln/benches/proof_benchmark.rs new file mode 100644 index 00000000..6338c9e6 --- /dev/null +++ b/rln/benches/proof_benchmark.rs @@ -0,0 +1,107 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use rln::prelude::*; +#[cfg(feature = "icicle")] +use rln::protocol::generate_zk_proof_icicle; +use zerokit_utils::merkle_tree::{ZerokitMerkleProof, ZerokitMerkleTree}; + +type ConfigOf = ::Config; + +fn get_test_witness() -> RLNWitnessInput { + let leaf_index = 3; + // Generate identity pair + let (identity_secret, id_commitment) = keygen().unwrap(); + let user_message_limit = Fr::from(100); + let rate_commitment = poseidon_hash(&[id_commitment, user_message_limit]).unwrap(); + + // Generate merkle tree + let default_leaf = Fr::from(0); + let mut tree = PoseidonTree::new( + DEFAULT_TREE_DEPTH, + default_leaf, + ConfigOf::::default(), + ) + .unwrap(); + tree.set(leaf_index, rate_commitment).unwrap(); + + let merkle_proof = tree.proof(leaf_index).unwrap(); + + let signal = b"hey hey"; + let x = hash_to_field_le(signal).unwrap(); + + // We set the remaining values to random ones + let epoch = hash_to_field_le(b"test-epoch").unwrap(); + let rln_identifier = hash_to_field_le(b"test-rln-identifier").unwrap(); + let external_nullifier = poseidon_hash(&[epoch, rln_identifier]).unwrap(); + + let message_id = Fr::from(1); + + RLNWitnessInput::new( + identity_secret, + user_message_limit, + message_id, + merkle_proof.get_path_elements(), + merkle_proof.get_path_index(), + x, + external_nullifier, + ) + .unwrap() +} + +fn proof_generation_benchmark(c: &mut Criterion) { + let witness = get_test_witness(); + let proving_key = zkey_from_folder(); + let graph_data = graph_from_folder(); + + let mut group = c.benchmark_group("proof_generation"); + + // Configure for shorter benchmark runs + group.sample_size(10); + group.measurement_time(std::time::Duration::from_secs(10)); + + // Single-threaded proof generation + // Note: This benchmark runs with the features enabled at compile time. + // To run single-threaded, compile without the "parallel" feature. + group.bench_function("standard", |b| { + b.iter(|| { + let _ = generate_zk_proof(proving_key, &witness, graph_data).unwrap(); + }) + }); + + group.finish(); +} + +#[cfg(feature = "icicle")] +fn proof_generation_icicle_benchmark(c: &mut Criterion) { + let _ = init_icicle_backend(); + + let witness = get_test_witness(); + let proving_key = zkey_from_folder(); + let graph_data = graph_from_folder(); + + let mut group = c.benchmark_group("proof_generation_icicle"); + + // Configure for shorter benchmark runs + group.sample_size(10); + group.measurement_time(std::time::Duration::from_secs(10)); + + // ICICLE GPU-accelerated proof generation + group.bench_function("icicle", |b| { + b.iter(|| { + let _ = generate_zk_proof_icicle(proving_key, &witness, graph_data).unwrap(); + }) + }); + + group.finish(); +} + +#[cfg(feature = "icicle")] +criterion_group!( + benches, + proof_generation_benchmark, + proof_generation_icicle_benchmark +); + +#[cfg(not(feature = "icicle"))] +criterion_group!(benches, proof_generation_benchmark); + +criterion_main!(benches); diff --git a/rln/src/circuit/icicle.rs b/rln/src/circuit/icicle.rs index 51fa1a60..67ad0117 100644 --- a/rln/src/circuit/icicle.rs +++ b/rln/src/circuit/icicle.rs @@ -4,3 +4,36 @@ mod convert; mod msm; pub(crate) mod proof; + +use icicle_runtime::Device; + +use crate::error::ProtocolError; + +/// Initialize the ICICLE backend for GPU acceleration. +/// +/// This function attempts to load the Metal backend (for macOS) or CUDA backend (for Linux/Windows) +/// and set it as the active device. If no GPU backend is available, it falls back to the CPU backend. +pub fn init_icicle_backend() -> Result { + let mut device_name = String::from("CPU"); + + icicle_runtime::runtime::load_backend_from_env_or_default()?; + + let metal_device = Device::new("METAL", 0); + if icicle_runtime::is_device_available(&metal_device) { + icicle_runtime::set_device(&metal_device)?; + device_name = String::from("METAL"); + return Ok(device_name); + } + + let cuda_device = Device::new("CUDA", 0); + if icicle_runtime::is_device_available(&cuda_device) { + icicle_runtime::set_device(&cuda_device)?; + device_name = String::from("CUDA"); + return Ok(device_name); + } + + let cpu_device = Device::new("CPU", 0); + icicle_runtime::set_device(&cpu_device)?; + + Ok(device_name) +} diff --git a/rln/src/circuit/mod.rs b/rln/src/circuit/mod.rs index d5f3be98..bb4a32b8 100644 --- a/rln/src/circuit/mod.rs +++ b/rln/src/circuit/mod.rs @@ -5,7 +5,7 @@ pub(crate) mod iden3calc; pub(crate) mod qap; #[cfg(feature = "icicle")] -pub(crate) mod icicle; +pub mod icicle; #[cfg(not(target_arch = "wasm32"))] use std::sync::LazyLock; diff --git a/rln/src/prelude.rs b/rln/src/prelude.rs index 8a5c00af..396e2d65 100644 --- a/rln/src/prelude.rs +++ b/rln/src/prelude.rs @@ -1,5 +1,7 @@ // This module re-exports the most commonly used types and functions from the RLN library +#[cfg(feature = "icicle")] +pub use crate::circuit::icicle::init_icicle_backend; #[cfg(not(target_arch = "wasm32"))] pub use crate::circuit::{graph_from_folder, zkey_from_folder}; #[cfg(feature = "pmtree-ft")] From 9a50e66a355f8758e46587dfd98874cbdf24ead9 Mon Sep 17 00:00:00 2001 From: vinhtc27 Date: Mon, 26 Jan 2026 16:23:35 +0700 Subject: [PATCH 06/16] chore(rln): disable icicle by default --- rln/Cargo.toml | 2 +- rln/benches/proof_benchmark.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rln/Cargo.toml b/rln/Cargo.toml index 0bac7726..066a507c 100644 --- a/rln/Cargo.toml +++ b/rln/Cargo.toml @@ -68,7 +68,7 @@ icicle-bn254 = { git = "https://github.com/ingonyama-zk/icicle.git", tag = "v4.0 criterion = { version = "0.8.0", features = ["html_reports"] } [features] -default = ["parallel", "pmtree-ft", "icicle"] +default = ["parallel", "pmtree-ft"] stateless = [] parallel = [ "rayon", diff --git a/rln/benches/proof_benchmark.rs b/rln/benches/proof_benchmark.rs index 6338c9e6..8e9c352d 100644 --- a/rln/benches/proof_benchmark.rs +++ b/rln/benches/proof_benchmark.rs @@ -72,7 +72,7 @@ fn proof_generation_benchmark(c: &mut Criterion) { #[cfg(feature = "icicle")] fn proof_generation_icicle_benchmark(c: &mut Criterion) { - let _ = init_icicle_backend(); + // let _ = init_icicle_backend(); let witness = get_test_witness(); let proving_key = zkey_from_folder(); From d97569c609b8bcb0fae61df98e89b240578e39d6 Mon Sep 17 00:00:00 2001 From: vinhtc27 Date: Mon, 26 Jan 2026 17:21:40 +0700 Subject: [PATCH 07/16] chore(rln): use latest code from master branch of icicle --- Cargo.lock | 8 ++++---- rln/Cargo.toml | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 75fb34ce..b48c8604 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -747,7 +747,7 @@ checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1" [[package]] name = "icicle-bn254" version = "4.0.0" -source = "git+https://github.com/ingonyama-zk/icicle.git?tag=v4.0.0#6c77775961b0e4dc2be359feec1ac11536c18043" +source = "git+https://github.com/ingonyama-zk/icicle.git#625532a624e5aaa6e9d31a1c92587f1fcc30dc76" dependencies = [ "cmake", "icicle-core", @@ -759,7 +759,7 @@ dependencies = [ [[package]] name = "icicle-core" version = "4.0.0" -source = "git+https://github.com/ingonyama-zk/icicle.git?tag=v4.0.0#6c77775961b0e4dc2be359feec1ac11536c18043" +source = "git+https://github.com/ingonyama-zk/icicle.git#625532a624e5aaa6e9d31a1c92587f1fcc30dc76" dependencies = [ "cmake", "hex", @@ -773,7 +773,7 @@ dependencies = [ [[package]] name = "icicle-hash" version = "4.0.0" -source = "git+https://github.com/ingonyama-zk/icicle.git?tag=v4.0.0#6c77775961b0e4dc2be359feec1ac11536c18043" +source = "git+https://github.com/ingonyama-zk/icicle.git#625532a624e5aaa6e9d31a1c92587f1fcc30dc76" dependencies = [ "cmake", "icicle-core", @@ -784,7 +784,7 @@ dependencies = [ [[package]] name = "icicle-runtime" version = "4.0.0" -source = "git+https://github.com/ingonyama-zk/icicle.git?tag=v4.0.0#6c77775961b0e4dc2be359feec1ac11536c18043" +source = "git+https://github.com/ingonyama-zk/icicle.git#625532a624e5aaa6e9d31a1c92587f1fcc30dc76" dependencies = [ "cmake", "once_cell", diff --git a/rln/Cargo.toml b/rln/Cargo.toml index 066a507c..abbfc263 100644 --- a/rln/Cargo.toml +++ b/rln/Cargo.toml @@ -60,9 +60,9 @@ serde = { version = "1.0.228", features = ["derive"] } document-features = { version = "0.2.12", optional = true } # GPU Acceleration (optional) -icicle-runtime = { git = "https://github.com/ingonyama-zk/icicle.git", tag = "v4.0.0", optional = true } -icicle-core = { git = "https://github.com/ingonyama-zk/icicle.git", tag = "v4.0.0", optional = true } -icicle-bn254 = { git = "https://github.com/ingonyama-zk/icicle.git", tag = "v4.0.0", optional = true } +icicle-runtime = { git = "https://github.com/ingonyama-zk/icicle.git", optional = true } +icicle-core = { git = "https://github.com/ingonyama-zk/icicle.git", optional = true } +icicle-bn254 = { git = "https://github.com/ingonyama-zk/icicle.git", optional = true } [dev-dependencies] criterion = { version = "0.8.0", features = ["html_reports"] } From 15a5e23a34bb6a5a8a5f776f5f0e804df02cfd9a Mon Sep 17 00:00:00 2001 From: vinhtc27 Date: Mon, 26 Jan 2026 18:35:54 +0700 Subject: [PATCH 08/16] chore(rln): add cargo make bench_icicle command --- rln/Cargo.toml | 1 + rln/Makefile.toml | 11 +++++++++++ rln/benches/proof_benchmark.rs | 5 ----- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/rln/Cargo.toml b/rln/Cargo.toml index abbfc263..3694cb39 100644 --- a/rln/Cargo.toml +++ b/rln/Cargo.toml @@ -98,6 +98,7 @@ harness = false [[bench]] name = "proof_benchmark" harness = false +required-features = ["pmtree-ft", "icicle"] [package.metadata.docs.rs] all-features = true diff --git a/rln/Makefile.toml b/rln/Makefile.toml index 4af3d6d0..602058d5 100644 --- a/rln/Makefile.toml +++ b/rln/Makefile.toml @@ -21,3 +21,14 @@ args = [ [tasks.bench] command = "cargo" args = ["bench"] + +[tasks.bench_icicle] +command = "cargo" +args = [ + "bench", + "--bench", + "proof_benchmark", + "--no-default-features", + "--features", + "pmtree-ft,icicle", +] diff --git a/rln/benches/proof_benchmark.rs b/rln/benches/proof_benchmark.rs index 8e9c352d..00f50721 100644 --- a/rln/benches/proof_benchmark.rs +++ b/rln/benches/proof_benchmark.rs @@ -70,7 +70,6 @@ fn proof_generation_benchmark(c: &mut Criterion) { group.finish(); } -#[cfg(feature = "icicle")] fn proof_generation_icicle_benchmark(c: &mut Criterion) { // let _ = init_icicle_backend(); @@ -94,14 +93,10 @@ fn proof_generation_icicle_benchmark(c: &mut Criterion) { group.finish(); } -#[cfg(feature = "icicle")] criterion_group!( benches, proof_generation_benchmark, proof_generation_icicle_benchmark ); -#[cfg(not(feature = "icicle"))] -criterion_group!(benches, proof_generation_benchmark); - criterion_main!(benches); From fd28a587d4ac583246afad0b9fad1c17ed5fbddc Mon Sep 17 00:00:00 2001 From: vinhtc27 Date: Fri, 30 Jan 2026 17:10:59 +0700 Subject: [PATCH 09/16] feat(rln): add async proof generation benchmarks --- Cargo.lock | 125 +++++++++++++++++++++++++++ rln/Cargo.toml | 9 +- rln/benches/async_proof_benchmark.rs | 118 +++++++++++++++++++++++++ rln/benches/proof_benchmark.rs | 9 -- 4 files changed, 251 insertions(+), 10 deletions(-) create mode 100644 rln/benches/async_proof_benchmark.rs diff --git a/Cargo.lock b/Cargo.lock index b48c8604..3e7226d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -462,6 +462,7 @@ dependencies = [ "serde", "serde_json", "tinytemplate", + "tokio", "walkdir", ] @@ -664,6 +665,95 @@ dependencies = [ "winapi", ] +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "fxhash" version = "0.2.1" @@ -1032,6 +1122,12 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "plotters" version = "0.3.7" @@ -1287,6 +1383,7 @@ dependencies = [ "cfg-if", "criterion", "document-features", + "futures", "icicle-bn254", "icicle-core", "icicle-runtime", @@ -1304,6 +1401,7 @@ dependencies = [ "tempfile", "thiserror", "tiny-keccak", + "tokio", "zeroize", "zerokit_utils", ] @@ -1483,6 +1581,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + [[package]] name = "sled" version = "0.34.7" @@ -1621,6 +1725,27 @@ dependencies = [ "serde_json", ] +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "pin-project-lite", + "tokio-macros", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "toml_datetime" version = "0.7.3" diff --git a/rln/Cargo.toml b/rln/Cargo.toml index 3694cb39..1497d582 100644 --- a/rln/Cargo.toml +++ b/rln/Cargo.toml @@ -65,7 +65,9 @@ icicle-core = { git = "https://github.com/ingonyama-zk/icicle.git", optional = t icicle-bn254 = { git = "https://github.com/ingonyama-zk/icicle.git", optional = true } [dev-dependencies] -criterion = { version = "0.8.0", features = ["html_reports"] } +criterion = { version = "0.8.0", features = ["html_reports", "async_tokio"] } +tokio = { version = "1.44", features = ["rt-multi-thread", "macros", "sync"] } +futures = "0.3" [features] default = ["parallel", "pmtree-ft"] @@ -100,6 +102,11 @@ name = "proof_benchmark" harness = false required-features = ["pmtree-ft", "icicle"] +[[bench]] +name = "async_proof_benchmark" +harness = false +required-features = ["pmtree-ft", "icicle"] + [package.metadata.docs.rs] all-features = true diff --git a/rln/benches/async_proof_benchmark.rs b/rln/benches/async_proof_benchmark.rs new file mode 100644 index 00000000..170425b7 --- /dev/null +++ b/rln/benches/async_proof_benchmark.rs @@ -0,0 +1,118 @@ +use std::{thread::available_parallelism, time::Duration}; + +use criterion::{criterion_group, criterion_main, Criterion, Throughput}; +use rln::prelude::*; +#[cfg(feature = "icicle")] +use rln::protocol::generate_zk_proof_icicle; +use tokio::{runtime::Builder, task::JoinSet}; +use zerokit_utils::merkle_tree::{ZerokitMerkleProof, ZerokitMerkleTree}; + +type ConfigOf = ::Config; + +fn get_proof_count() -> usize { + available_parallelism().map(|p| p.get() * 2).unwrap_or(8) +} + +fn get_test_witness() -> RLNWitnessInput { + let leaf_index = 3; + let (identity_secret, id_commitment) = keygen().unwrap(); + let user_message_limit = Fr::from(100); + let rate_commitment = poseidon_hash(&[id_commitment, user_message_limit]).unwrap(); + + let default_leaf = Fr::from(0); + let mut tree = PoseidonTree::new( + DEFAULT_TREE_DEPTH, + default_leaf, + ConfigOf::::default(), + ) + .unwrap(); + tree.set(leaf_index, rate_commitment).unwrap(); + + let merkle_proof = tree.proof(leaf_index).unwrap(); + + let signal = b"hey hey"; + let x = hash_to_field_le(signal).unwrap(); + + let epoch = hash_to_field_le(b"test-epoch").unwrap(); + let rln_identifier = hash_to_field_le(b"test-rln-identifier").unwrap(); + let external_nullifier = poseidon_hash(&[epoch, rln_identifier]).unwrap(); + + let message_id = Fr::from(1); + + RLNWitnessInput::new( + identity_secret, + user_message_limit, + message_id, + merkle_proof.get_path_elements(), + merkle_proof.get_path_index(), + x, + external_nullifier, + ) + .unwrap() +} + +fn async_proof_generation_benchmark(c: &mut Criterion) { + let rt = Builder::new_multi_thread().enable_all().build().unwrap(); + + let witness = get_test_witness(); + let proof_count = get_proof_count(); + + let mut group = c.benchmark_group("async_proof_generation"); + group.sample_size(10); + group.measurement_time(Duration::from_secs(30)); + group.throughput(Throughput::Elements(proof_count as u64)); + + group.bench_function("standard", |b| { + b.to_async(&rt).iter(|| async { + let mut set = JoinSet::new(); + for _ in 0..proof_count { + let witness = witness.clone(); + set.spawn_blocking(move || { + let proving_key = zkey_from_folder(); + let graph_data = graph_from_folder(); + generate_zk_proof(proving_key, &witness, graph_data).unwrap() + }); + } + set.join_all().await + }); + }); + + group.finish(); +} + +fn async_proof_generation_icicle_benchmark(c: &mut Criterion) { + let rt = Builder::new_multi_thread().enable_all().build().unwrap(); + + let witness = get_test_witness(); + let proof_count = get_proof_count(); + + let mut group = c.benchmark_group("async_proof_generation_icicle"); + group.sample_size(10); + group.measurement_time(Duration::from_secs(30)); + group.throughput(Throughput::Elements(proof_count as u64)); + + group.bench_function("icicle", |b| { + b.to_async(&rt).iter(|| async { + let mut set = JoinSet::new(); + for _ in 0..proof_count { + let witness = witness.clone(); + set.spawn_blocking(move || { + let proving_key = zkey_from_folder(); + let graph_data = graph_from_folder(); + generate_zk_proof_icicle(proving_key, &witness, graph_data).unwrap() + }); + } + set.join_all().await + }); + }); + + group.finish(); +} + +criterion_group!( + benches, + async_proof_generation_benchmark, + async_proof_generation_icicle_benchmark +); + +criterion_main!(benches); diff --git a/rln/benches/proof_benchmark.rs b/rln/benches/proof_benchmark.rs index 00f50721..41314d86 100644 --- a/rln/benches/proof_benchmark.rs +++ b/rln/benches/proof_benchmark.rs @@ -8,12 +8,10 @@ type ConfigOf = ::Config; fn get_test_witness() -> RLNWitnessInput { let leaf_index = 3; - // Generate identity pair let (identity_secret, id_commitment) = keygen().unwrap(); let user_message_limit = Fr::from(100); let rate_commitment = poseidon_hash(&[id_commitment, user_message_limit]).unwrap(); - // Generate merkle tree let default_leaf = Fr::from(0); let mut tree = PoseidonTree::new( DEFAULT_TREE_DEPTH, @@ -28,7 +26,6 @@ fn get_test_witness() -> RLNWitnessInput { let signal = b"hey hey"; let x = hash_to_field_le(signal).unwrap(); - // We set the remaining values to random ones let epoch = hash_to_field_le(b"test-epoch").unwrap(); let rln_identifier = hash_to_field_le(b"test-rln-identifier").unwrap(); let external_nullifier = poseidon_hash(&[epoch, rln_identifier]).unwrap(); @@ -54,13 +51,9 @@ fn proof_generation_benchmark(c: &mut Criterion) { let mut group = c.benchmark_group("proof_generation"); - // Configure for shorter benchmark runs group.sample_size(10); group.measurement_time(std::time::Duration::from_secs(10)); - // Single-threaded proof generation - // Note: This benchmark runs with the features enabled at compile time. - // To run single-threaded, compile without the "parallel" feature. group.bench_function("standard", |b| { b.iter(|| { let _ = generate_zk_proof(proving_key, &witness, graph_data).unwrap(); @@ -79,11 +72,9 @@ fn proof_generation_icicle_benchmark(c: &mut Criterion) { let mut group = c.benchmark_group("proof_generation_icicle"); - // Configure for shorter benchmark runs group.sample_size(10); group.measurement_time(std::time::Duration::from_secs(10)); - // ICICLE GPU-accelerated proof generation group.bench_function("icicle", |b| { b.iter(|| { let _ = generate_zk_proof_icicle(proving_key, &witness, graph_data).unwrap(); From f65c093f56c4498f5c89ef65a608f9348800222b Mon Sep 17 00:00:00 2001 From: vinhtc27 Date: Fri, 30 Jan 2026 17:16:55 +0700 Subject: [PATCH 10/16] chore(rln): add icicle init to async benchmark file --- rln/benches/async_proof_benchmark.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rln/benches/async_proof_benchmark.rs b/rln/benches/async_proof_benchmark.rs index 170425b7..fa120974 100644 --- a/rln/benches/async_proof_benchmark.rs +++ b/rln/benches/async_proof_benchmark.rs @@ -81,6 +81,8 @@ fn async_proof_generation_benchmark(c: &mut Criterion) { } fn async_proof_generation_icicle_benchmark(c: &mut Criterion) { + // let _ = init_icicle_backend(); + let rt = Builder::new_multi_thread().enable_all().build().unwrap(); let witness = get_test_witness(); From 4916cba91e34d9be293de0fa73cd21922303f42f Mon Sep 17 00:00:00 2001 From: vinhtc27 Date: Mon, 2 Feb 2026 14:55:29 +0700 Subject: [PATCH 11/16] chore(rln): remove cfg flags for icicle --- rln/benches/async_proof_benchmark.rs | 1 - rln/benches/proof_benchmark.rs | 1 - 2 files changed, 2 deletions(-) diff --git a/rln/benches/async_proof_benchmark.rs b/rln/benches/async_proof_benchmark.rs index fa120974..6a66c911 100644 --- a/rln/benches/async_proof_benchmark.rs +++ b/rln/benches/async_proof_benchmark.rs @@ -2,7 +2,6 @@ use std::{thread::available_parallelism, time::Duration}; use criterion::{criterion_group, criterion_main, Criterion, Throughput}; use rln::prelude::*; -#[cfg(feature = "icicle")] use rln::protocol::generate_zk_proof_icicle; use tokio::{runtime::Builder, task::JoinSet}; use zerokit_utils::merkle_tree::{ZerokitMerkleProof, ZerokitMerkleTree}; diff --git a/rln/benches/proof_benchmark.rs b/rln/benches/proof_benchmark.rs index 41314d86..aefc4780 100644 --- a/rln/benches/proof_benchmark.rs +++ b/rln/benches/proof_benchmark.rs @@ -1,6 +1,5 @@ use criterion::{criterion_group, criterion_main, Criterion}; use rln::prelude::*; -#[cfg(feature = "icicle")] use rln::protocol::generate_zk_proof_icicle; use zerokit_utils::merkle_tree::{ZerokitMerkleProof, ZerokitMerkleTree}; From 51fbdde5446418eb157414fbcc563cb5c6fa5471 Mon Sep 17 00:00:00 2001 From: vinhtc27 Date: Mon, 2 Feb 2026 18:05:30 +0700 Subject: [PATCH 12/16] chore(rln): simplify async benchmark --- rln/benches/async_proof_benchmark.rs | 67 +++++++++++++++++++++------- rln/benches/proof_benchmark.rs | 6 +-- 2 files changed, 55 insertions(+), 18 deletions(-) diff --git a/rln/benches/async_proof_benchmark.rs b/rln/benches/async_proof_benchmark.rs index 6a66c911..223a79a1 100644 --- a/rln/benches/async_proof_benchmark.rs +++ b/rln/benches/async_proof_benchmark.rs @@ -1,4 +1,11 @@ -use std::{thread::available_parallelism, time::Duration}; +use std::{ + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, + }, + thread::available_parallelism, + time::Duration, +}; use criterion::{criterion_group, criterion_main, Criterion, Throughput}; use rln::prelude::*; @@ -54,6 +61,8 @@ fn async_proof_generation_benchmark(c: &mut Criterion) { let rt = Builder::new_multi_thread().enable_all().build().unwrap(); let witness = get_test_witness(); + let proving_key = zkey_from_folder(); + let graph_data = graph_from_folder(); let proof_count = get_proof_count(); let mut group = c.benchmark_group("async_proof_generation"); @@ -67,9 +76,7 @@ fn async_proof_generation_benchmark(c: &mut Criterion) { for _ in 0..proof_count { let witness = witness.clone(); set.spawn_blocking(move || { - let proving_key = zkey_from_folder(); - let graph_data = graph_from_folder(); - generate_zk_proof(proving_key, &witness, graph_data).unwrap() + let _ = generate_zk_proof(&proving_key, &witness, &graph_data).unwrap(); }); } set.join_all().await @@ -80,12 +87,15 @@ fn async_proof_generation_benchmark(c: &mut Criterion) { } fn async_proof_generation_icicle_benchmark(c: &mut Criterion) { - // let _ = init_icicle_backend(); + let _ = init_icicle_backend(); let rt = Builder::new_multi_thread().enable_all().build().unwrap(); let witness = get_test_witness(); + let proving_key = zkey_from_folder(); + let graph_data = graph_from_folder(); let proof_count = get_proof_count(); + let cpu_workers = available_parallelism().map(|p| p.get()).unwrap_or(4); let mut group = c.benchmark_group("async_proof_generation_icicle"); group.sample_size(10); @@ -93,17 +103,44 @@ fn async_proof_generation_icicle_benchmark(c: &mut Criterion) { group.throughput(Throughput::Elements(proof_count as u64)); group.bench_function("icicle", |b| { - b.to_async(&rt).iter(|| async { - let mut set = JoinSet::new(); - for _ in 0..proof_count { - let witness = witness.clone(); - set.spawn_blocking(move || { - let proving_key = zkey_from_folder(); - let graph_data = graph_from_folder(); - generate_zk_proof_icicle(proving_key, &witness, graph_data).unwrap() - }); + let witness = witness.clone(); + + b.to_async(&rt).iter(|| { + let witness = witness.clone(); + + async move { + let remaining = Arc::new(AtomicUsize::new(proof_count)); + let mut set = JoinSet::new(); + + { + let remaining = remaining.clone(); + let witness = witness.clone(); + set.spawn_blocking(move || loop { + let prev = remaining.fetch_sub(1, Ordering::Relaxed); + if prev == 0 || prev > proof_count { + break; + } + let _ = + generate_zk_proof_icicle(&proving_key, &witness, &graph_data).unwrap(); + }) + }; + + let mut cpu_handles = Vec::new(); + for _ in 0..cpu_workers { + let remaining = remaining.clone(); + let witness = witness.clone(); + let handle = set.spawn_blocking(move || loop { + let prev = remaining.fetch_sub(1, Ordering::Relaxed); + if prev == 0 || prev > proof_count { + break; + } + let _ = generate_zk_proof(&proving_key, &witness, &graph_data).unwrap(); + }); + cpu_handles.push(handle); + } + + set.join_all().await; } - set.join_all().await }); }); diff --git a/rln/benches/proof_benchmark.rs b/rln/benches/proof_benchmark.rs index aefc4780..e50a6245 100644 --- a/rln/benches/proof_benchmark.rs +++ b/rln/benches/proof_benchmark.rs @@ -55,7 +55,7 @@ fn proof_generation_benchmark(c: &mut Criterion) { group.bench_function("standard", |b| { b.iter(|| { - let _ = generate_zk_proof(proving_key, &witness, graph_data).unwrap(); + let _ = generate_zk_proof(&proving_key, &witness, &graph_data).unwrap(); }) }); @@ -63,7 +63,7 @@ fn proof_generation_benchmark(c: &mut Criterion) { } fn proof_generation_icicle_benchmark(c: &mut Criterion) { - // let _ = init_icicle_backend(); + let _ = init_icicle_backend(); let witness = get_test_witness(); let proving_key = zkey_from_folder(); @@ -76,7 +76,7 @@ fn proof_generation_icicle_benchmark(c: &mut Criterion) { group.bench_function("icicle", |b| { b.iter(|| { - let _ = generate_zk_proof_icicle(proving_key, &witness, graph_data).unwrap(); + let _ = generate_zk_proof_icicle(&proving_key, &witness, &graph_data).unwrap(); }) }); From 48185d51c7da022d40c6e09571f99db96a494dd9 Mon Sep 17 00:00:00 2001 From: vinhtc27 Date: Wed, 4 Feb 2026 11:29:29 +0700 Subject: [PATCH 13/16] chore: fmt cleanup --- rln/benches/async_proof_benchmark.rs | 3 +-- rln/benches/proof_benchmark.rs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/rln/benches/async_proof_benchmark.rs b/rln/benches/async_proof_benchmark.rs index 223a79a1..bea2fc98 100644 --- a/rln/benches/async_proof_benchmark.rs +++ b/rln/benches/async_proof_benchmark.rs @@ -8,8 +8,7 @@ use std::{ }; use criterion::{criterion_group, criterion_main, Criterion, Throughput}; -use rln::prelude::*; -use rln::protocol::generate_zk_proof_icicle; +use rln::{prelude::*, protocol::generate_zk_proof_icicle}; use tokio::{runtime::Builder, task::JoinSet}; use zerokit_utils::merkle_tree::{ZerokitMerkleProof, ZerokitMerkleTree}; diff --git a/rln/benches/proof_benchmark.rs b/rln/benches/proof_benchmark.rs index e50a6245..27642dc6 100644 --- a/rln/benches/proof_benchmark.rs +++ b/rln/benches/proof_benchmark.rs @@ -1,6 +1,5 @@ use criterion::{criterion_group, criterion_main, Criterion}; -use rln::prelude::*; -use rln::protocol::generate_zk_proof_icicle; +use rln::{prelude::*, protocol::generate_zk_proof_icicle}; use zerokit_utils::merkle_tree::{ZerokitMerkleProof, ZerokitMerkleTree}; type ConfigOf = ::Config; From a927fca4cd8d4020b7b17913cdbd972f6947368d Mon Sep 17 00:00:00 2001 From: vinhtc27 Date: Wed, 4 Feb 2026 12:23:12 +0700 Subject: [PATCH 14/16] chore: final benchmark file --- rln/benches/async_proof_benchmark.rs | 15 ++++----------- rln/benches/proof_benchmark.rs | 2 +- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/rln/benches/async_proof_benchmark.rs b/rln/benches/async_proof_benchmark.rs index bea2fc98..b2111754 100644 --- a/rln/benches/async_proof_benchmark.rs +++ b/rln/benches/async_proof_benchmark.rs @@ -14,12 +14,6 @@ use zerokit_utils::merkle_tree::{ZerokitMerkleProof, ZerokitMerkleTree}; type ConfigOf = ::Config; -fn get_proof_count() -> usize { - available_parallelism().map(|p| p.get() * 2).unwrap_or(8) -} - -fn get_test_witness() -> RLNWitnessInput { - let leaf_index = 3; let (identity_secret, id_commitment) = keygen().unwrap(); let user_message_limit = Fr::from(100); let rate_commitment = poseidon_hash(&[id_commitment, user_message_limit]).unwrap(); @@ -62,7 +56,7 @@ fn async_proof_generation_benchmark(c: &mut Criterion) { let witness = get_test_witness(); let proving_key = zkey_from_folder(); let graph_data = graph_from_folder(); - let proof_count = get_proof_count(); + let proof_count = available_parallelism().map(|p| p.get() * 2).unwrap_or(8); let mut group = c.benchmark_group("async_proof_generation"); group.sample_size(10); @@ -86,15 +80,14 @@ fn async_proof_generation_benchmark(c: &mut Criterion) { } fn async_proof_generation_icicle_benchmark(c: &mut Criterion) { - let _ = init_icicle_backend(); + // let _ = init_icicle_backend(); let rt = Builder::new_multi_thread().enable_all().build().unwrap(); let witness = get_test_witness(); let proving_key = zkey_from_folder(); let graph_data = graph_from_folder(); - let proof_count = get_proof_count(); - let cpu_workers = available_parallelism().map(|p| p.get()).unwrap_or(4); + let proof_count = available_parallelism().map(|p| p.get() * 2).unwrap_or(8); let mut group = c.benchmark_group("async_proof_generation_icicle"); group.sample_size(10); @@ -125,7 +118,7 @@ fn async_proof_generation_icicle_benchmark(c: &mut Criterion) { }; let mut cpu_handles = Vec::new(); - for _ in 0..cpu_workers { + for _ in 0..proof_count { let remaining = remaining.clone(); let witness = witness.clone(); let handle = set.spawn_blocking(move || loop { diff --git a/rln/benches/proof_benchmark.rs b/rln/benches/proof_benchmark.rs index 27642dc6..5df8d809 100644 --- a/rln/benches/proof_benchmark.rs +++ b/rln/benches/proof_benchmark.rs @@ -62,7 +62,7 @@ fn proof_generation_benchmark(c: &mut Criterion) { } fn proof_generation_icicle_benchmark(c: &mut Criterion) { - let _ = init_icicle_backend(); + // let _ = init_icicle_backend(); let witness = get_test_witness(); let proving_key = zkey_from_folder(); From 56ecc92d0f97287bdb9f2559cdb077f55d201004 Mon Sep 17 00:00:00 2001 From: vinhtc27 Date: Wed, 4 Feb 2026 12:26:06 +0700 Subject: [PATCH 15/16] chore(rln): final --- rln/benches/async_proof_benchmark.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rln/benches/async_proof_benchmark.rs b/rln/benches/async_proof_benchmark.rs index b2111754..13eb7b22 100644 --- a/rln/benches/async_proof_benchmark.rs +++ b/rln/benches/async_proof_benchmark.rs @@ -14,6 +14,7 @@ use zerokit_utils::merkle_tree::{ZerokitMerkleProof, ZerokitMerkleTree}; type ConfigOf = ::Config; +fn get_test_witness() -> RLNWitnessInput { let (identity_secret, id_commitment) = keygen().unwrap(); let user_message_limit = Fr::from(100); let rate_commitment = poseidon_hash(&[id_commitment, user_message_limit]).unwrap(); From e7a31b99c199b9e9be820552978a9b45224d089c Mon Sep 17 00:00:00 2001 From: vinhtc27 Date: Wed, 4 Feb 2026 12:29:05 +0700 Subject: [PATCH 16/16] chore: fix benchmark file --- rln/benches/async_proof_benchmark.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rln/benches/async_proof_benchmark.rs b/rln/benches/async_proof_benchmark.rs index 13eb7b22..10f5d767 100644 --- a/rln/benches/async_proof_benchmark.rs +++ b/rln/benches/async_proof_benchmark.rs @@ -15,6 +15,7 @@ use zerokit_utils::merkle_tree::{ZerokitMerkleProof, ZerokitMerkleTree}; type ConfigOf = ::Config; fn get_test_witness() -> RLNWitnessInput { + let leaf_index = 3; let (identity_secret, id_commitment) = keygen().unwrap(); let user_message_limit = Fr::from(100); let rate_commitment = poseidon_hash(&[id_commitment, user_message_limit]).unwrap(); @@ -147,3 +148,4 @@ criterion_group!( ); criterion_main!(benches); +