solana_poseidon/
lib.rs

1#![cfg_attr(
2    not(feature = "agave-unstable-api"),
3    deprecated(
4        since = "3.1.0",
5        note = "This crate has been marked for formal inclusion in the Agave Unstable API. From \
6                v4.0.0 onward, the `agave-unstable-api` crate feature must be specified to \
7                acknowledge use of an interface that may break without warning."
8    )
9)]
10//! Hashing with the [Poseidon] hash function.
11//!
12//! [Poseidon]: https://www.poseidon-hash.info/
13
14use thiserror::Error;
15
16#[doc(hidden)]
17pub mod legacy;
18
19/// Length of Poseidon hash result.
20pub const HASH_BYTES: usize = 32;
21
22// PoseidonSyscallError must be removed once the
23// simplify_alt_bn128_syscall_error_codes feature gets activated
24#[derive(Error, Debug)]
25pub enum PoseidonSyscallError {
26    #[error("Invalid parameters.")]
27    InvalidParameters,
28    #[error("Invalid endianness.")]
29    InvalidEndianness,
30    #[error("Invalid number of inputs. Maximum allowed is 12.")]
31    InvalidNumberOfInputs,
32    #[error("Input is an empty slice.")]
33    EmptyInput,
34    #[error(
35        "Invalid length of the input. The length matching the modulus of the prime field is 32."
36    )]
37    InvalidInputLength,
38    #[error("Failed to convert bytest into a prime field element.")]
39    BytesToPrimeFieldElement,
40    #[error("Input is larger than the modulus of the prime field.")]
41    InputLargerThanModulus,
42    #[error("Failed to convert a vector of bytes into an array.")]
43    VecToArray,
44    #[error("Failed to convert the number of inputs from u64 to u8.")]
45    U64Tou8,
46    #[error("Failed to convert bytes to BigInt")]
47    BytesToBigInt,
48    #[error("Invalid width. Choose a width between 2 and 16 for 1 to 15 inputs.")]
49    InvalidWidthCircom,
50    #[error("Unexpected error")]
51    Unexpected,
52}
53
54impl From<u64> for PoseidonSyscallError {
55    fn from(error: u64) -> Self {
56        match error {
57            1 => PoseidonSyscallError::InvalidParameters,
58            2 => PoseidonSyscallError::InvalidEndianness,
59            3 => PoseidonSyscallError::InvalidNumberOfInputs,
60            4 => PoseidonSyscallError::EmptyInput,
61            5 => PoseidonSyscallError::InvalidInputLength,
62            6 => PoseidonSyscallError::BytesToPrimeFieldElement,
63            7 => PoseidonSyscallError::InputLargerThanModulus,
64            8 => PoseidonSyscallError::VecToArray,
65            9 => PoseidonSyscallError::U64Tou8,
66            10 => PoseidonSyscallError::BytesToBigInt,
67            11 => PoseidonSyscallError::InvalidWidthCircom,
68            _ => PoseidonSyscallError::Unexpected,
69        }
70    }
71}
72
73impl From<PoseidonSyscallError> for u64 {
74    fn from(error: PoseidonSyscallError) -> Self {
75        match error {
76            PoseidonSyscallError::InvalidParameters => 1,
77            PoseidonSyscallError::InvalidEndianness => 2,
78            PoseidonSyscallError::InvalidNumberOfInputs => 3,
79            PoseidonSyscallError::EmptyInput => 4,
80            PoseidonSyscallError::InvalidInputLength => 5,
81            PoseidonSyscallError::BytesToPrimeFieldElement => 6,
82            PoseidonSyscallError::InputLargerThanModulus => 7,
83            PoseidonSyscallError::VecToArray => 8,
84            PoseidonSyscallError::U64Tou8 => 9,
85            PoseidonSyscallError::BytesToBigInt => 10,
86            PoseidonSyscallError::InvalidWidthCircom => 11,
87            PoseidonSyscallError::Unexpected => 12,
88        }
89    }
90}
91
92/// Configuration parameters for the Poseidon hash function.
93///
94/// The parameters of each configuration consist of:
95///
96/// - **Elliptic curve type**: This defines the prime field in which the
97///   cryptographic operations are conducted.
98/// - **S-Box**: The substitution box used in the cryptographic rounds.
99/// - **Full rounds**: The number of full transformation rounds in the hash
100///   function.
101/// - **Partial rounds**: The number of partial transformation rounds in the
102///   hash function.
103///
104/// Each configuration variant's name is composed of its elliptic curve type
105/// followed by its S-Box specification.
106#[repr(u64)]
107pub enum Parameters {
108    /// Configuration using the Barreto–Naehrig curve with an embedding degree
109    /// of 12, defined over a 254-bit prime field.
110    ///
111    /// Configuration Details:
112    /// - **S-Box**: \( x^5 \)
113    /// - **Width**: \( 2 \leq t \leq 13 \)
114    /// - **Inputs**: \( 1 \leq n \leq 12 \)
115    /// - **Full rounds**: 8
116    /// - **Partial rounds**: Depending on width: [56, 57, 56, 60, 60, 63, 64,
117    ///   63, 60, 66, 60, 65]
118    Bn254X5 = 0,
119}
120
121impl TryFrom<u64> for Parameters {
122    type Error = PoseidonSyscallError;
123
124    fn try_from(value: u64) -> Result<Self, Self::Error> {
125        match value {
126            x if x == Parameters::Bn254X5 as u64 => Ok(Parameters::Bn254X5),
127            _ => Err(PoseidonSyscallError::InvalidParameters),
128        }
129    }
130}
131
132impl From<Parameters> for u64 {
133    fn from(value: Parameters) -> Self {
134        match value {
135            Parameters::Bn254X5 => 0,
136        }
137    }
138}
139
140/// Endianness of inputs and result.
141#[repr(u64)]
142pub enum Endianness {
143    /// Big-endian inputs and result.
144    BigEndian = 0,
145    /// Little-endian inputs and result.
146    LittleEndian,
147}
148
149impl TryFrom<u64> for Endianness {
150    type Error = PoseidonSyscallError;
151
152    fn try_from(value: u64) -> Result<Self, Self::Error> {
153        match value {
154            x if x == Endianness::BigEndian as u64 => Ok(Endianness::BigEndian),
155            x if x == Endianness::LittleEndian as u64 => Ok(Endianness::LittleEndian),
156            _ => Err(PoseidonSyscallError::InvalidEndianness),
157        }
158    }
159}
160
161impl From<Endianness> for u64 {
162    fn from(value: Endianness) -> Self {
163        match value {
164            Endianness::BigEndian => 0,
165            Endianness::LittleEndian => 1,
166        }
167    }
168}
169
170/// Poseidon hash result.
171#[repr(transparent)]
172pub struct PoseidonHash(pub [u8; HASH_BYTES]);
173
174impl PoseidonHash {
175    pub fn new(hash_array: [u8; HASH_BYTES]) -> Self {
176        Self(hash_array)
177    }
178
179    pub fn to_bytes(&self) -> [u8; HASH_BYTES] {
180        self.0
181    }
182}
183
184#[cfg(target_os = "solana")]
185pub use solana_define_syscall::definitions::sol_poseidon;
186
187/// Return a Poseidon hash for the given data with the given elliptic curve and
188/// endianness.
189///
190/// # Examples
191///
192/// ```rust
193/// use solana_poseidon::{hashv, Endianness, Parameters};
194///
195/// # fn test() {
196/// let input1 = [1u8; 32];
197/// let input2 = [2u8; 32];
198///
199/// let hash = hashv(Parameters::Bn254X5, Endianness::BigEndian, &[&input1, &input2]).unwrap();
200/// assert_eq!(
201///     hash.to_bytes(),
202///     [
203///         13, 84, 225, 147, 143, 138, 140, 28, 125, 235, 94, 3, 85, 242, 99, 25, 32, 123,
204///         132, 254, 156, 162, 206, 27, 38, 231, 53, 200, 41, 130, 25, 144
205///     ]
206/// );
207///
208/// let hash = hashv(Parameters::Bn254X5, Endianness::LittleEndian, &[&input1, &input2]).unwrap();
209/// assert_eq!(
210///     hash.to_bytes(),
211///     [
212///         144, 25, 130, 41, 200, 53, 231, 38, 27, 206, 162, 156, 254, 132, 123, 32, 25, 99,
213///         242, 85, 3, 94, 235, 125, 28, 140, 138, 143, 147, 225, 84, 13
214///     ]
215/// );
216/// # }
217/// ```
218#[allow(unused_variables)]
219pub fn hashv(
220    // This parameter is not used currently, because we support only one curve
221    // (BN254). It should be used in case we add more curves in the future.
222    parameters: Parameters,
223    endianness: Endianness,
224    vals: &[&[u8]],
225) -> Result<PoseidonHash, PoseidonSyscallError> {
226    // Perform the calculation inline, calling this from within a program is
227    // not supported.
228    #[cfg(not(target_os = "solana"))]
229    {
230        use {
231            ark_bn254::Fr,
232            light_poseidon::{Poseidon, PoseidonBytesHasher, PoseidonError},
233        };
234
235        #[allow(non_local_definitions)]
236        impl From<PoseidonError> for PoseidonSyscallError {
237            fn from(error: PoseidonError) -> Self {
238                match error {
239                    PoseidonError::InvalidNumberOfInputs { .. } => {
240                        PoseidonSyscallError::InvalidNumberOfInputs
241                    }
242                    PoseidonError::EmptyInput => PoseidonSyscallError::EmptyInput,
243                    PoseidonError::InvalidInputLength { .. } => {
244                        PoseidonSyscallError::InvalidInputLength
245                    }
246                    PoseidonError::BytesToPrimeFieldElement { .. } => {
247                        PoseidonSyscallError::BytesToPrimeFieldElement
248                    }
249                    PoseidonError::InputLargerThanModulus => {
250                        PoseidonSyscallError::InputLargerThanModulus
251                    }
252                    PoseidonError::VecToArray => PoseidonSyscallError::VecToArray,
253                    PoseidonError::U64Tou8 => PoseidonSyscallError::U64Tou8,
254                    PoseidonError::BytesToBigInt => PoseidonSyscallError::BytesToBigInt,
255                    PoseidonError::InvalidWidthCircom { .. } => {
256                        PoseidonSyscallError::InvalidWidthCircom
257                    }
258                }
259            }
260        }
261
262        let mut hasher =
263            Poseidon::<Fr>::new_circom(vals.len()).map_err(PoseidonSyscallError::from)?;
264        let res = match endianness {
265            Endianness::BigEndian => hasher.hash_bytes_be(vals),
266            Endianness::LittleEndian => hasher.hash_bytes_le(vals),
267        }
268        .map_err(PoseidonSyscallError::from)?;
269
270        Ok(PoseidonHash(res))
271    }
272    // Call via a system call to perform the calculation.
273    #[cfg(target_os = "solana")]
274    {
275        let mut hash_result = [0; HASH_BYTES];
276        let result = unsafe {
277            sol_poseidon(
278                parameters.into(),
279                endianness.into(),
280                vals as *const _ as *const u8,
281                vals.len() as u64,
282                &mut hash_result as *mut _ as *mut u8,
283            )
284        };
285
286        match result {
287            0 => Ok(PoseidonHash::new(hash_result)),
288            _ => Err(PoseidonSyscallError::Unexpected),
289        }
290    }
291}
292
293/// Return a Poseidon hash for the given data with the given elliptic curve and
294/// endianness.
295///
296/// # Examples
297///
298/// ```rust
299/// use solana_poseidon::{hash, Endianness, Parameters};
300///
301/// # fn test() {
302/// let input = [1u8; 32];
303///
304/// let result = hash(Parameters::Bn254X5, Endianness::BigEndian, &input).unwrap();
305/// assert_eq!(
306///     result.to_bytes(),
307///     [
308///         5, 191, 172, 229, 129, 238, 97, 119, 204, 25, 198, 197, 99, 99, 166, 136, 130, 241,
309///         30, 132, 7, 172, 99, 157, 185, 145, 224, 210, 127, 27, 117, 230
310///     ],
311/// );
312///
313/// let hash = hash(Parameters::Bn254X5, Endianness::LittleEndian, &input).unwrap();
314/// assert_eq!(
315///     hash.to_bytes(),
316///     [
317///         230, 117, 27, 127, 210, 224, 145, 185, 157, 99, 172, 7, 132, 30, 241, 130, 136,
318///         166, 99, 99, 197, 198, 25, 204, 119, 97, 238, 129, 229, 172, 191, 5
319///     ],
320/// );
321/// # }
322/// ```
323pub fn hash(
324    parameters: Parameters,
325    endianness: Endianness,
326    val: &[u8],
327) -> Result<PoseidonHash, PoseidonSyscallError> {
328    hashv(parameters, endianness, &[val])
329}
330
331#[cfg(test)]
332mod tests {
333    use super::*;
334
335    #[test]
336    fn test_poseidon_input_ones_be() {
337        let input = [1u8; 32];
338
339        let hash = hash(Parameters::Bn254X5, Endianness::BigEndian, &input).unwrap();
340        assert_eq!(
341            hash.to_bytes(),
342            [
343                5, 191, 172, 229, 129, 238, 97, 119, 204, 25, 198, 197, 99, 99, 166, 136, 130, 241,
344                30, 132, 7, 172, 99, 157, 185, 145, 224, 210, 127, 27, 117, 230
345            ]
346        );
347    }
348
349    #[test]
350    fn test_poseidon_input_ones_le() {
351        let input = [1u8; 32];
352
353        let hash = hash(Parameters::Bn254X5, Endianness::LittleEndian, &input).unwrap();
354        assert_eq!(
355            hash.to_bytes(),
356            [
357                230, 117, 27, 127, 210, 224, 145, 185, 157, 99, 172, 7, 132, 30, 241, 130, 136,
358                166, 99, 99, 197, 198, 25, 204, 119, 97, 238, 129, 229, 172, 191, 5
359            ],
360        );
361    }
362
363    #[test]
364    fn test_poseidon_input_ones_twos_be() {
365        let input1 = [1u8; 32];
366        let input2 = [2u8; 32];
367
368        let hash = hashv(
369            Parameters::Bn254X5,
370            Endianness::BigEndian,
371            &[&input1, &input2],
372        )
373        .unwrap();
374        assert_eq!(
375            hash.to_bytes(),
376            [
377                13, 84, 225, 147, 143, 138, 140, 28, 125, 235, 94, 3, 85, 242, 99, 25, 32, 123,
378                132, 254, 156, 162, 206, 27, 38, 231, 53, 200, 41, 130, 25, 144
379            ]
380        );
381    }
382
383    #[test]
384    fn test_poseidon_input_ones_twos_le() {
385        let input1 = [1u8; 32];
386        let input2 = [2u8; 32];
387
388        let hash = hashv(
389            Parameters::Bn254X5,
390            Endianness::LittleEndian,
391            &[&input1, &input2],
392        )
393        .unwrap();
394        assert_eq!(
395            hash.to_bytes(),
396            [
397                144, 25, 130, 41, 200, 53, 231, 38, 27, 206, 162, 156, 254, 132, 123, 32, 25, 99,
398                242, 85, 3, 94, 235, 125, 28, 140, 138, 143, 147, 225, 84, 13
399            ]
400        );
401    }
402
403    #[test]
404    fn test_poseidon_input_one() {
405        let input = [
406            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
407            0, 0, 1,
408        ];
409
410        let expected_hashes = [
411            [
412                41, 23, 97, 0, 234, 169, 98, 189, 193, 254, 108, 101, 77, 106, 60, 19, 14, 150,
413                164, 209, 22, 139, 51, 132, 139, 137, 125, 197, 2, 130, 1, 51,
414            ],
415            [
416                0, 122, 243, 70, 226, 211, 4, 39, 158, 121, 224, 169, 243, 2, 63, 119, 18, 148,
417                167, 138, 203, 112, 231, 63, 144, 175, 226, 124, 173, 64, 30, 129,
418            ],
419            [
420                2, 192, 6, 110, 16, 167, 42, 189, 43, 51, 195, 178, 20, 203, 62, 129, 188, 177,
421                182, 227, 9, 97, 205, 35, 194, 2, 177, 134, 115, 191, 37, 67,
422            ],
423            [
424                8, 44, 156, 55, 10, 13, 36, 244, 65, 111, 188, 65, 74, 55, 104, 31, 120, 68, 45,
425                39, 216, 99, 133, 153, 28, 23, 214, 252, 12, 75, 125, 113,
426            ],
427            [
428                16, 56, 150, 5, 174, 104, 141, 79, 20, 219, 133, 49, 34, 196, 125, 102, 168, 3,
429                199, 43, 65, 88, 156, 177, 191, 134, 135, 65, 178, 6, 185, 187,
430            ],
431            [
432                42, 115, 246, 121, 50, 140, 62, 171, 114, 74, 163, 229, 189, 191, 80, 179, 144, 53,
433                215, 114, 159, 19, 91, 151, 9, 137, 15, 133, 197, 220, 94, 118,
434            ],
435            [
436                34, 118, 49, 10, 167, 243, 52, 58, 40, 66, 20, 19, 157, 157, 169, 89, 190, 42, 49,
437                178, 199, 8, 165, 248, 25, 84, 178, 101, 229, 58, 48, 184,
438            ],
439            [
440                23, 126, 20, 83, 196, 70, 225, 176, 125, 43, 66, 51, 66, 81, 71, 9, 92, 79, 202,
441                187, 35, 61, 35, 11, 109, 70, 162, 20, 217, 91, 40, 132,
442            ],
443            [
444                14, 143, 238, 47, 228, 157, 163, 15, 222, 235, 72, 196, 46, 187, 68, 204, 110, 231,
445                5, 95, 97, 251, 202, 94, 49, 59, 138, 95, 202, 131, 76, 71,
446            ],
447            [
448                46, 196, 198, 94, 99, 120, 171, 140, 115, 48, 133, 79, 74, 112, 119, 193, 255, 146,
449                96, 228, 72, 133, 196, 184, 29, 209, 49, 173, 58, 134, 205, 150,
450            ],
451            [
452                0, 113, 61, 65, 236, 166, 53, 241, 23, 212, 236, 188, 235, 95, 58, 102, 220, 65,
453                66, 235, 112, 181, 103, 101, 188, 53, 143, 27, 236, 64, 187, 155,
454            ],
455            [
456                20, 57, 11, 224, 186, 239, 36, 155, 212, 124, 101, 221, 172, 101, 194, 229, 46,
457                133, 19, 192, 129, 193, 205, 114, 201, 128, 6, 9, 142, 154, 143, 190,
458            ],
459        ];
460
461        for (i, expected_hash) in expected_hashes.into_iter().enumerate() {
462            let inputs = vec![&input[..]; i + 1];
463            let hash = hashv(Parameters::Bn254X5, Endianness::BigEndian, &inputs).unwrap();
464            assert_eq!(hash.to_bytes(), expected_hash);
465        }
466    }
467
468    #[test]
469    fn test_poseidon_input_without_padding() {
470        let input = [1];
471
472        for i in 1..12 {
473            let inputs = vec![&input[..]; i + 1];
474            let res = hashv(Parameters::Bn254X5, Endianness::BigEndian, &inputs);
475            assert!(res.is_err());
476            let res = hashv(Parameters::Bn254X5, Endianness::LittleEndian, &inputs);
477            assert!(res.is_err());
478        }
479    }
480}