Skip to main content

snarkvm_algorithms/crypto_hash/
poseidon.rs

1// Copyright (c) 2019-2026 Provable Inc.
2// This file is part of the snarkVM library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use crate::{AlgebraicSponge, DuplexSpongeMode, nonnative_params::*};
17use snarkvm_fields::{FieldParameters, PoseidonParameters, PrimeField, ToConstraintField};
18use snarkvm_utilities::{BigInteger, FromBits, ToBits};
19
20use smallvec::SmallVec;
21use std::{
22    iter::Peekable,
23    ops::{Index, IndexMut},
24    sync::Arc,
25};
26
27#[derive(Copy, Clone, Debug)]
28pub struct State<F: PrimeField, const RATE: usize, const CAPACITY: usize> {
29    capacity_state: [F; CAPACITY],
30    rate_state: [F; RATE],
31}
32
33impl<F: PrimeField, const RATE: usize, const CAPACITY: usize> Default for State<F, RATE, CAPACITY> {
34    fn default() -> Self {
35        Self { capacity_state: [F::zero(); CAPACITY], rate_state: [F::zero(); RATE] }
36    }
37}
38
39impl<F: PrimeField, const RATE: usize, const CAPACITY: usize> State<F, RATE, CAPACITY> {
40    /// Returns an immutable iterator over the state.
41    pub fn iter(&self) -> impl Iterator<Item = &F> + Clone {
42        self.capacity_state.iter().chain(self.rate_state.iter())
43    }
44
45    /// Returns a mutable iterator over the state.
46    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut F> {
47        self.capacity_state.iter_mut().chain(self.rate_state.iter_mut())
48    }
49}
50
51impl<F: PrimeField, const RATE: usize, const CAPACITY: usize> Index<usize> for State<F, RATE, CAPACITY> {
52    type Output = F;
53
54    fn index(&self, index: usize) -> &Self::Output {
55        assert!(index < RATE + CAPACITY, "Index out of bounds: index is {} but length is {}", index, RATE + CAPACITY);
56        if index < CAPACITY { &self.capacity_state[index] } else { &self.rate_state[index - CAPACITY] }
57    }
58}
59
60impl<F: PrimeField, const RATE: usize, const CAPACITY: usize> IndexMut<usize> for State<F, RATE, CAPACITY> {
61    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
62        assert!(index < RATE + CAPACITY, "Index out of bounds: index is {} but length is {}", index, RATE + CAPACITY);
63        if index < CAPACITY { &mut self.capacity_state[index] } else { &mut self.rate_state[index - CAPACITY] }
64    }
65}
66
67#[derive(Clone, Debug, PartialEq, Eq)]
68pub struct Poseidon<F: PrimeField, const RATE: usize> {
69    parameters: Arc<PoseidonParameters<F, RATE, 1>>,
70}
71
72impl<F: PrimeField, const RATE: usize> Poseidon<F, RATE> {
73    /// Initializes a new instance of the cryptographic hash function.
74    pub fn setup() -> Self {
75        Self { parameters: Arc::new(F::default_poseidon_parameters::<RATE>().unwrap()) }
76    }
77
78    /// Evaluate the cryptographic hash function over a list of field elements
79    /// as input.
80    pub fn evaluate(&self, input: &[F]) -> F {
81        self.evaluate_many(input, 1)[0]
82    }
83
84    /// Evaluate the cryptographic hash function over a list of field elements
85    /// as input, and returns the specified number of field elements as
86    /// output.
87    pub fn evaluate_many(&self, input: &[F], num_outputs: usize) -> Vec<F> {
88        let mut sponge = PoseidonSponge::<F, RATE, 1>::new_with_parameters(&self.parameters);
89        sponge.absorb_native_field_elements(input);
90        sponge.squeeze_native_field_elements(num_outputs).to_vec()
91    }
92
93    /// Evaluate the cryptographic hash function over a non-fixed-length vector,
94    /// in which the length also needs to be hashed.
95    pub fn evaluate_with_len(&self, input: &[F]) -> F {
96        self.evaluate(&[vec![F::from(input.len() as u128)], input.to_vec()].concat())
97    }
98
99    pub fn parameters(&self) -> &Arc<PoseidonParameters<F, RATE, 1>> {
100        &self.parameters
101    }
102}
103
104/// A duplex sponge based using the Poseidon permutation.
105///
106/// This implementation of Poseidon is entirely from Fractal's implementation in
107/// [COS20][cos] with small syntax changes.
108///
109/// [cos]: https://eprint.iacr.org/2019/1076
110#[derive(Clone, Debug)]
111pub struct PoseidonSponge<F: PrimeField, const RATE: usize, const CAPACITY: usize> {
112    /// Sponge Parameters
113    parameters: Arc<PoseidonParameters<F, RATE, CAPACITY>>,
114    /// Current sponge's state (current elements in the permutation block)
115    state: State<F, RATE, CAPACITY>,
116    /// Current mode (whether its absorbing or squeezing)
117    pub mode: DuplexSpongeMode,
118    /// A persistent lookup table used when compressing elements.
119    adjustment_factor_lookup_table: Arc<[F]>,
120}
121
122impl<F: PrimeField, const RATE: usize> AlgebraicSponge<F, RATE> for PoseidonSponge<F, RATE, 1> {
123    type Parameters = Arc<PoseidonParameters<F, RATE, 1>>;
124
125    fn sample_parameters() -> Self::Parameters {
126        Arc::new(F::default_poseidon_parameters::<RATE>().unwrap())
127    }
128
129    fn new_with_parameters(parameters: &Self::Parameters) -> Self {
130        Self {
131            parameters: parameters.clone(),
132            state: State::default(),
133            mode: DuplexSpongeMode::Absorbing { next_absorb_index: 0 },
134            adjustment_factor_lookup_table: {
135                let capacity = F::size_in_bits() - 1;
136                let mut table = Vec::<F>::with_capacity(capacity);
137
138                let mut cur = F::one();
139                for _ in 0..capacity {
140                    table.push(cur);
141                    cur.double_in_place();
142                }
143
144                table.into()
145            },
146        }
147    }
148
149    /// Takes in field elements.
150    fn absorb_native_field_elements<T: ToConstraintField<F>>(&mut self, elements: &[T]) {
151        let input = elements.iter().flat_map(|e| e.to_field_elements().unwrap()).collect::<Vec<_>>();
152        if !input.is_empty() {
153            match self.mode {
154                DuplexSpongeMode::Absorbing { mut next_absorb_index } => {
155                    if next_absorb_index == RATE {
156                        self.permute();
157                        next_absorb_index = 0;
158                    }
159                    self.absorb_internal(next_absorb_index, &input);
160                }
161                DuplexSpongeMode::Squeezing { next_squeeze_index: _ } => {
162                    self.permute();
163                    self.absorb_internal(0, &input);
164                }
165            }
166        }
167    }
168
169    /// Takes in field elements.
170    fn absorb_nonnative_field_elements<Target: PrimeField>(&mut self, elements: impl IntoIterator<Item = Target>) {
171        Self::push_elements_to_sponge(self, elements, OptimizationType::Weight);
172    }
173
174    fn squeeze_nonnative_field_elements<Target: PrimeField>(&mut self, num: usize) -> SmallVec<[Target; 10]> {
175        self.get_fe(num, false)
176    }
177
178    fn squeeze_native_field_elements(&mut self, num_elements: usize) -> SmallVec<[F; 10]> {
179        if num_elements == 0 {
180            return SmallVec::<[F; 10]>::new();
181        }
182        let mut output = if num_elements <= 10 {
183            smallvec::smallvec_inline![F::zero(); 10]
184        } else {
185            smallvec::smallvec![F::zero(); num_elements]
186        };
187
188        match self.mode {
189            DuplexSpongeMode::Absorbing { next_absorb_index: _ } => {
190                self.permute();
191                self.squeeze_internal(0, &mut output[..num_elements]);
192            }
193            DuplexSpongeMode::Squeezing { mut next_squeeze_index } => {
194                if next_squeeze_index == RATE {
195                    self.permute();
196                    next_squeeze_index = 0;
197                }
198                self.squeeze_internal(next_squeeze_index, &mut output[..num_elements]);
199            }
200        }
201
202        output.truncate(num_elements);
203        output
204    }
205
206    /// Takes out field elements of 168 bits.
207    fn squeeze_short_nonnative_field_elements<Target: PrimeField>(&mut self, num: usize) -> SmallVec<[Target; 10]> {
208        self.get_fe(num, true)
209    }
210}
211
212impl<F: PrimeField, const RATE: usize> PoseidonSponge<F, RATE, 1> {
213    #[inline]
214    fn apply_ark(&mut self, round_number: usize) {
215        for (state_elem, ark_elem) in self.state.iter_mut().zip(&self.parameters.ark[round_number]) {
216            *state_elem += ark_elem;
217        }
218    }
219
220    #[inline]
221    fn apply_s_box(&mut self, is_full_round: bool) {
222        let pow_alpha_in_place: fn(&mut F) = match self.parameters.alpha {
223            3 => pow_3_in_place,
224            5 => pow_5_in_place,
225            17 => pow_17_in_place,
226            alpha => panic!("No optimized S-box for alpha = {alpha}"),
227        };
228
229        if is_full_round {
230            self.state.iter_mut().for_each(pow_alpha_in_place);
231        } else {
232            pow_alpha_in_place(&mut self.state[0]);
233        }
234    }
235
236    #[inline]
237    fn apply_mds(&mut self) {
238        let mut new_state = State::default();
239        let curr_state: Vec<F> = self.state.iter().copied().collect::<Vec<_>>();
240        new_state.iter_mut().zip(&self.parameters.mds).for_each(|(new_elem, mds_row)| {
241            *new_elem = F::sum_of_products(&curr_state, mds_row);
242        });
243        self.state = new_state;
244    }
245
246    #[inline]
247    fn permute(&mut self) {
248        // Determine the partial rounds range bound.
249        let partial_rounds = self.parameters.partial_rounds;
250        let full_rounds = self.parameters.full_rounds;
251        let full_rounds_over_2 = full_rounds / 2;
252        let partial_round_range = full_rounds_over_2..(full_rounds_over_2 + partial_rounds);
253
254        // Iterate through all rounds to permute.
255        for i in 0..(partial_rounds + full_rounds) {
256            let is_full_round = !partial_round_range.contains(&i);
257            self.apply_ark(i);
258            self.apply_s_box(is_full_round);
259            self.apply_mds();
260        }
261    }
262
263    /// Absorbs everything in elements, this does not end in an absorption.
264    #[inline]
265    fn absorb_internal(&mut self, mut rate_start: usize, input: &[F]) {
266        if !input.is_empty() {
267            let first_chunk_size = std::cmp::min(RATE - rate_start, input.len());
268            let num_elements_remaining = input.len() - first_chunk_size;
269            let (first_chunk, rest_chunk) = input.split_at(first_chunk_size);
270            let rest_chunks = rest_chunk.chunks(RATE);
271            // The total number of chunks is `elements[num_elements_remaining..].len() /
272            // RATE`, plus 1 for the remainder.
273            let total_num_chunks = 1 + // 1 for the first chunk
274                // We add all the chunks that are perfectly divisible by `RATE`
275                (num_elements_remaining / RATE) +
276                // And also add 1 if the last chunk is non-empty
277                // (i.e. if `num_elements_remaining` is not a multiple of `RATE`)
278                usize::from((num_elements_remaining % RATE) != 0);
279
280            // Absorb the input elements, `RATE` elements at a time, except for the first
281            // chunk, which is of size `RATE - rate_start`.
282            for (i, chunk) in std::iter::once(first_chunk).chain(rest_chunks).enumerate() {
283                for (element, state_elem) in chunk.iter().zip(&mut self.state.rate_state[rate_start..]) {
284                    *state_elem += element;
285                }
286                // Are we in the last chunk?
287                // If so, let's wrap up.
288                if i == total_num_chunks - 1 {
289                    self.mode = DuplexSpongeMode::Absorbing { next_absorb_index: rate_start + chunk.len() };
290                    return;
291                } else {
292                    self.permute();
293                }
294                rate_start = 0;
295            }
296        }
297    }
298
299    /// Squeeze |output| many elements. This does not end in a squeeze
300    #[inline]
301    fn squeeze_internal(&mut self, mut rate_start: usize, output: &mut [F]) {
302        let output_size = output.len();
303        if output_size != 0 {
304            let first_chunk_size = std::cmp::min(RATE - rate_start, output.len());
305            let num_output_remaining = output.len() - first_chunk_size;
306            let (first_chunk, rest_chunk) = output.split_at_mut(first_chunk_size);
307            assert_eq!(rest_chunk.len(), num_output_remaining);
308            let rest_chunks = rest_chunk.chunks_mut(RATE);
309            // The total number of chunks is `output[num_output_remaining..].len() / RATE`,
310            // plus 1 for the remainder.
311            let total_num_chunks = 1 + // 1 for the first chunk
312                // We add all the chunks that are perfectly divisible by `RATE`
313                (num_output_remaining / RATE) +
314                // And also add 1 if the last chunk is non-empty
315                // (i.e. if `num_output_remaining` is not a multiple of `RATE`)
316                usize::from((num_output_remaining % RATE) != 0);
317
318            // Absorb the input output, `RATE` output at a time, except for the first chunk,
319            // which is of size `RATE - rate_start`.
320            for (i, chunk) in std::iter::once(first_chunk).chain(rest_chunks).enumerate() {
321                let range = rate_start..(rate_start + chunk.len());
322                debug_assert_eq!(
323                    chunk.len(),
324                    self.state.rate_state[range.clone()].len(),
325                    "failed with squeeze {output_size} at rate {RATE} and rate_start {rate_start}"
326                );
327                chunk.copy_from_slice(&self.state.rate_state[range]);
328                // Are we in the last chunk?
329                // If so, let's wrap up.
330                if i == total_num_chunks - 1 {
331                    self.mode = DuplexSpongeMode::Squeezing { next_squeeze_index: (rate_start + chunk.len()) };
332                    return;
333                } else {
334                    self.permute();
335                }
336                rate_start = 0;
337            }
338        }
339    }
340
341    /// Compress every two elements if possible.
342    /// Provides a vector of (limb, num_of_additions), both of which are F.
343    pub fn compress_elements<TargetField: PrimeField, I: Iterator<Item = (F, F)>>(
344        &self,
345        mut src_limbs: Peekable<I>,
346        ty: OptimizationType,
347    ) -> Vec<F> {
348        let capacity = F::size_in_bits() - 1;
349        let mut dest_limbs = Vec::<F>::new();
350
351        let params = get_params(TargetField::size_in_bits(), F::size_in_bits(), ty);
352
353        // Prepare a reusable vector to be used in overhead calculation.
354        let mut num_bits = Vec::new();
355
356        while let Some(first) = src_limbs.next() {
357            let second = src_limbs.peek();
358
359            let first_max_bits_per_limb = params.bits_per_limb + crate::overhead!(first.1 + F::one(), &mut num_bits);
360            let second_max_bits_per_limb = if let Some(second) = second {
361                params.bits_per_limb + crate::overhead!(second.1 + F::one(), &mut num_bits)
362            } else {
363                0
364            };
365
366            if let Some(second) = second {
367                if first_max_bits_per_limb + second_max_bits_per_limb <= capacity {
368                    let adjustment_factor = &self.adjustment_factor_lookup_table[second_max_bits_per_limb];
369
370                    dest_limbs.push(first.0 * adjustment_factor + second.0);
371                    src_limbs.next();
372                } else {
373                    dest_limbs.push(first.0);
374                }
375            } else {
376                dest_limbs.push(first.0);
377            }
378        }
379
380        dest_limbs
381    }
382
383    /// Convert a `TargetField` element into limbs (not constraints)
384    /// This is an internal function that would be reused by a number of other
385    /// functions
386    pub fn get_limbs_representations<TargetField: PrimeField>(
387        elem: &TargetField,
388        optimization_type: OptimizationType,
389    ) -> SmallVec<[F; 10]> {
390        Self::get_limbs_representations_from_big_integer::<TargetField>(&elem.to_bigint(), optimization_type)
391    }
392
393    /// Obtain the limbs directly from a big int
394    pub fn get_limbs_representations_from_big_integer<TargetField: PrimeField>(
395        elem: &<TargetField as PrimeField>::BigInteger,
396        optimization_type: OptimizationType,
397    ) -> SmallVec<[F; 10]> {
398        let params = get_params(TargetField::size_in_bits(), F::size_in_bits(), optimization_type);
399
400        // Prepare a reusable vector for the BE bits.
401        let mut cur_bits = Vec::new();
402        // Push the lower limbs first
403        let mut limbs: SmallVec<[F; 10]> = SmallVec::new();
404        let mut cur = *elem;
405        for _ in 0..params.num_limbs {
406            cur.write_bits_be(&mut cur_bits); // `write_bits_be` is big endian
407            let cur_mod_r =
408                <F as PrimeField>::BigInteger::from_bits_be(&cur_bits[cur_bits.len() - params.bits_per_limb..])
409                    .unwrap(); // therefore, the lowest `bits_per_non_top_limb` bits is what we want.
410            limbs.push(F::from_bigint(cur_mod_r).unwrap());
411            cur.divn(params.bits_per_limb as u32);
412            // Clear the vector after every iteration so its allocation can be reused.
413            cur_bits.clear();
414        }
415
416        // then we reverse, so that the limbs are ``big limb first''
417        limbs.reverse();
418
419        limbs
420    }
421
422    /// Push elements to sponge, treated in the non-native field
423    /// representations.
424    pub fn push_elements_to_sponge<TargetField: PrimeField>(
425        &mut self,
426        src: impl IntoIterator<Item = TargetField>,
427        ty: OptimizationType,
428    ) {
429        let src_limbs = src
430            .into_iter()
431            .flat_map(|elem| {
432                let limbs = Self::get_limbs_representations(&elem, ty);
433                limbs.into_iter().map(|limb| (limb, F::one()))
434                // specifically set to one, since most gadgets in the constraint
435                // world would not have zero noise (due to the relatively weak
436                // normal form testing in `alloc`)
437            })
438            .peekable();
439
440        let dest_limbs = self.compress_elements::<TargetField, _>(src_limbs, ty);
441        self.absorb_native_field_elements(&dest_limbs);
442    }
443
444    /// obtain random bits from hashchain.
445    /// not guaranteed to be uniformly distributed, should only be used in
446    /// certain situations.
447    pub fn get_bits(&mut self, num_bits: usize) -> Vec<bool> {
448        let bits_per_element = F::size_in_bits() - 1;
449        let num_elements = num_bits.div_ceil(bits_per_element);
450
451        let src_elements = self.squeeze_native_field_elements(num_elements);
452        let mut dest_bits = Vec::<bool>::with_capacity(num_elements * bits_per_element);
453
454        let skip = (F::Parameters::REPR_SHAVE_BITS + 1) as usize;
455        for elem in src_elements.iter() {
456            // discard the highest bit
457            let elem_bits = elem.to_bigint().to_bits_be();
458            dest_bits.extend_from_slice(&elem_bits[skip..]);
459        }
460        dest_bits.truncate(num_bits);
461
462        dest_bits
463    }
464
465    /// obtain random field elements from hashchain.
466    /// not guaranteed to be uniformly distributed, should only be used in
467    /// certain situations.
468    pub fn get_fe<TargetField: PrimeField>(
469        &mut self,
470        num_elements: usize,
471        outputs_short_elements: bool,
472    ) -> SmallVec<[TargetField; 10]> {
473        let num_bits_per_nonnative = if outputs_short_elements {
474            168
475        } else {
476            TargetField::size_in_bits() - 1 // also omit the highest bit
477        };
478        let bits = self.get_bits(num_bits_per_nonnative * num_elements);
479
480        let mut lookup_table = Vec::<TargetField>::with_capacity(num_bits_per_nonnative);
481        let mut cur = TargetField::one();
482        for _ in 0..num_bits_per_nonnative {
483            lookup_table.push(cur);
484            cur.double_in_place();
485        }
486
487        let dest_elements = bits
488            .chunks_exact(num_bits_per_nonnative)
489            .map(|per_nonnative_bits| {
490                // technically, this can be done via BigInteger::from_bits; here, we use this
491                // method for consistency with the gadget counterpart
492                let mut res = TargetField::zero();
493
494                for (i, bit) in per_nonnative_bits.iter().rev().enumerate() {
495                    if *bit {
496                        res += &lookup_table[i];
497                    }
498                }
499                res
500            })
501            .collect::<SmallVec<_>>();
502        debug_assert_eq!(dest_elements.len(), num_elements);
503
504        dest_elements
505    }
506}
507
508// S-box functions to raise state elements to the values of alpha present in the
509// codebase: 3, 5 and 17. These are more performant than the general pow
510// function.
511#[inline]
512fn pow_3_in_place<F: PrimeField>(val: &mut F) {
513    let val_copy = *val;
514    val.square_in_place();
515    val.mul_assign(val_copy);
516}
517
518#[inline]
519fn pow_5_in_place<F: PrimeField>(val: &mut F) {
520    let val_copy = *val;
521    val.square_in_place();
522    val.square_in_place();
523    val.mul_assign(val_copy);
524}
525
526#[inline]
527fn pow_17_in_place<F: PrimeField>(val: &mut F) {
528    let val_copy = *val;
529    val.square_in_place();
530    val.square_in_place();
531    val.square_in_place();
532    val.square_in_place();
533    val.mul_assign(val_copy);
534}