snarkvm_circuit_algorithms/poseidon/
hash_to_group.rs

1// Copyright 2024 Aleo Network Foundation
2// This file is part of the snarkVM 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
16use super::*;
17
18impl<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(all(test, feature = "console"))]
40mod tests {
41    use super::*;
42    use snarkvm_circuit_types::environment::Circuit;
43    use snarkvm_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}