Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 52 additions & 9 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ members = [
"secp256r1",
"syscall/bls12-381-syscall",
"syscall/bn254-syscall",
"syscall/solana-bn254",
]
resolver = "2"

Expand Down Expand Up @@ -43,6 +44,7 @@ hashbrown = "0.15"
hex = "0.4.2"
hex-literal = "1.1.0"
hmac = "0.12"
light-poseidon = "0.4.0"
openssl = "0.10"
pairing = "0.23.0"
p256 = { version = "0.13", default-features = false, features = ["arithmetic", "expose-field"] }
Expand Down
24 changes: 24 additions & 0 deletions syscall/solana-bn254/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[package]
name = "solana-bn254"
description = "BN254 and Poseidon for Solana"
version = "0.1.0"
edition = "2024"
authors = { workspace = true }
repository = { workspace = true }
license = { workspace = true }
readme = "README.md"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

readme is missing


[features]

[dependencies]

[dev-dependencies]
criterion = { workspace = true }
rand = { workspace = true }
ark-ff = { workspace = true }
ark-bn254 = { workspace = true }
light-poseidon = { workspace = true }

[[bench]]
name = "poseidon_bench"
harness = false
124 changes: 124 additions & 0 deletions syscall/solana-bn254/benches/poseidon_bench.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
use ark_ff::PrimeField;
use criterion::{Criterion, criterion_group, criterion_main};
use light_poseidon::PoseidonHasher;
use rand::RngExt; // Required for the `.random()` trait method
use solana_bn254::{
backend::U256,
poseidon::{PoseidonConstants, SparseMatrix},
};

/// Generates a randomized field element safely constrained below the BN254 Fr modulus.
fn random_fr() -> U256 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe sample ark_bn254::Fr then convert to U256? or rename the function non_uniform_random_fr_for_bench()

let mut rng = rand::rng();
let mut limbs: [u64; 4] = rng.random();
// Mask the top u64 to guarantee it stays strictly below Fr::MODULUS
limbs[3] &= 0x0FFFFFFFFFFFFFFF;
U256::new(limbs)
}

/// Dynamically leaks a set of dummy parameters onto the heap.
/// Because Criterion runs this outside the hot-loop, the `O(1)` memory
/// leak per benchmark group is completely acceptable and ensures
/// we can provide the `'static` lifetimes required by the crate.
fn make_dummy_constants<const T: usize>(partial_rounds: usize) -> &'static PoseidonConstants<T> {
let num_round_constants = (9 * T) + partial_rounds;
let round_constants = (0..num_round_constants)
.map(|_| random_fr())
.collect::<Vec<_>>()
.leak();

let mut mds_matrix = [[U256::zero(); T]; T];
for row in mds_matrix.iter_mut() {
for val in row.iter_mut() {
*val = random_fr();
}
}
let mds_matrix = Box::leak(Box::new(mds_matrix));

let mut pre_sparse_matrix = [[U256::zero(); T]; T];
for row in pre_sparse_matrix.iter_mut() {
for val in row.iter_mut() {
*val = random_fr();
}
}
let pre_sparse_matrix = Box::leak(Box::new(pre_sparse_matrix));

let sparse_matrices = (0..partial_rounds)
.map(|_| {
let mut row = [U256::zero(); T];
let mut col = [U256::zero(); T];
for i in 0..T {
row[i] = random_fr();
col[i] = random_fr();
}
SparseMatrix { row, col }
})
.collect::<Vec<_>>()
.leak();

Box::leak(Box::new(PoseidonConstants {
full_rounds: 8,
partial_rounds,
round_constants,
mds_matrix,
pre_sparse_matrix,
sparse_matrices,
}))
}

macro_rules! bench_width {
($c:expr, $t:literal, $partial_rounds:literal) => {
let constants = make_dummy_constants::<$t>($partial_rounds);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it possible to use real constants for benchmark?


let mut group = $c.benchmark_group(concat!("poseidon_t", $t));
let nr_inputs = $t - 1;

// --- solana-bn254 ---
let state = core::array::from_fn(|_| random_fr());
group.bench_function("solana-bn254", |b| {
b.iter(|| {
// Uses std::hint::black_box instead of deprecated criterion::black_box
solana_bn254::poseidon::poseidon(std::hint::black_box(state), constants)
})
});

// --- light-poseidon ---
// Note: light-poseidon handles its own internal constant generation
// via `new_circom()`. We generate inputs from randomized byte arrays
// here, but because this happens outside the `b.iter()` block, it does
// not penalize the light-poseidon benchmark times.
let mut hasher = light_poseidon::Poseidon::<ark_bn254::Fr>::new_circom(nr_inputs).unwrap();
let mut rng = rand::rng();
let inputs: Vec<_> = (0..nr_inputs)
.map(|_| ark_bn254::Fr::from_be_bytes_mod_order(&rng.random::<[u8; 32]>()))
.collect();

group.bench_function("light-poseidon", |b| {
b.iter(|| hasher.hash(std::hint::black_box(&inputs)).unwrap())
});

group.finish();
};
}

fn bench_poseidon(c: &mut Criterion) {
// Solana `sol_poseidon` syscall supported parameters.
// These specific partial round counts are mandated by the SVM
// for state widths t=2..13 (which map to 1..12 inputs).
// [56, 57, 56, 60, 60, 63, 64, 63, 60, 66, 60, 65]
bench_width!(c, 2, 56);
bench_width!(c, 3, 57);
bench_width!(c, 4, 56);
bench_width!(c, 5, 60);
bench_width!(c, 6, 60);
bench_width!(c, 7, 63);
bench_width!(c, 8, 64);
bench_width!(c, 9, 63);
bench_width!(c, 10, 60);
bench_width!(c, 11, 66);
bench_width!(c, 12, 60);
bench_width!(c, 13, 65);
}

criterion_group!(benches, bench_poseidon);
criterion_main!(benches);
Loading