Skip to main content

p3_poseidon1/
lib.rs

1//! The Poseidon1 permutation.
2//!
3//! # Overview
4//!
5//! Poseidon1 is a cryptographic hash function designed for efficient verification inside
6//! zero-knowledge proof systems (SNARKs, STARKs, etc). It operates natively over
7//! prime fields, avoiding the overhead of bit-decomposition required by traditional hashes
8//! like SHA-256 or Keccak.
9//!
10//! This crate provides [`Poseidon1`], an optimized implementation that uses the **sparse matrix
11//! decomposition** from the original paper (Appendix B) to factor the dense MDS matrix into
12//! sparse components. This reduces the cost of each partial round from O(t^2) to O(t), where
13//! t = `WIDTH` is the state width.
14//!
15//! # Round Structure
16//!
17//! Poseidon1 alternates between two types of rounds:
18//!
19//! ```text
20//!   ┌──────────────────┐   ┌───────────────────┐   ┌──────────────────┐
21//!   │ RF/2 full rounds │ → │ RP partial rounds │ → │ RF/2 full rounds │
22//!   │ (S-box on all)   │   │ (S-box on s[0])   │   │ (S-box on all)   │
23//!   └──────────────────┘   └───────────────────┘   └──────────────────┘
24//! ```
25//!
26//! - **Full rounds** (RF total, split equally): apply the S-box x^D to every state element,
27//!   then multiply by the dense MDS matrix. These provide resistance against statistical
28//!   attacks (differential, linear, rebound).
29//!
30//! - **Partial rounds** (RP total): apply the S-box only to `state[0]`, then multiply by a
31//!   **sparse** matrix derived from the MDS factorization. These efficiently increase the
32//!   algebraic degree to resist interpolation and Gröbner basis attacks.
33//!
34//! Each round follows the sequence: **AddRoundConstants → S-box → MixLayer**.
35//!
36//! # References
37//!
38//! - Grassi et al., "Poseidon1: A New Hash Function for Zero-Knowledge Proof Systems",
39//!   USENIX Security 2021. <https://eprint.iacr.org/2019/458>
40//! - HorizenLabs reference implementation: <https://github.com/HorizenLabs/poseidon2>
41
42#![no_std]
43
44extern crate alloc;
45
46pub mod external;
47pub mod generic;
48pub mod internal;
49pub mod utils;
50
51use alloc::vec::Vec;
52use core::marker::PhantomData;
53
54pub use external::*;
55pub use generic::*;
56pub use internal::*;
57use p3_field::{Algebra, InjectiveMonomial, PrimeField};
58use p3_symmetric::{CryptographicPermutation, Permutation};
59use rand::distr::{Distribution, StandardUniform};
60use rand::{Rng, RngExt};
61
62/// Raw Poseidon1 parameters before the sparse matrix optimization.
63///
64/// These are the "textbook" parameters as generated by the Grain LFSR or any
65/// other parameter-generation script. They are transformed into the optimized
66/// sparse form used at runtime.
67///
68/// # Round Constant Layout
69///
70/// Constants are stored in a flat array with three consecutive sections:
71///
72/// ```text
73///   ┌──────────────┬──────────────────┬──────────────────┐
74///   │ initial full  │  partial rounds  │  terminal full   │
75///   │ (RF/2 items)  │  (RP items)      │  (RF/2 items)    │
76///   └──────────────┴──────────────────┴──────────────────┘
77/// ```
78///
79/// Each entry is a WIDTH-sized vector (one constant per state element per round).
80#[derive(Clone, Debug)]
81pub struct Poseidon1Constants<F, const WIDTH: usize> {
82    /// Total number of full rounds (split equally between initial and terminal).
83    pub rounds_f: usize,
84
85    /// Number of partial rounds.
86    pub rounds_p: usize,
87
88    /// First column of the circulant MDS matrix, stored as signed integers.
89    ///
90    /// This matches the representation used by the MDS crate, so concrete
91    /// fields can pass their verified constants directly without duplication.
92    /// During initialization, the dense form is expanded once for the sparse
93    /// decomposition, then discarded.
94    pub mds_circ_col: [i64; WIDTH],
95
96    /// Round constants, one WIDTH-sized vector per round.
97    ///
98    /// Total length = rounds_f + rounds_p.
99    pub round_constants: Vec<[F; WIDTH]>,
100}
101
102impl<F: PrimeField, const WIDTH: usize> Poseidon1Constants<F, WIDTH> {
103    /// Compute the optimized sparse-form constants from these raw parameters.
104    ///
105    /// This performs two transformations:
106    ///
107    /// 1. **Sparse matrix decomposition**: factors the dense MDS matrix into one
108    ///    dense transition matrix and several sparse matrices. Each sparse matrix
109    ///    is parameterized by two vectors of length WIDTH-1.
110    ///
111    /// 2. **Round constant compression**: via backward substitution through the
112    ///    inverse MDS matrix, reduces each partial round's full constant vector
113    ///    to a single scalar, except for the first partial round.
114    pub fn to_optimized(
115        &self,
116    ) -> (
117        FullRoundConstants<F, WIDTH>,
118        PartialRoundConstants<F, WIDTH>,
119    ) {
120        let half_f = self.rounds_f / 2;
121        let rounds_p = self.rounds_p;
122
123        // Split the flat round-constant array into three sections.
124        //
125        // Layout: [initial_full (RF/2) | partial (RP) | terminal_full (RF/2)]
126        let initial_rc: Vec<[F; WIDTH]> = self.round_constants[..half_f].to_vec();
127        let partial_rc: Vec<[F; WIDTH]> = self.round_constants[half_f..half_f + rounds_p].to_vec();
128        let terminal_rc: Vec<[F; WIDTH]> = self.round_constants[half_f + rounds_p..].to_vec();
129
130        // Expand circulant first column into dense MDS matrix for the sparse decomposition.
131        let mds: [[F; WIDTH]; WIDTH] = utils::circulant_to_dense(&self.mds_circ_col);
132
133        // Compress round constants and factor MDS into sparse matrices.
134        let (first_round_constants, opt_partial_rc, m_i, sparse_v, sparse_first_row) =
135            utils::compute_optimized_constants::<F, WIDTH>(&mds, rounds_p, &partial_rc);
136
137        // Compute textbook path constants (forward constant substitution).
138        let (textbook_scalar_constants, textbook_residual) =
139            utils::forward_constant_substitution::<F, WIDTH>(&mds, &partial_rc);
140
141        let full_constants = FullRoundConstants {
142            initial: initial_rc,
143            terminal: terminal_rc,
144            dense_mds: mds,
145        };
146
147        let partial_constants = PartialRoundConstants {
148            first_round_constants,
149            m_i,
150            sparse_first_row,
151            v: sparse_v,
152            round_constants: opt_partial_rc,
153            textbook_scalar_constants,
154            textbook_residual,
155        };
156
157        (full_constants, partial_constants)
158    }
159}
160
161/// The optimized Poseidon1 permutation.
162///
163/// Holds the pre-computed full and partial round layers and applies them
164/// in sequence: initial full rounds, then partial rounds, then terminal
165/// full rounds.
166#[derive(Clone, Debug)]
167pub struct Poseidon1<F, FullRoundPerm, PartialRoundPerm, const WIDTH: usize, const D: u64> {
168    /// The initial and terminal full round layers.
169    ///
170    /// Full rounds apply the S-box to every state element, then multiply
171    /// by the dense MDS matrix.
172    full_round_layer: FullRoundPerm,
173
174    /// The partial round layer using sparse matrix decomposition.
175    ///
176    /// Partial rounds apply the S-box only to the first state element,
177    /// then multiply by a sparse matrix in O(t) operations.
178    partial_round_layer: PartialRoundPerm,
179
180    _phantom: PhantomData<F>,
181}
182
183impl<F, FullRoundPerm, PartialRoundPerm, const WIDTH: usize, const D: u64>
184    Poseidon1<F, FullRoundPerm, PartialRoundPerm, WIDTH, D>
185where
186    F: PrimeField,
187    FullRoundPerm: FullRoundLayerConstructor<F, WIDTH>,
188    PartialRoundPerm: PartialRoundLayerConstructor<F, WIDTH>,
189{
190    /// Create a new optimized Poseidon1 from raw constants.
191    ///
192    /// Internally computes the sparse matrix decomposition and the
193    /// optimized round constants. This is the typical entry point.
194    pub fn new(raw: &Poseidon1Constants<F, WIDTH>) -> Self {
195        // Compile-time structural checks from the Poseidon specification.
196        const {
197            // Section 2.3: POSEIDONπ takes inputs of t ≥ 2 words.
198            assert!(
199                WIDTH >= 2,
200                "Poseidon1 requires WIDTH >= 2 (paper Section 2.3: t >= 2)"
201            );
202            // Section 2.3: S-box(x) = x^α where α ≥ 3.
203            assert!(
204                D >= 3,
205                "Poseidon1 requires D >= 3 (paper Section 2.3: alpha >= 3)"
206            );
207        }
208
209        // Section 5.5.1 / Eq.(2): RF ≥ 6 for statistical attack resistance.
210        assert!(
211            raw.rounds_f >= 6,
212            "Poseidon1 requires rounds_f >= 6 (paper Section 5.5.1, Eq.(2): statistical attacks)"
213        );
214        // Round constants layout: [initial_full (RF/2) | partial (RP) | terminal_full (RF/2)].
215        assert_eq!(
216            raw.round_constants.len(),
217            raw.rounds_f + raw.rounds_p,
218            "Poseidon1 round_constants length must equal rounds_f + rounds_p"
219        );
220
221        let (full_constants, partial_constants) = raw.to_optimized();
222        Self::new_from_precomputed(full_constants, partial_constants)
223    }
224
225    /// Create a new optimized Poseidon1 from pre-computed constants.
226    ///
227    /// Use this when the sparse matrix decomposition has already been performed
228    /// (e.g., constants loaded from a file or hardcoded).
229    fn new_from_precomputed(
230        full_constants: FullRoundConstants<F, WIDTH>,
231        partial_constants: PartialRoundConstants<F, WIDTH>,
232    ) -> Self {
233        Self {
234            full_round_layer: FullRoundPerm::new_from_constants(full_constants),
235            partial_round_layer: PartialRoundPerm::new_from_constants(partial_constants),
236            _phantom: PhantomData,
237        }
238    }
239
240    /// Create a new Poseidon1 with random round constants and a given MDS permutation.
241    ///
242    /// Builds the dense MDS matrix by applying the permutation to unit vectors,
243    /// generates random round constants, and computes the sparse matrix decomposition.
244    ///
245    /// Primarily useful for testing.
246    pub fn new_from_rng(
247        half_num_full_rounds: usize,
248        num_partial_rounds: usize,
249        mds: &impl Permutation<[F; WIDTH]>,
250        rng: &mut impl Rng,
251    ) -> Self
252    where
253        StandardUniform: Distribution<F>,
254    {
255        // Compile-time structural checks from the Poseidon specification.
256        const {
257            // Section 2.3: POSEIDONπ takes inputs of t ≥ 2 words.
258            assert!(
259                WIDTH >= 2,
260                "Poseidon1 requires WIDTH >= 2 (paper Section 2.3: t >= 2)"
261            );
262            // Section 2.3: S-box(x) = x^α where α ≥ 3.
263            assert!(
264                D >= 3,
265                "Poseidon1 requires D >= 3 (paper Section 2.3: alpha >= 3)"
266            );
267        }
268
269        let rounds_f = 2 * half_num_full_rounds;
270
271        // Section 5.5.1 / Eq.(2): RF ≥ 6 for statistical attack resistance.
272        assert!(
273            rounds_f >= 6,
274            "Poseidon1 requires rounds_f >= 6 (paper Section 5.5.1, Eq.(2): statistical attacks)"
275        );
276
277        let num_rounds = rounds_f + num_partial_rounds;
278
279        // Generate random round constants.
280        let round_constants: Vec<[F; WIDTH]> = (0..num_rounds)
281            .map(|_| core::array::from_fn(|_| rng.sample(StandardUniform)))
282            .collect();
283
284        // Build dense MDS by applying the permutation to unit vectors.
285        let columns: [[F; WIDTH]; WIDTH] = core::array::from_fn(|j| {
286            let mut e = [F::ZERO; WIDTH];
287            e[j] = F::ONE;
288            mds.permute(e)
289        });
290        // Transpose to row-major format.
291        let dense_mds: [[F; WIDTH]; WIDTH] =
292            core::array::from_fn(|i| core::array::from_fn(|j| columns[j][i]));
293
294        // Split round constants into three sections.
295        let half_f = half_num_full_rounds;
296        let initial_rc = round_constants[..half_f].to_vec();
297        let partial_rc = round_constants[half_f..half_f + num_partial_rounds].to_vec();
298        let terminal_rc = round_constants[half_f + num_partial_rounds..].to_vec();
299
300        // Compute optimized sparse constants.
301        let (first_round_constants, opt_partial_rc, m_i, sparse_v, sparse_first_row) =
302            utils::compute_optimized_constants::<F, WIDTH>(
303                &dense_mds,
304                num_partial_rounds,
305                &partial_rc,
306            );
307
308        // Compute textbook path constants (forward constant substitution).
309        let (textbook_scalar_constants, textbook_residual) =
310            utils::forward_constant_substitution::<F, WIDTH>(&dense_mds, &partial_rc);
311
312        let full_constants = FullRoundConstants {
313            initial: initial_rc,
314            terminal: terminal_rc,
315            dense_mds,
316        };
317
318        let partial_constants = PartialRoundConstants {
319            first_round_constants,
320            m_i,
321            sparse_first_row,
322            v: sparse_v,
323            round_constants: opt_partial_rc,
324            textbook_scalar_constants,
325            textbook_residual,
326        };
327
328        Self::new_from_precomputed(full_constants, partial_constants)
329    }
330}
331
332impl<F, A, FullRoundPerm, PartialRoundPerm, const WIDTH: usize, const D: u64>
333    Permutation<[A; WIDTH]> for Poseidon1<F, FullRoundPerm, PartialRoundPerm, WIDTH, D>
334where
335    F: PrimeField + InjectiveMonomial<D>,
336    A: Algebra<F> + Sync + InjectiveMonomial<D>,
337    FullRoundPerm: FullRoundLayer<A, WIDTH, D>,
338    PartialRoundPerm: PartialRoundLayer<A, WIDTH, D>,
339{
340    fn permute_mut(&self, state: &mut [A; WIDTH]) {
341        // RF/2 full rounds (S-box on all elements + dense MDS).
342        self.full_round_layer.permute_state_initial(state);
343        // RP partial rounds (S-box on state[0] only + sparse matrix).
344        self.partial_round_layer.permute_state(state);
345        // RF/2 full rounds (S-box on all elements + dense MDS).
346        self.full_round_layer.permute_state_terminal(state);
347    }
348}
349
350impl<F, A, FullRoundPerm, PartialRoundPerm, const WIDTH: usize, const D: u64>
351    CryptographicPermutation<[A; WIDTH]> for Poseidon1<F, FullRoundPerm, PartialRoundPerm, WIDTH, D>
352where
353    F: PrimeField + InjectiveMonomial<D>,
354    A: Algebra<F> + Sync + InjectiveMonomial<D>,
355    FullRoundPerm: FullRoundLayer<A, WIDTH, D>,
356    PartialRoundPerm: PartialRoundLayer<A, WIDTH, D>,
357{
358}