Skip to content

[bn254, poseidon] Add new poseidon implementation - #108

Open
samkim-crypto wants to merge 2 commits into
anza-xyz:masterfrom
samkim-crypto:bn254-ded
Open

[bn254, poseidon] Add new poseidon implementation#108
samkim-crypto wants to merge 2 commits into
anza-xyz:masterfrom
samkim-crypto:bn254-ded

Conversation

@samkim-crypto

@samkim-crypto samkim-crypto commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Adding poseidon implementation that has zero dependencies and has avx-512 optimization.

I was able to get good speedups over the reference light-poseidon crate:

  • The entire hashing process is strictly bounded to const-generic stack arrays ([U256; T]). This completely eliminates the heap allocation (Vec) and dynamic sizing overhead.
  • Sparse Matrix Partial Rounds: I precomputed the MDS matrix transitions into a sparse format. This reduces the heavy O(T^2) dense matrix-vector multiplications during the partial rounds down to an O(T) sparse matrix computation. This is probably the source of most improvement over light-poseidon.
  • Inlined Math: S-box operations (x^5) and Montgomery addition chains are explicitly unrolled and inlined to remove unnecessary loops and branching overhead.

I wrote code for architectures that have and don't have avx acceleration:

  1. Native Scalar (Non-AVX / Fallback)
    For CPUs lacking AVX-512 (e.g., Apple Silicon, older x86), I used a 4-limb (64-bit) pure-Rust backend.
  2. AVX-512 IFMA Acceleration
    On supported modern hardware, the dense matrix and full rounds are dynamically routed to a SIMD engine using vpmadd52 instructions. The 256-bit state is repacked into five 52-bit limbs across 64-bit AVX lanes. I left 12 bits of "headroom" per limb. This allows us to accumulate up to 4,096 matrix cross-products simultaneously without needing to propagate a carry or performing a Montgomery reduction until the matrix multiplication is finished. At peak state widths (T=13), the engine calculates 8 S-boxes perfectly in parallel, and collapses 169 scalar Montgomery multiplications into just 26 SIMD multiplications.
State Width (T) light-poseidon (Reference) Native Scalar (Non-AVX) Native AVX-512 (IFMA) Speedup (vs Reference)
T = 2 16.69 µs 9.78 µs 9.60 µs ~ 1.74x
T = 3 24.67 µs 14.34 µs 13.08 µs ~ 1.89x
T = 4 34.70 µs 18.84 µs 16.60 µs ~ 2.09x
T = 5 50.24 µs 25.05 µs 21.18 µs ~ 2.37x
T = 6 66.04 µs 30.84 µs 25.02 µs ~ 2.64x
T = 7 89.06 µs 38.59 µs 29.99 µs ~ 2.97x
T = 8 113.20 µs 45.81 µs 36.27 µs ~ 3.12x
T = 9 138.16 µs 52.95 µs 47.84 µs ~ 2.89x
T = 10 159.45 µs 58.73 µs 51.08 µs ~ 3.12x
T = 11 205.07 µs 70.74 µs 60.50 µs ~ 3.39x
T = 12 222.73 µs 75.00 µs 61.79 µs ~ 3.60x
T = 13 280.10 µs 86.96 µs 70.74 µs ~ 3.96x

Currently, I named this crate to be solana-bn254. I am planning on adding a native bn254 crate for the alt-bn128 syscalls as well. Once these are merged and audited, then I think we can rename the crate to be solana-bn254-syscall and replace the existing solana-bn254-syscall crate that depends on ark-bn254.

@samkim-crypto samkim-crypto changed the title [bn254] Add backend crate for bn254 and poseidon [bn254, poseidon] Add new poseidon implementation Aug 10, 2026
@samkim-crypto
samkim-crypto marked this pull request as ready for review August 10, 2026 09:09
@samkim-crypto
samkim-crypto requested a review from zz-sol August 10, 2026 09:09

@zz-sol zz-sol left a comment

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.

I think we need to add a constant.rs file to store a set of default constant.
Similar to, for instance,

then we can have a separate function that allows users to derive their own parameters.
we can use the default parameters for consistency checks against other libraries

};

/// 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()


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 a_limbs = [a.l0, a.l1, a.l2, a.l3, a.l4];

// CIOS Algorithm: Loop is fully unrolled by the LLVM compiler.
for i in 0..5 {

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.

Here

  • five radix-2^52 CIOS iterations use the Montgomery radix R=2^260,
  • the serial backend and Fr::R2 use R=2^256.
  • pack_8x only rearranges limbs and does not convert between the two Montgomery domains.

So the SIMD path computes a*b*2^-260 instead of a*b*2^-256.


i think it may be helpful to have a test for consistency with light_poseidon test vectors and used across platforms

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

}
new_state[i] = sum;
}
*state = new_state;

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.

It seems that the CIOS result may still be in [r, 2r) after carry propagation. apply_dense_matrix_simd subsequently passes this value to the scalar add implementation, which requires inputs to be strictly below r. please document here and in apply_dense_matrix_simd on the input criteria, if this is fine; or maybe we need to do a mod reduction first?

/// by 4 bits), followed by a multiplication with the precomputed
/// window value that matches the next 4 bits of the exponent.
#[inline(always)]
pub fn invert(a: &U256) -> 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.

short circuit for a \in {0, 1}.
also should the return type be Option<U256>?
not a strong preference as this only captures 0 case.


/// Computes `a^((p+1)/4) mod p` using a static 4-bit window addition chain.
#[inline(always)]
pub fn sqrt(a: &U256) -> 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.

seems that we don't need sqrt.
For sqrt, i think after the computation we need to check t^2 == a and return None if it fails -- there exist quadratic non-residues.

Also we can add some short circuit for 0 and 1

use super::*;

#[test]
fn test_fq_invert() {

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.

i think we don't use invert and sqrt in poseidon. but if we do want to include those functions, it may be better to have more tests for random inputs and for edge cases.

/// Like the Fq implementation, this strictly avoids dynamic branching or
/// loops in favor of a static 4-bit window addition chain.
#[inline(always)]
pub fn invert(a: &U256) -> 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.

same comments as on Fq::invert()

//! to prioritize cycle efficiency and lowest possible Compute Units.

pub mod backend;
pub mod poseidon;

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.

can we please have a test and check against light_poseidon (and perhaps arkworks?)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants