/ circuit / algorithms / src / poseidon / hash_to_group.rs
hash_to_group.rs
  1  // Copyright (c) 2019-2025 Alpha-Delta Network Inc.
  2  // This file is part of the alphavm 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  use super::*;
 17  
 18  impl<E: Environment, const RATE: usize> HashToGroup for Poseidon<E, RATE> {
 19      type Group = Group<E>;
 20      type Input = Field<E>;
 21      type Scalar = Scalar<E>;
 22  
 23      /// Returns an affine group element from hashing the input.
 24      #[inline]
 25      fn hash_to_group(&self, input: &[Self::Input]) -> Self::Group {
 26          // Ensure that the input is not empty.
 27          if input.is_empty() {
 28              E::halt("Input to hash to group cannot be empty")
 29          }
 30          // Compute `HashMany(input, 2)`.
 31          match self.hash_many(input, 2).iter().collect_tuple() {
 32              // Compute the group element as `MapToGroup(h0) + MapToGroup(h1)`.
 33              Some((h0, h1)) => Elligator2::encode(h1) + Elligator2::encode(h0),
 34              None => E::halt("Failed to compute the hash to group"),
 35          }
 36      }
 37  }
 38  
 39  #[cfg(test)]
 40  mod tests {
 41      use super::*;
 42      use alphavm_circuit_types::environment::Circuit;
 43      use alphavm_curves::{AffineCurve, ProjectiveCurve};
 44  
 45      use anyhow::Result;
 46  
 47      const ITERATIONS: u64 = 100;
 48      const DOMAIN: &str = "PoseidonCircuit0";
 49  
 50      macro_rules! check_hash_to_group {
 51          ($poseidon:ident, $mode:ident, $num_fields:expr, ($num_constants:expr, $num_public:expr, $num_private:expr, $num_constraints:expr)) => {{
 52              // Initialize Poseidon.
 53              let native = console::$poseidon::<<Circuit as Environment>::Network>::setup(DOMAIN)?;
 54              let circuit = $poseidon::<Circuit>::constant(native.clone());
 55  
 56              let rng = &mut TestRng::default();
 57  
 58              for i in 0..ITERATIONS {
 59                  // Sample a random input.
 60                  let input = (0..$num_fields).map(|_| Uniform::rand(rng)).collect::<Vec<_>>();
 61                  // Compute the expected hash.
 62                  let expected = console::HashToGroup::hash_to_group(&native, &input)?;
 63                  // Prepare the circuit input.
 64                  let circuit_input: Vec<Field<_>> = Inject::new(Mode::$mode, input);
 65  
 66                  Circuit::scope(format!("Poseidon HashToGroup {i}"), || {
 67                      // Perform the hash operation.
 68                      let candidate = circuit.hash_to_group(&circuit_input);
 69                      assert_scope!($num_constants, $num_public, $num_private, $num_constraints);
 70                      assert_eq!(expected, candidate.eject_value());
 71  
 72                      // Eject the value to inspect it further.
 73                      let candidate = candidate.eject_value();
 74                      assert!((*candidate).to_affine().is_on_curve());
 75                      assert!((*candidate).to_affine().is_in_correct_subgroup_assuming_on_curve());
 76                      assert_ne!(console::Group::<<Circuit as Environment>::Network>::zero(), candidate);
 77                      assert_ne!(console::Group::<<Circuit as Environment>::Network>::generator(), candidate);
 78  
 79                      let candidate_cofactor_inv = candidate.div_by_cofactor();
 80                      assert_eq!(candidate, candidate_cofactor_inv.mul_by_cofactor());
 81                  });
 82                  Circuit::reset();
 83              }
 84              Ok::<_, anyhow::Error>(())
 85          }};
 86      }
 87  
 88      #[test]
 89      fn test_poseidon2_hash_to_group_constant() -> Result<()> {
 90          check_hash_to_group!(Poseidon2, Constant, 2, (1059, 0, 0, 0))
 91      }
 92  
 93      #[test]
 94      fn test_poseidon2_hash_to_group_public() -> Result<()> {
 95          check_hash_to_group!(Poseidon2, Public, 2, (529, 0, 2026, 2036))
 96      }
 97  
 98      #[test]
 99      fn test_poseidon2_hash_to_group_private() -> Result<()> {
100          check_hash_to_group!(Poseidon2, Private, 2, (529, 0, 2026, 2036))
101      }
102  
103      #[test]
104      fn test_poseidon4_hash_to_group_constant() -> Result<()> {
105          check_hash_to_group!(Poseidon4, Constant, 2, (1059, 0, 0, 0))
106      }
107  
108      #[test]
109      fn test_poseidon4_hash_to_group_public() -> Result<()> {
110          check_hash_to_group!(Poseidon4, Public, 2, (529, 0, 2096, 2106))
111      }
112  
113      #[test]
114      fn test_poseidon4_hash_to_group_private() -> Result<()> {
115          check_hash_to_group!(Poseidon4, Private, 2, (529, 0, 2096, 2106))
116      }
117  
118      #[test]
119      fn test_poseidon8_hash_to_group_constant() -> Result<()> {
120          check_hash_to_group!(Poseidon8, Constant, 2, (1059, 0, 0, 0))
121      }
122  
123      #[test]
124      fn test_poseidon8_hash_to_group_public() -> Result<()> {
125          check_hash_to_group!(Poseidon8, Public, 2, (529, 0, 2236, 2246))
126      }
127  
128      #[test]
129      fn test_poseidon8_hash_to_group_private() -> Result<()> {
130          check_hash_to_group!(Poseidon8, Private, 2, (529, 0, 2236, 2246))
131      }
132  }