/ console / algorithms / src / elligator2 / encode.rs
encode.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> Elligator2<E> {
 19      /// Returns the encoded affine group element and sign, given a field element.
 20      pub fn encode(input: &Field<E>) -> Result<(Group<E>, bool)> {
 21          // Compute the encoding of the input field element.
 22          let (encoding, sign_high) = Self::encode_without_cofactor_clear(input)?;
 23  
 24          // Cofactor clear the twisted Edwards element (x, y).
 25          let group = encoding.mul_by_cofactor().to_affine();
 26          ensure!(group.is_on_curve(), "Elligator2 failed: element is not on curve");
 27          ensure!(group.is_in_correct_subgroup_assuming_on_curve(), "Elligator2 failed: element in incorrect subgroup");
 28  
 29          Ok((Group::new(group), sign_high))
 30      }
 31  
 32      /// Returns the encoded affine group element and sign, given a field element.
 33      pub(crate) fn encode_without_cofactor_clear(input: &Field<E>) -> Result<(Group<E>, bool)> {
 34          ensure!(
 35              Group::<E>::EDWARDS_D.legendre().is_qnr(),
 36              "D on the twisted Edwards curve must be a quadratic nonresidue"
 37          );
 38          ensure!(!input.is_zero(), "Inputs to Elligator2 must be nonzero (inverses will fail)");
 39  
 40          let one = Field::<E>::one();
 41  
 42          // Store the sign of the input, to be returned with the output.
 43          let sign_high = input > &input.neg();
 44  
 45          // Compute the mapping from Fq to E(Fq) as a Montgomery element (u, v).
 46          let (u, v) = {
 47              // Compute the coefficients for the Weierstrass form: y^2 == x^3 + A * x^2 + B * x.
 48              let (a, b) = match Group::<E>::MONTGOMERY_B.inverse() {
 49                  Ok(b_inverse) => (Group::MONTGOMERY_A * b_inverse, b_inverse.square()),
 50                  Err(_) => bail!("Montgomery B must be invertible in order to use Elligator2"),
 51              };
 52  
 53              // Let (u, r) = (D, input).
 54              let (u, r) = (Group::EDWARDS_D, input);
 55  
 56              // Let ur2 = u * r^2;
 57              let ur2 = u * r.square();
 58  
 59              // Ensure A^2 * ur^2 != B(1 + ur^2)^2.
 60              ensure!(a.square() * ur2 != b * (one + ur2).square(), "Elligator2 failed: A^2 * ur^2 == B(1 + ur^2)^2");
 61  
 62              // Let v = -A / (1 + ur^2).
 63              let v = -a * (one + ur2).inverse().map_err(|_| anyhow!("Elligator2 failed: (1 + ur^2) == 0"))?;
 64  
 65              // Ensure v is nonzero.
 66              ensure!(!v.is_zero(), "Elligator2 failed: v == 0");
 67  
 68              // Let e = legendre(v^3 + Av^2 + Bv).
 69              let v2 = v.square();
 70              let e = ((v2 * v) + (a * v2) + (b * v)).legendre();
 71  
 72              // Ensure e is nonzero.
 73              ensure!(!e.is_zero(), "Elligator2 failed: e == 0");
 74  
 75              // Let x = ev - ((1 - e) * A/2).
 76              let x = match e {
 77                  LegendreSymbol::Zero => -a * Field::<E>::half(),
 78                  LegendreSymbol::QuadraticResidue => v,
 79                  LegendreSymbol::QuadraticNonResidue => -v - a,
 80              };
 81  
 82              // Ensure x is nonzero.
 83              ensure!(!x.is_zero(), "Elligator2 failed: x == 0");
 84  
 85              // Let y = -e * sqrt(x^3 + Ax^2 + Bx).
 86              let x2 = x.square();
 87              let rhs = (x2 * x) + (a * x2) + (b * x);
 88              let value =
 89                  rhs.even_square_root().map_err(|_| anyhow!("Elligator2 failed: even_sqrt(x^3 + Ax^2 + Bx) failed"))?;
 90  
 91              let y = match e {
 92                  LegendreSymbol::Zero => Field::<E>::zero(),
 93                  LegendreSymbol::QuadraticResidue => -value,
 94                  LegendreSymbol::QuadraticNonResidue => value,
 95              };
 96  
 97              // Ensure y is nonzero.
 98              ensure!(!y.is_zero(), "Elligator2 failed: y == 0");
 99  
100              // Ensure (x, y) is a valid Weierstrass element on: y^2 == x^3 + A * x^2 + B * x.
101              ensure!(y.square() == rhs, "Elligator2 failed: y^2 != x^3 + A * x^2 + B * x");
102  
103              // Convert the Weierstrass element (x, y) to Montgomery element (u, v).
104              let u = x * Group::MONTGOMERY_B;
105              let v = y * Group::MONTGOMERY_B;
106  
107              // Ensure (u, v) is a valid Montgomery element on: B * v^2 == u^3 + A * u^2 + u
108              let u2 = u.square();
109              ensure!(
110                  Group::MONTGOMERY_B * v.square() == (u2 * u) + (Group::MONTGOMERY_A * u2) + u,
111                  "Elligator2 failed: B * v^2 != u^3 + A * u^2 + u"
112              );
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.inverse().map_err(|_| anyhow!("Elligator2 failed: v == 0"))?;
119          let y = (u - one) * (u + one).inverse().map_err(|_| anyhow!("Elligator2 failed: (u + 1) == 0"))?;
120  
121          // Recover the point from the twisted Edwards element (x, y).
122          let point = Group::from_xy_coordinates_unchecked(x, y);
123          // Ensure the recovered point is on the curve.
124          ensure!(point.to_affine().is_on_curve(), "Elligator2 failed: point is not on the curve");
125          // Return the recovered point.
126          Ok((point, sign_high))
127      }
128  }