Skip to main content

ec/
point_jacobi_quartic.rs

1//! Affine points on a Jacobi quartic curve.
2//!
3//! We use the affine model
4//!
5//! $$
6//! y^2 = d x^4 + 2 a x^2 + 1
7//! $$
8//!
9//! with neutral element $(0, 1)$ and negation $-(x, y) = (-x, y)$.
10//! The doubling formulas are taken from equations (9) and (10) in
11//! *Jacobi Quartic Curves Revisited*; the general addition uses affine
12//! formulas (1) and (2).
13//!
14//! Important: these are affine formulas. Like the existing Edwards code in this
15//! crate, they assume denominators are invertible. For exceptional inputs
16//! where the result leaves this affine chart, the code panics instead of trying
17//! to model the desingularized points at infinity.
18use core::fmt;
19use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
20
21use crate::curve_jacobi_quartic::JacobiQuarticCurve;
22use crate::point_ops::{PointAdd, PointOps};
23use fp::field_ops::FieldOps;
24
25/// An affine point `(x, y)` on a Jacobi quartic curve.
26#[derive(Debug, Clone, Copy)]
27pub struct JacobiQuarticPoint<F: FieldOps> {
28    /// The x coordinate of a point
29    pub x: F,
30    /// The y coordiante of a point
31    pub y: F,
32}
33
34impl<F> fmt::Display for JacobiQuarticPoint<F>
35where
36    F: FieldOps + fmt::Display,
37{
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        if self.is_identity() {
40            if f.alternate() {
41                write!(f, "JacobiQuarticPoint {{ O = (0,1) }}")
42            } else {
43                write!(f, "O")
44            }
45        } else if f.alternate() {
46            write!(f, "JacobiQuarticPoint {{ x = {}, y = {} }}", self.x, self.y)
47        } else {
48            write!(f, "({}, {})", self.x, self.y)
49        }
50    }
51}
52
53impl<F: FieldOps> PartialEq for JacobiQuarticPoint<F> {
54    fn eq(&self, other: &Self) -> bool {
55        self.x == other.x && self.y == other.y
56    }
57}
58
59impl<F: FieldOps> Eq for JacobiQuarticPoint<F> {}
60
61impl<F: FieldOps> JacobiQuarticPoint<F> {
62    /// Create a new point `(x,y)`
63    pub fn new(x: F, y: F) -> Self {
64        Self { x, y }
65    }
66
67    /// The neutral element `(0, 1)`.
68    pub fn identity() -> Self {
69        Self {
70            x: F::zero(),
71            y: F::one(),
72        }
73    }
74
75    /// Checks if the point is the identity
76    pub fn is_identity(&self) -> bool {
77        self.x == F::zero() && self.y == F::one()
78    }
79
80    /// The affine order-2 point `(0, -1)`.
81    pub fn order_two_point() -> Self {
82        Self {
83            x: F::zero(),
84            y: -F::one(),
85        }
86    }
87}
88
89impl<F> ConditionallySelectable for JacobiQuarticPoint<F>
90where
91    F: FieldOps + Copy,
92{
93    fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
94        Self {
95            x: F::conditional_select(&a.x, &b.x, choice),
96            y: F::conditional_select(&a.y, &b.y, choice),
97        }
98    }
99
100    fn conditional_assign(&mut self, other: &Self, choice: Choice) {
101        self.x.conditional_assign(&other.x, choice);
102        self.y.conditional_assign(&other.y, choice);
103    }
104
105    fn conditional_swap(a: &mut Self, b: &mut Self, choice: Choice) {
106        F::conditional_swap(&mut a.x, &mut b.x, choice);
107        F::conditional_swap(&mut a.y, &mut b.y, choice);
108    }
109}
110
111impl<F> ConstantTimeEq for JacobiQuarticPoint<F>
112where
113    F: FieldOps + Copy + ConstantTimeEq,
114{
115    fn ct_eq(&self, other: &Self) -> Choice {
116        self.x.ct_eq(&other.x) & self.y.ct_eq(&other.y)
117    }
118
119    fn ct_ne(&self, other: &Self) -> Choice {
120        !self.ct_eq(other)
121    }
122}
123
124impl<F: FieldOps> JacobiQuarticPoint<F> {
125    /// Negation on a Jacobi quartic: `-(x, y) = (-x, y)`.
126    pub fn negate(&self, _curve: &JacobiQuarticCurve<F>) -> Self {
127        Self::new(-self.x, self.y)
128    }
129
130    /// Dedicated affine doubling from equations (9) and (10):
131    ///
132    /// ```text
133    /// μ  = 2y / (2 + 2ax² − y²)
134    /// x₃ = μx
135    /// y₃ = μ(μ − y) − 1.
136    /// ```
137    ///
138    pub fn double(&self, curve: &JacobiQuarticCurve<F>) -> Self {
139        if self.is_identity() {
140            return *self;
141        }
142
143        let x2 = <F as FieldOps>::square(&self.x);
144        let y2 = <F as FieldOps>::square(&self.y);
145        let two = <F as FieldOps>::double(&F::one());
146
147        let denom = two + two * curve.a * x2 - y2;
148        let denom_inv = denom.invert().into_option()
149            .expect("Jacobi quartic doubling denominator vanished; result leaves affine chart or input is exceptional");
150
151        let mu = two * self.y * denom_inv;
152        let x3 = mu * self.x;
153        let y3 = mu * (mu - self.y) - F::one();
154
155        Self::new(x3, y3)
156    }
157
158    /// Affine addition using equations (1) and (2):
159    ///
160    /// ```text
161    /// x₃ = (x₁y₂ + y₁x₂)/(1 − d x₁²x₂²)
162    /// y₃ = ((y₁y₂ + 2ax₁x₂)(1 + d x₁²x₂²) + 2d x₁x₂(x₁² + x₂²))
163    ///      /(1 − d x₁²x₂²)².
164    /// ```
165    ///
166    pub fn add(&self, other: &Self, curve: &JacobiQuarticCurve<F>) -> Self {
167        if self.is_identity() {
168            return *other;
169        }
170        if other.is_identity() {
171            return *self;
172        }
173        if *self == *other {
174            return self.double(curve);
175        }
176        if *other == self.negate(curve) {
177            return Self::identity();
178        }
179
180        let x1 = self.x;
181        let y1 = self.y;
182        let x2 = other.x;
183        let y2 = other.y;
184
185        let x1_sq = <F as FieldOps>::square(&x1);
186        let x2_sq = <F as FieldOps>::square(&x2);
187        let x1x2 = x1 * x2;
188        let y1y2 = y1 * y2;
189        let dx4 = curve.d * x1_sq * x2_sq;
190        let two = <F as FieldOps>::double(&F::one());
191
192        let denom = F::one() - dx4;
193        let denom_inv = denom.invert().into_option()
194            .expect("Jacobi quartic addition denominator vanished; choose d nonsquare / odd-order subgroup or use a projective model");
195
196        let x3 = (x1 * y2 + y1 * x2) * denom_inv;
197
198        let numer_y = (y1y2 + two * curve.a * x1x2) * (F::one() + dx4)
199            + two * curve.d * x1x2 * (x1_sq + x2_sq);
200        let y3 = numer_y * <F as FieldOps>::square(&denom_inv);
201
202        Self::new(x3, y3)
203    }
204
205    /// Constant-time double-and-add in the same style as the Edwards code.
206    pub fn scalar_mul(&self, k: &[u64], curve: &JacobiQuarticCurve<F>) -> Self {
207        let mut result = Self::identity();
208
209        for &limb in k.iter().rev() {
210            for bit in (0..64).rev() {
211                let doubled = result.double(curve);
212                let added = doubled.add(self, curve);
213                let choice = Choice::from(((limb >> bit) & 1) as u8);
214                result = Self::conditional_select(&doubled, &added, choice);
215            }
216        }
217
218        result
219    }
220}
221
222impl<F: FieldOps> PointOps for JacobiQuarticPoint<F> {
223    type BaseField = F;
224    type Curve = JacobiQuarticCurve<F>;
225
226    fn identity(_curve: &Self::Curve) -> Self {
227        JacobiQuarticPoint::<F>::identity()
228    }
229
230    fn is_identity(&self) -> bool {
231        JacobiQuarticPoint::<F>::is_identity(self)
232    }
233
234    fn negate(&self, curve: &Self::Curve) -> Self {
235        JacobiQuarticPoint::<F>::negate(self, curve)
236    }
237
238    fn scalar_mul(&self, k: &[u64], curve: &Self::Curve) -> Self {
239        JacobiQuarticPoint::<F>::scalar_mul(self, k, curve)
240    }
241}
242
243impl<F: FieldOps> PointAdd for JacobiQuarticPoint<F> {
244    fn add(&self, other: &Self, curve: &Self::Curve) -> Self {
245        JacobiQuarticPoint::<F>::add(self, other, curve)
246    }
247}