Skip to main content

ec/
curve_edwards.rs

1//! Elliptic curve definition in Edwards form.
2//!
3//! # Odd characteristic
4//!
5//! The curve is given by
6//!
7//! $$
8//! x^2 + y^2 = 1 + d x^2 y^2
9//! $$
10//!
11//! Parameters: $d_1 = 0$ (unused), $d_2 = d$.
12//! Identity: $(0, 1)$. Complete when $d$ is not a square.
13//!
14//! # Characteristic $2$
15//!
16//! The curve is given by
17//!
18//! $$
19//! d_1(x + y) + d_2(x^2 + y^2)
20//! = xy + xy(x + y) + x^2 y^2
21//! $$
22//!
23//! Parameters: $d_1, d_2$ with $d_1 \ne 0$, $d_2 \ne d_1^2 + d_1$.
24//! Identity: $(0, 0)$. Complete when $\mathrm{Tr}(d_2) = 1$.
25//!
26//! # References
27//!
28//! - Binary Edwards curves (characteristic $2$): Bernstein–Lange–Rezaeian Farashahi (2008)
29//! - Odd characteristic: <https://hyperelliptic.org/EFD/g1p/auto-edwards.html>
30
31use core::fmt;
32use fp::field_ops::{FieldOps, FieldRandom};
33
34use crate::curve_ops::Curve;
35use crate::point_edwards::EdwardsPoint;
36
37/// An Edwards curve over a field `F`, covering both odd and even characteristic.
38///
39/// In odd characteristic only `d2` is used (the parameter `d`).
40/// In characteristic 2 both `d1` and `d2` are used.
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct EdwardsCurve<F: FieldOps> {
43    /// The invariant d1 in the equation
44    pub d1: F,
45    /// The invariant d2 in the equation
46    pub d2: F,
47}
48
49impl<F> fmt::Display for EdwardsCurve<F>
50where
51    F: FieldOps + fmt::Display,
52{
53    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54        if F::characteristic()[0] != 2 {
55            if f.alternate() {
56                write!(
57                    f,
58                    "EdwardsCurve {{\n  x^2 + y^2 = 1 + d x^2 y^2\n  d = {}\n}}",
59                    self.d2
60                )
61            } else {
62                write!(f, "x^2 + y^2 = 1 + ({})x^2y^2", self.d2)
63            }
64        } else {
65            if f.alternate() {
66                write!(
67                    f,
68                    "EdwardsCurve {{\n  d1(x+y) + d2(x^2+y^2) = xy + xy(x+y) + x^2y^2\n  d1 = {}\n  d2 = {}\n}}",
69                    self.d1, self.d2
70                )
71            } else {
72                write!(
73                    f,
74                    "{}(x+y) + {}(x^2+y^2) = xy + xy(x+y) + x^2y^2",
75                    self.d1, self.d2
76                )
77            }
78        }
79    }
80}
81
82impl<F: FieldOps + FieldRandom> EdwardsCurve<F> {
83    /// Construct an odd-characteristic Edwards curve `x² + y² = 1 + d·x²·y²`.
84    ///
85    /// Stores `d` as `d2`; `d1` is set to zero (unused).
86    pub fn new(d: F) -> Self {
87        assert!(F::characteristic()[0] != 2, "use new_binary() for char 2");
88        assert!(d != F::zero(), "d must be nonzero");
89        assert!(d != F::one(), "d must not be 1");
90        Self {
91            d1: F::zero(),
92            d2: d,
93        }
94    }
95
96    /// Construct a binary Edwards curve
97    /// `d₁(x+y) + d₂(x²+y²) = xy + xy(x+y) + x²y²`.
98    pub fn new_binary(d1: F, d2: F) -> Self {
99        assert_eq!(F::characteristic()[0], 2, "use new() for odd char");
100        assert!(d1 != F::zero(), "d1 must be nonzero");
101        let d1_sq = <F as FieldOps>::square(&d1);
102        assert!(d2 != d1_sq + d1, "d2 must differ from d1² + d1");
103        Self { d1, d2 }
104    }
105
106    /// Convenience accessor: the parameter `d` in odd characteristic.
107    pub fn d(&self) -> F {
108        self.d2
109    }
110
111    /// Check whether the affine point `(x, y)` lies on the curve.
112    pub fn contains(&self, x: &F, y: &F) -> bool {
113        if F::characteristic()[0] != 2 {
114            // x² + y² == 1 + d·x²·y²
115            let x2 = <F as FieldOps>::square(x);
116            let y2 = <F as FieldOps>::square(y);
117            x2 + y2 == F::one() + self.d2 * x2 * y2
118        } else {
119            // d₁(x+y) + d₂(x²+y²) == xy + xy(x+y) + x²y²
120            let x2 = <F as FieldOps>::square(x);
121            let y2 = <F as FieldOps>::square(y);
122            let xy = *x * *y;
123            let xpy = *x + *y;
124            self.d1 * xpy + self.d2 * (x2 + y2) == xy + xy * xpy + x2 * y2
125        }
126    }
127
128    /// Sample a random affine point on this Edwards curve using the provided RNG.
129    ///
130    /// This currently uses a square-root-based construction and is implemented
131    /// only for odd characteristic. It returns a point `P` such that
132    /// `self.is_on_curve(&P)` holds.
133    pub fn random_point(&self, rng: &mut (impl rand::CryptoRng + rand::Rng)) -> EdwardsPoint<F> {
134        assert!(
135            F::characteristic()[0] != 2,
136            "random_point currently implemented only for odd characteristic"
137        );
138
139        loop {
140            let x = F::random(rng);
141            let x2 = <F as FieldOps>::square(&x);
142            let denom = F::one() - self.d2 * x2;
143
144            if bool::from(denom.is_zero()) {
145                continue;
146            }
147
148            let rhs = (F::one() - x2) * denom.invert().unwrap();
149
150            if let Some(y) = rhs.sqrt().into_option() {
151                let p = EdwardsPoint::new(x, y);
152                debug_assert!(self.is_on_curve(&p));
153                return p;
154            }
155        }
156    }
157}
158
159impl<F: FieldOps + FieldRandom> Curve for EdwardsCurve<F> {
160    type BaseField = F;
161    type Point = EdwardsPoint<F>;
162
163    fn is_on_curve(&self, point: &Self::Point) -> bool {
164        self.contains(&point.x, &point.y)
165    }
166
167    fn random_point(&self, rng: &mut (impl rand::CryptoRng + rand::Rng)) -> Self::Point {
168       EdwardsCurve::random_point(self, rng)
169    }
170
171    fn j_invariant(&self) -> F {
172        if F::characteristic()[0] != 2 {
173            // j = 16(1 + 14d + d²)³ / (d(1 − d)⁴)
174            let d = self.d2;
175            let d2 = <F as FieldOps>::square(&d);
176            let two = <F as FieldOps>::double(&F::one());
177            let four = <F as FieldOps>::double(&two);
178            let eight = <F as FieldOps>::double(&four);
179            let fourteen = eight + four + two;
180            let sixteen = <F as FieldOps>::double(&eight);
181
182            let inner = F::one() + fourteen * d + d2;
183            let inner_cubed = inner * <F as FieldOps>::square(&inner);
184            let numer = sixteen * inner_cubed;
185
186            let one_minus_d = F::one() - d;
187            let omd2 = <F as FieldOps>::square(&one_minus_d);
188            let omd4 = <F as FieldOps>::square(&omd2);
189            let denom = d * omd4;
190
191            numer
192                * denom
193                    .invert()
194                    .into_option()
195                    .expect("d(1-d)^4 must be invertible")
196        } else {
197            // j = 1 / (d₁⁴ (d₁⁴ + d₁² + d₂²))
198            let d1_sq = <F as FieldOps>::square(&self.d1);
199            let d1_4 = <F as FieldOps>::square(&d1_sq);
200            let d2_sq = <F as FieldOps>::square(&self.d2);
201            let denom = d1_4 * (d1_4 + d1_sq + d2_sq);
202            denom
203                .invert()
204                .into_option()
205                .expect("j-invariant denominator must be invertible")
206        }
207    }
208
209    fn a_invariants(&self) -> Vec<Self::BaseField> {
210        if F::characteristic()[0] != 2 {
211            vec![self.d2]
212        } else {
213            vec![self.d1, self.d2]
214        }
215    }
216}