hash_to_group.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 use super::*; 20 21 impl<E: Environment, const RATE: usize> HashToGroup for Poseidon<E, RATE> { 22 type Input = Field<E>; 23 type Output = Group<E>; 24 25 /// Returns a group element from hashing the input. 26 #[inline] 27 fn hash_to_group(&self, input: &[Self::Input]) -> Result<Self::Output> { 28 // Ensure that the input is not empty. 29 ensure!(!input.is_empty(), "Input to hash to group cannot be empty"); 30 // Compute the group element as `MapToGroup(HashMany(input)[0]) + MapToGroup(HashMany(input)[1])`. 31 match self.hash_many(input, 2).iter().map(Elligator2::<E>::encode).collect_tuple() { 32 Some((Ok((h0, _)), Ok((h1, _)))) => Ok(h0 + h1), 33 _ => bail!("Poseidon failed to compute hash to group on the given input"), 34 } 35 } 36 } 37 38 #[cfg(test)] 39 mod tests { 40 use super::*; 41 use alphavm_console_types::environment::Console; 42 43 type CurrentEnvironment = Console; 44 45 const ITERATIONS: u64 = 1000; 46 47 macro_rules! check_hash_to_group { 48 ($poseidon:ident) => {{ 49 // Initialize Poseidon. 50 let poseidon = $poseidon::<CurrentEnvironment>::setup("HashToGroupTest")?; 51 52 // Ensure an empty input fails. 53 assert!(poseidon.hash_to_group(&[]).is_err()); 54 55 let mut rng = TestRng::default(); 56 57 for _ in 0..ITERATIONS { 58 for num_inputs in 1..8 { 59 // Sample random field elements. 60 let inputs = (0..num_inputs).map(|_| Uniform::rand(&mut rng)).collect::<Vec<_>>(); 61 62 // Compute the hash to group. 63 let candidate = poseidon.hash_to_group(&inputs)?; 64 assert!((*candidate).to_affine().is_on_curve()); 65 assert!((*candidate).to_affine().is_in_correct_subgroup_assuming_on_curve()); 66 assert_ne!(Group::<CurrentEnvironment>::zero(), candidate); 67 assert_ne!(Group::<CurrentEnvironment>::generator(), candidate); 68 69 let candidate_cofactor_inv = candidate.div_by_cofactor(); 70 assert_eq!(candidate, candidate_cofactor_inv.mul_by_cofactor()); 71 } 72 } 73 Ok(()) 74 }}; 75 } 76 77 #[test] 78 fn test_poseidon2_hash_to_group() -> Result<()> { 79 check_hash_to_group!(Poseidon2) 80 } 81 82 #[test] 83 fn test_poseidon4_hash_to_group() -> Result<()> { 84 check_hash_to_group!(Poseidon4) 85 } 86 87 #[test] 88 fn test_poseidon8_hash_to_group() -> Result<()> { 89 check_hash_to_group!(Poseidon8) 90 } 91 }