soroban_poseidon/lib.rs
1#![no_std]
2
3use soroban_sdk::{
4 bytesn,
5 crypto::{bls12_381::Fr as BlsScalar, BnScalar},
6 symbol_short, Env, Symbol, Vec, U256,
7};
8
9pub(crate) mod poseidon;
10pub(crate) mod poseidon2;
11
12#[cfg(test)]
13mod tests;
14
15pub use poseidon::{PoseidonConfig, PoseidonSponge};
16pub use poseidon2::{Poseidon2Config, Poseidon2Sponge};
17
18pub trait Field {
19 fn symbol() -> Symbol;
20 /// Returns the field modulus. Inputs to Poseidon/Poseidon2 must be less than this value.
21 fn modulus(env: &Env) -> U256;
22}
23
24impl Field for BnScalar {
25 fn symbol() -> Symbol {
26 symbol_short!("BN254")
27 }
28
29 fn modulus(env: &Env) -> U256 {
30 // BN254 scalar field modulus
31 U256::from_be_bytes(
32 env,
33 &bytesn!(
34 env,
35 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001
36 )
37 .into(),
38 )
39 }
40}
41
42impl Field for BlsScalar {
43 fn symbol() -> Symbol {
44 symbol_short!("BLS12_381")
45 }
46
47 fn modulus(env: &Env) -> U256 {
48 // BLS12-381 scalar field modulus
49 U256::from_be_bytes(
50 env,
51 &bytesn!(
52 env,
53 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001
54 )
55 .into(),
56 )
57 }
58}
59
60/// Computes a Poseidon hash matching circom's
61/// [implementation](https://github.com/iden3/circomlib/blob/master/circuits/poseidon.circom).
62///
63/// # Type Parameters
64///
65/// - `T`: State size. Must equal `inputs.len() + 1` (rate = T-1, capacity = 1).
66/// - `F`: Field type. Use [`BnScalar`] for BN254 or [`BlsScalar`] for BLS12-381.
67///
68/// # Supported Configurations
69///
70/// - BN254: `T` ∈ {2, 3, 4, 5, 6} (i.e., 1–5 inputs)
71/// - BLS12-381: `T` ∈ {2, 3, 4, 5, 6} (i.e., 1–5 inputs)
72///
73/// # Panics
74///
75/// - if `inputs.len() != T - 1`
76/// - if any input value ≥ the field modulus (inputs must be valid field elements)
77///
78/// # Example
79///
80/// ```
81/// use soroban_sdk::{bytesn, crypto::BnScalar, vec, Env, U256};
82/// use soroban_poseidon::poseidon_hash;
83///
84/// let env = Env::default();
85///
86/// // Hash two field elements (t=3)
87/// let inputs = vec![
88/// &env,
89/// U256::from_u32(&env, 1),
90/// U256::from_u32(&env, 2),
91/// ];
92/// let hash = poseidon_hash::<3, BnScalar>(&env, &inputs);
93///
94/// // Matches circom's Poseidon([1, 2])
95/// let expected = U256::from_be_bytes(
96/// &env,
97/// &bytesn!(&env, 0x115cc0f5e7d690413df64c6b9662e9cf2a3617f2743245519e19607a4417189a).into(),
98/// );
99/// assert_eq!(hash, expected);
100/// ```
101///
102/// # Repeated Hashing
103///
104/// For repeated hashing, create a [`PoseidonSponge`] once and call
105/// `compute_hash()` multiple times. This reuses the pre-initialized parameters
106/// (MDS matrix and round constants), but each hash computation is independent,
107/// i.e. the sponge state is reset between calls:
108///
109/// ```
110/// # use soroban_sdk::{crypto::BnScalar, vec, Env, U256};
111/// # use soroban_poseidon::PoseidonSponge;
112/// # let env = Env::default();
113/// let mut sponge = PoseidonSponge::<3, BnScalar>::new(&env);
114///
115/// let inputs1 = vec![&env, U256::from_u32(&env, 1), U256::from_u32(&env, 2)];
116/// let inputs2 = vec![&env, U256::from_u32(&env, 3), U256::from_u32(&env, 4)];
117///
118/// let h1 = sponge.compute_hash(&inputs1); // fresh hash
119/// let h2 = sponge.compute_hash(&inputs2); // another fresh hash (state was reset)
120/// ```
121pub fn poseidon_hash<const T: u32, F: Field>(env: &Env, inputs: &Vec<U256>) -> U256
122where
123 PoseidonSponge<T, F>: PoseidonConfig<T, F>,
124{
125 let mut sponge = PoseidonSponge::<T, F>::new(env);
126 sponge.compute_hash(inputs)
127}
128
129/// Computes a Poseidon2 hash matching noir's
130/// [implementation](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/hash/poseidon2.nr).
131///
132/// # Type Parameters
133///
134/// - `T`: State size. Must be ≥ `inputs.len() + 1`. Common usage is `T=4`
135/// (rate=3) matching noir's default.
136/// - `F`: Field type. Use [`BnScalar`] for BN254 or [`BlsScalar`] for
137/// BLS12-381.
138///
139/// # Supported Configurations
140///
141/// - BN254: `T` ∈ {2, 3, 4} (i.e., rate = 1, 2, or 3)
142/// - BLS12-381: `T` ∈ {2, 3, 4} (i.e., rate = 1, 2, or 3)
143///
144/// # Panics
145///
146/// - if `inputs.len() > T - 1` (rate exceeded)
147/// - if any input value ≥ the field modulus (inputs must be valid field elements)
148///
149/// # Capacity Initialization
150///
151/// The capacity element is initialized to `inputs.len() << 64`, matching noir's
152/// Poseidon2 implementation.
153///
154/// # Example
155///
156/// ```
157/// use soroban_sdk::{crypto::BnScalar, vec, Env, U256};
158/// use soroban_poseidon::poseidon2_hash;
159///
160/// let env = Env::default();
161///
162/// // Hash three field elements (t=4, rate=3)
163/// let inputs = vec![
164/// &env,
165/// U256::from_u32(&env, 1),
166/// U256::from_u32(&env, 2),
167/// U256::from_u32(&env, 3),
168/// ];
169/// let hash = poseidon2_hash::<4, BnScalar>(&env, &inputs);
170/// ```
171///
172/// # Repeated Hashing
173///
174/// For repeated hashing, create a [`Poseidon2Sponge`] once and call
175/// `compute_hash()` multiple times. This reuses the pre-initialized parameters
176/// (diagonal matrix and round constants), but each hash computation is
177/// independent, i.e. the sponge state is reset between calls:
178///
179/// ```
180/// # use soroban_sdk::{crypto::BnScalar, vec, Env, U256};
181/// # use soroban_poseidon::Poseidon2Sponge;
182/// # let env = Env::default();
183/// let mut sponge = Poseidon2Sponge::<4, BnScalar>::new(&env);
184///
185/// let inputs1 = vec![&env, U256::from_u32(&env, 1), U256::from_u32(&env, 2)];
186/// let inputs2 = vec![&env, U256::from_u32(&env, 3), U256::from_u32(&env, 4)];
187///
188/// let h1 = sponge.compute_hash(&inputs1); // fresh hash
189/// let h2 = sponge.compute_hash(&inputs2); // another fresh hash (state was reset)
190/// ```
191pub fn poseidon2_hash<const T: u32, F: Field>(env: &Env, inputs: &Vec<U256>) -> U256
192where
193 Poseidon2Sponge<T, F>: Poseidon2Config<T, F>,
194{
195 let mut sponge = Poseidon2Sponge::<T, F>::new(env);
196 sponge.compute_hash(inputs)
197}