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