Skip to main content

ec/
point_montgomery.rs

1//! X-only arithmetic on a Montgomery curve via the Kummer line.
2//!
3//!
4//! # Kummer-line representation
5//!
6//! A Montgomery point is represented only by its x-coordinate, in projective
7//! form `(X : Z)`, corresponding to the affine value `x = X / Z` when `Z ≠ 0`.
8//!
9//! This is not a full point on the elliptic curve: it is a point on the
10//! quotient `E / {±1}`. In other words, `P` and `-P` are identified.
11//!
12//! # Why projective x/z coordinates?
13//!
14//! The Montgomery ladder is naturally expressed using x-only formulas in
15//! projective coordinates. This avoids field inversions during the ladder and
16//! yields a uniform constant-time scalar multiplication pattern.
17//!
18//! # Important limitation
19//!
20//! Since the sign of `y` is forgotten, x-only arithmetic does **not** provide a
21//! full group law on points. In particular, ordinary addition `P + Q` is not
22//! available from x-coordinates alone.
23//!
24//! What *is* available is:
25//!
26//! - doubling,
27//! - differential addition: given `x(P)`, `x(Q)`, and `x(P - Q)`, compute
28//!   `x(P + Q)`,
29//! - scalar multiplication via the Montgomery ladder.
30
31use core::fmt;
32use subtle::{Choice, CtOption, ConditionallySelectable, ConstantTimeEq};
33
34use crate::curve_montgomery::MontgomeryCurve;
35use crate::point_ops::PointOps;
36use fp::field_ops::{FieldOps};
37
38/// A point on the Kummer line of a Montgomery curve, represented by `(X : Z)`.
39///
40/// The affine x-coordinate is `x = X / Z` when `Z ≠ 0`.
41/// A conventional choice is:
42/// - `(1 : 0)` for the identity image,
43/// - `(X : Z)` with `Z ≠ 0` for finite x-coordinates.
44#[derive(Debug, Clone, Copy)]
45pub struct KummerPoint<F: FieldOps + Copy> {
46    /// Projective x coordinate
47    pub x: F,
48    /// Projective z coordinate
49    pub z: F,
50}
51
52// ---------------------------------------------------------------------------
53// Manual trait impls
54// ---------------------------------------------------------------------------
55
56impl<F> fmt::Display for KummerPoint<F>
57where
58    F: FieldOps + Copy + fmt::Display,
59{
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        if self.is_identity() {
62            if f.alternate() {
63                write!(f, "KummerPoint {{ O = (1:0) }}")
64            } else {
65                write!(f, "O")
66            }
67        } else if f.alternate() {
68            match self.to_x().into_option() {
69                Some(x_aff) => write!(
70                    f,
71                    "KummerPoint {{ X:Z = ({}:{}), x = {} }}",
72                    self.x, self.z, x_aff
73                ),
74                None => write!(
75                    f,
76                    "KummerPoint {{ X:Z = ({}:{}) }}",
77                    self.x, self.z
78                ),
79            }
80        } else {
81            write!(f, "({}:{})", self.x, self.z)
82        }
83    }
84}
85
86impl<F: FieldOps> PartialEq for KummerPoint<F>
87where
88    F: FieldOps + ConstantTimeEq,
89{
90    /// Equality of projective x-line points.
91    ///
92    /// A standard criterion is cross-multiplication:
93    /// ```text
94    /// X1 Z2 = X2 Z1.
95    /// ```
96    fn eq(&self, other: &Self) -> bool {
97        self.x * other.z == other.x * self.z
98    }
99}
100
101impl<F: FieldOps> Eq for KummerPoint<F> where F: FieldOps + ConstantTimeEq {}
102
103// ---------------------------------------------------------------------------
104// Constructors
105// ---------------------------------------------------------------------------
106
107impl<F: FieldOps> KummerPoint<F> {
108    /// Construct a projective x-line point `(X : Z)` without validation.
109    pub fn new(x: F, z: F) -> Self {
110        assert!(!bool::from((x * z).is_zero()));
111        Self { x, z }
112    }
113
114    /// Construct the finite x-line point corresponding to the affine
115    /// x-coordinate `x`, i.e. `(x : 1)`.
116    pub fn from_x(x: F) -> Self {
117        Self { x, z: F::one() }
118    }
119
120    /// The image of the identity point on the Kummer line.
121    pub fn identity() -> Self {
122        Self {
123            x: F::one(),
124            z: F::zero(),
125        }
126    }
127
128    /// Return `true` if this point is the image of identity.
129    pub fn is_identity(&self) -> bool {
130        bool::from(self.z.is_zero())
131    }
132
133    /// Attempt to recover the affine x-coordinate (succeeds when Z != 0).
134    pub fn to_x(&self) -> CtOption<F> {
135        let z_inv = self.z.invert();
136        z_inv.map(|zinv| self.x * zinv)
137    }
138}
139
140// ---------------------------------------------------------------------------
141// Constant-time functionalities
142// ---------------------------------------------------------------------------
143
144impl<F> ConditionallySelectable for KummerPoint<F>
145where
146    F: FieldOps + Copy,
147{
148    fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
149        Self {
150            x: F::conditional_select(&a.x, &b.x, choice),
151            z: F::conditional_select(&a.z, &b.z, choice),
152        }
153    }
154
155    fn conditional_assign(&mut self, other: &Self, choice: Choice) {
156        F::conditional_assign(&mut self.x, &other.x, choice);
157        F::conditional_assign(&mut self.z, &other.z, choice);
158    }
159
160    fn conditional_swap(a: &mut Self, b: &mut Self, choice: Choice) {
161        F::conditional_swap(&mut a.x, &mut b.x, choice);
162        F::conditional_swap(&mut a.z, &mut b.z, choice);
163    }
164}
165
166impl<F> ConstantTimeEq for KummerPoint<F>
167where
168    F: FieldOps + Copy + ConstantTimeEq,
169{
170    /// Constant-time projective equality test on the Kummer line.
171    /// ```text
172    /// X1 Z2  ?=  X2 Z1
173    /// ```
174    fn ct_eq(&self, other: &Self) -> Choice {
175        let x1z2 = self.x * other.z;
176        let x2z1 = other.x * self.z;
177        x1z2.ct_eq(&x2z1)
178    }
179
180    fn ct_ne(&self, other: &Self) -> Choice {
181        !self.ct_eq(other)
182    }
183}
184
185// ---------------------------------------------------------------------------
186// X-only arithmetic
187// ---------------------------------------------------------------------------
188
189impl<F: FieldOps + Copy> KummerPoint<F> {
190    /// Point doubling on the Kummer line.
191    ///
192    /// Given `x(P)` in projective form `(X:Z)`, compute `x([2]P)`.
193    pub fn xdouble(&self, curve: &MontgomeryCurve<F>) -> Self {
194        if F::characteristic()[0] != 2 {
195            let sumsq = <F as FieldOps>::square(&(self.x + self.z));
196            let diffsq = <F as FieldOps>::square(&(self.x - self.z));
197            let fourxz = sumsq - diffsq;
198            let a24 = curve.a24();
199
200            let new_z = fourxz * (diffsq + a24 * fourxz);
201
202            Self {
203                x: sumsq * diffsq,
204                z: new_z,
205            }
206        } else {
207            let temp_x = self.x + curve.b * self.z;
208            let new_x = <F as FieldOps>::square(&<F as FieldOps>::square(&temp_x));
209            let xz = self.x * self.z;
210            let new_z = <F as FieldOps>::square(&xz);
211            Self { x: new_x, z: new_z }
212        }
213    }
214
215    /// Differential addition. Given (in projective form `(X:Z)`):
216    ///
217    /// - `self = x(P)`,
218    /// - `other = x(Q)`,
219    /// - `diff = x(P - Q)`,
220    ///
221    /// compute `x(P + Q)`.
222    pub fn xadd(&self, other: &Self, diff: &Self) -> Self {
223        if F::characteristic()[0] != 2 {
224            let u = (self.x - self.z) * (other.x + other.z);
225            let v = (self.x + self.z) * (other.x - other.z);
226
227            let new_x = diff.z * <F as FieldOps>::square(&(u + v));
228            let new_z = diff.x * <F as FieldOps>::square(&(u - v));
229
230            Self { x: new_x, z: new_z }
231        } else {
232            let x1z2 = self.x * other.z;
233            let x2z1 = other.x * self.z;
234            let sum_squared = <F as FieldOps>::square(&(x1z2 + x2z1));
235
236            let new_x = diff.x * sum_squared + diff.z * x1z2 * x2z1;
237            let new_z = diff.z * sum_squared;
238
239            Self { x: new_x, z: new_z }
240        }
241    }
242
243    /// Montgomery ladder for scalar multiplication.
244    ///
245    /// Given an x-line point `x(P)` and a scalar `k`, compute `x([k]P)`.
246    /// The scalar `k` is given as a slice of `u64` limbs in **little-endian**
247    /// order (same convention as `FieldOps::pow`).
248    pub fn scalar_mul(&self, k: &[u64], curve: &MontgomeryCurve<F>) -> Self {
249        if self.is_identity() || k.is_empty() {
250            return Self::identity();
251        }
252
253        // Ladder state:
254        // r0 = x([m]P)
255        // r1 = x([m+1]P)
256        let mut r0 = Self::identity();
257        let mut r1 = *self;
258
259        let mut swap = Choice::from(0u8);
260
261        for &limb in k.iter().rev() {
262            for bit in (0..64).rev() {
263                let ki = Choice::from(((limb >> bit) & 1) as u8);
264
265                // Swap exactly when the current bit differs from the previous one.
266                let do_swap = swap ^ ki;
267                Self::conditional_swap(&mut r0, &mut r1, do_swap);
268                swap = ki;
269
270                // After the swap, the invariant is arranged so that:
271                //   r0 <- [2]r0
272                //   r1 <- r0 + r1
273                // and x(r1 - r0) remains x(P).
274                let r0_dbl = r0.xdouble(curve);
275                let r0_plus_r1 = r0.xadd(&r1, self);
276
277                r0 = r0_dbl;
278                r1 = r0_plus_r1;
279            }
280        }
281
282        // Final swap to undo the last deferred permutation.
283        Self::conditional_swap(&mut r0, &mut r1, swap);
284
285        r0
286    }
287}
288
289// ---------------------------------------------------------------------------
290// PointOps bridge
291// ---------------------------------------------------------------------------
292
293impl<F> PointOps for KummerPoint<F>
294where
295    F: FieldOps + Copy,
296{
297    type BaseField = F;
298    type Curve = MontgomeryCurve<F>;
299
300    /// Return the identity image on the Kummer line.
301    fn identity(_curve: &Self::Curve) -> Self {
302        KummerPoint::<F>::identity()
303    }
304
305    /// Return `true` if this is the identity image.
306    fn is_identity(&self) -> bool {
307        KummerPoint::<F>::is_identity(self)
308    }
309
310    /// Negation is trivial on the Kummer line because `P` and `-P` have the
311    /// same image.
312    fn negate(&self, _curve: &Self::Curve) -> Self {
313        *self
314    }
315
316    /// Scalar multiplication is naturally implemented by the Montgomery ladder.
317    fn scalar_mul(&self, k: &[u64], curve: &Self::Curve) -> Self {
318        KummerPoint::<F>::scalar_mul(self, k, curve)
319    }
320}