miraland_program/
poseidon.rs

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