lib_q_poseidon/lib.rs
1//! Poseidon hash function optimized for zero-knowledge proofs
2//!
3//! This crate provides a field-native implementation of the Poseidon hash function,
4//! specifically optimized for use in STARK proof systems with `Complex<Mersenne31>`.
5//!
6//! # Design
7//!
8//! Poseidon is an algebraic hash function designed for efficient implementation in
9//! zero-knowledge proof systems. Unlike traditional hashes like SHA-3, Poseidon
10//! operates directly on field elements, making it orders of magnitude more efficient
11//! in circuit constraints.
12//!
13//! # Security
14//!
15//! - Uses round counts and an MDS construction inspired by the Poseidon design.
16//! - MDS matrices use a Cauchy construction (every square submatrix is invertible).
17//!
18//! WARNING: the round counts and sponge parameters in this crate have NOT been
19//! independently verified for the `Complex<Mersenne31>` extension field GF(p²).
20//! The standard Poseidon security analysis is stated over a prime field and does
21//! not directly cover this exact field and state. Do NOT rely on a specific
22//! bit-security level (e.g. 128-bit or 256-bit) for these parameters until they
23//! have been regenerated and analyzed for GF(p²).
24//!
25//! ## Parameter sets and the "Top Gun" degree-annihilation attacks (2026-08-09)
26//!
27//! This crate ships three parameter sets: **Poseidon-128** (original Poseidon,
28//! `GF(p²)` over Mersenne31, t=5, alpha=5, R_F=8, R_P=56), **Poseidon-256** (same
29//! field, t=7, alpha=5, R_F=8, R_P=60), and **Poseidon2-BabyBear** (the
30//! Plonky3/SP1 instance, t=16, alpha=7, R_F=8, R_P=13).
31//!
32//! Sanso & Vitto, "Top Gun: Degree Annihilation Attacks on Poseidon" (eprint
33//! preliminary, 2026), do **not** apply to any of the three sets, each for a
34//! reason the paper states in its own words:
35//! - Poseidon-128 / Poseidon-256: alpha=5, and the paper says larger exponents
36//! "such as alpha = 5 or alpha = 7 ... increase the local degree that must be
37//! annihilated" and are "expected to make the strategy less effective";
38//! separately, R_F=8 for both sets, and the paper's own R_F=8 experiment
39//! "did not yield a solution, suggesting that this direction should be
40//! revisited with different families of controls".
41//! - Poseidon2-BabyBear: it is Poseidon2, and the paper states "the initial
42//! linear transformation prevents the same two-round skip" that the attack's
43//! construction is built on; it also uses alpha=7 and R_F=8, the same two
44//! unfavourable conditions as above.
45//!
46//! Two caveats that matter and must not be dropped:
47//! 1. For Poseidon2-BabyBear, the paper's own (prior-work, classical
48//! one-round-skip) degree formula alpha^(R_F+R_P-1) gives 7^20 ≈ 2^56.1,
49//! against a generic CICO-2 cost on BabyBear of roughly 2^62 for the bare
50//! permutation. This is **not** a Top Gun contribution — Top Gun demonstrates
51//! no Poseidon2 annihilation anywhere in the paper. Whether a CICO-2 solve on
52//! the bare permutation reduces to a collision or preimage attack on our
53//! actual construction (a sponge with rate 7 / capacity 9 field elements,
54//! used for Merkle hashing and Fiat-Shamir) is **undetermined** — no such
55//! reduction was written or found. This is not a break; it is an open
56//! question worth tracking, not something to bury.
57//! 2. Poseidon-128 and Poseidon-256 are **not** reference Poseidon parameter
58//! sets: their MDS matrices are a locally-built Cauchy construction and
59//! their round constants come from a local SHAKE256 seed string (no
60//! Grain-LFSR, no external provenance — see the WARNING above and
61//! `constants.rs`). Top Gun §4.1 says exactly this category of deployment
62//! needs "parameter by parameter" analysis. That gap pre-dates and is
63//! independent of Top Gun; Top Gun does not create it and does not close it.
64//!
65//! Five related papers were sought but could not be obtained at assessment
66//! time, so their content is not reflected above: Merz & Rodriguez Garcia,
67//! "Skipping Class" (ePrint 2026/306) — the actual Poseidon2/Poseidon2b attack
68//! paper, and the largest open gap for the Poseidon2-BabyBear set; Zhao,
69//! Sanso, Vitto & Ding, "Graeffe-based attacks on Poseidon and NTT lower
70//! bounds" (ePrint 2025/1916); Bak et al. (ePrint 2025/2040); Bak et al.
71//! (ePrint 2026/150); and a Grassi et al. survey referenced by Top Gun.
72//!
73//! # Example
74//!
75//! ```rust,ignore
76//! use lib_q_poseidon::{Poseidon, Poseidon128};
77//! use lib_q_stark_field::extension::Complex;
78//! use lib_q_stark_mersenne31::Mersenne31;
79//!
80//! type Val = Complex<Mersenne31>;
81//!
82//! let hasher = Poseidon128::permutation();
83//! let input = vec![Val::from(1u32), Val::from(2u32)];
84//! let hash = hasher.hash(&input);
85//! ```
86
87#![cfg_attr(not(feature = "std"), no_std)]
88#![deny(unsafe_code)]
89#![deny(unused_qualifications)]
90
91#[cfg(feature = "alloc")]
92extern crate alloc;
93
94#[cfg(feature = "alloc")]
95use alloc::string::String;
96#[cfg(all(feature = "alloc", feature = "std"))]
97use alloc::string::ToString;
98
99mod constants;
100#[cfg(feature = "alloc")]
101mod params;
102#[cfg(feature = "alloc")]
103mod permutation;
104/// Value-level Poseidon2 permutation over BabyBear (width 16, the deployed
105/// Plonky3/SP1 instance). `no_std`/`alloc`-free; used by the Arm B membership AIR.
106pub mod poseidon2_baby_bear;
107#[cfg(feature = "alloc")]
108mod sponge;
109
110// Export constants for AIR constraint generation
111pub use constants::sbox;
112#[cfg(feature = "alloc")]
113pub use constants::{
114 mds_matrix_5x5,
115 mds_matrix_7x7,
116};
117#[cfg(feature = "alloc")]
118pub use constants::{
119 round_constants_128,
120 round_constants_256,
121};
122#[cfg(feature = "alloc")]
123pub use params::{
124 Poseidon128,
125 Poseidon256,
126 PoseidonField,
127 PoseidonParams,
128};
129#[cfg(feature = "alloc")]
130pub use permutation::{
131 PoseidonPermutation,
132 PoseidonState,
133};
134#[cfg(feature = "alloc")]
135pub use sponge::{
136 Poseidon,
137 PoseidonSponge,
138 PoseidonSpongeSqueeze,
139};
140
141#[cfg(feature = "wasm")]
142pub mod wasm;
143
144/// Error types for Poseidon operations
145#[derive(Debug, Clone, PartialEq, Eq)]
146pub enum PoseidonError {
147 /// Input size exceeds maximum allowed
148 InputTooLarge { max: usize, actual: usize },
149 /// Invalid parameter configuration
150 #[cfg(feature = "alloc")]
151 InvalidParams { reason: String },
152 /// Internal error during hashing
153 #[cfg(feature = "alloc")]
154 InternalError { reason: String },
155}
156
157impl core::fmt::Display for PoseidonError {
158 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
159 match self {
160 PoseidonError::InputTooLarge { max, actual } => {
161 write!(f, "Input size {} exceeds maximum {}", actual, max)
162 }
163 #[cfg(feature = "alloc")]
164 PoseidonError::InvalidParams { reason } => {
165 write!(f, "Invalid Poseidon parameters: {}", reason)
166 }
167 #[cfg(feature = "alloc")]
168 PoseidonError::InternalError { reason } => {
169 write!(f, "Internal Poseidon error: {}", reason)
170 }
171 }
172 }
173}
174
175#[cfg(all(feature = "alloc", feature = "std"))]
176impl From<PoseidonError> for lib_q_core::Error {
177 fn from(err: PoseidonError) -> Self {
178 lib_q_core::Error::InternalError {
179 operation: "Poseidon hash".into(),
180 details: err.to_string(),
181 }
182 }
183}
184
185#[cfg(all(not(feature = "alloc"), feature = "std"))]
186impl From<PoseidonError> for lib_q_core::Error {
187 fn from(err: PoseidonError) -> Self {
188 match err {
189 PoseidonError::InputTooLarge { .. } => lib_q_core::Error::InternalError {
190 operation: "Poseidon hash",
191 details: "input too large",
192 },
193 }
194 }
195}