Skip to content

Commit 984f63e

Browse files
authored
Add comprehensive Poseidon/Poseidon2 support for BN254 and BLS12-381 (#1663)
## Summary This PR provides comprehensive Poseidon and Poseidon2 hash function implementations for both BN254 and BLS12-381 curves, with extensive parameter coverage and test validation against external reference implementations. ## Changes ### Convenience Hash Methods - Added poseidon_hash<N>() - matches https://github.com/iden3/circomlib/blob/35e54ea21da3e8762557234298dbb553c175ea8d/circuits/poseidon.circom - Added poseidon2_hash<N>() - matches https://github.com/noir-lang/noir/blob/abfee1f54b20984172ba23482f4af160395cfba5/noir_stdlib/src/hash/poseidon2.nr ### Poseidon Parameters (poseidon_params.rs) - BN254: MDS matrix and round constants for t=2, t=3, t=4 (validated against circomlib) - BLS12-381: MDS matrix and round constants for t=2, t=3, t=4 (validated against reference Sage script and [poseidon-bls12381-circom](https://github.com/jmagan/poseidon-bls12381-circom)) ### Poseidon2 Parameters (poseidon2_params.rs) - BN254: Diagonal matrix (MAT_DIAG) and round constants for t=2, t=3, t=4 - BLS12-381: Diagonal matrix and round constants for t=2, t=3, t=4 - Parameters generated using reference Sage script and validated against reference test vectors (generated by the script) ### Sponge Implementations - PoseidonSponge and Poseidon2Sponge with configurable parameters via PoseidonConfig and Poseidon2Config - Proper capacity/rate handling matching reference implementations ### Test Coverage - Poseidon (BN254) - hash_n validated against circomlib - Poseidon (BLS12-381) - hash_n validated against poseidon-bls12381-circom - Poseidon2 (BN254) - hash validated against barretenberg, permutation validated against reference test vectors - Poseidon2 (BLS12-381) - permutation validated against reference test vectors
1 parent b3de265 commit 984f63e

File tree

6 files changed

+16031
-868
lines changed

6 files changed

+16031
-868
lines changed

soroban-sdk/src/crypto.rs

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ pub(crate) mod poseidon_params;
1414
pub mod poseidon_sponge;
1515
pub(crate) mod utils;
1616
pub use bn254::Fr as BnScalar;
17-
pub use poseidon2_sponge::Poseidon2Sponge;
18-
pub use poseidon_sponge::PoseidonSponge;
17+
pub use poseidon2_sponge::{Poseidon2Config, Poseidon2Sponge};
18+
pub use poseidon_sponge::{PoseidonConfig, PoseidonSponge};
1919

2020
/// A `BytesN<N>` generated by a cryptographic hash function.
2121
///
@@ -199,26 +199,45 @@ impl Crypto {
199199
bn254::Bn254::new(self.env())
200200
}
201201

202-
/// Performs a Poseidon hash using a sponge construction
203-
pub fn poseidon_hash(&self, inputs: &Vec<U256>, field: Symbol) -> U256 {
204-
// The initial value for the capacity element initialized with 0 for standard Poseidon
205-
let iv = U256::from_u32(&self.env, 0);
206-
let mut sponge = PoseidonSponge::new(&self.env, iv, field);
207-
for input in inputs.iter() {
208-
sponge.absorb(input);
209-
}
210-
sponge.squeeze()
202+
/// Computes a Poseidon hash matching circom's
203+
/// [implementation](https://github.com/iden3/circomlib/blob/35e54ea21da3e8762557234298dbb553c175ea8d/circuits/poseidon.circom)
204+
/// for input lengths up to 5 (`t ≤ 6`).
205+
///
206+
/// Internally it picks the state size `t` to match the input length, i.e.
207+
/// `t = N + 1` (rate = N, capacity = 1). For example, hashing 2 elements
208+
/// uses t=3.
209+
///
210+
/// Note: use [`poseidon_sponge::hash`] with a pre-constructed
211+
/// [`PoseidonConfig`] directly if:
212+
/// - You want to repeatedly hash with the same input size. Pre-constructing
213+
/// the config saves the cost of re-initialization.
214+
/// - You want to hash larger input sizes. The sponge will repeatedly
215+
/// permute and absorb until the entire input is consumed. This is a valid
216+
/// (and secure) sponge operation, even though it may not match circom's
217+
/// output, which always picks a larger state size (N+1) to hash inputs in
218+
/// one shot (up to t=17). If you need parameter support for larger `t`,
219+
/// please file an issue.
220+
pub fn poseidon_hash(&self, field_type: Symbol, inputs: &Vec<U256>) -> U256 {
221+
let config = PoseidonConfig::new(&self.env, field_type, inputs.len() as u32);
222+
poseidon_sponge::hash(&self.env, inputs, config)
211223
}
212224

213-
/// Performs a poseidon2 hash with a sponge construction equivalent to the one in the Barretenberg proving system
214-
pub fn poseidon2_hash(&self, inputs: &Vec<U256>, field: Symbol) -> U256 {
215-
// The initial value for the capacity element initialized with `input.len() * 2^24` for Poseidon2
216-
let iv = U256::from_u128(&self.env, (inputs.len() as u128) << 64);
217-
let mut sponge = Poseidon2Sponge::new(&self.env, iv, field);
218-
for input in inputs.iter() {
219-
sponge.absorb(input);
220-
}
221-
sponge.squeeze()
225+
/// Computes a Poseidon2 hash matching noir's
226+
/// [implementation](https://github.com/noir-lang/noir/blob/abfee1f54b20984172ba23482f4af160395cfba5/noir_stdlib/src/hash/poseidon2.nr).
227+
///
228+
/// Internally it always initializes the state with `t = 4`, regardless of
229+
/// input length. It alternates between absorbing and permuting until all
230+
/// input elements are consumed.
231+
///
232+
/// Note: use [`poseidon2_sponge::hash`] with a pre-constructed
233+
/// [`Poseidon2Config`] directly if:
234+
/// - You need to hash multiple times. Pre-constructing the config saves the
235+
/// cost of re-initialization.
236+
/// - You want to use a different state size (`t ≤ 4`).
237+
pub fn poseidon2_hash(&self, field_type: Symbol, inputs: &Vec<U256>) -> U256 {
238+
const INTERNAL_RATE: u32 = 3;
239+
let config = Poseidon2Config::new(&self.env, field_type, INTERNAL_RATE);
240+
poseidon2_sponge::hash(&self.env, inputs, config)
222241
}
223242
}
224243

0 commit comments

Comments
 (0)