Skip to main content

lib_q_poseidon/
sponge.rs

1//! Poseidon sponge construction for hashing
2//!
3//! This module implements the sponge construction on top of the Poseidon
4//! permutation. The API is split into an absorb phase ([`PoseidonSponge`]) and a
5//! squeeze phase ([`PoseidonSpongeSqueeze`]) so padding cannot be followed by
6//! further absorption (which would depart from the standard sponge).
7
8#[cfg(feature = "alloc")]
9extern crate alloc;
10
11#[cfg(feature = "alloc")]
12use alloc::vec::Vec;
13
14use lib_q_stark_field::extension::Complex;
15use lib_q_stark_mersenne31::Mersenne31;
16
17use crate::params::{
18    Poseidon128,
19    Poseidon256,
20    PoseidonField,
21    PoseidonParams,
22};
23use crate::permutation::PoseidonPermutation;
24
25#[derive(Debug, Clone)]
26struct SpongeState {
27    permutation: PoseidonPermutation,
28    state: Vec<PoseidonField>,
29    rate: usize,
30    capacity: usize,
31    absorbed: usize,
32}
33
34impl SpongeState {
35    fn new(params: PoseidonParams) -> Self {
36        use lib_q_stark_field::PrimeCharacteristicRing;
37        let state_width = params.state_width;
38        Self {
39            permutation: PoseidonPermutation::new(params.clone()),
40            state: alloc::vec![Complex::<Mersenne31>::ZERO; state_width],
41            rate: params.rate,
42            capacity: params.capacity,
43            absorbed: 0,
44        }
45    }
46
47    fn absorb(&mut self, elements: &[PoseidonField]) {
48        for &element in elements {
49            self.state[self.absorbed] += element;
50            self.absorbed += 1;
51
52            if self.absorbed >= self.rate {
53                self.state = self.permutation.permute(self.state.clone());
54                self.absorbed = 0;
55            }
56        }
57    }
58
59    /// Apply 10*1 padding in the rate, then permute. `absorbed` counts elements in the current rate block.
60    fn apply_padding_and_permute(mut self) -> Self {
61        use lib_q_stark_field::PrimeCharacteristicRing;
62
63        self.state[self.absorbed] += Complex::<Mersenne31>::ONE;
64        if self.absorbed + 1 < self.rate {
65            self.state[self.rate - 1] += Complex::<Mersenne31>::ONE;
66        }
67
68        self.state = self.permutation.permute(self.state.clone());
69        self.absorbed = 0;
70        self
71    }
72
73    fn squeeze(&mut self, num_elements: usize) -> Vec<PoseidonField> {
74        let mut output = Vec::with_capacity(num_elements);
75        let mut squeezed = 0;
76
77        while squeezed < num_elements {
78            if self.absorbed >= self.rate {
79                self.state = self.permutation.permute(self.state.clone());
80                self.absorbed = 0;
81            }
82
83            output.push(self.state[self.absorbed]);
84            self.absorbed += 1;
85            squeezed += 1;
86        }
87
88        output
89    }
90}
91
92/// Poseidon sponge in the absorb phase (before padding).
93///
94/// Call [`Self::finish_absorbing`] when all input has been absorbed to obtain a
95/// [`PoseidonSpongeSqueeze`]. Further absorption is rejected by the type system:
96/// the sponge state after padding is not a valid absorb continuation.
97#[derive(Debug, Clone)]
98pub struct PoseidonSponge(SpongeState);
99
100impl PoseidonSponge {
101    /// Create a new Poseidon sponge with the given parameters
102    pub fn new(params: PoseidonParams) -> Self {
103        Self(SpongeState::new(params))
104    }
105
106    /// Absorb field elements into the sponge
107    ///
108    /// # Arguments
109    ///
110    /// * `elements` - Field elements to absorb
111    pub fn absorb(&mut self, elements: &[PoseidonField]) {
112        self.0.absorb(elements);
113    }
114
115    /// Finish absorbing and apply padding (10*1 in rate only)
116    ///
117    /// Should be called after all input has been absorbed. Returns a value that
118    /// only supports [`PoseidonSpongeSqueeze::squeeze`], so additional [`PoseidonSponge::absorb`]
119    /// calls are impossible after padding (they would define a non-standard sponge).
120    ///
121    /// Standard sponge padding: add 1 at `state[absorbed]`; if that does not fill
122    /// the rate block (`absorbed + 1 < rate`), add 1 at `state[rate - 1]` to
123    /// distinguish single-block from multi-block inputs. Capacity is not written.
124    ///
125    /// # Compile-time safety
126    ///
127    /// After this call, [`PoseidonSponge`] is consumed; [`PoseidonSpongeSqueeze`] has no
128    /// `absorb`, so further input cannot be appended after padding:
129    ///
130    /// ```compile_fail,E0599
131    /// use lib_q_poseidon::{Poseidon128, PoseidonSponge};
132    /// let params = Poseidon128::params();
133    /// let sponge = PoseidonSponge::new(params);
134    /// let mut sponge = sponge.finish_absorbing();
135    /// sponge.absorb(&[]);
136    /// ```
137    pub fn finish_absorbing(self) -> PoseidonSpongeSqueeze {
138        PoseidonSpongeSqueeze(self.0.apply_padding_and_permute())
139    }
140
141    /// Finalize the sponge (apply padding and final permutation)
142    ///
143    /// Convenience for callers that need the full width state after padding without
144    /// squeezing. Otherwise use [`Self::finish_absorbing`] followed by
145    /// [`PoseidonSpongeSqueeze::squeeze`].
146    pub fn finalize(self) -> Vec<PoseidonField> {
147        self.finish_absorbing().into_state()
148    }
149
150    /// Get the capacity value
151    pub fn capacity(&self) -> usize {
152        self.0.capacity
153    }
154
155    /// Get the rate value
156    pub fn rate(&self) -> usize {
157        self.0.rate
158    }
159}
160
161/// Poseidon sponge after padding: squeeze output only.
162///
163/// Produced by [`PoseidonSponge::finish_absorbing`]. Absorption is complete; only
164/// [`Self::squeeze`] reads from the rate according to the sponge construction.
165#[derive(Debug, Clone)]
166pub struct PoseidonSpongeSqueeze(SpongeState);
167
168impl PoseidonSpongeSqueeze {
169    /// Squeeze output elements from the sponge
170    ///
171    /// # Arguments
172    ///
173    /// * `num_elements` - Number of field elements to squeeze
174    ///
175    /// # Returns
176    ///
177    /// Vector of squeezed field elements
178    pub fn squeeze(&mut self, num_elements: usize) -> Vec<PoseidonField> {
179        self.0.squeeze(num_elements)
180    }
181
182    /// Full permutation state after padding (including capacity cells).
183    pub fn into_state(self) -> Vec<PoseidonField> {
184        self.0.state
185    }
186
187    /// Get the capacity value
188    pub fn capacity(&self) -> usize {
189        self.0.capacity
190    }
191
192    /// Get the rate value
193    pub fn rate(&self) -> usize {
194        self.0.rate
195    }
196}
197
198/// High-level Poseidon hash interface
199pub trait Poseidon {
200    /// Hash a slice of field elements
201    ///
202    /// # Arguments
203    ///
204    /// * `input` - Input field elements to hash
205    ///
206    /// # Returns
207    ///
208    /// Hash output as field elements
209    fn hash(&self, input: &[PoseidonField]) -> Vec<PoseidonField>;
210
211    /// Hash and return a single field element
212    ///
213    /// # Arguments
214    ///
215    /// * `input` - Input field elements to hash
216    ///
217    /// # Returns
218    ///
219    /// First element of hash output
220    fn hash_single(&self, input: &[PoseidonField]) -> PoseidonField {
221        self.hash(input)[0]
222    }
223}
224
225impl Poseidon for Poseidon128 {
226    fn hash(&self, input: &[PoseidonField]) -> Vec<PoseidonField> {
227        let params = Self::params();
228        let mut sponge = PoseidonSponge::new(params);
229        sponge.absorb(input);
230        let mut sponge = sponge.finish_absorbing();
231        sponge.squeeze(1)
232    }
233}
234
235impl Poseidon for Poseidon256 {
236    fn hash(&self, input: &[PoseidonField]) -> Vec<PoseidonField> {
237        let params = Self::params();
238        let mut sponge = PoseidonSponge::new(params);
239        sponge.absorb(input);
240        let mut sponge = sponge.finish_absorbing();
241        sponge.squeeze(1)
242    }
243}
244
245#[cfg(test)]
246mod tests {
247    use super::*;
248
249    #[test]
250    fn test_sponge_absorb_squeeze() {
251        let params = Poseidon128::params();
252        let mut sponge = PoseidonSponge::new(params);
253        let input = alloc::vec![
254            Complex::<Mersenne31>::from(Mersenne31::new(1)),
255            Complex::<Mersenne31>::from(Mersenne31::new(2)),
256        ];
257        sponge.absorb(&input);
258        let mut sponge = sponge.finish_absorbing();
259        let output = sponge.squeeze(1);
260        assert_eq!(output.len(), 1);
261    }
262
263    #[test]
264    fn test_poseidon_hash_deterministic() {
265        use super::Poseidon;
266        let hasher = Poseidon128;
267        let input = alloc::vec![
268            Complex::<Mersenne31>::from(Mersenne31::new(1)),
269            Complex::<Mersenne31>::from(Mersenne31::new(2)),
270        ];
271        let hash1 = hasher.hash(&input);
272        let hash2 = hasher.hash(&input);
273        assert_eq!(hash1, hash2);
274    }
275}