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