1mod 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
32pub type Poseidon2<E> = Poseidon<E, 2>;
34pub type Poseidon4<E> = Poseidon<E, 4>;
36pub type Poseidon8<E> = Poseidon<E, 8>;
38
39#[derive(Clone, Debug)]
40pub struct Poseidon<E: Environment, const RATE: usize> {
41 domain: Field<E>,
43 parameters: Arc<PoseidonParameters<E::Field, RATE, CAPACITY>>,
45 empty_hash: OnceLock<Field<E>>,
47}
48
49impl<E: Environment, const RATE: usize> PartialEq for Poseidon<E, RATE> {
50 fn eq(&self, other: &Self) -> bool {
52 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 pub fn setup(domain: &str) -> Result<Self> {
61 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 pub fn domain(&self) -> Field<E> {
75 self.domain
76 }
77
78 pub fn parameters(&self) -> &Arc<PoseidonParameters<E::Field, RATE, CAPACITY>> {
80 &self.parameters
81 }
82
83 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 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 fn resources_path() -> PathBuf {
112 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
114 path.push("src");
115 path.push("poseidon");
116 path.push("resources");
117
118 if !path.exists() {
120 std::fs::create_dir_all(&path).unwrap_or_else(|_| panic!("Failed to create resources folder: {path:?}"));
121 }
122 path
124 }
125
126 #[track_caller]
128 fn assert_snapshot<S1: Into<String>, S2: Into<String>, C: Debug>(test_folder: S1, test_file: S2, candidate: C) {
129 let mut path = resources_path();
131 path.push(test_folder.into());
132
133 if !path.exists() {
135 std::fs::create_dir(&path).unwrap_or_else(|_| panic!("Failed to create test folder: {path:?}"));
136 }
137
138 path.push(test_file.into());
140 path.set_extension("snap");
141
142 if !path.exists() {
144 std::fs::File::create(&path).unwrap_or_else(|_| panic!("Failed to create file: {path:?}"));
145 }
146
147 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(¶meters);
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 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}