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