light_poseidon/lib.rs
1//! **light-poseidon** is a [Poseidon](https://eprint.iacr.org/2019/458) hash
2//! implementation in Rust created for [Light Protocol](https://www.lightprotocol.com/).
3//!
4//! # Parameters
5//!
6//! The library provides pre-generated parameters over the BN254 curve, however
7//! it can work with any parameters provided as long as developers take care
8//! of generating the round constants.
9//!
10//! Parameters provided by the library are:
11//!
12//! * *x^5* S-boxes
13//! * width - *2 ≤ t ≤ 13*
14//! * inputs - *1 ≤ n ≤ 12*
15//! * 8 full rounds and partial rounds depending on *t*: *[56, 57, 56, 60, 60, 63, 64, 63, 60, 66, 60, 65]*
16//!
17//! The parameters can be generated with:
18//!
19//! ```bash
20//! cargo xtask generate-poseidon-parameters
21//! ````
22//!
23//! # Output type
24//!
25//! [`Poseidon`](crate::Poseidon) type implements two traits which serve the purpose
26//! of returning the calculated hash in different representations:
27//!
28//! * [`PoseidonBytesHasher`](crate::PoseidonBytesHasher) with the
29//! `hash_bytes_be` and `hash_bytes_le` methods which returns a byte array.
30//! * [`PoseidonHasher`](crate::PoseidonHasher) with the `hash` method which returns
31//! [`ark_ff::PrimeField`](ark_ff::PrimeField). Might be useful if you want
32//! to immediately process the result with an another library which works with
33//! [`ark_ff::PrimeField`](ark_ff::PrimeField) types.
34//!
35//! # Examples
36//!
37//! Example with two simple big-endian byte inputs (converted to field elements)
38//! and BN254-based parameters provided by the library, with
39//! [`PoseidonBytesHasher`](crate::PoseidonHasher) trait and a byte array
40//! result:
41//!
42//! ```rust
43//! use light_poseidon::{Poseidon, PoseidonBytesHasher, parameters::bn254_x5};
44//! use ark_bn254::Fr;
45//! use ark_ff::{BigInteger, PrimeField};
46//!
47//! let mut poseidon = Poseidon::<Fr>::new_circom(2).unwrap();
48//!
49//! let hash = poseidon.hash_bytes_be(&[&[1u8; 32], &[2u8; 32]]).unwrap();
50//!
51//! println!("{:?}", hash);
52//! // Should print:
53//! // [
54//! // 13, 84, 225, 147, 143, 138, 140, 28, 125, 235, 94, 3, 85, 242, 99, 25, 32, 123, 132,
55//! // 254, 156, 162, 206, 27, 38, 231, 53, 200, 41, 130, 25, 144
56//! // ]
57//! ```
58//!
59//! With [`PoseidonHasher`](crate::PoseidonHasher) trait and
60//! [`ark_ff::PrimeField`](ark_ff::PrimeField) result:
61//!
62//! ```rust
63//! use light_poseidon::{Poseidon, PoseidonHasher, parameters::bn254_x5};
64//! use ark_bn254::Fr;
65//! use ark_ff::{BigInteger, PrimeField};
66//!
67//! let mut poseidon = Poseidon::<Fr>::new_circom(2).unwrap();
68//!
69//! let input1 = Fr::from_be_bytes_mod_order(&[1u8; 32]);
70//! let input2 = Fr::from_be_bytes_mod_order(&[2u8; 32]);
71//!
72//! let hash = poseidon.hash(&[input1, input2]).unwrap();
73//!
74//! // Do something with `hash`.
75//! ```
76//!
77//! # Implementation
78//!
79//! The implementation is compatible with the
80//! [original SageMath implementation](https://extgit.iaik.tugraz.at/krypto/hadeshash/-/tree/master/),
81//! but it was also inspired by the following ones:
82//!
83//! * [circomlibjs](https://github.com/iden3/circomlibjs)
84//! * [zero-knowledge-gadgets](https://github.com/webb-tools/zero-knowledge-gadgets)
85//!
86//! # Performance
87//!
88//! This repository contains a benchmark measuring the performance of this
89//! Poseidon implementation for given 1 - 12 random 32 bytes inputs.
90//!
91//! To run them, simply use:
92//!
93//! ```bash
94//! cargo bench
95//! ```
96//!
97//! This is the result from a host with the following hardware:
98//!
99//! * AMD Ryzen™ 9 7945HX with Radeon™ Graphics × 32
100//!
101//! ```norust
102//! poseidon_bn254_x5_1 time: [12.710 µs 12.735 µs 12.754 µs]
103//!
104//! poseidon_bn254_x5_2 time: [18.948 µs 18.963 µs 18.990 µs]
105//!
106//! poseidon_bn254_x5_3 time: [26.607 µs 26.611 µs 26.615 µs]
107//!
108//! poseidon_bn254_x5_4 time: [38.507 µs 38.513 µs 38.519 µs]
109//!
110//! poseidon_bn254_x5_5 time: [51.024 µs 51.031 µs 51.039 µs]
111//!
112//! poseidon_bn254_x5_6 time: [68.368 µs 68.375 µs 68.385 µs]
113//!
114//! poseidon_bn254_x5_7 time: [86.819 µs 86.886 µs 86.968 µs]
115//!
116//! poseidon_bn254_x5_8 time: [105.38 µs 105.49 µs 105.61 µs]
117//!
118//! poseidon_bn254_x5_9 time: [121.99 µs 122.00 µs 122.01 µs]
119//!
120//! poseidon_bn254_x5_10 time: [157.00 µs 157.02 µs 157.05 µs]
121//!
122//! poseidon_bn254_x5_11 time: [170.01 µs 170.04 µs 170.07 µs]
123//!
124//! poseidon_bn254_x5_12 time: [210.78 µs 210.81 µs 210.84 µs]
125//! ```
126//!
127//! # Security
128//!
129//! This library has been audited by [Veridise](https://veridise.com/). You can
130//! read the audit report [here](https://github.com/Lightprotocol/light-poseidon/blob/main/assets/audit.pdf).
131use ark_bn254::Fr;
132use ark_ff::{BigInteger, PrimeField, Zero};
133use thiserror::Error;
134
135pub mod parameters;
136
137pub const HASH_LEN: usize = 32;
138pub const MAX_X5_LEN: usize = 13;
139
140#[derive(Error, Debug, PartialEq)]
141pub enum PoseidonError {
142 #[error("Invalid number of inputs: {inputs}. Maximum allowed is {max_limit} ({width} - 1).")]
143 InvalidNumberOfInputs {
144 inputs: usize,
145 max_limit: usize,
146 width: usize,
147 },
148 #[error("Input is an empty slice.")]
149 EmptyInput,
150 #[error("Invalid length of the input: {len}. The length matching the modulus of the prime field is: {modulus_bytes_len}.")]
151 InvalidInputLength {
152 len: usize,
153 modulus_bytes_len: usize,
154 },
155 #[error("Failed to convert bytes {bytes:?} into a prime field element")]
156 BytesToPrimeFieldElement { bytes: Vec<u8> },
157 #[error("Input is larger than the modulus of the prime field.")]
158 InputLargerThanModulus,
159 #[error("Failed to convert a vector of bytes into an array.")]
160 VecToArray,
161 #[error("Failed to convert the number of inputs from u64 to u8.")]
162 U64Tou8,
163 #[error("Failed to convert bytes to BigInt")]
164 BytesToBigInt,
165 #[error("Invalid width: {width}. Choose a width between 2 and 16 for 1 to 15 inputs.")]
166 InvalidWidthCircom { width: usize, max_limit: usize },
167}
168
169/// Parameters for the Poseidon hash algorithm.
170pub struct PoseidonParameters<F: PrimeField> {
171 /// Round constants.
172 pub ark: Vec<F>,
173 /// MDS matrix.
174 pub mds: Vec<Vec<F>>,
175 /// Number of full rounds (where S-box is applied to all elements of the
176 /// state).
177 pub full_rounds: usize,
178 /// Number of partial rounds (where S-box is applied only to the first
179 /// element of the state).
180 pub partial_rounds: usize,
181 /// Number of prime fields in the state.
182 pub width: usize,
183 /// Exponential used in S-box to power elements of the state.
184 pub alpha: u64,
185}
186
187impl<F: PrimeField> PoseidonParameters<F> {
188 pub fn new(
189 ark: Vec<F>,
190 mds: Vec<Vec<F>>,
191 full_rounds: usize,
192 partial_rounds: usize,
193 width: usize,
194 alpha: u64,
195 ) -> Self {
196 Self {
197 ark,
198 mds,
199 full_rounds,
200 partial_rounds,
201 width,
202 alpha,
203 }
204 }
205}
206
207pub trait PoseidonHasher<F: PrimeField> {
208 /// Calculates a Poseidon hash for the given input of prime fields and
209 /// returns the result as a prime field.
210 ///
211 /// # Examples
212 ///
213 /// Example with two simple big-endian byte inputs (converted to prime
214 /// fields) and BN254-based parameters provided by the library.
215 ///
216 /// ```rust
217 /// use light_poseidon::{Poseidon, PoseidonHasher, parameters::bn254_x5};
218 /// use ark_bn254::Fr;
219 /// use ark_ff::{BigInteger, PrimeField};
220 ///
221 /// let mut poseidon = Poseidon::<Fr>::new_circom(2).unwrap();
222 ///
223 /// let input1 = Fr::from_be_bytes_mod_order(&[1u8; 32]);
224 /// let input2 = Fr::from_be_bytes_mod_order(&[2u8; 32]);
225 ///
226 /// let hash = poseidon.hash(&[input1, input2]).unwrap();
227 ///
228 /// // Do something with `hash`.
229 fn hash(&mut self, inputs: &[F]) -> Result<F, PoseidonError>;
230}
231
232pub trait PoseidonBytesHasher {
233 /// Calculates a Poseidon hash for the given input of big-endian byte slices
234 /// and returns the result as a byte array.
235 ///
236 /// # Examples
237 ///
238 /// Example with two simple big-endian byte inputs and BN254-based
239 /// parameters provided by the library.
240 ///
241 /// ```rust
242 /// use light_poseidon::{Poseidon, PoseidonBytesHasher, parameters::bn254_x5};
243 /// use ark_bn254::Fr;
244 /// use ark_ff::{BigInteger, PrimeField};
245 ///
246 /// let mut poseidon = Poseidon::<Fr>::new_circom(2).unwrap();
247 ///
248 /// let hash = poseidon.hash_bytes_be(&[&[1u8; 32], &[2u8; 32]]).unwrap();
249 ///
250 /// println!("{:?}", hash);
251 /// // Should print:
252 /// // [
253 /// // 13, 84, 225, 147, 143, 138, 140, 28, 125, 235, 94, 3, 85, 242, 99, 25, 32, 123, 132,
254 /// // 254, 156, 162, 206, 27, 38, 231, 53, 200, 41, 130, 25, 144
255 /// // ]
256 /// ```
257 ///
258 /// # Safety
259 ///
260 /// Unlike the
261 /// [`PrimeField::from_be_bytes_mod_order`](ark_ff::PrimeField::from_be_bytes_mod_order)
262 /// and [`Field::from_random_bytes`](ark_ff::Field::from_random_bytes)
263 /// methods, this function ensures that the input byte slice's length exactly matches
264 /// the modulus size of the prime field. If the size doesn't match, an error is returned.
265 ///
266 /// This strict check is designed to prevent unexpected behaviors and collisions
267 /// that might occur when using `from_be_bytes_mod_order` or `from_random_bytes`,
268 /// which simply take a subslice of the input if it's too large, potentially
269 /// leading to collisions.
270 fn hash_bytes_be(&mut self, inputs: &[&[u8]]) -> Result<[u8; HASH_LEN], PoseidonError>;
271 /// Calculates a Poseidon hash for the given input of little-endian byte
272 /// slices and returns the result as a byte array.
273 ///
274 /// # Examples
275 ///
276 /// Example with two simple little-endian byte inputs and BN254-based
277 /// parameters provided by the library.
278 ///
279 /// ```rust
280 /// use light_poseidon::{Poseidon, PoseidonBytesHasher, parameters::bn254_x5};
281 /// use ark_bn254::Fr;
282 /// use ark_ff::{BigInteger, PrimeField};
283 ///
284 /// let mut poseidon = Poseidon::<Fr>::new_circom(2).unwrap();
285 ///
286 /// let hash = poseidon.hash_bytes_le(&[&[1u8; 32], &[2u8; 32]]).unwrap();
287 ///
288 /// println!("{:?}", hash);
289 /// // Should print:
290 /// // [
291 /// // 144, 25, 130, 41, 200, 53, 231, 38, 27, 206, 162, 156, 254, 132, 123, 32, 25, 99, 242,
292 /// // 85, 3, 94, 235, 125, 28, 140, 138, 143, 147, 225, 84, 13
293 /// // ]
294 /// ```
295 ///
296 /// # Safety
297 ///
298 /// Unlike the
299 /// [`PrimeField::from_le_bytes_mod_order`](ark_ff::PrimeField::from_le_bytes_mod_order)
300 /// and [`Field::from_random_bytes`](ark_ff::Field::from_random_bytes)
301 /// methods, this function ensures that the input byte slice's length exactly matches
302 /// the modulus size of the prime field. If the size doesn't match, an error is returned.
303 ///
304 /// This strict check is designed to prevent unexpected behaviors and collisions
305 /// that might occur when using `from_be_bytes_mod_order` or `from_random_bytes`,
306 /// which simply take a subslice of the input if it's too large, potentially
307 /// leading to collisions.
308 fn hash_bytes_le(&mut self, inputs: &[&[u8]]) -> Result<[u8; HASH_LEN], PoseidonError>;
309}
310
311/// A stateful sponge performing Poseidon hash computation.
312pub struct Poseidon<F: PrimeField> {
313 params: PoseidonParameters<F>,
314 domain_tag: F,
315 state: Vec<F>,
316}
317
318impl<F: PrimeField> Poseidon<F> {
319 /// Returns a new Poseidon hasher based on the given parameters.
320 ///
321 /// Optionally, a domain tag can be provided. If it is not provided, it
322 /// will be set to zero.
323 pub fn new(params: PoseidonParameters<F>) -> Self {
324 Self::with_domain_tag(params, F::zero())
325 }
326
327 fn with_domain_tag(params: PoseidonParameters<F>, domain_tag: F) -> Self {
328 let width = params.width;
329 Self {
330 domain_tag,
331 params,
332 state: Vec::with_capacity(width),
333 }
334 }
335
336 #[inline(always)]
337 fn apply_ark(&mut self, round: usize) {
338 self.state.iter_mut().enumerate().for_each(|(i, a)| {
339 let c = self.params.ark[round * self.params.width + i];
340 *a += c;
341 });
342 }
343
344 #[inline(always)]
345 fn apply_sbox_full(&mut self) {
346 self.state.iter_mut().for_each(|a| {
347 *a = a.pow([self.params.alpha]);
348 });
349 }
350
351 #[inline(always)]
352 fn apply_sbox_partial(&mut self) {
353 self.state[0] = self.state[0].pow([self.params.alpha]);
354 }
355
356 #[inline(always)]
357 fn apply_mds(&mut self) {
358 self.state = self
359 .state
360 .iter()
361 .enumerate()
362 .map(|(i, _)| {
363 self.state
364 .iter()
365 .enumerate()
366 .fold(F::zero(), |acc, (j, a)| acc + *a * self.params.mds[i][j])
367 })
368 .collect();
369 }
370}
371
372impl<F: PrimeField> PoseidonHasher<F> for Poseidon<F> {
373 fn hash(&mut self, inputs: &[F]) -> Result<F, PoseidonError> {
374 if inputs.len() != self.params.width - 1 {
375 return Err(PoseidonError::InvalidNumberOfInputs {
376 inputs: inputs.len(),
377 max_limit: self.params.width - 1,
378 width: self.params.width,
379 });
380 }
381
382 self.state.push(self.domain_tag);
383
384 for input in inputs {
385 self.state.push(*input);
386 }
387
388 let all_rounds = self.params.full_rounds + self.params.partial_rounds;
389 let half_rounds = self.params.full_rounds / 2;
390
391 // full rounds + partial rounds
392 for round in 0..half_rounds {
393 self.apply_ark(round);
394 self.apply_sbox_full();
395 self.apply_mds();
396 }
397
398 for round in half_rounds..half_rounds + self.params.partial_rounds {
399 self.apply_ark(round);
400 self.apply_sbox_partial();
401 self.apply_mds();
402 }
403
404 for round in half_rounds + self.params.partial_rounds..all_rounds {
405 self.apply_ark(round);
406 self.apply_sbox_full();
407 self.apply_mds();
408 }
409
410 let result = self.state[0];
411 self.state.clear();
412 Ok(result)
413 }
414}
415
416macro_rules! impl_hash_bytes {
417 ($fn_name:ident, $bytes_to_prime_field_element_fn:ident, $to_bytes_fn:ident) => {
418 fn $fn_name(&mut self, inputs: &[&[u8]]) -> Result<[u8; HASH_LEN], PoseidonError> {
419 let inputs: Result<Vec<_>, _> = inputs
420 .iter()
421 .map(|input| validate_bytes_length::<F>(input))
422 .collect();
423 let inputs = inputs?;
424 let inputs: Result<Vec<_>, _> = inputs
425 .iter()
426 .map(|input| $bytes_to_prime_field_element_fn(input))
427 .collect();
428 let inputs = inputs?;
429 let hash = self.hash(&inputs)?;
430
431 hash.into_bigint()
432 .$to_bytes_fn()
433 .try_into()
434 .map_err(|_| PoseidonError::VecToArray)
435 }
436 };
437}
438
439impl<F: PrimeField> PoseidonBytesHasher for Poseidon<F> {
440 impl_hash_bytes!(hash_bytes_le, bytes_to_prime_field_element_le, to_bytes_le);
441 impl_hash_bytes!(hash_bytes_be, bytes_to_prime_field_element_be, to_bytes_be);
442}
443
444/// Checks whether a slice of bytes is not empty or its length does not exceed
445/// the modulus size od the prime field. If it does, an error is returned.
446///
447/// # Safety
448///
449/// [`PrimeField::from_be_bytes_mod_order`](ark_ff::PrimeField::from_be_bytes_mod_order)
450/// just takes a subslice of the input if it's too large, potentially leading
451/// to collisions. The purpose of this function is to prevent them by returning
452/// and error. It should be always used before converting byte slices to
453/// prime field elements.
454pub fn validate_bytes_length<F>(input: &[u8]) -> Result<&[u8], PoseidonError>
455where
456 F: PrimeField,
457{
458 let modulus_bytes_len = ((F::MODULUS_BIT_SIZE + 7) / 8) as usize;
459 if input.is_empty() {
460 return Err(PoseidonError::EmptyInput);
461 }
462 if input.len() > modulus_bytes_len {
463 return Err(PoseidonError::InvalidInputLength {
464 len: input.len(),
465 modulus_bytes_len,
466 });
467 }
468 Ok(input)
469}
470
471macro_rules! impl_bytes_to_prime_field_element {
472 ($name:ident, $from_bytes_method:ident, $endianess:expr) => {
473 #[doc = "Converts a slice of "]
474 #[doc = $endianess]
475 #[doc = "-endian bytes into a prime field element, \
476 represented by the [`ark_ff::PrimeField`](ark_ff::PrimeField) trait."]
477 pub fn $name<F>(input: &[u8]) -> Result<F, PoseidonError>
478 where
479 F: PrimeField,
480 {
481 let element = num_bigint::BigUint::$from_bytes_method(input);
482 let element = F::BigInt::try_from(element).map_err(|_| PoseidonError::BytesToBigInt)?;
483
484 // In theory, `F::from_bigint` should also perform a check whether input is
485 // larger than modulus (and return `None` if it is), but it's not reliable...
486 // To be sure, we check it ourselves.
487 if element >= F::MODULUS {
488 return Err(PoseidonError::InputLargerThanModulus);
489 }
490 let element = F::from_bigint(element).ok_or(PoseidonError::InputLargerThanModulus)?;
491
492 Ok(element)
493 }
494 };
495}
496
497impl_bytes_to_prime_field_element!(bytes_to_prime_field_element_le, from_bytes_le, "little");
498impl_bytes_to_prime_field_element!(bytes_to_prime_field_element_be, from_bytes_be, "big");
499
500impl<F: PrimeField> Poseidon<F> {
501 pub fn new_circom(nr_inputs: usize) -> Result<Poseidon<Fr>, PoseidonError> {
502 Self::with_domain_tag_circom(nr_inputs, Fr::zero())
503 }
504
505 pub fn with_domain_tag_circom(
506 nr_inputs: usize,
507 domain_tag: Fr,
508 ) -> Result<Poseidon<Fr>, PoseidonError> {
509 let width = nr_inputs + 1;
510 if width > MAX_X5_LEN {
511 return Err(PoseidonError::InvalidWidthCircom {
512 width,
513 max_limit: MAX_X5_LEN,
514 });
515 }
516
517 let params = crate::parameters::bn254_x5::get_poseidon_parameters::<Fr>(
518 (width).try_into().map_err(|_| PoseidonError::U64Tou8)?,
519 )?;
520 Ok(Poseidon::<Fr>::with_domain_tag(params, domain_tag))
521 }
522}