Skip to main content

arcana/ecc/
curve.rs

1//! Elliptic curve point operations on short Weierstrass curves y^2 = x^3 + ax + b.
2//!
3//! Uses Jacobian projective coordinates (X, Y, Z) where the affine point is
4//! (X/Z^2, Y/Z^3). The point at infinity is represented by Z = 0.
5//!
6//! Scalar multiplication uses a Montgomery ladder for constant-time execution.
7
8use super::field::*;
9
10/// A point on a short Weierstrass curve in Jacobian projective
11/// coordinates `(X, Y, Z)`.
12///
13/// The corresponding affine point is `(X / Z^2, Y / Z^3)`. The point
14/// at infinity is represented by `Z == 0`. Working in projective
15/// coordinates lets the point doubling and addition formulas avoid
16/// the field inversion that the affine forms would need on every
17/// operation; only [`Self::to_affine`] performs an inversion (once,
18/// at the boundary back to the user-visible representation).
19#[derive(Clone, Copy, Debug)]
20pub struct JacobianPoint<const LIMBS: usize> {
21    /// Projective X coordinate.
22    pub x: FieldElement<LIMBS>,
23    /// Projective Y coordinate.
24    pub y: FieldElement<LIMBS>,
25    /// Projective Z coordinate. `Z == 0` encodes the point at infinity.
26    pub z: FieldElement<LIMBS>,
27}
28
29impl<const LIMBS: usize> JacobianPoint<LIMBS> {
30    /// The point at infinity (identity element).
31    pub fn infinity() -> Self {
32        Self {
33            x: FieldElement::one(),
34            y: FieldElement::one(),
35            z: FieldElement::ZERO,
36        }
37    }
38
39    /// Create a point from affine coordinates (x, y). Z = 1.
40    pub fn from_affine(x: FieldElement<LIMBS>, y: FieldElement<LIMBS>) -> Self {
41        Self {
42            x,
43            y,
44            z: FieldElement::one(),
45        }
46    }
47
48    /// Returns `true` if this point is the point at infinity (Z == 0).
49    pub fn is_infinity(&self) -> bool {
50        self.z.is_zero()
51    }
52
53    /// Convert to affine coordinates (x, y). Returns None for point at infinity.
54    pub fn to_affine(&self, p: &[u64; LIMBS]) -> Option<(FieldElement<LIMBS>, FieldElement<LIMBS>)> {
55        if self.z.is_zero() {
56            return None;
57        }
58        let z_inv = field_inv(&self.z, p);
59        let z_inv2 = field_sqr(&z_inv, p);
60        let z_inv3 = field_mul(&z_inv2, &z_inv, p);
61        let x = field_mul(&self.x, &z_inv2, p);
62        let y = field_mul(&self.y, &z_inv3, p);
63        Some((x, y))
64    }
65}
66
67/// Parameters for a short Weierstrass curve.
68pub struct CurveParams<const LIMBS: usize> {
69    /// Field prime p.
70    pub p: [u64; LIMBS],
71    /// Coefficient a (for P-256 and P-384, a = -3 mod p).
72    pub a: FieldElement<LIMBS>,
73    /// Coefficient b.
74    pub b: FieldElement<LIMBS>,
75    /// X coordinate of the generator point `G`.
76    pub gx: FieldElement<LIMBS>,
77    /// Y coordinate of the generator point `G`.
78    pub gy: FieldElement<LIMBS>,
79    /// Order n.
80    pub n: [u64; LIMBS],
81    /// Bit length of `n` (aka `qlen` in RFC 6979).
82    ///
83    /// For all curves we currently ship, `qlen_bits` is a multiple of 8
84    /// **except for secp521r1** where it is 521 (not a multiple of 8).
85    /// RFC 6979 is careful to distinguish `qlen` from `rlen = 8*ceil(qlen/8)`
86    /// (aka `rlen_bytes = (qlen_bits + 7) / 8`), and for P-521 those two
87    /// values disagree by 7 bits. All RFC 6979 byte-length decisions
88    /// (int2octets, bits2octets, the HMAC-T accumulation loop) must use
89    /// `rlen_bytes`, *not* `LIMBS * 8`.
90    pub qlen_bits: usize,
91    /// **SEC1 §2.3.5 field element octet length** = `ceil(log2(p) / 8)`.
92    ///
93    /// This is the **external** width of a field element for all
94    /// serialization purposes (uncompressed / compressed SEC1 public
95    /// keys, raw r/s in a signature, ECDH shared-secret output). It is
96    /// distinct from the **internal** storage width `LIMBS * 8`, which
97    /// is an implementation detail driven by the 64-bit limb alignment.
98    ///
99    /// | Curve                 | LIMBS*8 (internal) | felem_bytes (external) |
100    /// |-----------------------|--------------------|-------------------------|
101    /// | P-256                 | 32                 | 32                      |
102    /// | P-384                 | 48                 | 48                      |
103    /// | secp256k1             | 32                 | 32                      |
104    /// | brainpoolP256r1       | 32                 | 32                      |
105    /// | brainpoolP384r1       | 48                 | 48                      |
106    /// | brainpoolP512r1       | 64                 | 64                      |
107    /// | **secp521r1 (P-521)** | **72**             | **66**                  |
108    ///
109    /// P-521 is the only curve where the two values disagree (576-bit
110    /// storage vs 521-bit field → 66 bytes externally). The 6 leading
111    /// bytes of `to_bytes_be()` on a P-521 field element are always
112    /// zero and must be stripped at the serialization boundary;
113    /// conversely, parsers must left-pad a 66-byte external value into
114    /// the 72-byte internal buffer before building a `FieldElement<9>`.
115    /// The 6 byte-aligned curves treat this as a no-op because
116    /// `felem_bytes == LIMBS * 8`.
117    pub felem_bytes: usize,
118}
119
120// ============================================================================
121// Curve parameter helpers
122// ============================================================================
123
124/// Decode a hex string into a `FieldElement<LIMBS>`. The hex is interpreted as
125/// big-endian and must contain an even number of hex digits.
126pub fn hex_to_fe<const LIMBS: usize>(hex: &str) -> FieldElement<LIMBS> {
127    let bytes: Vec<u8> = (0..hex.len())
128        .step_by(2)
129        .map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap())
130        .collect();
131    FieldElement::from_bytes_be(&bytes)
132}
133
134/// Decode a hex string into a `[u64; LIMBS]` (used for `p` and `n`).
135/// Big-endian hex; pads with leading zeros if shorter than LIMBS*8 bytes.
136pub fn hex_to_limbs<const LIMBS: usize>(hex: &str) -> [u64; LIMBS] {
137    hex_to_fe::<LIMBS>(hex).limbs
138}
139
140// Backwards-compatible aliases used by P-256/P-384 below.
141fn hex_to_fe4(hex: &str) -> FieldElement<4> {
142    hex_to_fe::<4>(hex)
143}
144fn hex_to_fe6(hex: &str) -> FieldElement<6> {
145    hex_to_fe::<6>(hex)
146}
147
148// ============================================================================
149// P-256 curve parameters
150// ============================================================================
151
152/// NIST P-256 / secp256r1 curve parameters (FIPS 186-4 §D.1.2.3).
153pub fn p256_params() -> CurveParams<4> {
154    CurveParams {
155        p: P256_P,
156        a: hex_to_fe4("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC"),
157        b: hex_to_fe4("5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B"),
158        gx: hex_to_fe4("6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296"),
159        gy: hex_to_fe4("4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5"),
160        n: P256_N,
161        qlen_bits: 256,
162        felem_bytes: 32,
163    }
164}
165
166/// NIST P-384 / secp384r1 curve parameters (FIPS 186-4 §D.1.2.4).
167pub fn p384_params() -> CurveParams<6> {
168    CurveParams {
169        p: P384_P,
170        a: hex_to_fe6(
171            "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC",
172        ),
173        b: hex_to_fe6(
174            "B3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF",
175        ),
176        gx: hex_to_fe6(
177            "AA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7",
178        ),
179        gy: hex_to_fe6(
180            "3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F",
181        ),
182        n: P384_N,
183        qlen_bits: 384,
184        felem_bytes: 48,
185    }
186}
187
188// ============================================================================
189// secp256k1 (SEC 2 v2.0 §2.4.1)  --  Bitcoin / Ethereum curve
190// ============================================================================
191//
192// y^2 = x^3 + 7  (i.e. a = 0, b = 7)
193// p = 2^256 - 2^32 - 977
194//   = FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
195
196/// secp256k1 curve parameters (SEC 2 v2.0 §2.4.1).
197///
198/// `y^2 = x^3 + 7` over `p = 2^256 - 2^32 - 977`. The Bitcoin /
199/// Ethereum signing curve, also widely used in Lightning, Nostr,
200/// and most blockchain ecosystems.
201pub fn secp256k1_params() -> CurveParams<4> {
202    CurveParams {
203        p: hex_to_limbs::<4>("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F"),
204        a: hex_to_fe::<4>("0000000000000000000000000000000000000000000000000000000000000000"),
205        b: hex_to_fe::<4>("0000000000000000000000000000000000000000000000000000000000000007"),
206        gx: hex_to_fe::<4>("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798"),
207        gy: hex_to_fe::<4>("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8"),
208        n: hex_to_limbs::<4>("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"),
209        qlen_bits: 256,
210        felem_bytes: 32,
211    }
212}
213
214// ============================================================================
215// brainpoolP256r1 (RFC 5639 §3.4)
216// ============================================================================
217
218/// brainpoolP256r1 curve parameters (BSI / RFC 5639 §3.4).
219pub fn brainpoolp256r1_params() -> CurveParams<4> {
220    CurveParams {
221        p: hex_to_limbs::<4>("A9FB57DBA1EEA9BC3E660A909D838D726E3BF623D52620282013481D1F6E5377"),
222        a: hex_to_fe::<4>("7D5A0975FC2C3057EEF67530417AFFE7FB8055C126DC5C6CE94A4B44F330B5D9"),
223        b: hex_to_fe::<4>("26DC5C6CE94A4B44F330B5D9BBD77CBF958416295CF7E1CE6BCCDC18FF8C07B6"),
224        gx: hex_to_fe::<4>("8BD2AEB9CB7E57CB2C4B482FFC81B7AFB9DE27E1E3BD23C23A4453BD9ACE3262"),
225        gy: hex_to_fe::<4>("547EF835C3DAC4FD97F8461A14611DC9C27745132DED8E545C1D54C72F046997"),
226        n: hex_to_limbs::<4>("A9FB57DBA1EEA9BC3E660A909D838D718C397AA3B561A6F7901E0E82974856A7"),
227        qlen_bits: 256,
228        felem_bytes: 32,
229    }
230}
231
232// ============================================================================
233// brainpoolP384r1 (RFC 5639 §3.6)
234// ============================================================================
235
236/// brainpoolP384r1 curve parameters (BSI / RFC 5639 §3.6).
237pub fn brainpoolp384r1_params() -> CurveParams<6> {
238    CurveParams {
239        p: hex_to_limbs::<6>(
240            "8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B412B1DA197FB71123ACD3A729901D1A71874700133107EC53",
241        ),
242        a: hex_to_fe::<6>(
243            "7BC382C63D8C150C3C72080ACE05AFA0C2BEA28E4FB22787139165EFBA91F90F8AA5814A503AD4EB04A8C7DD22CE2826",
244        ),
245        b: hex_to_fe::<6>(
246            "04A8C7DD22CE28268B39B55416F0447C2FB77DE107DCD2A62E880EA53EEB62D57CB4390295DBC9943AB78696FA504C11",
247        ),
248        gx: hex_to_fe::<6>(
249            "1D1C64F068CF45FFA2A63A81B7C13F6B8847A3E77EF14FE3DB7FCAFE0CBD10E8E826E03436D646AAEF87B2E247D4AF1E",
250        ),
251        gy: hex_to_fe::<6>(
252            "8ABE1D7520F9C2A45CB1EB8E95CFD55262B70B29FEEC5864E19C054FF99129280E4646217791811142820341263C5315",
253        ),
254        n: hex_to_limbs::<6>(
255            "8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B31F166E6CAC0425A7CF3AB6AF6B7FC3103B883202E9046565",
256        ),
257        qlen_bits: 384,
258        felem_bytes: 48,
259    }
260}
261
262// ============================================================================
263// brainpoolP512r1 (RFC 5639 §3.7)
264// ============================================================================
265
266/// brainpoolP512r1 curve parameters (BSI / RFC 5639 §3.7).
267pub fn brainpoolp512r1_params() -> CurveParams<8> {
268    CurveParams {
269        p: hex_to_limbs::<8>(
270            "AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA703308717D4D9B009BC66842AECDA12AE6A380E62881FF2F2D82C68528AA6056583A48F3",
271        ),
272        a: hex_to_fe::<8>(
273            "7830A3318B603B89E2327145AC234CC594CBDD8D3DF91610A83441CAEA9863BC2DED5D5AA8253AA10A2EF1C98B9AC8B57F1117A72BF2C7B9E7C1AC4D77FC94CA",
274        ),
275        b: hex_to_fe::<8>(
276            "3DF91610A83441CAEA9863BC2DED5D5AA8253AA10A2EF1C98B9AC8B57F1117A72BF2C7B9E7C1AC4D77FC94CADC083E67984050B75EBAE5DD2809BD638016F723",
277        ),
278        gx: hex_to_fe::<8>(
279            "81AEE4BDD82ED9645A21322E9C4C6A9385ED9F70B5D916C1B43B62EEF4D0098EFF3B1F78E2D0D48D50D1687B93B97D5F7C6D5047406A5E688B352209BCB9F822",
280        ),
281        gy: hex_to_fe::<8>(
282            "7DDE385D566332ECC0EABFA9CF7822FDF209F70024A57B1AA000C55B881F8111B2DCDE494A5F485E5BCA4BD88A2763AED1CA2B2FA8F0540678CD1E0F3AD80892",
283        ),
284        n: hex_to_limbs::<8>(
285            "AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA70330870553E5C414CA92619418661197FAC10471DB1D381085DDADDB58796829CA90069",
286        ),
287        qlen_bits: 512,
288        felem_bytes: 64,
289    }
290}
291
292// ============================================================================
293// secp521r1 / NIST P-521  (FIPS 186-4 §D.1.2.5, SEC 2 §2.9.1)
294// ============================================================================
295//
296// y^2 = x^3 - 3*x + b
297// p = 2^521 - 1   (a Mersenne prime, so p ≡ 3 (mod 4))
298// qlen = 521 bits (NOT a multiple of 8 -- the only curve we ship with
299// this property; see CurveParams::qlen_bits for the RFC 6979 implications)
300//
301// The field element width LIMBS = 9 uses 576 bits of storage, of which
302// the top 55 bits are always zero for values in [0, p). Likewise scalars
303// mod n have 55 zero leading bits in the 9-limb representation.
304
305/// secp521r1 / NIST P-521 curve parameters (FIPS 186-4 §D.1.2.5).
306///
307/// `y^2 = x^3 - 3*x + b` over the Mersenne prime `p = 2^521 - 1`.
308/// The only curve we ship with `qlen` not a multiple of 8 -- see
309/// [`CurveParams::qlen_bits`] and [`CurveParams::felem_bytes`] for
310/// the implications on RFC 6979 byte lengths and SEC1 octet
311/// encoding respectively.
312pub fn secp521r1_params() -> CurveParams<9> {
313    // All values verified against NIST SP 800-186 / FIPS 186-5 Appendix C.2.3
314    // and cross-checked via Python (on-curve + n*G == infinity).
315    CurveParams {
316        p: hex_to_limbs::<9>(
317            "01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
318        ),
319        a: hex_to_fe::<9>(
320            "01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC",
321        ),
322        b: hex_to_fe::<9>(
323            "0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00",
324        ),
325        gx: hex_to_fe::<9>(
326            "00C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66",
327        ),
328        gy: hex_to_fe::<9>(
329            "011839296A789A3BC0045C8A5FB42C7D1BD998F54449579B446817AFBD17273E662C97EE72995EF42640C550B9013FAD0761353C7086A272C24088BE94769FD16650",
330        ),
331        n: hex_to_limbs::<9>(
332            "01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409",
333        ),
334        qlen_bits: 521,
335        // P-521 is the only curve where felem_bytes (66) < LIMBS*8 (72).
336        // External serialization must strip the 6 leading zero bytes on
337        // output and the parser must left-pad them back in on input.
338        felem_bytes: 66,
339    }
340}
341
342// ============================================================================
343// Point operations
344// ============================================================================
345
346/// Check whether the affine point `(x, y)` lies on the short Weierstrass curve
347/// `y^2 = x^3 + a*x + b` defined by `params`.
348///
349/// **Critical for ECDH**: any externally-supplied public key must be validated
350/// with this function before being multiplied by a secret scalar. Otherwise an
351/// "invalid curve attack" can recover bits of the secret key by feeding crafted
352/// off-curve points whose order in the broken group is small.
353///
354/// Returns `true` iff the affine equation holds modulo `p`.
355pub fn is_on_curve<const LIMBS: usize>(
356    x: &FieldElement<LIMBS>,
357    y: &FieldElement<LIMBS>,
358    params: &CurveParams<LIMBS>,
359) -> bool {
360    let p = &params.p;
361    let y2 = field_sqr(y, p);
362    let x2 = field_sqr(x, p);
363    let x3 = field_mul(&x2, x, p);
364    let ax = field_mul(&params.a, x, p);
365    let rhs = field_add(&field_add(&x3, &ax, p), &params.b, p);
366    y2 == rhs
367}
368
369/// Point doubling in Jacobian coordinates for **any** short Weierstrass curve
370/// y^2 = x^3 + a*x + b. Uses the generic "dbl-2007-bl" formula by Bernstein-Lange
371/// (cost: 2M + 8S + 1*a-mul + 10add). Works for arbitrary `a`, including a=0
372/// (secp256k1) and the random `a` of Brainpool curves.
373///
374/// Reference: <https://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#doubling-dbl-2007-bl>
375///
376/// **Constant-time**: no branches on point values. When the input is the
377/// point at infinity (`Z1 == 0`), the formulas naturally produce `Z3 == 0`
378/// as well (since `Z3 = (Y1+Z1)^2 - YY - ZZ = Y1^2 - Y1^2 - 0 = 0`), so no
379/// explicit special-case branch is needed. The output `(X3, Y3, 0)` is a
380/// non-canonical but valid Jacobian encoding of the point at infinity.
381pub fn point_double<const LIMBS: usize>(
382    pt: &JacobianPoint<LIMBS>,
383    params: &CurveParams<LIMBS>,
384) -> JacobianPoint<LIMBS> {
385    let p = &params.p;
386
387    // XX   = X1^2
388    // YY   = Y1^2
389    // YYYY = YY^2
390    // ZZ   = Z1^2
391    let xx = field_sqr(&pt.x, p);
392    let yy = field_sqr(&pt.y, p);
393    let yyyy = field_sqr(&yy, p);
394    let zz = field_sqr(&pt.z, p);
395
396    // S = 2*((X1+YY)^2 - XX - YYYY)
397    let x_plus_yy = field_add(&pt.x, &yy, p);
398    let x_plus_yy_sq = field_sqr(&x_plus_yy, p);
399    let s_inner = field_sub(&field_sub(&x_plus_yy_sq, &xx, p), &yyyy, p);
400    let s = field_add(&s_inner, &s_inner, p);
401
402    // M = 3*XX + a*ZZ^2
403    let two_xx = field_add(&xx, &xx, p);
404    let three_xx = field_add(&two_xx, &xx, p);
405    let zz_sq = field_sqr(&zz, p);
406    let a_zz_sq = field_mul(&params.a, &zz_sq, p);
407    let m = field_add(&three_xx, &a_zz_sq, p);
408
409    // T = M^2 - 2*S
410    let m_sq = field_sqr(&m, p);
411    let two_s = field_add(&s, &s, p);
412    let t = field_sub(&m_sq, &two_s, p);
413
414    // X3 = T
415    let x3 = t;
416
417    // Y3 = M*(S - T) - 8*YYYY
418    let s_minus_t = field_sub(&s, &t, p);
419    let m_term = field_mul(&m, &s_minus_t, p);
420    let two_yyyy = field_add(&yyyy, &yyyy, p);
421    let four_yyyy = field_add(&two_yyyy, &two_yyyy, p);
422    let eight_yyyy = field_add(&four_yyyy, &four_yyyy, p);
423    let y3 = field_sub(&m_term, &eight_yyyy, p);
424
425    // Z3 = (Y1+Z1)^2 - YY - ZZ
426    //    = 0 iff Z1 = 0 (input is infinity), yielding infinity output.
427    let y_plus_z = field_add(&pt.y, &pt.z, p);
428    let y_plus_z_sq = field_sqr(&y_plus_z, p);
429    let z3 = field_sub(&field_sub(&y_plus_z_sq, &yy, p), &zz, p);
430
431    JacobianPoint { x: x3, y: y3, z: z3 }
432}
433
434/// Jacobian point addition: `P + Q`. Uses the generic "add-2007-bl" formula
435/// (no assumption on Z coordinates).
436///
437/// **Not constant-time**: branches on whether `P == Q` (to delegate to
438/// [`point_double`]) and on whether `P == -Q` (to short-circuit to infinity).
439/// Intended for operations on **public** points only, such as the Shamir
440/// trick in [`double_scalar_mul`] used by ECDSA verify.
441///
442/// For the CT-critical scalar multiplication path (signing, ECDH), use
443/// `point_add_ct` which is uniform over all P, Q but requires the
444/// caller to respect the Montgomery ladder invariant `R1 - R0 == P` so
445/// that `P == Q` can never occur (the only case the CT variant cannot
446/// handle via the generic formulas alone).
447///
448/// Reference: <https://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#addition-add-2007-bl>
449pub fn point_add<const LIMBS: usize>(
450    p_pt: &JacobianPoint<LIMBS>,
451    q_pt: &JacobianPoint<LIMBS>,
452    params: &CurveParams<LIMBS>,
453) -> JacobianPoint<LIMBS> {
454    let p = &params.p;
455
456    // Handle identity cases with CT selection at end.
457    let p_inf = p_pt.z.is_zero();
458    let q_inf = q_pt.z.is_zero();
459
460    // Z1^2, Z1^3
461    let z1z1 = field_sqr(&p_pt.z, p);
462    let z1z1z1 = field_mul(&z1z1, &p_pt.z, p);
463
464    // Z2^2, Z2^3
465    let z2z2 = field_sqr(&q_pt.z, p);
466    let z2z2z2 = field_mul(&z2z2, &q_pt.z, p);
467
468    // U1 = X1 * Z2^2, U2 = X2 * Z1^2
469    let u1 = field_mul(&p_pt.x, &z2z2, p);
470    let u2 = field_mul(&q_pt.x, &z1z1, p);
471
472    // S1 = Y1 * Z2^3, S2 = Y2 * Z1^3
473    let s1 = field_mul(&p_pt.y, &z2z2z2, p);
474    let s2 = field_mul(&q_pt.y, &z1z1z1, p);
475
476    // H = U2 - U1
477    let h = field_sub(&u2, &u1, p);
478    // r = S2 - S1
479    let r = field_sub(&s2, &s1, p);
480
481    // If H == 0 and r == 0, points are equal -> double.
482    let h_zero = h.is_zero();
483    let r_zero = r.is_zero();
484
485    if !p_inf && !q_inf && h_zero && r_zero {
486        return point_double(p_pt, params);
487    }
488    // If H == 0 and r != 0, points are inverses -> infinity.
489    if !p_inf && !q_inf && h_zero && !r_zero {
490        return JacobianPoint::infinity();
491    }
492
493    let h2 = field_sqr(&h, p);
494    let h3 = field_mul(&h2, &h, p);
495    let u1h2 = field_mul(&u1, &h2, p);
496
497    // X3 = r^2 - H^3 - 2*U1*H^2
498    let r2 = field_sqr(&r, p);
499    let u1h2_2 = field_add(&u1h2, &u1h2, p);
500    let x3 = field_sub(&field_sub(&r2, &h3, p), &u1h2_2, p);
501
502    // Y3 = r*(U1*H^2 - X3) - S1*H^3
503    let u1h2_minus_x3 = field_sub(&u1h2, &x3, p);
504    let r_term = field_mul(&r, &u1h2_minus_x3, p);
505    let s1h3 = field_mul(&s1, &h3, p);
506    let y3 = field_sub(&r_term, &s1h3, p);
507
508    // Z3 = Z1 * Z2 * H
509    let z1z2 = field_mul(&p_pt.z, &q_pt.z, p);
510    let z3 = field_mul(&z1z2, &h, p);
511
512    let result = JacobianPoint { x: x3, y: y3, z: z3 };
513
514    // CT select for infinity cases.
515    if p_inf {
516        *q_pt
517    } else if q_inf {
518        *p_pt
519    } else {
520        result
521    }
522}
523
524/// Constant-time Jacobian point addition for the Montgomery-ladder path.
525///
526/// Same `add-2007-bl` formula as [`point_add`], but:
527/// - No early-return on `P == Q` or `P == -Q`. The ladder invariant
528///   `R1 - R0 == P` (with `P != O`) guarantees those inputs never occur
529///   with both points finite. When they *do* occur the formulas give
530///   `Z3 = 0` (= infinity); for `P == -Q` that is the algebraically
531///   correct answer, and for `P == Q` it is wrong (should be `2P`) --
532///   but the ladder guarantees this is unreachable.
533/// - The `p_inf` / `q_inf` selection at the end uses a branchless
534///   [`ct_select_point`] cascade instead of if/else, so the returned
535///   control flow is independent of the point values.
536///
537/// **Callers must ensure `P != Q`** (otherwise a silent wrong result on
538/// that single branch). The Montgomery ladder enforces this by
539/// construction; do not reuse this function outside that context.
540fn point_add_ct<const LIMBS: usize>(
541    p_pt: &JacobianPoint<LIMBS>,
542    q_pt: &JacobianPoint<LIMBS>,
543    params: &CurveParams<LIMBS>,
544) -> JacobianPoint<LIMBS> {
545    let p = &params.p;
546
547    let p_inf = p_pt.z.is_zero();
548    let q_inf = q_pt.z.is_zero();
549
550    let z1z1 = field_sqr(&p_pt.z, p);
551    let z1z1z1 = field_mul(&z1z1, &p_pt.z, p);
552    let z2z2 = field_sqr(&q_pt.z, p);
553    let z2z2z2 = field_mul(&z2z2, &q_pt.z, p);
554
555    let u1 = field_mul(&p_pt.x, &z2z2, p);
556    let u2 = field_mul(&q_pt.x, &z1z1, p);
557    let s1 = field_mul(&p_pt.y, &z2z2z2, p);
558    let s2 = field_mul(&q_pt.y, &z1z1z1, p);
559
560    let h = field_sub(&u2, &u1, p);
561    let r = field_sub(&s2, &s1, p);
562
563    let h2 = field_sqr(&h, p);
564    let h3 = field_mul(&h2, &h, p);
565    let u1h2 = field_mul(&u1, &h2, p);
566
567    let r2 = field_sqr(&r, p);
568    let u1h2_2 = field_add(&u1h2, &u1h2, p);
569    let x3 = field_sub(&field_sub(&r2, &h3, p), &u1h2_2, p);
570
571    let u1h2_minus_x3 = field_sub(&u1h2, &x3, p);
572    let r_term = field_mul(&r, &u1h2_minus_x3, p);
573    let s1h3 = field_mul(&s1, &h3, p);
574    let y3 = field_sub(&r_term, &s1h3, p);
575
576    let z1z2 = field_mul(&p_pt.z, &q_pt.z, p);
577    let z3 = field_mul(&z1z2, &h, p);
578
579    let general = JacobianPoint { x: x3, y: y3, z: z3 };
580
581    // Branchless select cascade:
582    //   q_inf => p_pt, else p_inf => q_pt, else general.
583    // When both are infinity the p_inf override wins, producing q_pt, which
584    // is itself infinity -- correct.
585    let mut out = general;
586    out = ct_select_point(p_pt, &out, q_inf as u64);
587    out = ct_select_point(q_pt, &out, p_inf as u64);
588    out
589}
590
591/// Constant-time conditional select of a Jacobian point.
592///
593/// Returns `a` if `condition != 0`, else `b`. No branch, no data-dependent
594/// memory access. `condition` is expected to be 0 or 1 (the caller enforces
595/// this by deriving it from `bool as u64` or a masked scalar bit).
596fn ct_select_point<const LIMBS: usize>(
597    a: &JacobianPoint<LIMBS>,
598    b: &JacobianPoint<LIMBS>,
599    condition: u64,
600) -> JacobianPoint<LIMBS> {
601    let mask = 0u64.wrapping_sub(condition);
602    let inv = !mask;
603    let mut out = JacobianPoint {
604        x: FieldElement { limbs: [0u64; LIMBS] },
605        y: FieldElement { limbs: [0u64; LIMBS] },
606        z: FieldElement { limbs: [0u64; LIMBS] },
607    };
608    for i in 0..LIMBS {
609        out.x.limbs[i] = (a.x.limbs[i] & mask) | (b.x.limbs[i] & inv);
610        out.y.limbs[i] = (a.y.limbs[i] & mask) | (b.y.limbs[i] & inv);
611        out.z.limbs[i] = (a.z.limbs[i] & mask) | (b.z.limbs[i] & inv);
612    }
613    out
614}
615
616/// Scalar multiplication using the **constant-time Montgomery ladder**.
617/// Computes `k * P` for a scalar `k` and a non-infinity point `P`.
618///
619/// # Constant-time properties
620///
621/// - Fixed iteration count `LIMBS * 64` -- independent of `k`.
622/// - Each iteration performs exactly one `ct_swap`, one `point_add_ct`
623///   and one [`point_double`], in that order. No branch depends on any
624///   scalar bit beyond the `ct_swap` mask.
625/// - [`point_double`] and `point_add_ct` themselves are uniform: they
626///   always compute the generic formulas and then apply branchless
627///   selects for the `Z == 0` (infinity) edge cases that occur during
628///   the leading-zero bits of `k`.
629///
630/// # Ladder invariant
631///
632/// At every step of the scan, `R1 - R0 == P`. This guarantees that
633/// `point_add_ct` is never called with `R0 == R1` (which would require
634/// `P == O`; `P` is assumed non-infinity). The `R0 == -R1` case is
635/// algebraically valid (the formulas give `Z3 = 0` = infinity) and
636/// therefore handled with no special-casing.
637pub fn scalar_mul_point<const LIMBS: usize>(
638    k: &FieldElement<LIMBS>,
639    point: &JacobianPoint<LIMBS>,
640    params: &CurveParams<LIMBS>,
641) -> JacobianPoint<LIMBS> {
642    let mut r0 = JacobianPoint::infinity();
643    let mut r1 = *point;
644
645    let total_bits = LIMBS * 64;
646    for i in (0..total_bits).rev() {
647        let limb_idx = i / 64;
648        let bit_idx = i % 64;
649        let bit = (k.limbs[limb_idx] >> bit_idx) & 1;
650
651        ct_swap(&mut r0, &mut r1, bit);
652        r1 = point_add_ct(&r0, &r1, params);
653        r0 = point_double(&r0, params);
654        ct_swap(&mut r0, &mut r1, bit);
655    }
656
657    r0
658}
659
660/// Constant-time conditional swap of two points.
661fn ct_swap<const LIMBS: usize>(a: &mut JacobianPoint<LIMBS>, b: &mut JacobianPoint<LIMBS>, condition: u64) {
662    let mask = 0u64.wrapping_sub(condition);
663    for i in 0..LIMBS {
664        let t = mask & (a.x.limbs[i] ^ b.x.limbs[i]);
665        a.x.limbs[i] ^= t;
666        b.x.limbs[i] ^= t;
667
668        let t = mask & (a.y.limbs[i] ^ b.y.limbs[i]);
669        a.y.limbs[i] ^= t;
670        b.y.limbs[i] ^= t;
671
672        let t = mask & (a.z.limbs[i] ^ b.z.limbs[i]);
673        a.z.limbs[i] ^= t;
674        b.z.limbs[i] ^= t;
675    }
676}
677
678/// Compute k1*G + k2*Q (used in ECDSA verify).
679/// Not constant-time in the public values (signature verification is public).
680pub fn double_scalar_mul<const LIMBS: usize>(
681    k1: &FieldElement<LIMBS>,
682    g: &JacobianPoint<LIMBS>,
683    k2: &FieldElement<LIMBS>,
684    q: &JacobianPoint<LIMBS>,
685    params: &CurveParams<LIMBS>,
686) -> JacobianPoint<LIMBS> {
687    // Shamir's trick: scan bits of k1 and k2 simultaneously.
688    let total_bits = LIMBS * 64;
689    let mut result = JacobianPoint::infinity();
690
691    // Precompute: G, Q, G+Q
692    let g_plus_q = point_add(g, q, params);
693
694    for i in (0..total_bits).rev() {
695        result = point_double(&result, params);
696
697        let limb_idx = i / 64;
698        let bit_idx = i % 64;
699        let b1 = (k1.limbs[limb_idx] >> bit_idx) & 1;
700        let b2 = (k2.limbs[limb_idx] >> bit_idx) & 1;
701
702        let to_add = match (b1, b2) {
703            (1, 1) => Some(&g_plus_q),
704            (1, 0) => Some(g),
705            (0, 1) => Some(q),
706            _ => None,
707        };
708
709        if let Some(pt) = to_add {
710            result = point_add(&result, pt, params);
711        }
712    }
713
714    result
715}
716
717#[cfg(test)]
718mod tests {
719    use super::*;
720
721    fn hex_to_bytes(hex: &str) -> Vec<u8> {
722        (0..hex.len())
723            .step_by(2)
724            .map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap())
725            .collect()
726    }
727
728    #[test]
729    fn test_p256_generator_on_curve() {
730        let params = p256_params();
731        let p = &params.p;
732
733        // Verify y^2 = x^3 + ax + b for the generator.
734        let x = &params.gx;
735        let y = &params.gy;
736        let y2 = field_sqr(y, p);
737
738        let x2 = field_sqr(x, p);
739        let x3 = field_mul(&x2, x, p);
740        let ax = field_mul(&params.a, x, p);
741        let rhs = field_add(&field_add(&x3, &ax, p), &params.b, p);
742
743        assert_eq!(y2, rhs, "Generator point not on curve");
744    }
745
746    #[test]
747    fn test_p256_scalar_mul_generator() {
748        let params = p256_params();
749        let g = JacobianPoint::from_affine(params.gx, params.gy);
750
751        // 1*G should equal G.
752        let one = FieldElement::<4>::one();
753        let result = scalar_mul_point(&one, &g, &params);
754        let (rx, ry) = result.to_affine(&params.p).unwrap();
755        assert_eq!(rx, params.gx);
756        assert_eq!(ry, params.gy);
757    }
758
759    #[test]
760    fn test_p256_double_generator() {
761        let params = p256_params();
762        let g = JacobianPoint::from_affine(params.gx, params.gy);
763
764        // 2*G via scalar_mul.
765        let two = FieldElement::<4>::from_bytes_be(&[2]);
766        let result = scalar_mul_point(&two, &g, &params);
767        let (rx, _ry) = result.to_affine(&params.p).unwrap();
768
769        // 2*G via point_double.
770        let doubled = point_double(&g, &params);
771        let (dx, _dy) = doubled.to_affine(&params.p).unwrap();
772
773        assert_eq!(rx, dx);
774    }
775
776    #[test]
777    fn test_p256_n_times_g_is_infinity() {
778        let params = p256_params();
779        let g = JacobianPoint::from_affine(params.gx, params.gy);
780
781        // n*G should be the point at infinity.
782        let n = FieldElement::<4> { limbs: params.n };
783        let result = scalar_mul_point(&n, &g, &params);
784        assert!(result.is_infinity(), "n*G should be infinity");
785    }
786
787    /// Generic check: verify y^2 == x^3 + a*x + b for the generator.
788    fn check_generator_on_curve<const LIMBS: usize>(params: &CurveParams<LIMBS>) {
789        assert!(is_on_curve(&params.gx, &params.gy, params), "Generator not on curve");
790    }
791
792    /// Generic check: n*G must be the point at infinity. This is the strongest
793    /// quick sanity check on the (p, a, b, G, n) tuple together.
794    fn check_n_times_g_is_infinity<const LIMBS: usize>(params: &CurveParams<LIMBS>) {
795        let g = JacobianPoint::from_affine(params.gx, params.gy);
796        let n = FieldElement::<LIMBS> { limbs: params.n };
797        let result = scalar_mul_point(&n, &g, params);
798        assert!(result.is_infinity(), "n*G is not infinity");
799    }
800
801    #[test]
802    fn test_p384_generator_on_curve() {
803        check_generator_on_curve(&p384_params());
804    }
805
806    #[test]
807    fn test_p384_n_times_g_is_infinity() {
808        check_n_times_g_is_infinity(&p384_params());
809    }
810
811    #[test]
812    fn test_secp256k1_generator_on_curve() {
813        check_generator_on_curve(&secp256k1_params());
814    }
815
816    #[test]
817    fn test_secp256k1_n_times_g_is_infinity() {
818        check_n_times_g_is_infinity(&secp256k1_params());
819    }
820
821    #[test]
822    fn test_brainpoolp256r1_generator_on_curve() {
823        check_generator_on_curve(&brainpoolp256r1_params());
824    }
825
826    #[test]
827    fn test_brainpoolp256r1_n_times_g_is_infinity() {
828        check_n_times_g_is_infinity(&brainpoolp256r1_params());
829    }
830
831    #[test]
832    fn test_brainpoolp384r1_generator_on_curve() {
833        check_generator_on_curve(&brainpoolp384r1_params());
834    }
835
836    #[test]
837    fn test_brainpoolp384r1_n_times_g_is_infinity() {
838        check_n_times_g_is_infinity(&brainpoolp384r1_params());
839    }
840
841    #[test]
842    fn test_brainpoolp512r1_generator_on_curve() {
843        check_generator_on_curve(&brainpoolp512r1_params());
844    }
845
846    #[test]
847    fn test_brainpoolp512r1_n_times_g_is_infinity() {
848        check_n_times_g_is_infinity(&brainpoolp512r1_params());
849    }
850
851    #[test]
852    fn test_secp521r1_generator_on_curve() {
853        check_generator_on_curve(&secp521r1_params());
854    }
855
856    #[test]
857    fn test_secp521r1_n_times_g_is_infinity() {
858        check_n_times_g_is_infinity(&secp521r1_params());
859    }
860
861    #[test]
862    fn test_p256_known_2g() {
863        // Known value of 2*G for P-256.
864        let params = p256_params();
865        let g = JacobianPoint::from_affine(params.gx, params.gy);
866
867        let two = FieldElement::<4>::from_bytes_be(&[2]);
868        let result = scalar_mul_point(&two, &g, &params);
869        let (rx, ry) = result.to_affine(&params.p).unwrap();
870
871        let expected_x = hex_to_bytes("7CF27B188D034F7E8A52380304B51AC3C08969E277F21B35A60B48FC47669978");
872        let expected_y = hex_to_bytes("07775510DB8ED040293D9AC69F7430DBBA7DADE63CE982299E04B79D227873D1");
873
874        assert_eq!(rx.to_bytes_be(), expected_x);
875        assert_eq!(ry.to_bytes_be(), expected_y);
876    }
877}