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}