Skip to main content

snarkvm_console_algorithms/poseidon/
mod.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
16mod helpers;
17
18mod hash;
19mod hash_many;
20mod hash_to_group;
21mod hash_to_scalar;
22mod prf;
23
24use crate::{Elligator2, poseidon::helpers::*};
25use snarkvm_console_types::prelude::*;
26use snarkvm_fields::{PoseidonDefaultField, PoseidonParameters};
27
28use std::sync::{Arc, OnceLock};
29
30const CAPACITY: usize = 1;
31
32/// Poseidon2 is a cryptographic hash function of input rate 2.
33pub type Poseidon2<E> = Poseidon<E, 2>;
34/// Poseidon4 is a cryptographic hash function of input rate 4.
35pub type Poseidon4<E> = Poseidon<E, 4>;
36/// Poseidon8 is a cryptographic hash function of input rate 8.
37pub type Poseidon8<E> = Poseidon<E, 8>;
38
39#[derive(Clone, Debug)]
40pub struct Poseidon<E: Environment, const RATE: usize> {
41    /// The domain separator for the Poseidon hash function.
42    domain: Field<E>,
43    /// The Poseidon parameters for hashing.
44    parameters: Arc<PoseidonParameters<E::Field, RATE, CAPACITY>>,
45    /// The Merkle empty hash of this instance, once something has asked for it.
46    empty_hash: OnceLock<Field<E>>,
47}
48
49impl<E: Environment, const RATE: usize> PartialEq for Poseidon<E, RATE> {
50    /// Ignores the cached empty hash, which is derived from the domain and parameters.
51    fn eq(&self, other: &Self) -> bool {
52        // Destructured so that a new field must be considered here.
53        let Self { domain, parameters, empty_hash: _ } = self;
54        *domain == other.domain && *parameters == other.parameters
55    }
56}
57
58impl<E: Environment, const RATE: usize> Poseidon<E, RATE> {
59    /// Initializes a new instance of Poseidon.
60    pub fn setup(domain: &str) -> Result<Self> {
61        // Ensure the given domain is within the allowed size in bits.
62        let num_bits = domain.len().saturating_mul(8);
63        let max_bits = Field::<E>::size_in_data_bits();
64        ensure!(num_bits <= max_bits, "Domain cannot exceed {max_bits} bits, found {num_bits} bits");
65
66        Ok(Self {
67            domain: Field::<E>::new_domain_separator(domain),
68            parameters: Arc::new(E::Field::default_poseidon_parameters::<RATE>()?),
69            empty_hash: OnceLock::new(),
70        })
71    }
72
73    /// Returns the domain separator for the hash function.
74    pub fn domain(&self) -> Field<E> {
75        self.domain
76    }
77
78    /// Returns the Poseidon parameters for hashing.
79    pub fn parameters(&self) -> &Arc<PoseidonParameters<E::Field, RATE, CAPACITY>> {
80        &self.parameters
81    }
82
83    /// Returns the Merkle empty hash of this instance, computing it on first use.
84    ///
85    /// This is `PathHash::hash_children(0, 0)` from `snarkvm-console-collections`, and
86    /// `test_cached_empty_hash_matches_the_computed_one` checks that the two agree. The
87    /// hash depends on the domain, so it is cached per instance, not per type.
88    pub fn merkle_empty_hash(&self) -> Result<Field<E>> {
89        if let Some(empty_hash) = self.empty_hash.get() {
90            return Ok(*empty_hash);
91        }
92        // Prepend the nodes with a `1field` byte.
93        let input = [Field::<E>::one(), Field::<E>::zero(), Field::<E>::zero()];
94        let empty_hash = Hash::hash(self, &input)?;
95        Ok(*self.empty_hash.get_or_init(|| empty_hash))
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102    use snarkvm_console_types::environment::Console;
103    use snarkvm_curves::edwards_bls12::Fq;
104    use snarkvm_fields::{PoseidonDefaultField, PoseidonGrainLFSR};
105
106    type CurrentEnvironment = Console;
107
108    use std::{path::PathBuf, sync::Arc};
109
110    /// Returns the path to the `resources` folder for this module.
111    fn resources_path() -> PathBuf {
112        // Construct the path for the `resources` folder.
113        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
114        path.push("src");
115        path.push("poseidon");
116        path.push("resources");
117
118        // Create the `resources` folder, if it does not exist.
119        if !path.exists() {
120            std::fs::create_dir_all(&path).unwrap_or_else(|_| panic!("Failed to create resources folder: {path:?}"));
121        }
122        // Output the path.
123        path
124    }
125
126    /// Loads the given `test_folder/test_file` and asserts the given `candidate` matches the expected values.
127    #[track_caller]
128    fn assert_snapshot<S1: Into<String>, S2: Into<String>, C: Debug>(test_folder: S1, test_file: S2, candidate: C) {
129        // Construct the path for the test folder.
130        let mut path = resources_path();
131        path.push(test_folder.into());
132
133        // Create the test folder, if it does not exist.
134        if !path.exists() {
135            std::fs::create_dir(&path).unwrap_or_else(|_| panic!("Failed to create test folder: {path:?}"));
136        }
137
138        // Construct the path for the test file.
139        path.push(test_file.into());
140        path.set_extension("snap");
141
142        // Create the test file, if it does not exist.
143        if !path.exists() {
144            std::fs::File::create(&path).unwrap_or_else(|_| panic!("Failed to create file: {path:?}"));
145        }
146
147        // Assert the test file is equal to the expected value.
148        expect_test::expect_file![path].assert_eq(&format!("{candidate:?}"));
149    }
150
151    #[test]
152    fn test_grain_lfsr() -> Result<()> {
153        let mut lfsr = PoseidonGrainLFSR::new(false, 253, 3, 8, 31);
154        assert_snapshot("test_grain_lfsr", "first_sample", lfsr.get_field_elements_rejection_sampling::<Fq>(1)?);
155        assert_snapshot("test_grain_lfsr", "second_sample", lfsr.get_field_elements_rejection_sampling::<Fq>(1)?);
156        Ok(())
157    }
158
159    #[test]
160    fn test_sponge() {
161        const RATE: usize = 2;
162        let parameters = Arc::new(Fq::default_poseidon_parameters::<RATE>().unwrap());
163
164        for absorb in 0..10 {
165            for squeeze in 0..10 {
166                let iteration = format!("absorb_{absorb}_squeeze_{squeeze}");
167
168                let mut sponge = PoseidonSponge::<CurrentEnvironment, RATE, CAPACITY>::new(&parameters);
169                sponge.absorb(&vec![Field::<CurrentEnvironment>::from_u64(1237812u64); absorb]);
170
171                let next_absorb_index = if absorb % RATE != 0 || absorb == 0 { absorb % RATE } else { RATE };
172                assert_eq!(sponge.mode, DuplexSpongeMode::Absorbing { next_absorb_index }, "{iteration}");
173
174                assert_snapshot("test_sponge", &iteration, sponge.squeeze(u16::try_from(squeeze).unwrap()));
175
176                let next_squeeze_index = if squeeze % RATE != 0 || squeeze == 0 { squeeze % RATE } else { RATE };
177                match squeeze == 0 {
178                    true => assert_eq!(sponge.mode, DuplexSpongeMode::Absorbing { next_absorb_index }, "{iteration}"),
179                    false => assert_eq!(sponge.mode, DuplexSpongeMode::Squeezing { next_squeeze_index }, "{iteration}"),
180                }
181            }
182        }
183    }
184
185    #[test]
186    fn test_parameters() {
187        fn single_rate_test<const RATE: usize>() {
188            let parameters = Fq::default_poseidon_parameters::<RATE>().unwrap();
189            assert_snapshot("test_parameters", format!("rate_{RATE}_ark"), parameters.ark);
190            assert_snapshot("test_parameters", format!("rate_{RATE}_mds"), parameters.mds);
191        }
192        // Optimized for constraints.
193        single_rate_test::<2>();
194        single_rate_test::<3>();
195        single_rate_test::<4>();
196        single_rate_test::<5>();
197        single_rate_test::<6>();
198        single_rate_test::<7>();
199        single_rate_test::<8>();
200    }
201
202    #[test]
203    fn test_suite_hash2() {
204        fn test_case_hash2(index: u8, input: Vec<Field<CurrentEnvironment>>) {
205            let poseidon2 = Poseidon2::<Console>::setup("Poseidon2").unwrap();
206            assert_snapshot("test_hash", format!("rate_2_test_{index}"), poseidon2.hash(&input).unwrap());
207        }
208        test_case_hash2(0, vec![]);
209        test_case_hash2(1, vec![Field::<Console>::from_u8(0)]);
210        test_case_hash2(2, vec![Field::<Console>::from_u8(1)]);
211        test_case_hash2(3, vec![Field::<Console>::from_u8(0), Field::<Console>::from_u8(1)]);
212        test_case_hash2(4, vec![Field::<Console>::from_u8(7), Field::<Console>::from_u8(6)]);
213    }
214
215    #[test]
216    fn test_suite_hash4() {
217        fn test_case_hash4(index: u8, input: Vec<Field<CurrentEnvironment>>) {
218            let poseidon4 = Poseidon4::<Console>::setup("Poseidon4").unwrap();
219            assert_snapshot("test_hash", format!("rate_4_test_{index}"), poseidon4.hash(&input).unwrap());
220        }
221        test_case_hash4(0, vec![]);
222        test_case_hash4(1, vec![Field::<Console>::from_u8(0)]);
223        test_case_hash4(2, vec![Field::<Console>::from_u8(1)]);
224        test_case_hash4(3, vec![Field::<Console>::from_u8(0), Field::<Console>::from_u8(1)]);
225        test_case_hash4(4, vec![Field::<Console>::from_u8(7), Field::<Console>::from_u8(6)]);
226        test_case_hash4(5, vec![
227            Field::<Console>::from_str(
228                "3801852864665033841774715284518384682376829752661853198612247855579120198106field",
229            )
230            .unwrap(),
231            Field::<Console>::from_str(
232                "8354898322875240371401674517397790035008442020361740574117886421279083828480field",
233            )
234            .unwrap(),
235            Field::<Console>::from_str(
236                "4810388512520169167962815122521832339992376865086300759308552937986944510606field",
237            )
238            .unwrap(),
239        ]);
240        test_case_hash4(6, vec![
241            Field::<Console>::from_str(
242                "3801852864665033841774715284518384682376829752661853198612247855579120198106field",
243            )
244            .unwrap(),
245            Field::<Console>::from_str(
246                "8354898322875240371401674517397790035008442020361740574117886421279083828480field",
247            )
248            .unwrap(),
249            Field::<Console>::from_str(
250                "4810388512520169167962815122521832339992376865086300759308552937986944510606field",
251            )
252            .unwrap(),
253            Field::<Console>::from_str(
254                "1806278863067630397941269234951941896370617486625414347832536440203404317871field",
255            )
256            .unwrap(),
257        ]);
258        test_case_hash4(7, vec![
259            Field::<Console>::from_str(
260                "3801852864665033841774715284518384682376829752661853198612247855579120198106field",
261            )
262            .unwrap(),
263            Field::<Console>::from_str(
264                "8354898322875240371401674517397790035008442020361740574117886421279083828480field",
265            )
266            .unwrap(),
267            Field::<Console>::from_str(
268                "4810388512520169167962815122521832339992376865086300759308552937986944510606field",
269            )
270            .unwrap(),
271            Field::<Console>::from_str(
272                "1806278863067630397941269234951941896370617486625414347832536440203404317871field",
273            )
274            .unwrap(),
275            Field::<Console>::from_str(
276                "4017177598231920767921734423139954103557056461408532722673217828464276314809field",
277            )
278            .unwrap(),
279        ]);
280    }
281
282    #[test]
283    fn test_suite_hash8() {
284        fn test_case_hash8(index: u16, input: Vec<Field<CurrentEnvironment>>) {
285            let poseidon8 = Poseidon8::<Console>::setup("Poseidon8").unwrap();
286            assert_snapshot("test_hash", format!("rate_8_test_{index}"), poseidon8.hash(&input).unwrap());
287        }
288        test_case_hash8(0, vec![]);
289        test_case_hash8(1, vec![Field::<Console>::from_u8(0)]);
290        test_case_hash8(2, vec![Field::<Console>::from_u8(1)]);
291        test_case_hash8(3, vec![Field::<Console>::from_u8(0), Field::<Console>::from_u8(1)]);
292        test_case_hash8(4, vec![Field::<Console>::from_u8(7), Field::<Console>::from_u8(6)]);
293        test_case_hash8(5, vec![
294            Field::<Console>::from_str(
295                "3801852864665033841774715284518384682376829752661853198612247855579120198106field",
296            )
297            .unwrap(),
298            Field::<Console>::from_str(
299                "8354898322875240371401674517397790035008442020361740574117886421279083828480field",
300            )
301            .unwrap(),
302            Field::<Console>::from_str(
303                "4810388512520169167962815122521832339992376865086300759308552937986944510606field",
304            )
305            .unwrap(),
306        ]);
307        test_case_hash8(6, vec![
308            Field::<Console>::from_str(
309                "3801852864665033841774715284518384682376829752661853198612247855579120198106field",
310            )
311            .unwrap(),
312            Field::<Console>::from_str(
313                "8354898322875240371401674517397790035008442020361740574117886421279083828480field",
314            )
315            .unwrap(),
316            Field::<Console>::from_str(
317                "4810388512520169167962815122521832339992376865086300759308552937986944510606field",
318            )
319            .unwrap(),
320            Field::<Console>::from_str(
321                "1806278863067630397941269234951941896370617486625414347832536440203404317871field",
322            )
323            .unwrap(),
324        ]);
325        test_case_hash8(7, vec![
326            Field::<Console>::from_str(
327                "3801852864665033841774715284518384682376829752661853198612247855579120198106field",
328            )
329            .unwrap(),
330            Field::<Console>::from_str(
331                "8354898322875240371401674517397790035008442020361740574117886421279083828480field",
332            )
333            .unwrap(),
334            Field::<Console>::from_str(
335                "4810388512520169167962815122521832339992376865086300759308552937986944510606field",
336            )
337            .unwrap(),
338            Field::<Console>::from_str(
339                "1806278863067630397941269234951941896370617486625414347832536440203404317871field",
340            )
341            .unwrap(),
342            Field::<Console>::from_str(
343                "4017177598231920767921734423139954103557056461408532722673217828464276314809field",
344            )
345            .unwrap(),
346        ]);
347        test_case_hash8(8, vec![
348            Field::<Console>::from_str(
349                "2241061724039470158487229089505123379386376040366677537043719491567584322339field",
350            )
351            .unwrap(),
352            Field::<Console>::from_str(
353                "4450395467941419565906844040025562669400620759737863109185235386261110553073field",
354            )
355            .unwrap(),
356            Field::<Console>::from_str(
357                "3763549180544198711495347718218896634621699987767108409942867882747700142403field",
358            )
359            .unwrap(),
360            Field::<Console>::from_str(
361                "1834649076610684411560795826346579299134200286711220272747136514724202486145field",
362            )
363            .unwrap(),
364            Field::<Console>::from_str(
365                "3330794675297759513930533281299019673013197332462213086257974185952740704073field",
366            )
367            .unwrap(),
368            Field::<Console>::from_str(
369                "5929621997900969559642343088519370677943323262633114245367700983937202243619field",
370            )
371            .unwrap(),
372            Field::<Console>::from_str(
373                "8211311402459203356251863974142333868284569297703150729090604853345946857386field",
374            )
375            .unwrap(),
376        ]);
377        test_case_hash8(9, vec![
378            Field::<Console>::from_str(
379                "160895951580389706659907027483151875213333010019551276998320919296228647317field",
380            )
381            .unwrap(),
382            Field::<Console>::from_str(
383                "8334099740396373026754940038411748941117628023990297711605274995172393663866field",
384            )
385            .unwrap(),
386            Field::<Console>::from_str(
387                "6508516067551208838086421306235504440162527555399726948591414865066786644888field",
388            )
389            .unwrap(),
390            Field::<Console>::from_str(
391                "5260580011132523115913756761919139190330166964648541423363604516046903841683field",
392            )
393            .unwrap(),
394            Field::<Console>::from_str(
395                "1066299182733912299977577599302716102002738653010828827086884529157392046228field",
396            )
397            .unwrap(),
398            Field::<Console>::from_str(
399                "1977519953625589014039847898215240724041194773120013187722954068145627219929field",
400            )
401            .unwrap(),
402            Field::<Console>::from_str(
403                "1618348632868002512910764605250139381231860094469042556990470848701700964713field",
404            )
405            .unwrap(),
406            Field::<Console>::from_str(
407                "1157459381876765943377450451674060447297483544491073402235960067133285590974field",
408            )
409            .unwrap(),
410        ]);
411        test_case_hash8(10, vec![
412            Field::<Console>::from_str(
413                "3912308888616251672812272013988802988420414245857866136212784631403027079860field",
414            )
415            .unwrap(),
416            Field::<Console>::from_str(
417                "4100923705771018951561873336835055979905965765839649442185404560120892958216field",
418            )
419            .unwrap(),
420            Field::<Console>::from_str(
421                "5701101373789959818781445339314572139971317958997296225671698446757742149719field",
422            )
423            .unwrap(),
424            Field::<Console>::from_str(
425                "5785597627944719799683455467917641287692417422465938462034769734951914291948field",
426            )
427            .unwrap(),
428            Field::<Console>::from_str(
429                "214818498460401597228033958287537426429167258531438668351703993840760770582field",
430            )
431            .unwrap(),
432            Field::<Console>::from_str(
433                "4497884203527978976088488455523871581608892729212445595385399904032800522087field",
434            )
435            .unwrap(),
436            Field::<Console>::from_str(
437                "4010331535874074900042223641934450423780782982190514529696596753456937384201field",
438            )
439            .unwrap(),
440            Field::<Console>::from_str(
441                "6067637133445382691713836557146174628934072680692724940823629181144890569742field",
442            )
443            .unwrap(),
444            Field::<Console>::from_str(
445                "5966421531117752671625849775894572561179958822813329961720805067254995723444field",
446            )
447            .unwrap(),
448        ]);
449    }
450}