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(¶meters); 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 }