snarkvm_circuit_algorithms/elligator2/
encode.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> Elligator2<E> {
19    /// Returns the encoded affine group element, given a field element.
20    /// Note: Unlike the console implementation, this function does not return the sign bit.
21    pub fn encode(input: &Field<E>) -> Group<E> {
22        // Ensure D on the twisted Edwards curve is a quadratic nonresidue.
23        debug_assert!(console::Group::<E::Network>::EDWARDS_D.legendre().is_qnr());
24
25        // Ensure the input is nonzero.
26        E::assert_neq(input, Field::<E>::zero());
27
28        // Define `1` as a constant.
29        let one = Field::one();
30
31        // Define the Montgomery curve coefficients A and B.
32        let montgomery_a = Field::constant(console::Group::<E::Network>::MONTGOMERY_A);
33        let montgomery_b = Field::constant(console::Group::<E::Network>::MONTGOMERY_B);
34        let montgomery_b_inverse = montgomery_b.inverse();
35        let montgomery_b2 = montgomery_b.square();
36        let montgomery_b3 = &montgomery_b2 * &montgomery_b;
37
38        // Define the twisted Edwards curve coefficient D.
39        let edwards_d = Field::constant(console::Group::<E::Network>::EDWARDS_D);
40
41        // Define the coefficients for the Weierstrass form: y^2 == x^3 + A * x^2 + B * x.
42        let a = &montgomery_a * &montgomery_b_inverse;
43        let a_half = &a * Field::constant(console::Field::half());
44        let b = montgomery_b_inverse.square();
45
46        // Define the MODULUS_MINUS_ONE_DIV_TWO as a constant.
47        let modulus_minus_one_div_two = match E::BaseField::from_bigint(E::BaseField::modulus_minus_one_div_two()) {
48            Some(modulus_minus_one_div_two) => Field::constant(console::Field::new(modulus_minus_one_div_two)),
49            None => E::halt("Failed to initialize MODULUS_MINUS_ONE_DIV_TWO as a constant"),
50        };
51
52        // Compute the mapping from Fq to E(Fq) as a Montgomery element (u, v).
53        let (u, v) = {
54            // Let ur2 = D * input^2;
55            let ur2 = edwards_d * input.square();
56            let one_plus_ur2 = &one + &ur2;
57            // Verify A^2 * ur^2 != B(1 + ur^2)^2.
58            E::assert_neq(a.square() * &ur2, &b * one_plus_ur2.square());
59
60            // Let v = -A / (1 + ur^2).
61            let v = -&a / one_plus_ur2;
62
63            // Let e = legendre(v^3 + Av^2 + Bv).
64            let v2 = v.square();
65            let e = ((&v2 * &v) + (&a * &v2) + (&b * &v)).pow(modulus_minus_one_div_two);
66
67            // Let x = ev - ((1 - e) * A/2).
68            let x = (&e * &v) - ((&one - &e) * a_half);
69
70            // Let y = -e * sqrt(x^3 + Ax^2 + Bx).
71            let x2 = x.square();
72            let x3 = &x2 * &x;
73            let rhs = &x3 + (&a * &x2) + (&b * &x);
74
75            // Witness the even square root of `rhs`.
76            let rhs_square_root: Field<E> = witness!(|rhs| {
77                match rhs.square_root() {
78                    Ok(sqrt) => {
79                        // Get the least significant bit of the square root.
80                        // Note that the unwrap is safe since the number of bits is always greater than zero.
81                        match *sqrt.to_bits_be().last().unwrap() {
82                            // If the lsb is set, return the negated square root.
83                            true => -sqrt,
84                            // Otherwise, return the square root.
85                            false => sqrt,
86                        }
87                    }
88                    Err(_) => console::Field::zero(),
89                }
90            });
91            // Verify that the square root is even.
92            // Note that the unwrap is safe since the number of bits is always greater than zero,
93            E::assert(!rhs_square_root.to_bits_be().last().unwrap());
94
95            let y = -&e * rhs_square_root;
96
97            // Ensure v * e * x * y != 0.
98            E::assert_neq(&v * &e * &x * &y, Field::<E>::zero());
99
100            // Ensure (x, y) is a valid Weierstrass element on: y^2 == x^3 + A * x^2 + B * x.
101            let y2 = y.square();
102            E::assert_eq(&y2, rhs);
103
104            // Convert the Weierstrass element (x, y) to Montgomery element (u, v).
105            let u = x * &montgomery_b;
106            let v = y * &montgomery_b;
107
108            // Ensure (u, v) is a valid Montgomery element on: B * v^2 == u^3 + A * u^2 + u
109            let u2 = &x2 * &montgomery_b2;
110            let u3 = &x3 * &montgomery_b3;
111            let v2 = &y2 * &montgomery_b3;
112            E::assert_eq(v2, u3 + (montgomery_a * u2) + &u);
113
114            (u, v)
115        };
116
117        // Convert the Montgomery element (u, v) to the twisted Edwards element (x, y).
118        let x = &u / v;
119        let y = (&u - &one) / (u + &one);
120
121        // Recover the point and check that it is 1) on the curve, and 2) in the correct subgroup.
122        let encoding = Group::from_xy_coordinates_unchecked(x, y);
123        // Ensure the encoding is on the curve.
124        encoding.enforce_on_curve();
125        // Cofactor clear the twisted Edwards element (x, y).
126        encoding.mul_by_cofactor()
127    }
128}
129
130#[cfg(all(test, feature = "console"))]
131mod tests {
132    use super::*;
133    use snarkvm_circuit_types::environment::Circuit;
134    use snarkvm_utilities::{TestRng, Uniform};
135
136    const ITERATIONS: u64 = 1_000;
137
138    fn check_encode(mode: Mode, num_constants: u64, num_public: u64, num_private: u64, num_constraints: u64) {
139        let mut rng = TestRng::default();
140
141        for _ in 0..ITERATIONS {
142            // Sample a random element.
143            let given = Uniform::rand(&mut rng);
144
145            // Compute the expected native result.
146            let (expected, _sign) = console::Elligator2::<<Circuit as Environment>::Network>::encode(&given).unwrap();
147
148            // Initialize the input field element.
149            let input = Field::<Circuit>::new(mode, given);
150
151            // Encode the input.
152            Circuit::scope("Elligator2::encode", || {
153                let candidate = Elligator2::encode(&input);
154                assert_eq!(expected, candidate.eject_value());
155                assert_scope!(num_constants, num_public, num_private, num_constraints);
156            });
157            Circuit::reset();
158        }
159    }
160
161    #[test]
162    fn test_encode_constant() {
163        check_encode(Mode::Constant, 527, 0, 0, 0);
164    }
165
166    #[test]
167    fn test_encode_public() {
168        check_encode(Mode::Public, 263, 0, 875, 880);
169    }
170
171    #[test]
172    fn test_encode_private() {
173        check_encode(Mode::Private, 263, 0, 875, 880);
174    }
175}