Skip to main content

cryptography/public_key/
ec.rs

1//! Elliptic-curve arithmetic over short-Weierstrass prime-field curves.
2//!
3//! Supported curve form:
4//!
5//! ```math
6//! y^2 = x^3 + ax + b  \pmod{p}
7//! ```
8//!
9//! where `p` is prime and the curve parameters `(a, b, n, h, G)` define a
10//! subgroup of prime order `n` with cofactor `h`.
11//!
12//! This module is the arithmetic substrate for elliptic-curve public-key
13//! schemes such as `ECDH`, `ECDSA`, and `EC-ElGamal`. It provides:
14//!
15//! - [`CurveParams`] — curve parameters with precomputed field and scalar
16//!   Montgomery contexts.
17//! - [`AffinePoint`] — a curve point in affine `(x, y)` coordinates, or `∞`.
18//! - Named-curve constructors: [`p256`], [`p384`], [`secp256k1`].
19//! - SEC 1 byte encoding and decoding for uncompressed and compressed points.
20//! - Random scalar sampling and ECDH shared-point computation.
21//!
22//! ## Coordinate system
23//!
24//! All scalar multiplications use Jacobian projective coordinates `(X : Y : Z)`
25//! internally to avoid a costly field inversion on every intermediate step.
26//! The affine point `(x, y)` maps to `(x·Z², y·Z³, Z)` for any `Z ≠ 0`.  A
27//! single inversion converts the final result back to affine.
28//!
29//! ## Field arithmetic
30//!
31//! Field operations delegate to the same [`MontgomeryCtx`] that backs RSA,
32//! `ElGamal`, and `DSA` elsewhere in the crate.  [`CurveParams`] stores one
33//! `MontgomeryCtx` for the field prime `p` (used in point arithmetic) and one
34//! for the subgroup order `n` (used in scalar arithmetic), both pre-built at
35//! construction time.
36//!
37//! ## Side-channel note
38//!
39//! The scalar multiplication in this module uses a left-to-right double-and-add
40//! loop and is **not constant-time**.  Branches and memory accesses depend on
41//! the secret scalar, so the current implementation is unsuitable in an
42//! adversarial environment where a side-channel attacker can observe timing or
43//! power consumption.  A constant-time Montgomery ladder should replace the
44//! loop before exposing scalar multiplication to such an environment.
45
46use crate::public_key::bigint::{BigUint, MontgomeryCtx};
47use crate::public_key::gf2m::{gf2m_add, gf2m_half_trace, gf2m_inv, gf2m_mul, gf2m_sq};
48use crate::public_key::primes::{mod_inverse, random_nonzero_below};
49use crate::Csprng;
50
51// ─── Core types ─────────────────────────────────────────────────────────────
52
53/// Discriminates between prime-field and binary-extension-field arithmetic.
54///
55/// Prime-field curves use Montgomery arithmetic via [`MontgomeryCtx`].
56/// Binary-field curves use polynomial arithmetic over `GF(2^m)`; `poly` is the
57/// irreducible polynomial encoded as a `BigUint` bit-pattern and `degree` is
58/// its degree `m`.
59#[derive(Clone, Debug)]
60pub(crate) enum FieldCtx {
61    /// Short-Weierstrass curve over a prime field `F_p`.
62    Prime(MontgomeryCtx),
63    /// Short-Weierstrass curve over a binary extension field GF(2^m).
64    Binary {
65        /// Irreducible polynomial of degree `degree`, encoded as a `BigUint`.
66        poly: BigUint,
67        /// Degree of the extension, i.e. m in GF(2^m).
68        degree: usize,
69    },
70}
71
72/// Parameters for a short-Weierstrass elliptic curve y² = x³ + ax + b (mod p).
73///
74/// All coordinates and coefficients are ordinary residues in `[0, p)`.  The
75/// two `MontgomeryCtx` fields are pre-built at construction and shared by
76/// every arithmetic operation on the curve.
77///
78/// A `CurveParams` value is relatively large (two Montgomery contexts plus six
79/// `BigUint`s) but heap-allocated and cheap to clone once built.
80#[derive(Clone, Debug)]
81pub struct CurveParams {
82    /// Field prime — all point coordinates are reduced modulo `p`.
83    pub p: BigUint,
84    /// Curve coefficient `a` in `F_p`.
85    pub a: BigUint,
86    /// Curve coefficient `b` in `F_p`.
87    pub b: BigUint,
88    /// Prime order of the base-point subgroup.
89    pub n: BigUint,
90    /// Cofactor `h`.  For all named curves here `h = 1`.
91    pub h: u64,
92    /// x-coordinate of the standard base point `G`.
93    pub gx: BigUint,
94    /// y-coordinate of the standard base point `G`.
95    pub gy: BigUint,
96    /// Field context: Montgomery arithmetic for prime fields, polynomial
97    /// arithmetic for binary extension fields.
98    ///
99    /// For prime curves, this holds a precomputed [`MontgomeryCtx`] for
100    /// arithmetic mod `p`.  For binary curves, this holds the irreducible
101    /// polynomial and degree; `p` stores the polynomial as a bit-pattern.
102    pub(crate) field: FieldCtx,
103    /// Precomputed Montgomery context for scalar arithmetic mod `n`.
104    ///
105    /// This is kept alongside the field context because scalar-field helpers
106    /// (signatures, Diffie-Hellman, and related protocols) need the same
107    /// modulus repeatedly even when a particular module is not using it yet.
108    pub(crate) _scalar: MontgomeryCtx,
109    /// Byte length of a field element: `⌈p.bits() / 8⌉`.
110    ///
111    /// Used for fixed-length point encoding; coordinates are zero-padded to
112    /// this length so that every encoded coordinate has the same width.
113    pub coord_len: usize,
114}
115
116/// An affine curve point, or the point at infinity.
117///
118/// The coordinates are ordinary residues in `[0, p)`.  The point at infinity
119/// is the group identity: `P + ∞ = P` and `n·G = ∞`.
120#[derive(Clone, Debug, Eq, PartialEq)]
121pub struct AffinePoint {
122    /// x-coordinate.  Meaningful only when `!infinity`.
123    pub x: BigUint,
124    /// y-coordinate.  Meaningful only when `!infinity`.
125    pub y: BigUint,
126    /// `true` when this represents the point at infinity (the group identity).
127    pub infinity: bool,
128}
129
130/// Jacobian projective coordinates `(X : Y : Z)`.
131///
132/// The affine point `(x, y)` corresponds to `(X, Y, Z)` with `x = X/Z²` and
133/// `y = Y/Z³`, for any non-zero `Z`.  This representation eliminates field
134/// inversions in every intermediate addition and doubling step; a single
135/// inversion recovers affine coordinates at the end.  The point at infinity
136/// is represented with `Z = 0`.
137struct JacobianPoint {
138    x: BigUint,
139    y: BigUint,
140    z: BigUint,
141}
142
143// ─── AffinePoint ────────────────────────────────────────────────────────────
144
145impl AffinePoint {
146    /// The group identity (point at infinity).
147    #[must_use]
148    pub fn infinity() -> Self {
149        Self {
150            x: BigUint::zero(),
151            y: BigUint::zero(),
152            infinity: true,
153        }
154    }
155
156    /// A finite affine point `(x, y)`.
157    ///
158    /// The caller is responsible for ensuring that `(x, y)` lies on the
159    /// intended curve; use [`CurveParams::is_on_curve`] to validate.
160    #[must_use]
161    pub fn new(x: BigUint, y: BigUint) -> Self {
162        Self {
163            x,
164            y,
165            infinity: false,
166        }
167    }
168
169    /// Return `true` if this is the point at infinity.
170    #[must_use]
171    pub fn is_infinity(&self) -> bool {
172        self.infinity
173    }
174}
175
176// ─── JacobianPoint ──────────────────────────────────────────────────────────
177
178impl JacobianPoint {
179    /// The point at infinity in Jacobian form (`Z = 0`).
180    fn infinity() -> Self {
181        // X and Y are irrelevant when Z = 0; set them to 1 to avoid allocating
182        // limb vectors unnecessarily.
183        Self {
184            x: BigUint::one(),
185            y: BigUint::one(),
186            z: BigUint::zero(),
187        }
188    }
189
190    fn is_infinity(&self) -> bool {
191        self.z.is_zero()
192    }
193
194    /// Lift an affine point to Jacobian coordinates with `Z = 1`.
195    ///
196    /// Setting `Z = 1` means `X = x·1² = x` and `Y = y·1³ = y`, so the
197    /// Jacobian coordinates are just the affine coordinates unchanged.
198    fn from_affine(p: &AffinePoint) -> Self {
199        if p.infinity {
200            return Self::infinity();
201        }
202        Self {
203            x: p.x.clone(),
204            y: p.y.clone(),
205            z: BigUint::one(),
206        }
207    }
208
209    /// Project back to affine coordinates.
210    ///
211    /// Recovers `x = X/Z²` and `y = Y/Z³` by computing the field inverse of
212    /// `Z` via Fermat's little theorem: `Z⁻¹ = Z^{p−2} mod p` (valid because
213    /// `p` is prime).  This is the only modular exponentiation (and therefore
214    /// the only costly operation) that the scalar-multiplication loop pays per
215    /// call; every intermediate step uses inversion-free Jacobian arithmetic.
216    fn to_affine(&self, curve: &CurveParams) -> AffinePoint {
217        if self.is_infinity() {
218            return AffinePoint::infinity();
219        }
220        // Fast path: if Z = 1 (as set by from_affine) no inversion is needed.
221        if self.z == BigUint::one() {
222            return AffinePoint::new(self.x.clone(), self.y.clone());
223        }
224
225        let ctx = curve.prime_ctx();
226        let p = &curve.p;
227
228        // z_inv = Z^{p-2} mod p  (Fermat inversion over a prime field)
229        let p_minus_2 = p.sub_ref(&BigUint::from_u64(2));
230        let z_inv = ctx.pow(&self.z, &p_minus_2);
231
232        // z_inv2 = Z^{-2}  and  z_inv3 = Z^{-3}
233        let z_inv2 = ctx.square(&z_inv);
234        let z_inv3 = ctx.mul(&z_inv2, &z_inv);
235
236        let x = ctx.mul(&self.x, &z_inv2);
237        let y = ctx.mul(&self.y, &z_inv3);
238
239        AffinePoint::new(x, y)
240    }
241}
242
243// ─── Field helpers ──────────────────────────────────────────────────────────
244
245/// `(a + b) mod p`.
246///
247/// Both inputs must be in `[0, p)`.  The sum is at most `2p − 2`, so at most
248/// one subtraction is needed to reduce back into `[0, p)`.
249#[inline]
250fn field_add(a: &BigUint, b: &BigUint, p: &BigUint) -> BigUint {
251    let sum = a.add_ref(b);
252    if &sum >= p {
253        sum.sub_ref(p)
254    } else {
255        sum
256    }
257}
258
259/// `(a − b) mod p`.
260///
261/// Both inputs must be in `[0, p)`.
262#[inline]
263fn field_sub(a: &BigUint, b: &BigUint, p: &BigUint) -> BigUint {
264    if a >= b {
265        a.sub_ref(b)
266    } else {
267        // a < b: result = p − (b − a).  Since both are in [0, p), the
268        // difference b − a is in (0, p), and p − (b − a) is in (0, p).
269        p.sub_ref(&b.sub_ref(a))
270    }
271}
272
273/// `(−a) mod p`.
274#[inline]
275fn field_neg(a: &BigUint, p: &BigUint) -> BigUint {
276    if a.is_zero() {
277        BigUint::zero()
278    } else {
279        p.sub_ref(a)
280    }
281}
282
283/// Pad `bytes` to `len` bytes by prepending zero bytes.
284///
285/// `BigUint::to_be_bytes` strips leading zero bytes; point encoding needs
286/// fixed-width coordinates so that every field element occupies the same
287/// number of bytes regardless of its value.
288fn pad_to(bytes: Vec<u8>, len: usize) -> Vec<u8> {
289    if bytes.len() >= len {
290        return bytes;
291    }
292    let mut out = vec![0u8; len - bytes.len()];
293    out.extend_from_slice(&bytes);
294    out
295}
296
297// ─── Point arithmetic ───────────────────────────────────────────────────────
298
299/// Point doubling in Jacobian coordinates.
300///
301/// Uses the general short-Weierstrass doubling formulas from the Explicit
302/// Formulas Database (Hankerson–Menezes–Vanstone, Guide to ECC, §3.2.2):
303///
304/// ```text
305/// A  = 4·X·Y²
306/// B  = 3·X² + a·Z⁴
307/// X' = B² − 2·A
308/// Y' = B·(A − X') − 8·Y⁴
309/// Z' = 2·Y·Z
310/// ```
311///
312/// This handles any curve coefficient `a`, including the common `a = −3` of
313/// the NIST curves (no special case is needed for `a = −3` for correctness,
314/// though a specialised formula would be marginally faster).
315fn point_double_jacobian(curve: &CurveParams, p: &JacobianPoint) -> JacobianPoint {
316    if p.is_infinity() {
317        return JacobianPoint::infinity();
318    }
319
320    let ctx = curve.prime_ctx();
321    let m = &curve.p;
322
323    // Y² and Y⁴
324    let y2 = ctx.square(&p.y);
325    let y4 = ctx.square(&y2);
326
327    // A = 4·X·Y²
328    let xy2 = ctx.mul(&p.x, &y2);
329    let two_xy2 = field_add(&xy2, &xy2, m);
330    let a = field_add(&two_xy2, &two_xy2, m);
331
332    // X²; Z² and Z⁴
333    let x2 = ctx.square(&p.x);
334    let z2 = ctx.square(&p.z);
335    let z4 = ctx.square(&z2);
336
337    // B = 3·X² + a·Z⁴
338    let three_x2 = field_add(&field_add(&x2, &x2, m), &x2, m);
339    let a_coeff_z4 = ctx.mul(&curve.a, &z4);
340    let b = field_add(&three_x2, &a_coeff_z4, m);
341
342    // X' = B² − 2·A
343    let b2 = ctx.square(&b);
344    let two_a = field_add(&a, &a, m);
345    let x_new = field_sub(&b2, &two_a, m);
346
347    // Y' = B·(A − X') − 8·Y⁴
348    let a_minus_x = field_sub(&a, &x_new, m);
349    let b_times = ctx.mul(&b, &a_minus_x);
350    let two_y4 = field_add(&y4, &y4, m);
351    let four_y4 = field_add(&two_y4, &two_y4, m);
352    let eight_y4 = field_add(&four_y4, &four_y4, m);
353    let y_new = field_sub(&b_times, &eight_y4, m);
354
355    // Z' = 2·Y·Z
356    let yz = ctx.mul(&p.y, &p.z);
357    let z_new = field_add(&yz, &yz, m);
358
359    JacobianPoint {
360        x: x_new,
361        y: y_new,
362        z: z_new,
363    }
364}
365
366/// Point addition in Jacobian coordinates.
367///
368/// Uses the standard complete-Jacobian formulas (EFD `add-2007-bl`):
369///
370/// ```text
371/// U₁ = X₁·Z₂²,   U₂ = X₂·Z₁²
372/// S₁ = Y₁·Z₂³,   S₂ = Y₂·Z₁³
373/// H  = U₂ − U₁,  R  = S₂ − S₁
374/// X₃ = R² − H³ − 2·U₁·H²
375/// Y₃ = R·(U₁·H² − X₃) − S₁·H³
376/// Z₃ = H·Z₁·Z₂
377/// ```
378///
379/// The `H = 0` branch handles both the doubling case (`R = 0` too, meaning
380/// `P₁ = P₂`) and the point-at-infinity case (`R ≠ 0`, meaning `P₁ = −P₂`).
381fn point_add_jacobian(
382    curve: &CurveParams,
383    p1: &JacobianPoint,
384    p2: &JacobianPoint,
385) -> JacobianPoint {
386    if p1.is_infinity() {
387        return JacobianPoint {
388            x: p2.x.clone(),
389            y: p2.y.clone(),
390            z: p2.z.clone(),
391        };
392    }
393    if p2.is_infinity() {
394        return JacobianPoint {
395            x: p1.x.clone(),
396            y: p1.y.clone(),
397            z: p1.z.clone(),
398        };
399    }
400
401    let ctx = curve.prime_ctx();
402    let m = &curve.p;
403
404    let z1_2 = ctx.square(&p1.z);
405    let z2_2 = ctx.square(&p2.z);
406    let z1_3 = ctx.mul(&z1_2, &p1.z);
407    let z2_3 = ctx.mul(&z2_2, &p2.z);
408
409    let u1 = ctx.mul(&p1.x, &z2_2);
410    let u2 = ctx.mul(&p2.x, &z1_2);
411    let s1 = ctx.mul(&p1.y, &z2_3);
412    let s2 = ctx.mul(&p2.y, &z1_3);
413
414    let h = field_sub(&u2, &u1, m);
415    let r = field_sub(&s2, &s1, m);
416
417    if h.is_zero() {
418        return if r.is_zero() {
419            // P₁ = P₂: use the doubling formula instead of the addition
420            // formula (which has a division by H = 0 and would give garbage).
421            point_double_jacobian(curve, p1)
422        } else {
423            // P₁ = −P₂: the sum is the point at infinity.
424            JacobianPoint::infinity()
425        };
426    }
427
428    let h2 = ctx.square(&h);
429    let h3 = ctx.mul(&h2, &h);
430    let u1h2 = ctx.mul(&u1, &h2);
431
432    // X₃ = R² − H³ − 2·U₁·H²
433    let r2 = ctx.square(&r);
434    let two_u1h2 = field_add(&u1h2, &u1h2, m);
435    let x3 = field_sub(&field_sub(&r2, &h3, m), &two_u1h2, m);
436
437    // Y₃ = R·(U₁·H² − X₃) − S₁·H³
438    let u1h2_minus_x3 = field_sub(&u1h2, &x3, m);
439    let r_term = ctx.mul(&r, &u1h2_minus_x3);
440    let s1h3 = ctx.mul(&s1, &h3);
441    let y3 = field_sub(&r_term, &s1h3, m);
442
443    // Z₃ = H·Z₁·Z₂
444    let hz1 = ctx.mul(&h, &p1.z);
445    let z3 = ctx.mul(&hz1, &p2.z);
446
447    JacobianPoint {
448        x: x3,
449        y: y3,
450        z: z3,
451    }
452}
453
454/// Scalar multiplication `k·P` via left-to-right binary double-and-add.
455///
456/// The loop stays in Jacobian coordinates from start to finish and converts
457/// back to affine exactly once at the end, paying one field inversion instead
458/// of one per bit.
459///
460/// **Side-channel note**: branching in the inner loop depends on the bit value
461/// of `k`.  This is not constant-time; see the module-level note.
462fn scalar_mul_jacobian(curve: &CurveParams, point: &AffinePoint, k: &BigUint) -> AffinePoint {
463    if k.is_zero() || point.is_infinity() {
464        return AffinePoint::infinity();
465    }
466
467    let mut result = JacobianPoint::infinity();
468    let p_jac = JacobianPoint::from_affine(point);
469
470    // Scan from the most-significant bit down to bit 0.
471    for i in (0..k.bits()).rev() {
472        result = point_double_jacobian(curve, &result);
473        if k.bit(i) {
474            result = point_add_jacobian(curve, &result, &p_jac);
475        }
476    }
477
478    result.to_affine(curve)
479}
480
481// ─── Binary-curve affine arithmetic ─────────────────────────────────────────
482
483/// On-curve check for binary Weierstrass curves: `y² + xy = x³ + ax² + b`.
484fn is_on_curve_binary(
485    x: &BigUint,
486    y: &BigUint,
487    a: &BigUint,
488    b: &BigUint,
489    poly: &BigUint,
490    degree: usize,
491) -> bool {
492    // lhs = y² + x·y
493    let y2 = gf2m_sq(y, poly, degree);
494    let xy = gf2m_mul(x, y, poly, degree);
495    let lhs = gf2m_add(&y2, &xy);
496    // rhs = x³ + a·x² + b
497    let x2 = gf2m_sq(x, poly, degree);
498    let x3 = gf2m_mul(x, &x2, poly, degree);
499    let ax2 = gf2m_mul(a, &x2, poly, degree);
500    let rhs = gf2m_add(&gf2m_add(&x3, &ax2), b);
501    lhs == rhs
502}
503
504/// Point addition for binary Weierstrass curves in affine coordinates.
505///
506/// Uses the standard formula for P ≠ Q, neither at infinity (Hankerson et al.,
507/// §3.1):
508/// ```text
509/// λ = (yP + yQ) / (xP + xQ)
510/// xR = λ² + λ + xP + xQ + a
511/// yR = λ(xP + xR) + xR + yP
512/// ```
513fn add_binary(
514    p: &AffinePoint,
515    q: &AffinePoint,
516    a: &BigUint,
517    poly: &BigUint,
518    degree: usize,
519) -> AffinePoint {
520    if p.is_infinity() {
521        return q.clone();
522    }
523    if q.is_infinity() {
524        return p.clone();
525    }
526
527    let x1 = &p.x;
528    let y1 = &p.y;
529    let x2 = &q.x;
530    let y2 = &q.y;
531
532    let dx = gf2m_add(x1, x2);
533    if dx.is_zero() {
534        // x1 = x2: either P = Q (double) or Q = −P (sum = ∞).
535        let dy = gf2m_add(y1, y2);
536        return if dy.is_zero() {
537            // y1 = y2 and x1 = x2 → P = Q.
538            double_binary(p, a, poly, degree)
539        } else {
540            // Q = −P (since −P = (xP, xP ⊕ yP), so xP ⊕ yP = yQ when xP = xQ
541            // and the sum is the identity).
542            AffinePoint::infinity()
543        };
544    }
545
546    // λ = (y1 + y2) / (x1 + x2)
547    let dy = gf2m_add(y1, y2);
548    let dx_inv = gf2m_inv(&dx, poly, degree).expect("dx is non-zero");
549    let lambda = gf2m_mul(&dy, &dx_inv, poly, degree);
550
551    // xR = λ² + λ + x1 + x2 + a
552    let lambda_sq = gf2m_sq(&lambda, poly, degree);
553    let mut xr = gf2m_add(&lambda_sq, &lambda);
554    xr.bitxor_assign(x1);
555    xr.bitxor_assign(x2);
556    xr.bitxor_assign(a);
557
558    // yR = λ(x1 + xR) + xR + y1
559    let x1_xr = gf2m_add(x1, &xr);
560    let lambda_term = gf2m_mul(&lambda, &x1_xr, poly, degree);
561    let mut yr = gf2m_add(&lambda_term, &xr);
562    yr.bitxor_assign(y1);
563
564    AffinePoint::new(xr, yr)
565}
566
567/// Point doubling for binary Weierstrass curves in affine coordinates.
568///
569/// Uses the standard formula for P ≠ O, xP ≠ 0 (Hankerson et al., §3.1):
570/// ```text
571/// λ = xP + yP / xP
572/// xR = λ² + λ + a
573/// yR = xP² + (λ + 1)·xR
574/// ```
575///
576/// If `xP = 0` then `2P = ∞` (P is its own inverse).
577fn double_binary(p: &AffinePoint, a: &BigUint, poly: &BigUint, degree: usize) -> AffinePoint {
578    if p.is_infinity() {
579        return AffinePoint::infinity();
580    }
581    if p.x.is_zero() {
582        // xP = 0 implies −P = (0, yP) = P, so 2P = ∞.
583        return AffinePoint::infinity();
584    }
585
586    let x1 = &p.x;
587    let y1 = &p.y;
588
589    // λ = x1 + y1 / x1
590    let x1_inv = gf2m_inv(x1, poly, degree).expect("x is non-zero");
591    let y1_over_x1 = gf2m_mul(y1, &x1_inv, poly, degree);
592    let lambda = gf2m_add(x1, &y1_over_x1);
593
594    // xR = λ² + λ + a
595    let lambda_sq = gf2m_sq(&lambda, poly, degree);
596    let mut xr = gf2m_add(&lambda_sq, &lambda);
597    xr.bitxor_assign(a);
598
599    // yR = x1² + (λ + 1)·xR
600    let x1_sq = gf2m_sq(x1, poly, degree);
601    let lambda_plus_1 = gf2m_add(&lambda, &BigUint::one());
602    let lambda_plus_1_xr = gf2m_mul(&lambda_plus_1, &xr, poly, degree);
603    let yr = gf2m_add(&x1_sq, &lambda_plus_1_xr);
604
605    AffinePoint::new(xr, yr)
606}
607
608/// Scalar multiplication for binary curves using left-to-right double-and-add
609/// in affine coordinates (no Jacobian optimisation).
610fn scalar_mul_binary(curve: &CurveParams, point: &AffinePoint, k: &BigUint) -> AffinePoint {
611    if k.is_zero() || point.is_infinity() {
612        return AffinePoint::infinity();
613    }
614
615    let mut result = AffinePoint::infinity();
616    for i in (0..k.bits()).rev() {
617        result = curve.double(&result);
618        if k.bit(i) {
619            result = curve.add(&result, point);
620        }
621    }
622    result
623}
624
625// ─── CurveParams ────────────────────────────────────────────────────────────
626
627impl CurveParams {
628    /// Construct curve parameters from raw field values.
629    ///
630    /// Returns `None` if the field prime `p` or subgroup order `n` is even,
631    /// which would prevent building a Montgomery context.  Well-formed
632    /// cryptographic curves always have an odd prime field and odd prime order,
633    /// so `None` indicates a programming error in the caller.
634    #[must_use]
635    pub fn new(
636        field_prime: BigUint,
637        curve_a: BigUint,
638        curve_b: BigUint,
639        subgroup_order: BigUint,
640        cofactor: u64,
641        base_x: BigUint,
642        base_y: BigUint,
643    ) -> Option<Self> {
644        let field = MontgomeryCtx::new(&field_prime)?;
645        let scalar = MontgomeryCtx::new(&subgroup_order)?;
646        let coord_len = field_prime.bits().div_ceil(8);
647        Some(Self {
648            p: field_prime,
649            a: curve_a,
650            b: curve_b,
651            n: subgroup_order,
652            h: cofactor,
653            gx: base_x,
654            gy: base_y,
655            field: FieldCtx::Prime(field),
656            _scalar: scalar,
657            coord_len,
658        })
659    }
660
661    /// Construct binary-curve parameters for a short-Weierstrass curve over
662    /// GF(2^m): `y² + xy = x³ + ax² + b`.
663    ///
664    /// - `poly` is the irreducible polynomial of degree `degree`, encoded as a
665    ///   `BigUint` bit-pattern.
666    /// - `n` must be an odd prime (the scalar-field Montgomery context
667    ///   requires this).
668    ///
669    /// Returns `None` if `n` is even (which would indicate malformed curve
670    /// parameters).
671    #[must_use]
672    pub fn new_binary(
673        modulus_poly: BigUint,
674        degree: usize,
675        curve_a: BigUint,
676        curve_b: BigUint,
677        subgroup_order: BigUint,
678        cofactor: u64,
679        base_point: (BigUint, BigUint),
680    ) -> Option<Self> {
681        let (base_x, base_y) = base_point;
682        let scalar = MontgomeryCtx::new(&subgroup_order)?;
683        let coord_len = degree.div_ceil(8);
684        let field_prime = modulus_poly.clone();
685        Some(Self {
686            p: field_prime,
687            a: curve_a,
688            b: curve_b,
689            n: subgroup_order,
690            h: cofactor,
691            gx: base_x,
692            gy: base_y,
693            field: FieldCtx::Binary {
694                poly: modulus_poly,
695                degree,
696            },
697            _scalar: scalar,
698            coord_len,
699        })
700    }
701
702    /// Return a reference to the prime-field Montgomery context.
703    ///
704    /// # Panics
705    ///
706    /// Panics if called on a binary-curve `CurveParams`.  Internal callers
707    /// must only invoke this from code paths that are gated on
708    /// `FieldCtx::Prime`.
709    fn prime_ctx(&self) -> &MontgomeryCtx {
710        match &self.field {
711            FieldCtx::Prime(ctx) => ctx,
712            FieldCtx::Binary { .. } => {
713                panic!("prime_ctx called on a binary-field curve")
714            }
715        }
716    }
717
718    /// Return the field degree `m` if this is a binary-extension-field curve,
719    /// or `None` for a prime-field curve.
720    #[must_use]
721    pub fn gf2m_degree(&self) -> Option<usize> {
722        match &self.field {
723            FieldCtx::Binary { degree, .. } => Some(*degree),
724            FieldCtx::Prime(_) => None,
725        }
726    }
727
728    /// The standard base point `G = (Gx, Gy)`.
729    #[must_use]
730    pub fn base_point(&self) -> AffinePoint {
731        AffinePoint::new(self.gx.clone(), self.gy.clone())
732    }
733
734    /// Return `true` if `point` lies on this curve.
735    ///
736    /// For prime-field curves verifies `y² ≡ x³ + ax + b (mod p)`.
737    /// For binary-field curves verifies `y² + xy = x³ + ax² + b` in GF(2^m).
738    /// The point at infinity trivially passes.
739    #[must_use]
740    pub fn is_on_curve(&self, point: &AffinePoint) -> bool {
741        if point.infinity {
742            return true;
743        }
744        match &self.field {
745            FieldCtx::Prime(ctx) => {
746                // lhs = y²
747                let lhs = ctx.square(&point.y);
748                // rhs = x³ + a·x + b
749                let x2 = ctx.square(&point.x);
750                let x3 = ctx.mul(&x2, &point.x);
751                let ax = ctx.mul(&self.a, &point.x);
752                let rhs = field_add(&field_add(&x3, &ax, &self.p), &self.b, &self.p);
753                lhs == rhs
754            }
755            FieldCtx::Binary { poly, degree } => {
756                is_on_curve_binary(&point.x, &point.y, &self.a, &self.b, poly, *degree)
757            }
758        }
759    }
760
761    /// Negate a point.
762    ///
763    /// Prime curves: `(x, y)` → `(x, −y mod p)`.
764    /// Binary curves: `(x, y)` → `(x, x ⊕ y)` (since −1 = 1 in GF(2)).
765    #[must_use]
766    pub fn negate(&self, point: &AffinePoint) -> AffinePoint {
767        if point.infinity {
768            return point.clone();
769        }
770        match &self.field {
771            FieldCtx::Prime(_) => AffinePoint::new(point.x.clone(), field_neg(&point.y, &self.p)),
772            FieldCtx::Binary { .. } => {
773                // −P = (xP, xP ⊕ yP)
774                let neg_y = gf2m_add(&point.x, &point.y);
775                AffinePoint::new(point.x.clone(), neg_y)
776            }
777        }
778    }
779
780    /// Add two affine curve points.
781    #[must_use]
782    pub fn add(&self, p: &AffinePoint, q: &AffinePoint) -> AffinePoint {
783        match &self.field {
784            FieldCtx::Prime(_) => {
785                let pj = JacobianPoint::from_affine(p);
786                let qj = JacobianPoint::from_affine(q);
787                point_add_jacobian(self, &pj, &qj).to_affine(self)
788            }
789            FieldCtx::Binary { poly, degree } => add_binary(p, q, &self.a, poly, *degree),
790        }
791    }
792
793    /// Double an affine curve point (`2P`).
794    #[must_use]
795    pub fn double(&self, p: &AffinePoint) -> AffinePoint {
796        match &self.field {
797            FieldCtx::Prime(_) => {
798                let pj = JacobianPoint::from_affine(p);
799                point_double_jacobian(self, &pj).to_affine(self)
800            }
801            FieldCtx::Binary { poly, degree } => double_binary(p, &self.a, poly, *degree),
802        }
803    }
804
805    /// Scalar multiplication `k·P`.
806    ///
807    /// Returns the point at infinity when `k = 0` or `P = ∞`.
808    #[must_use]
809    pub fn scalar_mul(&self, point: &AffinePoint, k: &BigUint) -> AffinePoint {
810        match &self.field {
811            FieldCtx::Prime(_) => scalar_mul_jacobian(self, point, k),
812            FieldCtx::Binary { .. } => scalar_mul_binary(self, point, k),
813        }
814    }
815
816    /// Compute the ECDH shared point `d·Q`.
817    ///
818    /// In Diffie-Hellman, Alice holds private scalar `d` and receives Bob's
819    /// public point `Q = d_B·G`; the shared secret is the x-coordinate of
820    /// `d·Q = d·d_B·G`.
821    #[must_use]
822    pub fn diffie_hellman(
823        &self,
824        private_scalar: &BigUint,
825        public_point: &AffinePoint,
826    ) -> AffinePoint {
827        self.scalar_mul(public_point, private_scalar)
828    }
829
830    /// Sample a uniform random scalar in `[1, n)`.
831    ///
832    /// This is the standard private-key range for ECDH and ECDSA.  The scalar
833    /// is sampled by rejection sampling over the `n`-bit range, which is the
834    /// FIPS 186-5 recommended method.
835    ///
836    /// # Panics
837    ///
838    /// Panics only if the curve order `n` is malformed (`n <= 1`), which would
839    /// indicate a bug in the curve parameters.
840    pub fn random_scalar<R: Csprng>(&self, rng: &mut R) -> BigUint {
841        // random_nonzero_below returns None only if n ≤ 1, which cannot happen
842        // for any valid cryptographic curve.
843        random_nonzero_below(rng, &self.n)
844            .expect("curve order n is always > 1 for any valid cryptographic curve")
845    }
846
847    /// Generate a random key pair `(d, Q)` where `Q = d·G`.
848    ///
849    /// Returns `(private_scalar, public_point)`.
850    ///
851    /// # Panics
852    ///
853    /// Panics only if the curve parameters are malformed in a way that makes
854    /// [`random_scalar`][Self::random_scalar] fail.
855    pub fn generate_keypair<R: Csprng>(&self, rng: &mut R) -> (BigUint, AffinePoint) {
856        let d = self.random_scalar(rng);
857        let q = self.scalar_mul(&self.base_point(), &d);
858        (d, q)
859    }
860
861    /// Compute `k⁻¹ mod n` (modular inverse of a scalar modulo the subgroup order).
862    ///
863    /// Used in ECDSA signing.  Returns `None` if `k = 0` (which the caller
864    /// must prevent; a zero nonce breaks ECDSA signing regardless).
865    #[must_use]
866    pub fn scalar_invert(&self, k: &BigUint) -> Option<BigUint> {
867        mod_inverse(k, &self.n)
868    }
869
870    /// Encode a point as an uncompressed SEC 1 byte string.
871    ///
872    /// Format: `04 || x (coord_len bytes big-endian) || y (coord_len bytes big-endian)`.
873    ///
874    /// The leading `04` tag is the SEC 1 v2.0 uncompressed-point identifier.
875    /// The total length is `1 + 2·coord_len` bytes.  The point at infinity
876    /// encodes as the single byte `00`.
877    #[must_use]
878    pub fn encode_point(&self, point: &AffinePoint) -> Vec<u8> {
879        if point.infinity {
880            return vec![0x00];
881        }
882        let mut out = Vec::with_capacity(1 + 2 * self.coord_len);
883        out.push(0x04);
884        out.extend_from_slice(&pad_to(point.x.to_be_bytes(), self.coord_len));
885        out.extend_from_slice(&pad_to(point.y.to_be_bytes(), self.coord_len));
886        out
887    }
888
889    /// Encode a point in compressed SEC 1 form.
890    ///
891    /// Prime curves: format `02 || x` if `y` is even, `03 || x` if `y` is odd.
892    /// Binary curves: format `02 || x` if LSB(y·x⁻¹) = 0, `03 || x` otherwise
893    /// (per FIPS 186-4 §4.3.6; falls back to `02` when `x = 0`).
894    ///
895    /// The point at infinity encodes as `00`.
896    ///
897    /// # Panics
898    ///
899    /// Panics only if an internal binary-field invariant is violated after the
900    /// explicit `x = 0` guard, which would indicate a bug in the compression
901    /// logic.
902    #[must_use]
903    pub fn encode_point_compressed(&self, point: &AffinePoint) -> Vec<u8> {
904        if point.infinity {
905            return vec![0x00];
906        }
907        let parity = match &self.field {
908            FieldCtx::Prime(_) => point.y.is_odd(),
909            FieldCtx::Binary { poly, degree } => {
910                if point.x.is_zero() {
911                    false
912                } else {
913                    let x_inv =
914                        gf2m_inv(&point.x, poly, *degree).expect("x is non-zero in binary curve");
915                    let z = gf2m_mul(&point.y, &x_inv, poly, *degree);
916                    z.is_odd()
917                }
918            }
919        };
920        let tag = if parity { 0x03u8 } else { 0x02u8 };
921        let mut out = Vec::with_capacity(1 + self.coord_len);
922        out.push(tag);
923        out.extend_from_slice(&pad_to(point.x.to_be_bytes(), self.coord_len));
924        out
925    }
926
927    /// Decode an uncompressed or compressed SEC 1 point.
928    ///
929    /// Returns `None` for any of:
930    /// - wrong byte length for the tag,
931    /// - unrecognised tag byte,
932    /// - coordinates that fail the on-curve check,
933    /// - prime-field compressed encoding on a curve with `p ≢ 3 (mod 4)`,
934    /// - binary-field compressed encoding with an invalid x-coordinate.
935    #[must_use]
936    pub fn decode_point(&self, bytes: &[u8]) -> Option<AffinePoint> {
937        if bytes == [0x00] {
938            return Some(AffinePoint::infinity());
939        }
940        match bytes.first()? {
941            0x04 => {
942                // Uncompressed: 1 + 2·coord_len bytes (same for prime and binary).
943                let expected_len = 1 + 2 * self.coord_len;
944                if bytes.len() != expected_len {
945                    return None;
946                }
947                let coord_bytes = &bytes[1..];
948                let x = BigUint::from_be_bytes(&coord_bytes[..self.coord_len]);
949                let y = BigUint::from_be_bytes(&coord_bytes[self.coord_len..]);
950                let pt = AffinePoint::new(x, y);
951                if self.is_on_curve(&pt) {
952                    Some(pt)
953                } else {
954                    None
955                }
956            }
957            tag @ (0x02 | 0x03) => {
958                // Compressed: 1 + coord_len bytes.
959                let expected_len = 1 + self.coord_len;
960                if bytes.len() != expected_len {
961                    return None;
962                }
963                let x = BigUint::from_be_bytes(&bytes[1..]);
964                let odd_tag = *tag == 0x03;
965                match &self.field {
966                    FieldCtx::Prime(_) => {
967                        let y = self.field_sqrt_from_x(&x, odd_tag)?;
968                        Some(AffinePoint::new(x, y))
969                    }
970                    FieldCtx::Binary { poly, degree } => {
971                        self.decompress_binary_point(&x, odd_tag, poly, *degree)
972                    }
973                }
974            }
975            _ => None,
976        }
977    }
978
979    /// Recover a binary-curve y-coordinate from a compressed x and parity bit.
980    ///
981    /// Uses the standard FIPS 186-4 decompression algorithm:
982    /// 1. Compute β = x + a + b·x⁻² in GF(2^m).
983    /// 2. Solve z² + z = β via the half-trace (valid for odd m and Tr(β) = 0).
984    /// 3. Recover y = z·x; select z or z+1 based on `odd_z` (LSB of y·x⁻¹).
985    /// 4. Verify the point lies on the curve.
986    fn decompress_binary_point(
987        &self,
988        x: &BigUint,
989        odd_z: bool,
990        poly: &BigUint,
991        degree: usize,
992    ) -> Option<AffinePoint> {
993        if x.is_zero() {
994            // x = 0 implies 2P = ∞; decompression for this edge case requires
995            // a field square root which we omit (not used by any FIPS base point).
996            return None;
997        }
998        // β = x + a + b·x⁻²
999        let x_inv = gf2m_inv(x, poly, degree)?;
1000        let x_inv2 = gf2m_sq(&x_inv, poly, degree);
1001        let b_x_inv2 = gf2m_mul(&self.b, &x_inv2, poly, degree);
1002        let beta = gf2m_add(&gf2m_add(x, &self.a), &b_x_inv2);
1003
1004        // Solve z² + z = β.
1005        let z0 = gf2m_half_trace(&beta, poly, degree);
1006        // The two solutions differ by 1; choose by LSB parity.
1007        let z = if z0.is_odd() == odd_z {
1008            z0
1009        } else {
1010            gf2m_add(&z0, &BigUint::one())
1011        };
1012
1013        let y = gf2m_mul(&z, x, poly, degree);
1014        let pt = AffinePoint::new(x.clone(), y);
1015        if self.is_on_curve(&pt) {
1016            Some(pt)
1017        } else {
1018            None
1019        }
1020    }
1021
1022    /// Recover the `y`-coordinate from `x` using the curve equation, selecting
1023    /// the root with the requested parity.
1024    ///
1025    /// For a prime `p ≡ 3 (mod 4)`, the square root of `u mod p` is
1026    /// `u^{(p+1)/4} mod p` — a single modular exponentiation.  This covers
1027    /// P-256, P-384, and secp256k1 (all have `p ≡ 3 mod 4`).
1028    ///
1029    /// Returns `None` if `x` produces no square root in `F_p` (the `x`
1030    /// coordinate is not on the curve) or if `p ≢ 3 (mod 4)`.
1031    fn field_sqrt_from_x(&self, x: &BigUint, odd_y: bool) -> Option<BigUint> {
1032        let ctx = self.prime_ctx();
1033
1034        // Curve equation: rhs = x³ + a·x + b (mod p).
1035        let x2 = ctx.square(x);
1036        let x3 = ctx.mul(&x2, x);
1037        let ax = ctx.mul(&self.a, x);
1038        let rhs = field_add(&field_add(&x3, &ax, &self.p), &self.b, &self.p);
1039
1040        // Verify p ≡ 3 (mod 4) so the exponent (p+1)/4 is an integer.
1041        if self.p.rem_u64(4) != 3 {
1042            return None;
1043        }
1044        let exp = {
1045            let p_plus_1 = self.p.add_ref(&BigUint::one());
1046            // (p + 1) / 4 is an integer because p ≡ 3 (mod 4) implies
1047            // p + 1 ≡ 0 (mod 4).
1048            let (q, _) = p_plus_1.div_rem(&BigUint::from_u64(4));
1049            q
1050        };
1051
1052        let y_candidate = ctx.pow(&rhs, &exp);
1053
1054        // Verify that the candidate is actually a square root (not every x
1055        // has a square root mod p; about half do).
1056        if ctx.square(&y_candidate) != rhs {
1057            return None;
1058        }
1059
1060        // Select the root with the requested parity.
1061        let y = if y_candidate.is_odd() == odd_y {
1062            y_candidate
1063        } else {
1064            field_neg(&y_candidate, &self.p)
1065        };
1066        Some(y)
1067    }
1068}
1069
1070// ─── Named curves ────────────────────────────────────────────────────────────
1071
1072/// Parse a compact hexadecimal string (spaces ignored) into a `BigUint`.
1073///
1074/// Used only for named-curve constant construction; panics on invalid input,
1075/// which would indicate a bug in the constant tables below.
1076fn from_hex(hex: &str) -> BigUint {
1077    // Strip spaces so the hex strings in the constants below can be written
1078    // as the familiar 8-nibble groups that match the NIST/SEC 2 specifications.
1079    let cleaned: String = hex.chars().filter(|c| !c.is_ascii_whitespace()).collect();
1080    assert!(
1081        cleaned.len().is_multiple_of(2),
1082        "hex string must have even length: {cleaned}"
1083    );
1084    let bytes: Vec<u8> = (0..cleaned.len())
1085        .step_by(2)
1086        .map(|i| u8::from_str_radix(&cleaned[i..i + 2], 16).expect("valid hex digit"))
1087        .collect();
1088    BigUint::from_be_bytes(&bytes)
1089}
1090
1091/// NIST P-256 (secp256r1).
1092///
1093/// References: NIST FIPS 186-5, SEC 2 v2.0 §2.4.2.
1094///
1095/// Curve equation: y² = x³ − 3x + b  (mod p), equivalently a = p − 3.
1096///
1097/// Security level: ~128-bit classical, ~64-bit quantum (Grover).
1098///
1099/// # Panics
1100///
1101/// Panics only if the embedded curve constants are malformed, which would
1102/// indicate a bug in this module.
1103#[must_use]
1104pub fn p256() -> CurveParams {
1105    // p = 2^256 − 2^224 + 2^192 + 2^96 − 1
1106    let p = from_hex(
1107        "FFFFFFFF 00000001 00000000 00000000 \
1108         00000000 FFFFFFFF FFFFFFFF FFFFFFFF",
1109    );
1110    // a = p − 3 (the NIST P-curves use a = −3 for an efficient doubling formula)
1111    let a = from_hex(
1112        "FFFFFFFF 00000001 00000000 00000000 \
1113         00000000 FFFFFFFF FFFFFFFF FFFFFFFC",
1114    );
1115    let b = from_hex(
1116        "5AC635D8 AA3A93E7 B3EBBD55 769886BC \
1117         651D06B0 CC53B0F6 3BCE3C3E 27D2604B",
1118    );
1119    // n = prime order of the base-point subgroup
1120    let n = from_hex(
1121        "FFFFFFFF 00000000 FFFFFFFF FFFFFFFF \
1122         BCE6FAAD A7179E84 F3B9CAC2 FC632551",
1123    );
1124    let gx = from_hex(
1125        "6B17D1F2 E12C4247 F8BCE6E5 63A440F2 \
1126         77037D81 2DEB33A0 F4A13945 D898C296",
1127    );
1128    let gy = from_hex(
1129        "4FE342E2 FE1A7F9B 8EE7EB4A 7C0F9E16 \
1130         2BCE3357 6B315ECE CBB64068 37BF51F5",
1131    );
1132    CurveParams::new(p, a, b, n, 1, gx, gy).expect("P-256 parameters are well-formed")
1133}
1134
1135/// NIST P-384 (secp384r1).
1136///
1137/// References: NIST FIPS 186-5, SEC 2 v2.0 §2.5.1.
1138///
1139/// Curve equation: y² = x³ − 3x + b  (mod p).
1140///
1141/// Security level: ~192-bit classical, ~96-bit quantum (Grover).
1142///
1143/// # Panics
1144///
1145/// Panics only if the embedded curve constants are malformed, which would
1146/// indicate a bug in this module.
1147#[must_use]
1148pub fn p384() -> CurveParams {
1149    // p = 2^384 − 2^128 − 2^96 + 2^32 − 1
1150    let p = from_hex(
1151        "FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF \
1152         FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE \
1153         FFFFFFFF 00000000 00000000 FFFFFFFF",
1154    );
1155    // a = p − 3
1156    let a = from_hex(
1157        "FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF \
1158         FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE \
1159         FFFFFFFF 00000000 00000000 FFFFFFFC",
1160    );
1161    let b = from_hex(
1162        "B3312FA7 E23EE7E4 988E056B E3F82D19 \
1163         181D9C6E FE814112 0314088F 5013875A \
1164         C656398D 8A2ED19D 2A85C8ED D3EC2AEF",
1165    );
1166    let n = from_hex(
1167        "FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF \
1168         FFFFFFFF FFFFFFFF C7634D81 F4372DDF \
1169         581A0DB2 48B0A77A ECEC196A CCC52973",
1170    );
1171    let gx = from_hex(
1172        "AA87CA22 BE8B0537 8EB1C71E F320AD74 \
1173         6E1D3B62 8BA79B98 59F741E0 82542A38 \
1174         5502F25D BF55296C 3A545E38 72760AB7",
1175    );
1176    let gy = from_hex(
1177        "3617DE4A 96262C6F 5D9E98BF 9292DC29 \
1178         F8F41DBD 289A147C E9DA3113 B5F0B8C0 \
1179         0A60B1CE 1D7E819D 7A431D7C 90EA0E5F",
1180    );
1181    CurveParams::new(p, a, b, n, 1, gx, gy).expect("P-384 parameters are well-formed")
1182}
1183
1184/// Koblitz curve secp256k1.
1185///
1186/// Reference: SEC 2 v2.0 §2.4.1.  Used by Bitcoin, Ethereum, and related
1187/// protocols.
1188///
1189/// Curve equation: y² = x³ + 7  (mod p), i.e. a = 0, b = 7.
1190///
1191/// Security level: ~128-bit classical, ~64-bit quantum (Grover).
1192///
1193/// # Panics
1194///
1195/// Panics only if the embedded curve constants are malformed, which would
1196/// indicate a bug in this module.
1197#[must_use]
1198pub fn secp256k1() -> CurveParams {
1199    // p = 2^256 − 2^32 − 2^9 − 2^8 − 2^7 − 2^6 − 2^4 − 1
1200    let p = from_hex(
1201        "FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF \
1202         FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F",
1203    );
1204    // a = 0: the curve has no linear term, giving a particularly fast doubling
1205    // formula (the 3·X² + a·Z⁴ term reduces to just 3·X²).
1206    let a = BigUint::zero();
1207    // b = 7
1208    let b = BigUint::from_u64(7);
1209    let n = from_hex(
1210        "FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE \
1211         BAAEDCE6 AF48A03B BFD25E8C D0364141",
1212    );
1213    let gx = from_hex(
1214        "79BE667E F9DCBBAC 55A06295 CE870B07 \
1215         029BFCDB 2DCE28D9 59F2815B 16F81798",
1216    );
1217    let gy = from_hex(
1218        "483ADA77 26A3C465 5DA4FBFC 0E1108A8 \
1219         FD17B448 A6855419 9C47D08F FB10D4B8",
1220    );
1221    CurveParams::new(p, a, b, n, 1, gx, gy).expect("secp256k1 parameters are well-formed")
1222}
1223
1224/// NIST P-192 (secp192r1).
1225///
1226/// Reference: NIST FIPS 186-5.  Largely superseded by P-256 in modern
1227/// deployments, but still encountered in legacy systems and TLS stacks.
1228///
1229/// Curve equation: y² = x³ − 3x + b  (mod p).
1230///
1231/// Security level: ~96-bit classical, ~48-bit quantum (Grover).
1232///
1233/// Note: p ≡ 3 (mod 4), so compressed-point decoding is supported.
1234///
1235/// # Panics
1236///
1237/// Panics only if the embedded curve constants are malformed, which would
1238/// indicate a bug in this module.
1239#[must_use]
1240pub fn p192() -> CurveParams {
1241    // p = 2^192 − 2^64 − 1
1242    let p = from_hex("FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFFFF FFFFFFFF");
1243    // a = p − 3
1244    let a = from_hex("FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFFFF FFFFFFFC");
1245    let b = from_hex("64210519 E59C80E7 0FA7E9AB 72243049 FEB8DEEC C146B9B1");
1246    let n = from_hex("FFFFFFFF FFFFFFFF FFFFFFFF 99DEF836 146BC9B1 B4D22831");
1247    let gx = from_hex("188DA80E B03090F6 7CBF20EB 43A18800 F4FF0AFD 82FF1012");
1248    let gy = from_hex("07192B95 FFC8DA78 631011ED 6B24CDD5 73F977A1 1E794811");
1249    CurveParams::new(p, a, b, n, 1, gx, gy).expect("P-192 parameters are well-formed")
1250}
1251
1252/// NIST P-224 (secp224r1).
1253///
1254/// Reference: NIST FIPS 186-5.  A 224-bit curve that offers ~112-bit
1255/// classical security.
1256///
1257/// Curve equation: y² = x³ − 3x + b  (mod p).
1258///
1259/// **Note**: p ≡ 1 (mod 4) for P-224, so the fast Blum-modulus square-root
1260/// shortcut used by [`CurveParams::decode_point`] for compressed points is
1261/// unavailable.  Compressed-point encoding still works; decoding returns
1262/// `None`.  Use uncompressed encoding for P-224 interoperability.
1263///
1264/// # Panics
1265///
1266/// Panics only if the embedded curve constants are malformed, which would
1267/// indicate a bug in this module.
1268#[must_use]
1269pub fn p224() -> CurveParams {
1270    // p = 2^224 − 2^96 + 1
1271    let p = from_hex(
1272        "FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF \
1273         00000000 00000000 00000001",
1274    );
1275    // a = p − 3
1276    let a = from_hex(
1277        "FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE \
1278         FFFFFFFF FFFFFFFF FFFFFFFE",
1279    );
1280    let b = from_hex(
1281        "B4050A85 0C04B3AB F5413256 5044B0B7 \
1282         D7BFD8BA 270B3943 2355FFB4",
1283    );
1284    let n = from_hex(
1285        "FFFFFFFF FFFFFFFF FFFFFFFF FFFF16A2 \
1286         E0B8F03E 13DD2945 5C5C2A3D",
1287    );
1288    let gx = from_hex(
1289        "B70E0CBD 6BB4BF7F 321390B9 4A03C1D3 \
1290         56C21122 343280D6 115C1D21",
1291    );
1292    let gy = from_hex(
1293        "BD376388 B5F723FB 4C22DFE6 CD4375A0 \
1294         5A074764 44D58199 85007E34",
1295    );
1296    CurveParams::new(p, a, b, n, 1, gx, gy).expect("P-224 parameters are well-formed")
1297}
1298
1299/// NIST P-521 (secp521r1).
1300///
1301/// References: NIST FIPS 186-5, SEC 2 v2.0 §2.6.1.
1302///
1303/// The field prime is the Mersenne prime 2^521 − 1.  At ~256-bit classical
1304/// security it is the highest-security NIST curve and is used in
1305/// applications demanding long-term security.
1306///
1307/// Curve equation: y² = x³ − 3x + b  (mod p).
1308///
1309/// Note: p ≡ 3 (mod 4) (since 2^521 − 1 ≡ −1 ≡ 3 mod 4), so compressed
1310/// point decoding is supported.  Field elements and coordinates occupy 66
1311/// bytes (521 bits rounds up to 66 bytes).
1312///
1313/// # Panics
1314///
1315/// Panics only if the embedded curve constants are malformed, which would
1316/// indicate a bug in this module.
1317#[must_use]
1318pub fn p521() -> CurveParams {
1319    // p = 2^521 − 1  (a Mersenne prime: one leading bit, 65 bytes of 0xFF)
1320    let p = from_hex(
1321        "01FF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF \
1322         FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF \
1323         FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF \
1324         FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF",
1325    );
1326    // a = p − 3
1327    let a = from_hex(
1328        "01FF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF \
1329         FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF \
1330         FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF \
1331         FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFC",
1332    );
1333    let b = from_hex(
1334        "0051 953EB961 8E1C9A1F 929A21A0 B68540EE \
1335         A2DA725B 99B315F3 B8B48991 8EF109E1 \
1336         56193951 EC7E937B 1652C0BD 3BB1BF07 \
1337         3573DF88 3D2C34F1 EF451FD4 6B503F00",
1338    );
1339    let n = from_hex(
1340        "01FF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF \
1341         FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFA \
1342         51868783 BF2F966B 7FCC0148 F709A5D0 \
1343         3BB5C9B8 899C47AE BB6FB71E 91386409",
1344    );
1345    let gx = from_hex(
1346        "00C6 858E06B7 0404E9CD 9E3ECB66 2395B442 \
1347         9C648139 053FB521 F828AF60 6B4D3DBA \
1348         A14B5E77 EFE75928 FE1DC127 A2FFA8DE \
1349         3348B3C1 856A429B F97E7E31 C2E5BD66",
1350    );
1351    let gy = from_hex(
1352        "0118 39296A78 9A3BC004 5C8A5FB4 2C7D1BD9 \
1353         98F54449 579B4468 17AFBD17 273E662C \
1354         97EE7299 5EF42640 C550B901 3FAD0761 \
1355         353C7086 A272C240 88BE9476 9FD16650",
1356    );
1357    CurveParams::new(p, a, b, n, 1, gx, gy).expect("P-521 parameters are well-formed")
1358}
1359
1360// ─── FIPS 186-4 Binary curves ────────────────────────────────────────────────
1361//
1362// All ten curves use the binary Weierstrass form y² + xy = x³ + ax² + b over
1363// GF(2^m).  Parameters are from FIPS 186-4 Appendix D.  The irreducible
1364// polynomials are:
1365//
1366//   GF(2^163): x^163 + x^7  + x^6 + x^3 + 1
1367//   GF(2^233): x^233 + x^74 + 1
1368//   GF(2^283): x^283 + x^12 + x^7 + x^5 + 1
1369//   GF(2^409): x^409 + x^87 + 1
1370//   GF(2^571): x^571 + x^10 + x^5 + x^2 + 1
1371
1372/// NIST B-163 (FIPS 186-4 Appendix D.1.2.1).
1373///
1374/// Binary Weierstrass curve over GF(2^163).  Security level ~80-bit classical.
1375///
1376/// # Panics
1377///
1378/// Panics only if the embedded curve constants are malformed, which would
1379/// indicate a bug in this module.
1380#[must_use]
1381pub fn b163() -> CurveParams {
1382    let poly = from_hex("0800000000000000000000000000000000000000C9");
1383    let a = BigUint::one();
1384    let b = from_hex("020A601907B8C953CA1481EB10512F78744A3205FD");
1385    let n = from_hex("040000000000000000000292FE77E70C12A4234C33");
1386    let gx = from_hex("03F0EBA16286A2D57EA0991168D4994637E8343E36");
1387    let gy = from_hex("00D51FBC6C71A0094FA2CDD545B11C5C0C797324F1");
1388    CurveParams::new_binary(poly, 163, a, b, n, 2, (gx, gy))
1389        .expect("B-163 parameters are well-formed")
1390}
1391
1392/// NIST K-163 (FIPS 186-4 Appendix D.1.2.2).
1393///
1394/// Koblitz binary curve over GF(2^163).  Security level ~80-bit classical.
1395///
1396/// # Panics
1397///
1398/// Panics only if the embedded curve constants are malformed, which would
1399/// indicate a bug in this module.
1400#[must_use]
1401pub fn k163() -> CurveParams {
1402    let poly = from_hex("0800000000000000000000000000000000000000C9");
1403    let a = BigUint::one();
1404    let b = BigUint::one();
1405    let n = from_hex("04000000000000000000020108A2E0CC0D99F8A5EF");
1406    let gx = from_hex("02FE13C0537BBC11ACAA07D793DE4E6D5E5C94EEE8");
1407    let gy = from_hex("0289070FB05D38FF58321F2E800536D538CCDAA3D9");
1408    CurveParams::new_binary(poly, 163, a, b, n, 2, (gx, gy))
1409        .expect("K-163 parameters are well-formed")
1410}
1411
1412/// NIST B-233 (FIPS 186-4 Appendix D.1.2.3).
1413///
1414/// Binary Weierstrass curve over GF(2^233).  Security level ~112-bit classical.
1415///
1416/// # Panics
1417///
1418/// Panics only if the embedded curve constants are malformed, which would
1419/// indicate a bug in this module.
1420#[must_use]
1421pub fn b233() -> CurveParams {
1422    let mut poly = BigUint::zero();
1423    for bit in [233, 74, 0] {
1424        poly.set_bit(bit);
1425    }
1426    let a = BigUint::one();
1427    let b = from_hex("0066647EDE6C332C7F8C0923BB58213B333B20E9CE4281FE115F7D8F90AD");
1428    let n = from_hex("01000000000000000000000000000013E974E72F8A6922031D2603CFE0D7");
1429    let gx = from_hex("00FAC9DFCBAC8313BB2139F1BB755FEF65BC391F8B36F8F8EB7371FD558B");
1430    let gy = from_hex("01006A08A41903350678E58528BEBF8A0BEFF867A7CA36716F7E01F81052");
1431    CurveParams::new_binary(poly, 233, a, b, n, 2, (gx, gy))
1432        .expect("B-233 parameters are well-formed")
1433}
1434
1435/// NIST K-233 (FIPS 186-4 Appendix D.1.2.4).
1436///
1437/// Koblitz binary curve over GF(2^233).  Security level ~112-bit classical.
1438///
1439/// # Panics
1440///
1441/// Panics only if the embedded curve constants are malformed, which would
1442/// indicate a bug in this module.
1443#[must_use]
1444pub fn k233() -> CurveParams {
1445    let mut poly = BigUint::zero();
1446    for bit in [233, 74, 0] {
1447        poly.set_bit(bit);
1448    }
1449    let a = BigUint::zero();
1450    let b = BigUint::one();
1451    let n = from_hex("008000000000000000000000000000069D5BB915BCD46EFB1AD5F173ABDF");
1452    let gx = from_hex("017232BA853A7E731AF129F22FF4149563A419C26BF50A4C9D6EEFAD6126");
1453    let gy = from_hex("01DB537DECE819B7F70F555A67C427A8CD9BF18AEB9B56E0C11056FAE6A3");
1454    CurveParams::new_binary(poly, 233, a, b, n, 4, (gx, gy))
1455        .expect("K-233 parameters are well-formed")
1456}
1457
1458/// NIST B-283 (FIPS 186-4 Appendix D.1.2.5).
1459///
1460/// Binary Weierstrass curve over GF(2^283).  Security level ~128-bit classical.
1461///
1462/// # Panics
1463///
1464/// Panics only if the embedded curve constants are malformed, which would
1465/// indicate a bug in this module.
1466#[must_use]
1467pub fn b283() -> CurveParams {
1468    let mut poly = BigUint::zero();
1469    for bit in [283, 12, 7, 5, 0] {
1470        poly.set_bit(bit);
1471    }
1472    let a = BigUint::one();
1473    let b = from_hex("027B680AC8B8596DA5A4AF8A19A0303FCA97FD7645309FA2A581485AF6263E313B79A2F5");
1474    let n = from_hex("03FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEF90399660FC938A90165B042A7CEFADB307");
1475    let gx = from_hex("05F939258DB7DD90E1934F8C70B0DFEC2EED25B8557EAC9C80E2E198F8CDBECD86B12053");
1476    let gy = from_hex("03676854FE24141CB98FE6D4B20D02B4516FF702350EDDB0826779C813F0DF45BE8112F4");
1477    CurveParams::new_binary(poly, 283, a, b, n, 2, (gx, gy))
1478        .expect("B-283 parameters are well-formed")
1479}
1480
1481/// NIST K-283 (FIPS 186-4 Appendix D.1.2.6).
1482///
1483/// Koblitz binary curve over GF(2^283).  Security level ~128-bit classical.
1484///
1485/// # Panics
1486///
1487/// Panics only if the embedded curve constants are malformed, which would
1488/// indicate a bug in this module.
1489#[must_use]
1490pub fn k283() -> CurveParams {
1491    let mut poly = BigUint::zero();
1492    for bit in [283, 12, 7, 5, 0] {
1493        poly.set_bit(bit);
1494    }
1495    let a = BigUint::zero();
1496    let b = BigUint::one();
1497    let n = from_hex("01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9AE2ED07577265DFF7F94451E061E163C61");
1498    let gx = from_hex("0503213F78CA44883F1A3B8162F188E553CD265F23C1567A16876913B0C2AC2458492836");
1499    let gy = from_hex("01CCDA380F1C9E318D90F95D07E5426FE87E45C0E8184698E45962364E34116177DD2259");
1500    CurveParams::new_binary(poly, 283, a, b, n, 4, (gx, gy))
1501        .expect("K-283 parameters are well-formed")
1502}
1503
1504/// NIST B-409 (FIPS 186-4 Appendix D.1.2.7).
1505///
1506/// Binary Weierstrass curve over GF(2^409).  Security level ~192-bit classical.
1507///
1508/// # Panics
1509///
1510/// Panics only if the embedded curve constants are malformed, which would
1511/// indicate a bug in this module.
1512#[must_use]
1513pub fn b409() -> CurveParams {
1514    let mut poly = BigUint::zero();
1515    for bit in [409, 87, 0] {
1516        poly.set_bit(bit);
1517    }
1518    let a = BigUint::one();
1519    let b = from_hex(
1520        "0021A5C2C8EE9FEB5C4B9A753B7B476B7FD6422EF1F3DD674761FA99D6AC27C8A9A197B272822F6CD57A55AA4F50AE317B13545F",
1521    );
1522    let n = from_hex(
1523        "010000000000000000000000000000000000000000000000012F7B6E4B64E2C26F2B04E76B1B9D77B6CCBB99EE3A7BCED5CB4ECB",
1524    );
1525    let gx = from_hex(
1526        "015D4860D088DDB3496B0C6064756260441CDE4AF1771D4DB01FFE5B34E59703DC255A868A1180515603AEAB60794E54BB7996A7",
1527    );
1528    let gy = from_hex(
1529        "0061B1CFAB6BE5F32BBFA78324ED106A7636B9C5A7BD198D0158AA4F5488D08F38514F1FDF4B4F40D2181B3681C364BA0273C706",
1530    );
1531    CurveParams::new_binary(poly, 409, a, b, n, 2, (gx, gy))
1532        .expect("B-409 parameters are well-formed")
1533}
1534
1535/// NIST K-409 (FIPS 186-4 Appendix D.1.2.8).
1536///
1537/// Koblitz binary curve over GF(2^409).  Security level ~192-bit classical.
1538///
1539/// # Panics
1540///
1541/// Panics only if the embedded curve constants are malformed, which would
1542/// indicate a bug in this module.
1543#[must_use]
1544pub fn k409() -> CurveParams {
1545    let mut poly = BigUint::zero();
1546    for bit in [409, 87, 0] {
1547        poly.set_bit(bit);
1548    }
1549    let a = BigUint::zero();
1550    let b = BigUint::one();
1551    let n = from_hex(
1552        "007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE5F83B2D4EA20400EC4557D5ED3E3E7CA5B4B5C83B8E01E5FCF",
1553    );
1554    let gx = from_hex(
1555        "0060F05F658F49C1AD3AB1890F7184210EFD0987E307C84C27ACCFB8F9F67CC2C460189EB5AAAA62EE222EB1B35540CFE9023746",
1556    );
1557    let gy = from_hex(
1558        "01E369050B7C4E42ACBA1DACBF04299C3460782F918EA427E6325165E9EA10E3DA5F6C42E9C55215AA9CA27A5863EC48D8E0286B",
1559    );
1560    CurveParams::new_binary(poly, 409, a, b, n, 4, (gx, gy))
1561        .expect("K-409 parameters are well-formed")
1562}
1563
1564/// NIST B-571 (FIPS 186-4 Appendix D.1.2.9).
1565///
1566/// Binary Weierstrass curve over GF(2^571).  Security level ~256-bit classical.
1567///
1568/// # Panics
1569///
1570/// Panics only if the embedded curve constants are malformed, which would
1571/// indicate a bug in this module.
1572#[must_use]
1573pub fn b571() -> CurveParams {
1574    let mut poly = BigUint::zero();
1575    for bit in [571, 10, 5, 2, 0] {
1576        poly.set_bit(bit);
1577    }
1578    let a = BigUint::one();
1579    let b = from_hex(
1580        "02F40E7E2221F295DE297117B7F3D62F5C6A97FFCB8CEFF1CD6BA8CE4A9A18AD84FFABBD8EFA59332BE7AD6756A66E294AFD185A78FF12AA520E4DE739BACA0C7FFEFF7F2955727A",
1581    );
1582    let n = from_hex(
1583        "03FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE661CE18FF55987308059B186823851EC7DD9CA1161DE93D5174D66E8382E9BB2FE84E47",
1584    );
1585    let gx = from_hex(
1586        "0303001D34B856296C16C0D40D3CD7750A93D1D2955FA80AA5F40FC8DB7B2ABDBDE53950F4C0D293CDD711A35B67FB1499AE60038614F1394ABFA3B4C850D927E1E7769C8EEC2D19",
1587    );
1588    let gy = from_hex(
1589        "037BF27342DA639B6DCCFFFEB73D69D78C6C27A6009CBBCA1980F8533921E8A684423E43BAB08A576291AF8F461BB2A8B3531D2F0485C19B16E2F1516E23DD3C1A4827AF1B8AC15B",
1590    );
1591    CurveParams::new_binary(poly, 571, a, b, n, 2, (gx, gy))
1592        .expect("B-571 parameters are well-formed")
1593}
1594
1595/// NIST K-571 (FIPS 186-4 Appendix D.1.2.10).
1596///
1597/// Koblitz binary curve over GF(2^571).  Security level ~256-bit classical.
1598///
1599/// # Panics
1600///
1601/// Panics only if the embedded curve constants are malformed, which would
1602/// indicate a bug in this module.
1603#[must_use]
1604pub fn k571() -> CurveParams {
1605    let mut poly = BigUint::zero();
1606    for bit in [571, 10, 5, 2, 0] {
1607        poly.set_bit(bit);
1608    }
1609    let a = BigUint::zero();
1610    let b = BigUint::one();
1611    let n = from_hex(
1612        "020000000000000000000000000000000000000000000000000000000000000000000000131850E1F19A63E4B391A8DB917F4138B630D84BE5D639381E91DEB45CFE778F637C1001",
1613    );
1614    let gx = from_hex(
1615        "026EB7A859923FBC82189631F8103FE4AC9CA2970012D5D46024804801841CA44370958493B205E647DA304DB4CEB08CBBD1BA39494776FB988B47174DCA88C7E2945283A01C8972",
1616    );
1617    let gy = from_hex(
1618        "0349DC807F4FBF374F4AEADE3BCA95314DD58CEC9F307A54FFC61EFC006D8A2C9D4979C0AC44AEA74FBEBBB9F772AEDCB620B01A7BA7AF1B320430C8591984F601CD4C143EF1C7A3",
1619    );
1620    CurveParams::new_binary(poly, 571, a, b, n, 4, (gx, gy))
1621        .expect("K-571 parameters are well-formed")
1622}
1623
1624// ─── Tests ───────────────────────────────────────────────────────────────────
1625
1626#[cfg(test)]
1627mod tests {
1628    use super::*;
1629
1630    // ── P-256 ──────────────────────────────────────────────────────────────
1631
1632    #[test]
1633    fn p256_base_point_on_curve() {
1634        let curve = p256();
1635        let g = curve.base_point();
1636        assert!(
1637            curve.is_on_curve(&g),
1638            "P-256 base point G must satisfy y² = x³ + ax + b"
1639        );
1640    }
1641
1642    #[test]
1643    fn p256_double_equals_add_self() {
1644        // 2G computed via doubling must equal G + G computed via addition.
1645        let curve = p256();
1646        let g = curve.base_point();
1647        let via_double = curve.double(&g);
1648        let via_add = curve.add(&g, &g);
1649        assert_eq!(
1650            via_double, via_add,
1651            "2G via double must equal G+G via add for P-256"
1652        );
1653        assert!(curve.is_on_curve(&via_double), "2G must lie on P-256");
1654    }
1655
1656    #[test]
1657    fn p256_scalar_mul_matches_repeated_add() {
1658        // 4G via scalar_mul must equal 2G + 2G via add.
1659        let curve = p256();
1660        let g = curve.base_point();
1661        let four_g_scalar = curve.scalar_mul(&g, &BigUint::from_u64(4));
1662        let two_g = curve.double(&g);
1663        let four_g_add = curve.add(&two_g, &two_g);
1664        assert_eq!(
1665            four_g_scalar, four_g_add,
1666            "4G via scalar_mul must equal 2G+2G"
1667        );
1668    }
1669
1670    #[test]
1671    fn p256_order_times_base_point_is_infinity() {
1672        // n·G = ∞ by definition of the subgroup order.
1673        let curve = p256();
1674        let g = curve.base_point();
1675        let n = curve.n.clone();
1676        let result = curve.scalar_mul(&g, &n);
1677        assert!(
1678            result.is_infinity(),
1679            "n·G must be the point at infinity for P-256"
1680        );
1681    }
1682
1683    #[test]
1684    fn p256_negation_sums_to_infinity() {
1685        // P + (−P) = ∞.
1686        let curve = p256();
1687        let g = curve.base_point();
1688        let neg_g = curve.negate(&g);
1689        let sum = curve.add(&g, &neg_g);
1690        assert!(sum.is_infinity(), "G + (−G) must be the point at infinity");
1691    }
1692
1693    #[test]
1694    fn p256_encode_decode_uncompressed_roundtrip() {
1695        let curve = p256();
1696        let g = curve.base_point();
1697        let encoded = curve.encode_point(&g);
1698        let decoded = curve
1699            .decode_point(&encoded)
1700            .expect("decode must succeed for a valid point");
1701        assert_eq!(
1702            decoded, g,
1703            "uncompressed encode/decode must be the identity"
1704        );
1705    }
1706
1707    #[test]
1708    fn p256_encode_decode_compressed_roundtrip() {
1709        let curve = p256();
1710        let g = curve.base_point();
1711        let encoded = curve.encode_point_compressed(&g);
1712        let decoded = curve
1713            .decode_point(&encoded)
1714            .expect("compressed decode must succeed");
1715        assert_eq!(decoded, g, "compressed encode/decode must be the identity");
1716    }
1717
1718    #[test]
1719    fn p256_infinity_encodes_as_single_zero_byte() {
1720        let curve = p256();
1721        let inf = AffinePoint::infinity();
1722        let enc = curve.encode_point(&inf);
1723        assert_eq!(enc, vec![0x00]);
1724        let dec = curve.decode_point(&enc).expect("decode of infinity");
1725        assert!(dec.is_infinity());
1726    }
1727
1728    #[test]
1729    fn p256_decode_rejects_bad_length() {
1730        let curve = p256();
1731        let g = curve.base_point();
1732        let mut enc = curve.encode_point(&g);
1733        enc.pop(); // truncate by one byte
1734        assert!(
1735            curve.decode_point(&enc).is_none(),
1736            "truncated encoding must be rejected"
1737        );
1738    }
1739
1740    #[test]
1741    fn p256_decode_rejects_off_curve_point() {
1742        let curve = p256();
1743        // Start with a valid uncompressed encoding and corrupt the y coordinate.
1744        let g = curve.base_point();
1745        let mut enc = curve.encode_point(&g);
1746        let last = enc.last_mut().unwrap();
1747        *last ^= 0xff; // flip the low byte of y
1748        assert!(
1749            curve.decode_point(&enc).is_none(),
1750            "off-curve point must be rejected"
1751        );
1752    }
1753
1754    // ── P-384 ──────────────────────────────────────────────────────────────
1755
1756    #[test]
1757    fn p384_base_point_on_curve() {
1758        let curve = p384();
1759        assert!(curve.is_on_curve(&curve.base_point()));
1760    }
1761
1762    #[test]
1763    fn p384_double_equals_add_self() {
1764        let curve = p384();
1765        let g = curve.base_point();
1766        assert_eq!(curve.double(&g), curve.add(&g, &g));
1767    }
1768
1769    #[test]
1770    fn p384_order_times_base_point_is_infinity() {
1771        let curve = p384();
1772        let n = curve.n.clone();
1773        let result = curve.scalar_mul(&curve.base_point(), &n);
1774        assert!(result.is_infinity());
1775    }
1776
1777    // ── secp256k1 ──────────────────────────────────────────────────────────
1778
1779    #[test]
1780    fn secp256k1_base_point_on_curve() {
1781        let curve = secp256k1();
1782        assert!(curve.is_on_curve(&curve.base_point()));
1783    }
1784
1785    #[test]
1786    fn secp256k1_double_equals_add_self() {
1787        let curve = secp256k1();
1788        let g = curve.base_point();
1789        assert_eq!(curve.double(&g), curve.add(&g, &g));
1790    }
1791
1792    #[test]
1793    fn secp256k1_order_times_base_point_is_infinity() {
1794        let curve = secp256k1();
1795        let n = curve.n.clone();
1796        let result = curve.scalar_mul(&curve.base_point(), &n);
1797        assert!(result.is_infinity());
1798    }
1799
1800    #[test]
1801    fn secp256k1_encode_decode_compressed_roundtrip() {
1802        let curve = secp256k1();
1803        let g = curve.base_point();
1804        let enc = curve.encode_point_compressed(&g);
1805        let dec = curve.decode_point(&enc).expect("decode must succeed");
1806        assert_eq!(dec, g);
1807    }
1808
1809    // ── ECDH smoke test ────────────────────────────────────────────────────
1810
1811    #[test]
1812    fn p256_ecdh_shared_secret_agrees() {
1813        use crate::CtrDrbgAes256;
1814
1815        let curve = p256();
1816        let mut rng = CtrDrbgAes256::new(&[0xab; 48]);
1817
1818        let (d_a, q_a) = curve.generate_keypair(&mut rng);
1819        let (d_b, q_b) = curve.generate_keypair(&mut rng);
1820
1821        // Both parties should derive the same shared point.
1822        let shared_a = curve.diffie_hellman(&d_a, &q_b);
1823        let shared_b = curve.diffie_hellman(&d_b, &q_a);
1824        assert_eq!(shared_a, shared_b, "ECDH shared points must agree");
1825        assert!(
1826            !shared_a.is_infinity(),
1827            "ECDH shared point must not be infinity"
1828        );
1829        assert!(
1830            curve.is_on_curve(&shared_a),
1831            "ECDH shared point must lie on the curve"
1832        );
1833    }
1834
1835    // ── scalar_invert ──────────────────────────────────────────────────────
1836
1837    // ── P-192 ──────────────────────────────────────────────────────────────
1838
1839    #[test]
1840    fn p192_base_point_on_curve() {
1841        let curve = p192();
1842        assert!(curve.is_on_curve(&curve.base_point()));
1843    }
1844
1845    #[test]
1846    fn p192_double_equals_add_self() {
1847        let curve = p192();
1848        let g = curve.base_point();
1849        assert_eq!(curve.double(&g), curve.add(&g, &g));
1850    }
1851
1852    #[test]
1853    fn p192_encode_decode_uncompressed_roundtrip() {
1854        let curve = p192();
1855        let g = curve.base_point();
1856        let enc = curve.encode_point(&g);
1857        let dec = curve.decode_point(&enc).expect("P-192 uncompressed decode");
1858        assert_eq!(dec, g);
1859    }
1860
1861    #[test]
1862    fn p192_encode_decode_compressed_roundtrip() {
1863        let curve = p192();
1864        let g = curve.base_point();
1865        let enc = curve.encode_point_compressed(&g);
1866        let dec = curve.decode_point(&enc).expect("P-192 compressed decode");
1867        assert_eq!(dec, g);
1868    }
1869
1870    // ── P-224 ──────────────────────────────────────────────────────────────
1871
1872    #[test]
1873    fn p224_base_point_on_curve() {
1874        let curve = p224();
1875        assert!(curve.is_on_curve(&curve.base_point()));
1876    }
1877
1878    #[test]
1879    fn p224_double_equals_add_self() {
1880        let curve = p224();
1881        let g = curve.base_point();
1882        assert_eq!(curve.double(&g), curve.add(&g, &g));
1883    }
1884
1885    #[test]
1886    fn p224_uncompressed_roundtrip() {
1887        // P-224 has p ≡ 1 (mod 4); only uncompressed encoding is supported.
1888        let curve = p224();
1889        let g = curve.base_point();
1890        let enc = curve.encode_point(&g);
1891        let dec = curve.decode_point(&enc).expect("P-224 uncompressed decode");
1892        assert_eq!(dec, g);
1893    }
1894
1895    #[test]
1896    fn p224_compressed_decode_returns_none() {
1897        // Compressed decoding is intentionally unsupported for P-224.
1898        let curve = p224();
1899        let enc = curve.encode_point_compressed(&curve.base_point());
1900        assert!(
1901            curve.decode_point(&enc).is_none(),
1902            "P-224 compressed decoding must return None (p ≡ 1 mod 4)"
1903        );
1904    }
1905
1906    // ── P-521 ──────────────────────────────────────────────────────────────
1907
1908    #[test]
1909    fn p521_base_point_on_curve() {
1910        let curve = p521();
1911        assert!(curve.is_on_curve(&curve.base_point()));
1912    }
1913
1914    #[test]
1915    fn p521_double_equals_add_self() {
1916        let curve = p521();
1917        let g = curve.base_point();
1918        assert_eq!(curve.double(&g), curve.add(&g, &g));
1919    }
1920
1921    #[test]
1922    fn p521_encode_decode_compressed_roundtrip() {
1923        let curve = p521();
1924        let g = curve.base_point();
1925        let enc = curve.encode_point_compressed(&g);
1926        let dec = curve.decode_point(&enc).expect("P-521 compressed decode");
1927        assert_eq!(dec, g);
1928    }
1929
1930    // ── scalar_invert ──────────────────────────────────────────────────────
1931
1932    #[test]
1933    fn p256_scalar_invert_roundtrip() {
1934        // k * k⁻¹ ≡ 1 (mod n)
1935        let curve = p256();
1936        let k = BigUint::from_u64(0x1234_5678_9abc_def0);
1937        let k_inv = curve
1938            .scalar_invert(&k)
1939            .expect("k is non-zero and coprime with n");
1940        // Verify: k * k_inv mod n == 1
1941        let product = BigUint::mod_mul(&k, &k_inv, &curve.n);
1942        assert_eq!(product, BigUint::one(), "k * k⁻¹ must equal 1 mod n");
1943    }
1944
1945    // ── Binary curves — base-point on-curve ───────────────────────────────
1946
1947    macro_rules! binary_base_point_on_curve {
1948        ($name:ident, $constructor:ident) => {
1949            #[test]
1950            fn $name() {
1951                let curve = $constructor();
1952                let g = curve.base_point();
1953                assert!(
1954                    curve.is_on_curve(&g),
1955                    "{} base point must satisfy y² + xy = x³ + ax² + b",
1956                    stringify!($constructor)
1957                );
1958            }
1959        };
1960    }
1961
1962    binary_base_point_on_curve!(b163_base_point_on_curve, b163);
1963    binary_base_point_on_curve!(k163_base_point_on_curve, k163);
1964    binary_base_point_on_curve!(b233_base_point_on_curve, b233);
1965    binary_base_point_on_curve!(k233_base_point_on_curve, k233);
1966    binary_base_point_on_curve!(b283_base_point_on_curve, b283);
1967    binary_base_point_on_curve!(k283_base_point_on_curve, k283);
1968    binary_base_point_on_curve!(b409_base_point_on_curve, b409);
1969    binary_base_point_on_curve!(k409_base_point_on_curve, k409);
1970    binary_base_point_on_curve!(b571_base_point_on_curve, b571);
1971    binary_base_point_on_curve!(k571_base_point_on_curve, k571);
1972
1973    // ── Binary curves — double-add consistency ────────────────────────────
1974
1975    macro_rules! binary_double_add_consistency {
1976        ($name:ident, $constructor:ident) => {
1977            #[test]
1978            fn $name() {
1979                let curve = $constructor();
1980                let g = curve.base_point();
1981                let via_double = curve.double(&g);
1982                let via_add = curve.add(&g, &g);
1983                assert_eq!(
1984                    via_double,
1985                    via_add,
1986                    "2G via double must equal G+G via add for {}",
1987                    stringify!($constructor)
1988                );
1989                assert!(
1990                    curve.is_on_curve(&via_double),
1991                    "2G must lie on {}",
1992                    stringify!($constructor)
1993                );
1994            }
1995        };
1996    }
1997
1998    binary_double_add_consistency!(b163_double_add_consistency, b163);
1999    binary_double_add_consistency!(k163_double_add_consistency, k163);
2000    binary_double_add_consistency!(b233_double_add_consistency, b233);
2001    binary_double_add_consistency!(k233_double_add_consistency, k233);
2002    binary_double_add_consistency!(b283_double_add_consistency, b283);
2003    binary_double_add_consistency!(k283_double_add_consistency, k283);
2004    // Skip the slow 409/571 curves in base tests; covered by order tests below.
2005
2006    // ── Binary curves — negation ──────────────────────────────────────────
2007
2008    #[test]
2009    fn b163_negation_sums_to_infinity() {
2010        let curve = b163();
2011        let g = curve.base_point();
2012        let neg_g = curve.negate(&g);
2013        let sum = curve.add(&g, &neg_g);
2014        assert!(sum.is_infinity(), "G + (-G) must be infinity on B-163");
2015    }
2016
2017    #[test]
2018    fn k163_negation_sums_to_infinity() {
2019        let curve = k163();
2020        let g = curve.base_point();
2021        let neg_g = curve.negate(&g);
2022        let sum = curve.add(&g, &neg_g);
2023        assert!(sum.is_infinity(), "G + (-G) must be infinity on K-163");
2024    }
2025
2026    // ── Binary curves — order: n·G = ∞ ───────────────────────────────────
2027
2028    macro_rules! binary_order_test {
2029        ($name:ident, $constructor:ident) => {
2030            #[test]
2031            fn $name() {
2032                let curve = $constructor();
2033                let g = curve.base_point();
2034                let n = curve.n.clone();
2035                let result = curve.scalar_mul(&g, &n);
2036                assert!(
2037                    result.is_infinity(),
2038                    "n·G must be the point at infinity for {}",
2039                    stringify!($constructor)
2040                );
2041            }
2042        };
2043    }
2044
2045    binary_order_test!(b163_order_times_base_is_infinity, b163);
2046    binary_order_test!(k163_order_times_base_is_infinity, k163);
2047    binary_order_test!(b233_order_times_base_is_infinity, b233);
2048    binary_order_test!(k233_order_times_base_is_infinity, k233);
2049
2050    // ── Binary curves — encode/decode round-trips ─────────────────────────
2051
2052    #[test]
2053    fn b163_encode_decode_uncompressed() {
2054        let curve = b163();
2055        let g = curve.base_point();
2056        let enc = curve.encode_point(&g);
2057        let dec = curve.decode_point(&enc).expect("B-163 uncompressed decode");
2058        assert_eq!(dec, g);
2059    }
2060
2061    #[test]
2062    fn k163_encode_decode_uncompressed() {
2063        let curve = k163();
2064        let g = curve.base_point();
2065        let enc = curve.encode_point(&g);
2066        let dec = curve.decode_point(&enc).expect("K-163 uncompressed decode");
2067        assert_eq!(dec, g);
2068    }
2069
2070    #[test]
2071    fn b163_encode_decode_compressed() {
2072        let curve = b163();
2073        let g = curve.base_point();
2074        let enc = curve.encode_point_compressed(&g);
2075        let dec = curve.decode_point(&enc).expect("B-163 compressed decode");
2076        assert_eq!(dec, g);
2077    }
2078
2079    #[test]
2080    fn k163_encode_decode_compressed() {
2081        let curve = k163();
2082        let g = curve.base_point();
2083        let enc = curve.encode_point_compressed(&g);
2084        let dec = curve.decode_point(&enc).expect("K-163 compressed decode");
2085        assert_eq!(dec, g);
2086    }
2087
2088    // ── Binary curves — ECDH agreement ───────────────────────────────────
2089
2090    #[test]
2091    fn b163_ecdh_shared_secret_agrees() {
2092        use crate::CtrDrbgAes256;
2093        let curve = b163();
2094        let mut rng = CtrDrbgAes256::new(&[0x42; 48]);
2095        let (da, qa) = curve.generate_keypair(&mut rng);
2096        let (db, qb) = curve.generate_keypair(&mut rng);
2097        let shared_a = curve.diffie_hellman(&da, &qb);
2098        let shared_b = curve.diffie_hellman(&db, &qa);
2099        assert_eq!(shared_a, shared_b, "B-163 ECDH shared points must agree");
2100        assert!(!shared_a.is_infinity());
2101        assert!(curve.is_on_curve(&shared_a));
2102    }
2103
2104    #[test]
2105    fn k283_ecdh_shared_secret_agrees() {
2106        use crate::CtrDrbgAes256;
2107        let curve = k283();
2108        let mut rng = CtrDrbgAes256::new(&[0x7F; 48]);
2109        let (da, qa) = curve.generate_keypair(&mut rng);
2110        let (db, qb) = curve.generate_keypair(&mut rng);
2111        let shared_a = curve.diffie_hellman(&da, &qb);
2112        let shared_b = curve.diffie_hellman(&db, &qa);
2113        assert_eq!(shared_a, shared_b, "K-283 ECDH shared points must agree");
2114        assert!(!shared_a.is_infinity());
2115    }
2116}