jf_poseidon2/
lib.rs

1// Copyright (c) 2024 Espresso Systems (espressosys.com)
2// This file is part of the Jellyfish library.
3
4// You should have received a copy of the MIT License
5// along with the Jellyfish library. If not, see <https://mit-license.org/>.
6
7//! The Poseidon2 permutation.
8//!
9//! # Available instances
10//! We have implemented Poseidon2 instances: (field, state_size)
11//! - (Bls12_381::Fr, 2), (Bls12_381::Fr, 3)
12//! - (Bn254::Fr, 3)
13//!
14//! This implementation was based upon the following resources:
15//! - https://github.com/HorizenLabs/poseidon2/blob/main/plain_implementations/src/poseidon2/poseidon2.rs
16//! - https://eprint.iacr.org/2023/323.pdf
17//! - https://github.com/Plonky3/Plonky3/blob/main/poseidon2/
18
19#![no_std]
20#![deny(missing_docs)]
21
22use ark_ff::PrimeField;
23use ark_std::{borrow::ToOwned, marker::PhantomData, string::String};
24use displaydoc::Display;
25
26pub mod constants;
27pub mod crhf;
28mod external;
29mod internal;
30pub mod sponge;
31
32/// Parameters required for a Poseidon2 permutation instance.
33///
34/// # Generic parameters
35/// - `F`: field choice
36/// - `T`: state size = rate + capacity, `T` is made generic for easy trait
37///   bound on `permute<F,T>(input: [F; N])` and type safety on `external_rc()`
38///   return type.
39pub trait Poseidon2Params<F: PrimeField, const T: usize>: Clone {
40    /// t: state size = rate + capacity
41    const T: usize = T;
42    /// d: sbox degree
43    const D: usize;
44    /// round_F: number of external rounds (incl. initial and terminal)
45    /// round_F = 2 * round_f
46    const EXT_ROUNDS: usize;
47    /// round_P: number of internal rounds
48    const INT_ROUNDS: usize;
49
50    /// round constants for all external rounds
51    fn external_rc() -> &'static [[F; T]];
52    /// round constants for internal rounds
53    fn internal_rc() -> &'static [F];
54    /// diffusion (diagonal) matrix minus one used in internal rounds
55    fn internal_mat_diag_m_1() -> &'static [F; T];
56
57    /// A default sanity check on the parameters and constant getters
58    ///
59    /// State size only supports: 2, 3, 4, 8, 12, 16, 20, 24 for now
60    /// S-box size only supports: 3, 5, 7, 11
61    ///
62    /// # Round constants
63    /// Rust doesn't permit generic param to be used in const operations, thus
64    /// leveraging type system to ensure sanity such as `const INT_RC: &'static
65    /// [F; Self::INT_ROUNDS]` is not allowed.
66    fn sanity_check() -> bool {
67        let ext_rc = Self::external_rc();
68        let int_rc = Self::internal_rc();
69
70        // TODO: consider adding more security-related check, incl. number of internal
71        // rounds in terms of field size to achieve 128-bit security. see
72        // `poseidon2_round_numbers_128` in plonky3, we skip for now as GCD is not
73        // implemented in arkworks, and params are trusted.
74        [2, 3, 4, 8, 12, 16, 20, 24].contains(&T)
75            && [3, 5, 7, 11].contains(&Self::D)
76            && ext_rc.len() == Self::EXT_ROUNDS
77            && int_rc.len() == Self::INT_ROUNDS
78            && Self::EXT_ROUNDS % 2 == 0
79    }
80}
81
82/// A Poseidon2 permutation family <https://eprint.iacr.org/2023/323>
83pub struct Poseidon2<F: PrimeField>(PhantomData<F>);
84
85impl<F: PrimeField> Poseidon2<F> {
86    /// Apply Poseidon2 permutation on `input` and return the permuted result
87    pub fn permute<P, const T: usize>(input: &[F; T]) -> [F; T]
88    where
89        P: Poseidon2Params<F, T>,
90    {
91        let mut input = input.to_owned();
92        Self::permute_mut::<P, T>(&mut input);
93        input
94    }
95
96    /// Apply Poseidon2 permutation on `input` in place
97    pub fn permute_mut<P, const T: usize>(input: &mut [F; T])
98    where
99        P: Poseidon2Params<F, T>,
100    {
101        assert!(P::sanity_check(), "Unexpected: Invalid Poseidon2 param!");
102        // M_e * x
103        external::matmul_external(input);
104
105        // Initial external rounds (first EXT_ROUNDS/2 rounds)
106        let ext_rc = P::external_rc();
107        for rc in ext_rc.iter().take(P::EXT_ROUNDS / 2) {
108            external::permute_state(input, rc, P::D);
109        }
110
111        // Internal rounds
112        let int_rc = P::internal_rc();
113        let mat_diag_minus_1 = P::internal_mat_diag_m_1();
114        for rc in int_rc.iter() {
115            internal::permute_state(input, *rc, P::D, mat_diag_minus_1);
116        }
117
118        // Terminal external rounds (second EXT_ROUNDS/2 rounds)
119        for rc in ext_rc.iter().take(P::EXT_ROUNDS).skip(P::EXT_ROUNDS / 2) {
120            external::permute_state(input, rc, P::D);
121        }
122    }
123}
124
125// @credit: `add_rc_and_sbox_generic()` in plonky3
126/// add RCs to the entire state
127#[inline(always)]
128pub(crate) fn add_rcs<F: PrimeField, const T: usize>(state: &mut [F; T], rc: &[F; T]) {
129    for i in 0..T {
130        state[i] += rc[i];
131    }
132}
133
134/// `s -> s^d`
135#[inline(always)]
136pub(crate) fn s_box<F: PrimeField>(val: &mut F, d: usize) {
137    if d == 5 {
138        // Perform unrolled computation for val^5, faster
139        let original = *val;
140        val.square_in_place();
141        val.square_in_place();
142        *val *= &original;
143    } else {
144        *val = val.pow([d as u64]);
145    }
146}
147
148/// Poseidon2 Error type
149#[derive(Debug, Clone, Display, Eq, PartialEq)]
150pub enum Poseidon2Error {
151    /// Bad parameter: {0}
152    ParamErr(String),
153}
154impl ark_std::error::Error for Poseidon2Error {}