diff --git a/ec/src/curve_legendre.rs b/ec/src/curve_legendre.rs new file mode 100644 index 0000000..f8372d6 --- /dev/null +++ b/ec/src/curve_legendre.rs @@ -0,0 +1,314 @@ +//! Elliptic curve definition in Legendre form. +//! +//! # Equation +//! +//! The **Legendre** family over a field `F` of odd characteristic is +//! +//! $$ +//! E_\lambda : y^2 = x(x-1)(x-\lambda), +//! \qquad \lambda \neq 0,1. +//! $$ +//! +//! Expanding the right-hand side gives the equivalent Weierstrass model +//! +//! $$ +//! y^2 = x^3 - (1+\lambda)x^2 + \lambda x, +//! $$ +//! +//! so the associated $a$-invariants are +//! +//! $$ +//! (a_1,a_2,a_3,a_4,a_6) = (0,\,-(1+\lambda),\,0,\,\lambda,\,0). +//! $$ +//! +//! # Distinguished points +//! +//! The Legendre form makes the full rational $2$-torsion visible: +//! +//! $$ +//! O,\quad (0,0),\quad (1,0),\quad (\lambda,0). +//! $$ +//! +//! The group identity is the point at infinity `O`. +//! +//! # j-invariant +//! +//! The $j$-invariant of `E_λ` is +//! +//! $$ +//! j(E_\lambda)=256\frac{(\lambda^2-\lambda+1)^3}{\lambda^2(\lambda-1)^2}. +//! $$ +//! +//! # References +//! +//! - Roland Auer and Jaap Top, *Legendre Elliptic Curves over Finite Fields*, +//! Journal of Number Theory 95 (2002), 303–312. +//! - HongFeng Wu and RongQuan Feng, +//! *On the isomorphism classes of Legendre elliptic curves over finite fields*, +//! Sci. China Math. 54(9) (2011), 1885–1890. +use core::fmt; +use fp::field_ops::{FieldOps, FieldRandom}; + +use crate::curve_ops::Curve; +use crate::point_legendre::LegendrePoint; +use crate::curve_weierstrass::WeierstrassCurve; + +/// A Legendre elliptic curve over a field `F`. +/// +/// This stores the parameter `λ` of the curve +/// +/// $$ +/// E_\lambda : y^2 = x(x-1)(x-\lambda). +/// $$ +/// +/// The nonsingular case is exactly `λ != 0` and `λ != 1` +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct LegendreCurve { + /// The Legendre parameter $\lambda$ in: + /// $$ + /// E_{\lambda} : y^2 = x(x - 1)(x - \lambda) + /// $$ + /// + /// For a nonsingular Legendre curve one must have $\lambda \neq 0$ and $\lambda \neq 1$. + pub lambda: F, +} + +impl LegendreCurve { + /// Construct the Legendre curve $y^2 = x(x-1)(x-\lambda).$ + pub fn new(lambda: F) -> Self { + Self { lambda } + } + + /// Returns `true` if and only if the model is singular. + /// + /// For Legendre form, singularity occurs exactly when `λ ∈ {0,1}` + pub fn is_singular(&self) -> bool { + self.lambda == F::zero() || self.lambda == F::one() + } + + /// Returns the affine right-hand side + /// + /// $$ + /// x(x-1)(x-\lambda). + /// $$ + pub fn rhs(&self, x: &F) -> F { + *x * (*x - F::one()) * (*x - self.lambda) + } + /// Checks whether the affine point `(x, y)` satisfies + /// + /// $$ + /// y^2 = x(x-1)(x-\lambda). + /// $$ + pub fn contains(&self, x: &F, y: &F) -> bool { + let lhs = ::square(y); + let rhs = self.rhs(x); + lhs == rhs + } + + /// Samples a random affine point on the curve. + /// + /// This uses a square-root based strategy in odd characteristic: + /// choose a random `x`, compute `rhs(x)`, and return `(x, y)` whenever + /// `rhs(x)` is a square. + /// + /// # Panics + /// + /// Panics if the curve is singular. + pub fn random_point(&self, rng: &mut (impl rand::CryptoRng + rand::Rng)) -> LegendrePoint { + assert!( + !self.is_singular(), + "cannot sample points on a singular Legendre curve" + ); + + loop { + let x = F::random(rng); + let rhs = self.rhs(&x); + + if let Some(y) = rhs.sqrt().into_option() { + let use_neg = (rng.next_u32() & 1) == 1; + let y = if use_neg { -y } else { y }; + return LegendrePoint::new(x, y); + } + } + } + + /// Returns the Weierstrass `a`-invariants of the expanded model + /// + /// $$ + /// y^2 = x^3 - (1+\lambda)x^2 + \lambda x. + /// $$ + /// + /// Namely: + /// + /// $$ + /// 0,\,-(1+\lambda),\,0,\,\lambda,\,0. + /// $$ + pub fn j_invariant_model(&self) -> F { + assert!( + !self.is_singular(), + "j-invariant is undefined for a singular Legendre curve" + ); + + let lambda_sq = ::square(&self.lambda); + let t = lambda_sq - self.lambda + F::one(); + let num = F::from_u64(256) * t * t * t; + + let lambda_minus_one = self.lambda - F::one(); + let den = lambda_sq * ::square(&lambda_minus_one); + + let den_inv = den + .invert() + .into_option() + .expect("denominator must be invertible for λ != 0,1"); + + num * den_inv + } + + /// Returns the a-invariants of + /// + /// y² = x³ - (1+λ)x² + λx + /// + /// i.e. [a1, a2, a3, a4, a6] = [0, -(1+λ), 0, λ, 0]. + pub fn a_invariants(&self) -> [F; 5] { + [ + F::zero(), + -(F::one() + self.lambda), + F::zero(), + self.lambda, + F::zero(), + ] + } + + /// Returns the same curve written in general Weierstrass form. + /// + /// The Legendre equation + /// + /// `y² = x(x-1)(x-λ)` + /// + /// expands to + /// + /// `y² = x³ - (1+λ)x² + λx`, + /// + /// so the corresponding Weierstrass coefficients are + /// + /// `[a1, a2, a3, a4, a6] = [0, -(1+λ), 0, λ, 0]`. + pub fn to_weierstrass(&self) -> WeierstrassCurve { + WeierstrassCurve::new( + F::zero(), + -(F::one() + self.lambda), + F::zero(), + self.lambda, + F::zero(), + ) + } + + /// Returns a short Weierstrass model isomorphic to this Legendre curve. + /// + /// Over fields of characteristic different from `2` and `3`, the change of + /// variables + /// + /// `x = X + (1+λ)/3`, `y = Y` + /// + /// transforms + /// + /// `y² = x³ - (1+λ)x² + λx` + /// + /// into + /// + /// `Y² = X³ + AX + B` + /// + /// where + /// + /// `A = -(λ² - λ + 1)/3` + /// + /// `B = -((λ + 1)(λ - 2)(2λ - 1))/27`. + /// + /// # Panics + /// + /// Panics if the characteristic is `<= 3`, or if the curve is singular. + pub fn to_short_weierstrass(&self) -> WeierstrassCurve { + assert!( + !self.is_singular(), + "cannot convert a singular Legendre curve to short Weierstrass form" + ); + assert!( + F::characteristic()[0] > 3, + "short Weierstrass conversion requires characteristic != 2, 3" + ); + + let two = F::from_u64(2); + let three = F::from_u64(3); + let twenty_seven = F::from_u64(27); + + let three_inv = three + .invert() + .into_option() + .expect("3 must be invertible in characteristic != 3"); + + let twenty_seven_inv = twenty_seven + .invert() + .into_option() + .expect("27 must be invertible in characteristic != 3"); + + let lambda_sq = ::square(&self.lambda); + + let a = -(lambda_sq - self.lambda + F::one()) * three_inv; + + let b = -( + (self.lambda + F::one()) + * (self.lambda - two) + * (two * self.lambda - F::one()) + ) * twenty_seven_inv; + + WeierstrassCurve::new_short(a, b) + } +} + +impl fmt::Display for LegendreCurve +where + F: FieldOps + fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if f.alternate() { + write!( + f, + "LegendreCurve {{\n y^2 = x(x-1)(x - {})\n}}", + self.lambda + ) + } else { + write!(f, "y^2 = x(x-1)(x - {})", self.lambda) + } + } +} + +impl Curve for LegendreCurve { + type BaseField = F; + type Point = LegendrePoint; + + fn is_on_curve(&self, point: &Self::Point) -> bool { + if point.infinity { + true + } else { + let lhs = ::square(&point.y); + let rhs = self.rhs(&point.x); + lhs == rhs + } + } + + fn random_point(&self, rng: &mut (impl rand::CryptoRng + rand::Rng)) -> Self::Point { + LegendreCurve::random_point(self, rng) + } + + /// Returns the $j$-invariant of the curve. + /// + /// This is a complete invariant of elliptic curves over algebraically + /// closed fields up to isomorphism. + fn j_invariant(&self) -> F { + LegendreCurve::j_invariant_model(&self) + } + + /// Returns the $a$-invariants as a vector. + fn a_invariants(&self) -> Vec { + LegendreCurve::a_invariants(self).to_vec() + } +} diff --git a/ec/src/lib.rs b/ec/src/lib.rs index 25d9ae6..afa8cb3 100644 --- a/ec/src/lib.rs +++ b/ec/src/lib.rs @@ -14,11 +14,13 @@ pub mod point_montgomery; pub mod curve_montgomery; pub mod point_edwards; pub mod curve_edwards; - pub mod curve_jacobi_quartic; pub mod point_jacobi_quartic; pub mod curve_jacobi_intersection; pub mod point_jacobi_intersection; - pub mod curve_hessian; pub mod point_hessian; +/// Point used in the Legendre form. +pub mod point_legendre; +///! Elliptic curve definition in Legendre form. +pub mod curve_legendre; \ No newline at end of file diff --git a/ec/src/point_legendre.rs b/ec/src/point_legendre.rs new file mode 100644 index 0000000..8844a01 --- /dev/null +++ b/ec/src/point_legendre.rs @@ -0,0 +1,362 @@ +//! Point representation and group law for Legendre curves. +//! +//! We work with the Legendre family +//! +//! $$ +//! E_\lambda : y^2 = x(x-1)(x-\lambda) +//! $$ +//! +//! over fields of odd characteristic. +//! +//! # Representation +//! +//! Points are stored in affine coordinates `(x, y)` together with a distinguished +//! point at infinity `O`, represented by `infinity = true`. +//! +//! # Group law +//! +//! Writing the curve as +//! +//! $$ +//! y^2 = x^3 - (1+\lambda)x^2 + \lambda x, +//! $$ +//! +//! the affine group law is the usual chord-and-tangent law for a Weierstrass +//! model with an `x²` term. +//! +//! - Identity: `O` +//! - Negation: `-(x,y) = (x,-y)` +//! - Visible 2-torsion: +//! +//! $$ +//! (0,0),\quad (1,0),\quad (\lambda,0). +//! $$ +//! +//! For distinct finite points `P=(x₁,y₁)` and `Q=(x₂,y₂)` with `x₁ != x₂`: +//! +//! $$ +//! m = \frac{y_2-y_1}{x_2-x_1}, +//! $$ +//! +//! $$ +//! x_3 = m^2 + (1+\lambda) - x_1 - x_2, +//! \qquad +//! y_3 = m(x_1-x_3)-y_1. +//! $$ +//! +//! For doubling `P=(x₁,y₁)` with `y₁ != 0`: +//! +//! $$ +//! m = \frac{3x_1^2 - 2(1+\lambda)x_1 + \lambda}{2y_1}, +//! $$ +//! +//! $$ +//! x_3 = m^2 + (1+\lambda) - 2x_1, +//! \qquad +//! y_3 = m(x_1-x_3)-y_1. +//! $$ +//! +//! # References +//! +//! - Roland Auer and Jaap Top, *Legendre Elliptic Curves over Finite Fields*, +//! Journal of Number Theory 95 (2002), 303–312. +//! - HongFeng Wu and RongQuan Feng, +//! *On the isomorphism classes of Legendre elliptic curves over finite fields*, +//! Sci. China Math. 54(9) (2011), 1885–1890. + +use core::fmt; +//use std::os::unix::raw::ino_t; +//use crypto_bigint::modular::ConstMontyForm; +use crate::curve_legendre::LegendreCurve; +use crate::point_ops::PointOps; +use fp::field_ops::FieldOps; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; + +/// A point on a Legendre elliptic curve over `F`. +/// +/// The identity element is the point at infinity. It is represented by +/// `infinity = true`; in that case the `x` and `y` fields are dummy values. +#[derive(Debug, Clone, Copy)] +pub struct LegendrePoint { + /// Affine x-coordinate. + pub x: F, + /// Affine y-coordinate. + pub y: F, + /// Whether this point is the point at infinity. + pub infinity: bool, +} + +impl PartialEq for LegendrePoint +where + F: FieldOps + ConstantTimeEq, +{ + fn eq(&self, other: &Self) -> bool { + match (self.infinity, other.infinity) { + (true, true) => true, + (true, false) => false, + (false, true) => false, + (false, false) => self.x == other.x && self.y == other.y, + } + } +} + +impl fmt::Display for LegendrePoint +where + F: FieldOps + fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.infinity { + if f.alternate() { + write!(f, "LegendrePoint {{ O }}") + } else { + write!(f, "O") + } + } else if f.alternate() { + write!(f, "LegendrePoint {{ x = {}, y = {} }}", self.x, self.y) + } else { + write!(f, "({}, {})", self.x, self.y) + } + } +} + +impl LegendrePoint { + /// Constructs a finite affine point `(x, y)`. + /// + /// No on-curve check is performed. Use + /// [`LegendreCurve::contains`] or [`crate::curve_ops::Curve::is_on_curve`] + /// if validation is needed. + pub fn new(x: F, y: F) -> Self { + Self { x, y, infinity: false } + } + + /// Returns the identity element `O`. + /// + /// Internally this is represented by `infinity = true`; the stored + /// coordinates are dummy values. + pub fn identity() -> Self { + Self { + x: F::zero(), + y: F::one(), + infinity: true, + } + } + + /// Returns `true` if this point is the identity. + pub fn is_identity(&self) -> bool { + self.infinity + } +} +impl ConditionallySelectable for LegendrePoint +where + F: FieldOps + Copy, +{ + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + let ai = a.infinity as u8; + let bi = b.infinity as u8; + let infinity = u8::conditional_select(&ai, &bi, choice) != 0; + + Self { + x: F::conditional_select(&a.x, &b.x, choice), + y: F::conditional_select(&a.y, &b.y, choice), + infinity, + } + } + + fn conditional_assign(&mut self, other: &Self, choice: Choice) { + F::conditional_assign(&mut self.x, &other.x, choice); + F::conditional_assign(&mut self.y, &other.y, choice); + + let mut inf = self.infinity as u8; + let other_inf = other.infinity as u8; + inf.conditional_assign(&other_inf, choice); + self.infinity = inf != 0; + } + + fn conditional_swap(a: &mut Self, b: &mut Self, choice: Choice) { + F::conditional_swap(&mut a.x, &mut b.x, choice); + F::conditional_swap(&mut a.y, &mut b.y, choice); + + let mut ai = a.infinity as u8; + let mut bi = b.infinity as u8; + u8::conditional_swap(&mut ai, &mut bi, choice); + a.infinity = ai != 0; + b.infinity = bi != 0; + } +} + +impl ConstantTimeEq for LegendrePoint +where + F: FieldOps + Copy + ConstantTimeEq, +{ + fn ct_eq(&self, other: &Self) -> Choice { + let self_inf = Choice::from(self.infinity as u8); + let other_inf = Choice::from(other.infinity as u8); + + let both_inf = self_inf & other_inf; + let both_finite = !self_inf & !other_inf; + let coords_eq = self.x.ct_eq(&other.x) & self.y.ct_eq(&other.y); + + both_inf | (both_finite & coords_eq) + } + + fn ct_ne(&self, other: &Self) -> Choice { + !self.ct_eq(other) + } +} + +impl LegendrePoint { + /// + /// For a finite affine point `(x, y)` on a Legendre curve, the inverse is + /// `(x, -y)`. The point at infinity is its own inverse. + pub fn negate(&self, _curve: &LegendreCurve) -> Self { + if self.infinity { + return Self::identity(); + } + Self::new(self.x, -self.y) + } + + /// Doubles the point: returns `[2]P`. + /// + /// If `P = O`, returns `O`. + /// If `y = 0`, then `P` is a nontrivial 2-torsion point and `[2]P = O`. + pub fn double(&self, curve: &LegendreCurve) -> Self { + if self.infinity { + return Self::identity(); + } + + let denom = ::double(&self.y); + let denom_inv = match denom.invert().into_option() { + Some(inv) => inv, + None => return Self::identity(), + }; + + let x1_sq = ::square(&self.x); + let one_plus_lambda = F::one() + curve.lambda; + + let m = (F::from_u64(3) * x1_sq + - (F::from_u64(2) * one_plus_lambda) * self.x + + curve.lambda) + * denom_inv; + + let x3 = ::square(&m) + one_plus_lambda - F::from_u64(2) * self.x; + let y3 = m * (self.x - x3) - self.y; + + Self::new(x3, y3) + } + + /// Adds two points: returns `P + Q`. + /// + /// Handles the usual special cases: + /// - `O + Q = Q` + /// - `P + O = P` + /// - `P = Q` uses doubling + /// - `P = -Q` returns `O` + /// + /// For distinct finite points `P = (x1, y1)` and `Q = (x2, y2)` with + /// `x1 != x2`, the slope is + /// + /// `m = (y2 - y1) / (x2 - x1)` + /// + /// and + /// + /// `x3 = m^2 + (1+λ) - x1 - x2` + /// + /// `y3 = m(x1 - x3) - y1`. + pub fn add(&self, other: &Self, curve: &LegendreCurve) -> Self { + if self.infinity { + return *other; + } + if other.infinity { + return *self; + } + + if self.x == other.x { + if self.y == other.y { + return self.double(curve); + } + return Self::identity(); + } + + let dx = other.x - self.x; + let dy = other.y - self.y; + + let dx_inv = dx + .invert() + .into_option() + .expect("x1 != x2, so dx must be invertible"); + + let m = dy * dx_inv; + let one_plus_lambda = F::one() + curve.lambda; + + let x3 = ::square(&m) + one_plus_lambda - self.x - other.x; + let y3 = m * (self.x - x3) - self.y; + + Self::new(x3, y3) + } + + /// Multiply `self` by `k` + /// + /// # Arguments + /// + /// * `&self` - Point on curve (type: `Self`) + /// * `k` - Integer (type: `&[u64]`) + /// * `curve` - The curve we're on (type: `& as PointOps>::Curve`) + /// + /// # Returns + /// + /// The point `k * self` (type: `Self`) + pub fn scalar_mul(&self, k: &[u64], curve: & as PointOps>::Curve) -> Self { + let mut r0 = Self::identity(); + let mut r1 = self.clone(); + + for &limb in k.iter().rev() { + for bit in (0..64).rev() { + let choice = Choice::from(((limb >> bit) & 1) as u8); + + Self::conditional_swap(&mut r0, &mut r1, choice); + + let sum = r0.add(&r1, curve); + let dbl = r0.double(curve); + r1 = sum; + r0 = dbl; + + Self::conditional_swap(&mut r0, &mut r1, choice); + } + } + + r0 + } +} + +impl PointOps for LegendrePoint +where + F: FieldOps, +{ + type BaseField = F; + type Curve = LegendreCurve; + + fn identity(_curve: &Self::Curve) -> Self { + LegendrePoint::::identity() + } + + fn is_identity(&self) -> bool { + self.infinity + } + + fn negate(&self, curve: &Self::Curve) -> Self { + LegendrePoint::::negate(self, curve) + } + + fn scalar_mul(&self, k: &[u64], curve: &Self::Curve) -> Self { + LegendrePoint::::scalar_mul(self, k, curve) + } +} + +impl crate::point_ops::PointAdd for LegendrePoint +where + F: FieldOps, +{ + fn add(&self, other: &Self, curve: &Self::Curve) -> Self { + LegendrePoint::::add(self, other, curve) + } +} diff --git a/ec/tests/curves_legendre_tests.rs b/ec/tests/curves_legendre_tests.rs new file mode 100644 index 0000000..34253a2 --- /dev/null +++ b/ec/tests/curves_legendre_tests.rs @@ -0,0 +1,132 @@ +use crypto_bigint::{Uint, const_prime_monty_params}; +use fp::fp_element::FpElement; +use fp::field_ops::FieldOps; +use ec::curve_legendre::LegendreCurve; +use ec::curve_ops::Curve; +use ec::point_legendre::LegendrePoint; + +const_prime_monty_params!(Fp19Mod, Uint<1>, "0000000000000013", 2); +type F19 = FpElement; +fn fp(n: u64) -> F19 { + F19::from_u64(n) +} + +fn curve() -> LegendreCurve { + LegendreCurve::new(fp(3)) +} + +#[test] +fn legendre_curve_detects_singularity() { + assert!(LegendreCurve::new(F19::zero()).is_singular()); + assert!(LegendreCurve::new(F19::one()).is_singular()); + assert!(!curve().is_singular()); +} + +#[test] +fn legendre_curve_a_invariants_are_correct() { + let c = curve(); + let a = c.a_invariants(); + + let four = F19::from(FpElement::from_u64(4)); + let three = F19::from(FpElement::from_u64(3)); + + assert_eq!(a.len(), 5); + assert_eq!(a[0], F19::zero()); + assert_eq!(a[1], -four); // -(1 + λ) = -(1 + 3) = -4 + assert_eq!(a[2], F19::zero()); + assert_eq!(a[3], F19::from(three)); + assert_eq!(a[4], F19::zero()); +} + +#[test] +fn legendre_curve_identity_is_on_curve() { + let c = curve(); + assert!(c.is_on_curve(&LegendrePoint::identity())); +} + +#[test] +fn legendre_curve_visible_two_torsion_is_on_curve() { + let c = curve(); + let three = F19::from(FpElement::from_u64(3)); + + + assert!(c.is_on_curve(&LegendrePoint::new(F19::zero(), F19::zero()))); + assert!(c.is_on_curve(&LegendrePoint::new(F19::one(), F19::zero()))); + assert!(c.is_on_curve(&LegendrePoint::new(three, F19::zero()))); +} + +#[test] +fn legendre_curve_rejects_off_curve_point() { + let c = curve(); + let two = F19::from(FpElement::from_u64(2)); + + assert!(!c.is_on_curve(&LegendrePoint::new(two, two))); +} + +#[test] +fn legendre_curve_j_invariant_matches_closed_formula() { + let c = curve(); + let lambda = F19::from(FpElement::from_u64(3)); + let two_five_six = F19::from(FpElement::from_u64(256)); + + + + + let lambda_sq = ::square(&lambda); + let t = lambda_sq - lambda + F19::one(); + let num = two_five_six * t * t * t; + + let den = lambda_sq * ::square(&(lambda - F19::one())); + let den_inv = den.invert().into_option().unwrap(); + + let expected = num * den_inv; + + assert_eq!(c.j_invariant(), expected); +} + + +#[test] +fn legendre_to_general_weierstrass_matches_a_invariants() { + let c = curve(); // e.g. λ = 3 over F_19 + let w = c.to_weierstrass(); + + assert_eq!(w.a1, F19::zero()); + assert_eq!(w.a2, -(F19::one() + c.lambda)); + assert_eq!(w.a3, F19::zero()); + assert_eq!(w.a4, c.lambda); + assert_eq!(w.a6, F19::zero()); +} + +#[test] +fn legendre_to_general_weierstrass_preserves_j() { + let c = curve(); + let w = c.to_weierstrass(); + + assert_eq!(c.j_invariant(), w.j_invariant()); +} + +#[test] +fn legendre_to_short_weierstrass_preserves_j() { + let c = curve(); + let w = c.to_short_weierstrass(); + + assert_eq!(c.j_invariant(), w.j_invariant()); +} + +#[test] +fn legendre_short_weierstrass_coordinate_shift_works() { + let c = curve(); + let w = c.to_short_weierstrass(); + + let p = LegendrePoint::new(fp(2), fp(6)); + assert!(c.is_on_curve(&p)); + + let three = fp(3); + let three_inv = three.invert().into_option().unwrap(); + let shift = (F19::one() + c.lambda) * three_inv; + + let xw = p.x - shift; + let yw = p.y; + + assert!(w.contains(&xw, &yw)); +} \ No newline at end of file diff --git a/ec/tests/point_legendre_tests.rs b/ec/tests/point_legendre_tests.rs new file mode 100644 index 0000000..3c40089 --- /dev/null +++ b/ec/tests/point_legendre_tests.rs @@ -0,0 +1,238 @@ +//! Tests for Legendre point operations (group law and scalar multiplication). +//! +//! We work over F_19 with the Legendre curve +//! +//! y^2 = x(x-1)(x-3). +//! +//! This curve has 16 rational points in total, and the point P = (2,6) +//! has order 8. The visible 2-torsion points are (0,0), (1,0), and (3,0). + +use crypto_bigint::{const_prime_monty_params, Uint}; + +use fp::field_ops::FieldOps; +use fp::fp_element::FpElement; + +use ec::curve_legendre::LegendreCurve; +use ec::curve_ops::Curve; +use ec::point_legendre::LegendrePoint; + +// =========================================================================== +// Field definitions +// =========================================================================== + +const_prime_monty_params!(Fp19Mod, Uint<1>, "0000000000000013", 2); +type F19 = FpElement; + +fn fp(n: u64) -> F19 { + F19::from_u64(n) +} + +fn curve() -> LegendreCurve { + LegendreCurve::new(fp(3)) +} + +// =========================================================================== +// Helpers +// =========================================================================== + +/// Brute-force all affine points on y^2 = x(x-1)(x-3) over F_19. +fn all_legendre_19(c: &LegendreCurve) -> Vec> { + let mut pts = Vec::new(); + for xv in 0..19u64 { + for yv in 0..19u64 { + let p = LegendrePoint::new(fp(xv), fp(yv)); + if c.is_on_curve(&p) { + pts.push(p); + } + } + } + pts +} + +/// A point of order 8 on the test curve. +fn p() -> LegendrePoint { + LegendrePoint::new(fp(2), fp(6)) +} + +// =========================================================================== +// Point tests over F_19 +// =========================================================================== + +#[test] +fn f19_identity_is_identity() { + let id = LegendrePoint::::identity(); + + assert!(id.is_identity()); + assert!(id.infinity); + assert_eq!(id.x, F19::zero()); + assert_eq!(id.y, F19::one()); +} +#[test] +fn f19_add_identity() { + let c = curve(); + let id = LegendrePoint::::identity(); + let p = p(); + + assert_eq!(p.add(&id, &c), p); + assert_eq!(id.add(&p, &c), p); +} + +#[test] +fn f19_negation() { + let c = curve(); + let p = p(); + let neg = p.negate(&c); + + assert_eq!(neg, LegendrePoint::new(fp(2), fp(13))); + assert!(p.add(&neg, &c).is_identity()); +} + +#[test] +fn f19_negate_then_add_for_all_points() { + let c = curve(); + for p in all_legendre_19(&c) { + let neg = p.negate(&c); + assert!( + p.add(&neg, &c).is_identity(), + "P + (-P) != O for P = {}", + p + ); + } +} + +#[test] +fn f19_visible_two_torsion_doubles_to_identity() { + let c = curve(); + + let t0 = LegendrePoint::new(fp(0), fp(0)); + let t1 = LegendrePoint::new(fp(1), fp(0)); + let tl = LegendrePoint::new(fp(3), fp(0)); + + assert_eq!(t0.double(&c), LegendrePoint::identity()); + assert_eq!(t1.double(&c), LegendrePoint::identity()); + assert_eq!(tl.double(&c), LegendrePoint::identity()); +} + +#[test] +fn f19_double_known_vector() { + let c = curve(); + let p = p(); + + // 2*(2,6) = (7,15) + let p2 = p.double(&c); + assert_eq!(p2, LegendrePoint::new(fp(7), fp(15))); + assert!(c.is_on_curve(&p2)); +} + +#[test] +fn f19_add_known_vector() { + let c = curve(); + let p = p(); + let q = LegendrePoint::new(fp(7), fp(15)); + + // (2,6) + (7,15) = (18,7) + let sum = p.add(&q, &c); + assert_eq!(sum, LegendrePoint::new(fp(18), fp(7))); + assert!(c.is_on_curve(&sum)); +} + +#[test] +fn f19_add_self_matches_double() { + let c = curve(); + let p = p(); + + assert_eq!(p.add(&p, &c), p.double(&c)); +} + +#[test] +fn f19_double_on_curve_for_all_points() { + let c = curve(); + for p in all_legendre_19(&c) { + let dbl = p.double(&c); + assert!(c.is_on_curve(&dbl)); + } +} + +#[test] +fn f19_add_on_curve_small_sample() { + let c = curve(); + let pts = all_legendre_19(&c); + + for i in 0..pts.len().min(8) { + for j in 0..pts.len().min(8) { + let s = pts[i].add(&pts[j], &c); + assert!(c.is_on_curve(&s)); + } + } +} + +#[test] +fn f19_commutativity_small_sample() { + let c = curve(); + let pts = all_legendre_19(&c); + + for i in 0..pts.len().min(8) { + for j in (i + 1)..pts.len().min(8) { + assert_eq!(pts[i].add(&pts[j], &c), pts[j].add(&pts[i], &c)); + } + } +} + +#[test] +fn f19_associativity_small_sample() { + let c = curve(); + let pts = all_legendre_19(&c); + + assert!(pts.len() >= 3); + let p = pts[0]; + let q = pts[1]; + let r = pts[2]; + + assert_eq!( + p.add(&q, &c).add(&r, &c), + p.add(&q.add(&r, &c), &c), + ); +} + +#[test] +fn f19_scalar_mul_order() { + let c = curve(); + let p = p(); + + // P = (2,6) has order 8 on this curve. + assert!(p.scalar_mul(&[8], &c).is_identity()); +} + +#[test] +fn f19_scalar_mul_consistency() { + let c = curve(); + let p = p(); + + let seven_p = p.scalar_mul(&[7], &c); + let mut acc = LegendrePoint::::identity(); + for _ in 0..7 { + acc = acc.add(&p, &c); + } + + assert_eq!(seven_p, acc); +} + +#[test] +fn f19_scalar_mul_double() { + let c = curve(); + let p = p(); + + assert_eq!(p.scalar_mul(&[2], &c), p.double(&c)); +} + +#[test] +fn f19_chain_on_curve() { + let c = curve(); + let p = p(); + + let mut acc = p; + for _ in 1..16 { + acc = acc.add(&p, &c); + assert!(c.is_on_curve(&acc)); + } +} \ No newline at end of file