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