Skip to main content

ic_ec/
p256.rs

1//! NIST P-256 (secp256r1, prime256v1).
2//!
3//! The most widely deployed approved curve. The field, group law, and schemes
4//! come from `crate::nist`; this module supplies the constants and the
5//! public API.
6
7use crate::mont_field;
8use crate::nist::arith::{sqrt_p3mod4, Field};
9use crate::nist::point::Curve;
10use crate::nist::{ecdh, ecdsa};
11use ic_core::traits::{Algorithm, KeyAgreement, SelfTest, SignatureScheme};
12use ic_core::{ensure, Result};
13
14// On 64-bit targets the field uses arithmetic specialised to p's shape; see
15// `nist::p256_field`. Elsewhere, the generic Montgomery word loop.
16#[cfg(all(target_pointer_width = "64", not(ic_limb32)))]
17mont_field!(
18    Fp,
19    4,
20    32,
21    crate::nist::p256_field::P,
22    "The P-256 coordinate field, GF(p) with p = 2^256 - 2^224 + 2^192 + 2^96 - 1.",
23    mul = crate::nist::p256_field::active::mul,
24    square = crate::nist::p256_field::active::square,
25    invert = invert_field,
26    add = crate::nist::p256_field::active::add,
27    sub = crate::nist::p256_field::active::sub
28);
29
30#[cfg(not(all(target_pointer_width = "64", not(ic_limb32))))]
31mont_field!(
32    Fp,
33    4,
34    32,
35    crate::nist::p256_field::P,
36    "The P-256 coordinate field, GF(p) with p = 2^256 - 2^224 + 2^192 + 2^96 - 1."
37);
38
39/// The group order `n`, little-endian.
40const N: [u64; 4] = [
41    0xf3b9_cac2_fc63_2551,
42    0xbce6_faad_a717_9e84,
43    0xffff_ffff_ffff_ffff,
44    0xffff_ffff_0000_0000,
45];
46
47// On 64-bit targets the scalar ring inverts by Bernstein-Yang divsteps rather
48// than by Fermat; see `nist::safegcd`. Everything else is the generic ring.
49#[cfg(all(target_pointer_width = "64", not(ic_limb32)))]
50mont_field!(
51    Fn,
52    4,
53    32,
54    N,
55    "The P-256 scalar ring, Z/nZ where n is the order of the base point.",
56    mul = Self::mont_mul_raw,
57    square = Self::mont_square_raw,
58    invert = invert_scalar,
59    add = Self::add_raw,
60    sub = Self::sub_raw
61);
62
63#[cfg(not(all(target_pointer_width = "64", not(ic_limb32))))]
64mont_field!(
65    Fn,
66    4,
67    32,
68    N,
69    "The P-256 scalar ring, Z/nZ where n is the order of the base point."
70);
71
72/// `a^-1` in the coordinate field, as [`invert_scalar`] does it: 1.0
73/// microseconds against 1.6 for the addition chain in `p256_field`, which
74/// stays as the tests' reference.
75#[cfg(all(target_pointer_width = "64", not(ic_limb32)))]
76fn invert_field(a: [u64; 4]) -> [u64; 4] {
77    const R3: [u64; 4] = Fp::mont_mul_const(Fp::R2, Fp::R2);
78    crate::nist::p256_field::active::mul(crate::nist::safegcd::invert::<FpModulus>(&a), R3)
79}
80
81/// The prime, for the divsteps.
82#[cfg(all(target_pointer_width = "64", not(ic_limb32)))]
83pub(crate) struct FpModulus;
84
85#[cfg(all(target_pointer_width = "64", not(ic_limb32)))]
86impl crate::nist::safegcd::Modulus<5> for FpModulus {
87    const INFO: crate::nist::safegcd::ModInfo<5> =
88        crate::nist::safegcd::ModInfo::new(crate::nist::p256_field::P);
89}
90
91/// The order, for the divsteps.
92#[cfg(all(target_pointer_width = "64", not(ic_limb32)))]
93pub(crate) struct FnModulus;
94
95#[cfg(all(target_pointer_width = "64", not(ic_limb32)))]
96impl crate::nist::safegcd::Modulus<5> for FnModulus {
97    const INFO: crate::nist::safegcd::ModInfo<5> = crate::nist::safegcd::ModInfo::new(N);
98}
99
100/// `a^-1` in the scalar ring, Montgomery form in and out, by divsteps.
101///
102/// The divsteps invert the integer they are given, so a Montgomery `a * R`
103/// comes back as `a^-1 * R^-1`; one Montgomery multiplication by `R^3`
104/// makes that `a^-1 * R`.
105#[cfg(all(target_pointer_width = "64", not(ic_limb32)))]
106fn invert_scalar(a: [u64; 4]) -> [u64; 4] {
107    const R3: [u64; 4] = Fn::mont_mul_const(Fn::R2, Fn::R2);
108    Fn::mont_mul_raw(crate::nist::safegcd::invert::<FnModulus>(&a), R3)
109}
110
111/// The P-256 curve.
112#[derive(Debug, Clone, Copy)]
113pub struct P256;
114
115// Its own generator table, with its own storage; see the macro, which
116// emits the table under `std` and the windowed multiplication without it.
117crate::nist::gentable::generator_table_for!(P256, 32);
118
119impl Curve for P256 {
120    type Field = Fp;
121    type Scalar = Fn;
122
123    const NAME: &'static str = "P-256";
124    const FIELD_BYTES: usize = 32;
125    const SCALAR_BYTES: usize = 32;
126    const ORDER_BITS: usize = 256;
127
128    /// `b = 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b`
129    const B: Fp = Fp::to_mont_const([
130        0x3bce_3c3e_27d2_604b,
131        0x651d_06b0_cc53_b0f6,
132        0xb3eb_bd55_7698_86bc,
133        0x5ac6_35d8_aa3a_93e7,
134    ]);
135
136    const GX: Fp = Fp::to_mont_const([
137        0xf4a1_3945_d898_c296,
138        0x7703_7d81_2deb_33a0,
139        0xf8bc_e6e5_63a4_40f2,
140        0x6b17_d1f2_e12c_4247,
141    ]);
142
143    const GY: Fp = Fp::to_mont_const([
144        0xcbb6_4068_37bf_51f5,
145        0x2bce_3357_6b31_5ece,
146        0x8ee7_eb4a_7c0f_9e16,
147        0x4fe3_42e2_fe1a_7f9b,
148    ]);
149
150    /// `p = 3 mod 4`, so a square root is `x^((p+1)/4)`.
151    fn sqrt(x: &Fp) -> Fp {
152        // p = 3 mod 4, so the root is x^((p+1)/4). The shared helper computes
153        // that exponent rather than this file unrolling it by limb: an unrolled
154        // shift is easy to get subtly wrong and would only misbehave on inputs
155        // rare enough that a round-trip test would not find them.
156        sqrt_p3mod4(x, Fp::MODULUS, |v, e| v.pow(e))
157    }
158
159    fn field_from_slice(bytes: &[u8]) -> Option<Fp> {
160        let mut b = [0u8; 32];
161        if bytes.len() != 32 {
162            return None;
163        }
164        b.copy_from_slice(bytes);
165        Fp::from_bytes(&b)
166    }
167
168    fn scalar_from_slice(bytes: &[u8]) -> Option<Fn> {
169        let mut b = [0u8; 32];
170        if bytes.len() != 32 {
171            return None;
172        }
173        b.copy_from_slice(bytes);
174        Fn::from_bytes(&b)
175    }
176
177    #[cfg(target_arch = "x86_64")]
178    #[inline(always)]
179    fn double_fast(x: &Fp, y: &Fp, z: &Fp) -> Option<[Fp; 3]> {
180        crate::nist::p256_x86::point_double(&x.0, &y.0, &z.0).map(|[x, y, z]| [Fp(x), Fp(y), Fp(z)])
181    }
182
183    #[cfg(target_arch = "x86_64")]
184    #[inline(always)]
185    fn scan_affine(entries: &[AffinePoint], masks: &[u64]) -> Option<AffinePoint> {
186        Some(crate::nist::p256_x86::scan_affine(entries, masks))
187    }
188
189    #[cfg(target_arch = "x86_64")]
190    #[inline(always)]
191    fn scan_point(entries: &[Point], masks: &[u64]) -> Option<[Fp; 3]> {
192        let [x, y, z] = crate::nist::p256_x86::scan_point(entries, masks);
193        Some([Fp(x), Fp(y), Fp(z)])
194    }
195
196    #[cfg(target_arch = "x86_64")]
197    #[inline(always)]
198    fn add_fast(p: [&Fp; 3], q: [&Fp; 3]) -> Option<([Fp; 3], Fp, Fp)> {
199        crate::nist::p256_x86::point_add([&p[0].0, &p[1].0, &p[2].0], [&q[0].0, &q[1].0, &q[2].0])
200            .map(|([x, y, z], h, r)| ([Fp(x), Fp(y), Fp(z)], Fp(h), Fp(r)))
201    }
202
203    #[cfg(target_arch = "x86_64")]
204    #[inline(always)]
205    fn add_affine_fast(p: [&Fp; 3], q: [&Fp; 2]) -> Option<([Fp; 3], Fp, Fp)> {
206        crate::nist::p256_x86::point_add_affine([&p[0].0, &p[1].0, &p[2].0], [&q[0].0, &q[1].0])
207            .map(|([x, y, z], h, r)| ([Fp(x), Fp(y), Fp(z)], Fp(h), Fp(r)))
208    }
209
210    fn scalar_reduce_slice(bytes: &[u8]) -> Fn {
211        let mut b = [0u8; 32];
212        let n = core::cmp::min(32, bytes.len());
213        // Take the leftmost bytes, which is what bits2int does when the input
214        // is at least as wide as the group order.
215        b[32 - n..].copy_from_slice(&bytes[..n]);
216        Fn::from_bytes_reduced(&b)
217    }
218}
219
220impl ecdsa::EcdsaCurve for P256 {
221    type Digest = ic_hash::Sha256;
222    type Hmac = ic_mac::HmacSha256;
223}
224
225/// ECDSA over P-256 with SHA-256.
226pub struct EcdsaP256Sha256;
227
228impl Algorithm for EcdsaP256Sha256 {
229    const ID: &'static str = "ecdsa-p256-sha256";
230    const NAME: &'static str = "ECDSA P-256 with SHA-256";
231}
232
233impl SignatureScheme for EcdsaP256Sha256 {
234    const PRIVATE_KEY_LEN: usize = 32;
235    /// SEC1 uncompressed: `0x04 || X || Y`.
236    const PUBLIC_KEY_LEN: usize = 65;
237    /// Fixed-width `r || s`.
238    const SIGNATURE_LEN: usize = 64;
239
240    fn public_key(private_key: &[u8], out: &mut [u8]) -> Result<()> {
241        ecdsa::public_key::<P256>(private_key, out)
242    }
243
244    fn sign(private_key: &[u8], message: &[u8], signature: &mut [u8]) -> Result<()> {
245        ecdsa::sign::<P256>(private_key, message, signature)
246    }
247
248    fn verify(public_key: &[u8], message: &[u8], signature: &[u8]) -> Result<()> {
249        ecdsa::verify::<P256>(public_key, message, signature)
250    }
251}
252
253impl EcdsaP256Sha256 {
254    /// Verify a signature over a digest the caller computed, for signatures
255    /// made with a hash other than SHA-256.
256    ///
257    /// `digest` must be one of [`crate::PREHASH_LENS`] and at least 32
258    /// bytes, so that the hash is at least as strong as the curve; narrower is
259    /// refused with `InvalidLength`. Which hash produced it is the caller's to
260    /// establish: nothing here can tell.
261    pub fn verify_prehash(public_key: &[u8], digest: &[u8], signature: &[u8]) -> Result<()> {
262        ecdsa::verify_prehash::<P256>(public_key, digest, signature)
263    }
264
265    /// Compute the public key in SEC1 compressed form (33 bytes).
266    pub fn public_key_compressed(private_key: &[u8], out: &mut [u8]) -> Result<()> {
267        ecdsa::public_key_compressed::<P256>(private_key, out)
268    }
269
270    /// Rewrite a signature to its low-`s` form, if it is not already.
271    ///
272    /// ECDSA is malleable: `(r, s)` and `(r, n - s)` are both valid for the
273    /// same message, so a signature is not a unique identifier unless one form
274    /// is chosen. FIPS 186-5 and RFC 6979 accept both, and this library signs
275    /// and verifies per the standard, so normalization is offered rather than
276    /// imposed -- apply it when a signature doubles as a database key or a
277    /// transaction id.
278    pub fn normalize_s(signature: &mut [u8]) -> Result<()> {
279        ecdsa::normalize_s::<P256>(signature)
280    }
281
282    /// Whether a signature is already in low-`s` form.
283    pub fn has_low_s(signature: &[u8]) -> Result<bool> {
284        ecdsa::has_low_s::<P256>(signature)
285    }
286}
287
288impl SelfTest for EcdsaP256Sha256 {
289    fn self_test() -> Result<()> {
290        // RFC 6979 A.2.5: P-256, SHA-256, message "sample".
291        let mut key = [0u8; 32];
292        ic_core::codec::hex_decode(
293            b"c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721",
294            &mut key,
295        )?;
296        let mut want = [0u8; 64];
297        ic_core::codec::hex_decode(
298            b"efd48b2aacb6a8fd1140dd9cd45e81d69d2c877b56aaf991c34d0ea84eaf3716f7cb1c942d657c41d436c7a1b6e29f65f3e900dbb9aff4064dc4ab2f843acda8",
299            &mut want,
300        )?;
301
302        let mut sig = [0u8; 64];
303        <Self as SignatureScheme>::sign(&key, b"sample", &mut sig)?;
304        ensure!(
305            ic_core::ct::verify(&want, &sig),
306            SelfTestFailed,
307            "ecdsa-p256-sha256"
308        );
309
310        let mut pk = [0u8; 65];
311        <Self as SignatureScheme>::public_key(&key, &mut pk)?;
312        <Self as SignatureScheme>::verify(&pk, b"sample", &sig)?;
313
314        // A flipped bit must be rejected.
315        sig[0] ^= 1;
316        ensure!(
317            <Self as SignatureScheme>::verify(&pk, b"sample", &sig).is_err(),
318            SelfTestFailed,
319            "ecdsa-p256-sha256"
320        );
321        Ok(())
322    }
323}
324
325/// ECDH over P-256.
326pub struct EcdhP256;
327
328impl Algorithm for EcdhP256 {
329    const ID: &'static str = "ecdh-p256";
330    const NAME: &'static str = "ECDH P-256";
331}
332
333impl KeyAgreement for EcdhP256 {
334    const PRIVATE_KEY_LEN: usize = 32;
335    /// SEC1 uncompressed: `0x04 || X || Y`.
336    const PUBLIC_KEY_LEN: usize = 65;
337    const SHARED_SECRET_LEN: usize = 32;
338
339    fn public_key(private_key: &[u8], out: &mut [u8]) -> Result<()> {
340        ecdh::public_key::<P256>(private_key, out)
341    }
342
343    fn agree(private_key: &[u8], peer_public_key: &[u8], out: &mut [u8]) -> Result<()> {
344        ecdh::agree::<P256>(private_key, peer_public_key, out)
345    }
346}
347
348impl EcdhP256 {
349    /// Compute the public key in SEC1 compressed form (33 bytes).
350    ///
351    /// Interoperates with TLS, COSE, and JOSE, which all prefer compressed
352    /// points. [`KeyAgreement::agree`] accepts either form.
353    pub fn public_key_compressed(private_key: &[u8], out: &mut [u8]) -> Result<()> {
354        ecdh::public_key_compressed::<P256>(private_key, out)
355    }
356}
357
358impl SelfTest for EcdhP256 {
359    fn self_test() -> Result<()> {
360        // NIST CAVP ECC CDH, P-256, the first published key-agreement case.
361        let mut d = [0u8; 32];
362        ic_core::codec::hex_decode(
363            b"7d7dc5f71eb29ddaf80d6214632eeae03d9058af1fb6d22ed80badb62bc1a534",
364            &mut d,
365        )?;
366        let mut peer = [0u8; 65];
367        peer[0] = 0x04;
368        ic_core::codec::hex_decode(
369            b"700c48f77f56584c5cc632ca65640db91b6bacce3a4df6b42ce7cc838833d287",
370            &mut peer[1..33],
371        )?;
372        ic_core::codec::hex_decode(
373            b"db71e509e3fd9b060ddb20ba5c51dcc5948d46fbf640dfe0441782cab85fa4ac",
374            &mut peer[33..],
375        )?;
376        let mut want = [0u8; 32];
377        ic_core::codec::hex_decode(
378            b"46fc62106420ff012e54a434fbdd2d25ccc5852060561e68040dd7778997bd7b",
379            &mut want,
380        )?;
381
382        let mut got = [0u8; 32];
383        <Self as KeyAgreement>::agree(&d, &peer, &mut got)?;
384        ensure!(
385            ic_core::ct::verify(&want, &got),
386            SelfTestFailed,
387            "ecdh-p256"
388        );
389        Ok(())
390    }
391}
392
393/// A P-256 point in Jacobian coordinates.
394pub type Point = crate::nist::point::Point<P256>;
395/// A P-256 point in affine coordinates.
396pub type AffinePoint = crate::nist::point::AffinePoint<P256>;
397
398#[cfg(test)]
399mod tests {
400    use super::*;
401    use ic_core::codec::{hex, unhex};
402
403    /// Where P-256's time goes, piece by piece. A measurement, not a check:
404    /// run with `cargo test --release -p ic-ec -- --ignored --nocapture
405    /// where_the_time_goes`.
406    #[test]
407    #[ignore = "timing; run manually with --release"]
408    fn where_the_time_goes() {
409        use crate::nist::arith::Field;
410        use std::hint::black_box;
411        use std::time::Instant;
412        fn ns(label: &str, iters: u32, mut f: impl FnMut()) {
413            let mut best = f64::INFINITY;
414            for _ in 0..7 {
415                let t = Instant::now();
416                for _ in 0..iters {
417                    f();
418                }
419                best = best.min(t.elapsed().as_secs_f64() * 1e9 / iters as f64);
420            }
421            std::println!("{label:32} {best:10.1} ns");
422        }
423        let a = Fp::to_mont([7, 11, 13, 17]);
424        let b = Fp::to_mont([19, 23, 29, 31]);
425        ns("fp mul", 2_000_000, || {
426            black_box(black_box(a).mul(&black_box(b)));
427        });
428        ns("fp square", 2_000_000, || {
429            black_box(black_box(a).square());
430        });
431        ns("fp add", 2_000_000, || {
432            black_box(black_box(a).add(&black_box(b)));
433        });
434        ns("fp sub", 2_000_000, || {
435            black_box(black_box(a).sub(&black_box(b)));
436        });
437        ns("fp invert", 2_000, || {
438            black_box(black_box(a).invert());
439        });
440        let g = Point::generator();
441        let p = g.double();
442        ns("point double", 200_000, || {
443            black_box(black_box(p).double());
444        });
445        ns("point add (complete)", 200_000, || {
446            black_box(black_box(p).add(&black_box(g)));
447        });
448        ns("point add (unequal)", 200_000, || {
449            black_box(black_box(p).add_unequal(&black_box(g)));
450        });
451        let ga = g.to_affine().unwrap();
452        ns("point add (affine, unequal)", 200_000, || {
453            black_box(black_box(p).add_affine_unequal(&black_box(ga.x), &black_box(ga.y)));
454        });
455        let k = Fn::to_mont([0x1234_5678, 0x9abc_def0, 0x1357_9bdf, 0x2468_ace0]);
456        ns("scalar invert", 2_000, || {
457            black_box(black_box(k).invert());
458        });
459        ns("mul_generator (table)", 2_000, || {
460            black_box(Point::mul_generator(&black_box(k)));
461        });
462        ns("mul_scalar (windowed)", 500, || {
463            black_box(black_box(p).mul_scalar(&black_box(k)));
464        });
465        ns("mul_scalar_vartime", 500, || {
466            black_box(black_box(p).mul_scalar_vartime(&black_box(k)));
467        });
468        ns("to_affine", 2_000, || {
469            black_box(black_box(p).to_affine());
470        });
471        let mut pk = [0u8; 65];
472        EcdhP256::public_key(&[0x5a; 32], &mut pk).unwrap();
473        let mut shared = [0u8; 32];
474        ns("ecdh agree", 500, || {
475            EcdhP256::agree(&[0x5a; 32], &pk, &mut shared).unwrap();
476        });
477        ns("decode point", 2_000, || {
478            black_box(AffinePoint::from_sec1(black_box(&pk)));
479        });
480        ns("operational", 2_000, || {
481            black_box(ic_core::module::operational()).unwrap();
482        });
483        let mut sig = [0u8; 64];
484        ns("ecdsa sign", 500, || {
485            EcdsaP256Sha256::sign(&[0x5a; 32], b"message", &mut sig).unwrap();
486        });
487    }
488
489    fn scalar(v: u64) -> Fn {
490        Fn::to_mont([v, 0, 0, 0])
491    }
492
493    fn fp(v: u64) -> Fp {
494        Fp::to_mont([v, 0, 0, 0])
495    }
496
497    // -- field ------------------------------------------------------------
498
499    #[test]
500    fn montgomery_constants_are_consistent() {
501        assert_eq!(Fp::MODULUS[0].wrapping_mul(Fp::NEG_INV), u64::MAX, "p");
502        assert_eq!(Fn::MODULUS[0].wrapping_mul(Fn::NEG_INV), u64::MAX, "n");
503    }
504
505    #[test]
506    fn small_arithmetic_matches_integers() {
507        assert_eq!(fp(2).add(&fp(3)), fp(5));
508        assert_eq!(fp(5).sub(&fp(3)), fp(2));
509        assert_eq!(fp(6).mul(&fp(7)), fp(42));
510        assert_eq!(fp(9).square(), fp(81));
511        assert_eq!(fp(5).double(), fp(10));
512        assert_eq!(fp(5).triple(), fp(15));
513        assert_eq!(Fp::ONE.from_mont(), [1, 0, 0, 0]);
514    }
515
516    #[test]
517    fn inversion_is_correct() {
518        for v in [1u64, 2, 3, 19, 65537, u32::MAX as u64] {
519            assert_eq!(fp(v).mul(&fp(v).invert()), Fp::ONE, "1/{v} in Fp");
520            assert_eq!(scalar(v).mul(&scalar(v).invert()), Fn::ONE, "1/{v} in Fn");
521        }
522        assert_eq!(Fp::ZERO.invert(), Fp::ZERO);
523        assert_eq!(Fn::ZERO.invert(), Fn::ZERO);
524    }
525
526    /// Both rings' inversion -- divsteps on 64-bit targets -- against the
527    /// generic Fermat exponentiation, value for value in Montgomery form, on
528    /// the edges and on a spread of values from repeated squaring.
529    #[test]
530    fn inversion_matches_fermat() {
531        let mut x = fp(0x1234_5678_9abc_def1);
532        let mut k = scalar(0x0fed_cba9_8765_4321);
533        let mut fps = std::vec![
534            Fp::ONE,
535            Fp::ONE.neg(),
536            Fp::ONE.double(),
537            Fp::ONE.neg().double()
538        ];
539        let mut fns = std::vec![
540            Fn::ONE,
541            Fn::ONE.neg(),
542            Fn::ONE.double(),
543            Fn::ONE.neg().double()
544        ];
545        for _ in 0..300 {
546            x = x.square().add(&Fp::ONE);
547            k = k.square().add(&Fn::ONE);
548            fps.push(x);
549            fns.push(k);
550        }
551        for v in fps {
552            assert_eq!(v.invert().0, Fp::invert_raw(v.0), "{v:?}");
553        }
554        for v in fns {
555            assert_eq!(v.invert().0, Fn::invert_raw(v.0), "{v:?}");
556        }
557    }
558
559    #[test]
560    fn arithmetic_laws_hold_on_large_values() {
561        let a = P256::field_from_slice(&[0x3a; 32]).unwrap();
562        let b = P256::field_from_slice(&[0x91; 32]).unwrap();
563        let c = P256::field_from_slice(&[0xc7; 32]).unwrap();
564        assert_eq!(a.mul(&b).mul(&c), a.mul(&b.mul(&c)), "associativity");
565        assert_eq!(a.mul(&b), b.mul(&a), "commutativity");
566        assert_eq!(
567            a.mul(&b.add(&c)),
568            a.mul(&b).add(&a.mul(&c)),
569            "distributivity"
570        );
571        assert_eq!(a.add(&a.neg()), Fp::ZERO);
572    }
573
574    #[test]
575    fn byte_encoding_round_trips_and_rejects_non_canonical() {
576        let bytes = [0x7fu8; 32];
577        let a = P256::field_from_slice(&bytes).unwrap();
578        assert_eq!(a.to_bytes(), bytes);
579
580        // p itself must be refused but reduce to zero.
581        let mut p_bytes = [0u8; 32];
582        for i in 0..4 {
583            let hi = 32 - i * 8;
584            p_bytes[hi - 8..hi].copy_from_slice(&Fp::MODULUS[i].to_be_bytes());
585        }
586        assert!(P256::field_from_slice(&p_bytes).is_none());
587    }
588
589    // -- group law --------------------------------------------------------
590
591    /// Validates B, GX, GY and the curve equation together: if any of the four
592    /// constants were mistranscribed, the base point would not satisfy it.
593    #[test]
594    fn the_base_point_is_on_the_curve() {
595        let g = Point::generator().to_affine().unwrap();
596        assert!(bool::from(g.is_on_curve()));
597    }
598
599    /// Validates the group order n against the base point.
600    #[test]
601    fn the_base_point_has_order_n() {
602        let n_minus_1 = Fn::ZERO.sub(&Fn::ONE);
603        let p = Point::generator().mul_scalar(&n_minus_1);
604        assert!(
605            bool::from(p.ct_eq(&Point::generator().neg())),
606            "[n-1]G == -G"
607        );
608        assert!(
609            bool::from(p.add(&Point::generator()).is_identity()),
610            "[n]G is the identity"
611        );
612    }
613
614    #[test]
615    fn identity_and_negation_behave() {
616        let g = Point::generator();
617        assert!(bool::from(g.add(&Point::identity()).ct_eq(&g)));
618        assert!(bool::from(Point::identity().add(&g).ct_eq(&g)));
619        assert!(bool::from(Point::identity().double().is_identity()));
620        assert!(bool::from(g.add(&g.neg()).is_identity()));
621        assert!(Point::identity().to_affine().is_none());
622    }
623
624    /// The exceptional case a naive Jacobian addition gets wrong.
625    #[test]
626    fn addition_handles_equal_inputs_as_a_doubling() {
627        let g = Point::generator();
628        assert!(bool::from(g.add(&g).ct_eq(&g.double())));
629        let p = g.mul_scalar(&scalar(5));
630        assert!(bool::from(p.add(&p).ct_eq(&p.double())));
631    }
632
633    /// The variable-time path must agree with the constant-time one.
634    ///
635    /// The RFC 6979 vectors reach it with a couple of scalars, which says
636    /// little about a recoding whose digit pattern differs for every scalar.
637    /// The values here stress it: zero, one, a scalar that carries at every
638    /// position, alternating bits, and the top of the byte range -- which is
639    /// above the group order and so exercises the carry the extra limb exists
640    /// for.
641    #[test]
642    fn the_vartime_multiplication_agrees_with_the_ladder() {
643        let g = Point::generator();
644
645        let mut checked = 0;
646        for raw in [
647            [0u8; 32],
648            {
649                let mut v = [0u8; 32];
650                v[31] = 1;
651                v
652            },
653            [0xffu8; 32],
654            [0x55u8; 32],
655            [0xaau8; 32],
656            [0x9du8; 32],
657        ] {
658            let k = Fn::from_bytes_reduced(&raw);
659            assert!(
660                bool::from(g.mul_scalar_vartime(&k).ct_eq(&g.mul_scalar(&k))),
661                "vartime and ladder differ for {raw:02x?}"
662            );
663            checked += 1;
664        }
665        assert_eq!(checked, 6, "the comparison did not run");
666    }
667
668    #[test]
669    fn scalar_multiplication_matches_repeated_addition() {
670        let g = Point::generator();
671        let mut acc = Point::identity();
672        for k in 1..=10u64 {
673            acc = acc.add(&g);
674            assert!(bool::from(acc.ct_eq(&g.mul_scalar(&scalar(k)))), "[{k}]G");
675        }
676    }
677
678    #[test]
679    fn scalar_multiplication_is_linear() {
680        let g = Point::generator();
681        let a = scalar(1_234_567);
682        let b = scalar(7_654_321);
683        assert!(bool::from(
684            g.mul_scalar(&a.add(&b))
685                .ct_eq(&g.mul_scalar(&a).add(&g.mul_scalar(&b)))
686        ));
687        assert!(bool::from(
688            g.mul_scalar(&a)
689                .mul_scalar(&b)
690                .ct_eq(&g.mul_scalar(&a.mul(&b)))
691        ));
692    }
693
694    /// The published `[2]G`, an independent check on the group law rather than
695    /// on self-consistency.
696    #[test]
697    fn two_g_matches_the_published_value() {
698        let two_g = Point::generator().double().to_affine().unwrap();
699        assert_eq!(
700            hex(two_g.x.to_bytes().as_ref()),
701            "7cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978"
702        );
703        assert_eq!(
704            hex(two_g.y.to_bytes().as_ref()),
705            "07775510db8ed040293d9ac69f7430dbba7dade63ce982299e04b79d227873d1"
706        );
707    }
708
709    #[test]
710    fn sec1_round_trips_in_both_forms() {
711        let g = Point::generator();
712        for k in [1u64, 2, 3, 4, 5, 6, 7, 8] {
713            let p = g.mul_scalar(&scalar(k)).to_affine().unwrap();
714            let mut unc = [0u8; 65];
715            let mut comp = [0u8; 33];
716            assert!(p.write_uncompressed(&mut unc));
717            assert!(p.write_compressed(&mut comp));
718            assert_eq!(unc[0], 0x04);
719            assert!(comp[0] == 0x02 || comp[0] == 0x03);
720
721            let a = AffinePoint::from_sec1(&unc).unwrap();
722            let b = AffinePoint::from_sec1(&comp).unwrap();
723            assert_eq!(a.x, p.x);
724            assert_eq!(a.y, p.y);
725            assert_eq!(b.x, p.x);
726            assert_eq!(b.y, p.y, "compressed y for [{k}]G");
727        }
728    }
729
730    #[test]
731    fn decoding_rejects_bad_encodings() {
732        let g = Point::generator().to_affine().unwrap();
733        let mut unc = [0u8; 65];
734        assert!(g.write_uncompressed(&mut unc));
735
736        assert!(AffinePoint::from_sec1(&[]).is_none());
737        assert!(AffinePoint::from_sec1(&[0u8; 65]).is_none(), "identity");
738        assert!(AffinePoint::from_sec1(&unc[..64]).is_none(), "truncated");
739
740        let mut bad = unc;
741        bad[0] = 0x05;
742        assert!(AffinePoint::from_sec1(&bad).is_none(), "bad tag");
743
744        let mut bad = unc;
745        bad[64] ^= 1;
746        assert!(AffinePoint::from_sec1(&bad).is_none(), "off curve");
747    }
748
749    // -- ECDSA ------------------------------------------------------------
750
751    const KEY: &str = "c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721";
752
753    /// RFC 6979 A.2.5, message "sample". Matching this exercises the field, the
754    /// group law, the scalar ring, the nonce derivation, and the signing
755    /// equation in one shot.
756    #[test]
757    fn rfc6979_sample_vector() {
758        let key = unhex(KEY).unwrap();
759        let mut sig = [0u8; 64];
760        EcdsaP256Sha256::sign(&key, b"sample", &mut sig).unwrap();
761        assert_eq!(
762            hex(&sig[..32]),
763            "efd48b2aacb6a8fd1140dd9cd45e81d69d2c877b56aaf991c34d0ea84eaf3716",
764            "r"
765        );
766        assert_eq!(
767            hex(&sig[32..]),
768            "f7cb1c942d657c41d436c7a1b6e29f65f3e900dbb9aff4064dc4ab2f843acda8",
769            "s"
770        );
771    }
772
773    /// RFC 6979 A.2.5, message "test".
774    #[test]
775    fn rfc6979_test_vector() {
776        let key = unhex(KEY).unwrap();
777        let mut sig = [0u8; 64];
778        EcdsaP256Sha256::sign(&key, b"test", &mut sig).unwrap();
779        assert_eq!(
780            hex(&sig[..32]),
781            "f1abb023518351cd71d881567b1ea663ed3efcf6c5132b354f28d3b0b7d38367",
782            "r"
783        );
784        assert_eq!(
785            hex(&sig[32..]),
786            "019f4113742a2b14bd25926b49c649155f267e60d3814b4c0cc84250e46f0083",
787            "s"
788        );
789    }
790
791    #[test]
792    fn rfc6979_public_key() {
793        let key = unhex(KEY).unwrap();
794        let mut pk = [0u8; 65];
795        EcdsaP256Sha256::public_key(&key, &mut pk).unwrap();
796        assert_eq!(
797            hex(&pk[1..33]),
798            "60fed4ba255a9d31c961eb74c6356d68c049b8923b61fa6ce669622e60f29fb6",
799            "Ux"
800        );
801        assert_eq!(
802            hex(&pk[33..]),
803            "7903fe1008b8bc99a41ae9e95628bc64f2f1b20c2d7e9f5177a3c294d4462299",
804            "Uy"
805        );
806    }
807
808    #[test]
809    fn signing_is_deterministic_and_message_bound() {
810        let key = unhex(KEY).unwrap();
811        let mut a = [0u8; 64];
812        let mut b = [0u8; 64];
813        EcdsaP256Sha256::sign(&key, b"same", &mut a).unwrap();
814        EcdsaP256Sha256::sign(&key, b"same", &mut b).unwrap();
815        assert_eq!(a, b, "RFC 6979 signing must not depend on an RNG");
816
817        EcdsaP256Sha256::sign(&key, b"other", &mut b).unwrap();
818        assert_ne!(&a[..32], &b[..32], "r must differ between messages");
819    }
820
821    #[test]
822    fn sign_and_verify_round_trip() {
823        let key = unhex(KEY).unwrap();
824        let mut pk = [0u8; 65];
825        EcdsaP256Sha256::public_key(&key, &mut pk).unwrap();
826        for message in [&b""[..], b"short", &[0x5au8; 1000][..]] {
827            let mut sig = [0u8; 64];
828            EcdsaP256Sha256::sign(&key, message, &mut sig).unwrap();
829            EcdsaP256Sha256::verify(&pk, message, &sig).unwrap();
830        }
831    }
832
833    #[test]
834    fn verification_rejects_tampering() {
835        let key = unhex(KEY).unwrap();
836        let mut pk = [0u8; 65];
837        EcdsaP256Sha256::public_key(&key, &mut pk).unwrap();
838        let mut sig = [0u8; 64];
839        EcdsaP256Sha256::sign(&key, b"authentic", &mut sig).unwrap();
840
841        assert!(EcdsaP256Sha256::verify(&pk, b"forged", &sig).is_err());
842        let mut bad = sig;
843        bad[0] ^= 1;
844        assert!(EcdsaP256Sha256::verify(&pk, b"authentic", &bad).is_err());
845        let mut bad = sig;
846        bad[63] ^= 1;
847        assert!(EcdsaP256Sha256::verify(&pk, b"authentic", &bad).is_err());
848
849        let mut other = [0u8; 65];
850        EcdsaP256Sha256::public_key(&[0x11u8; 32], &mut other).unwrap();
851        assert!(EcdsaP256Sha256::verify(&other, b"authentic", &sig).is_err());
852    }
853
854    #[test]
855    fn verification_rejects_degenerate_signatures() {
856        let key = unhex(KEY).unwrap();
857        let mut pk = [0u8; 65];
858        EcdsaP256Sha256::public_key(&key, &mut pk).unwrap();
859
860        let mut zero_r = [0u8; 64];
861        zero_r[63] = 1;
862        assert!(EcdsaP256Sha256::verify(&pk, b"m", &zero_r).is_err());
863
864        let mut zero_s = [0u8; 64];
865        zero_s[31] = 1;
866        assert!(EcdsaP256Sha256::verify(&pk, b"m", &zero_s).is_err());
867
868        let n_bytes =
869            unhex("ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551").unwrap();
870        let mut at_n = [0u8; 64];
871        at_n[..32].copy_from_slice(&n_bytes);
872        at_n[32..].copy_from_slice(&n_bytes);
873        assert!(EcdsaP256Sha256::verify(&pk, b"m", &at_n).is_err());
874    }
875
876    #[test]
877    fn signing_rejects_invalid_private_keys() {
878        let mut sig = [0u8; 64];
879        assert!(
880            EcdsaP256Sha256::sign(&[0u8; 32], b"m", &mut sig).is_err(),
881            "zero"
882        );
883        assert!(
884            EcdsaP256Sha256::sign(&[0xffu8; 32], b"m", &mut sig).is_err(),
885            ">= n"
886        );
887        assert!(
888            EcdsaP256Sha256::sign(&[1u8; 31], b"m", &mut sig).is_err(),
889            "short"
890        );
891    }
892
893    /// Both `(r, s)` and `(r, n - s)` verify; normalization picks one.
894    #[test]
895    fn malleability_and_normalization() {
896        let key = unhex(KEY).unwrap();
897        let mut pk = [0u8; 65];
898        EcdsaP256Sha256::public_key(&key, &mut pk).unwrap();
899        let mut sig = [0u8; 64];
900        EcdsaP256Sha256::sign(&key, b"sample", &mut sig).unwrap();
901        assert!(
902            !EcdsaP256Sha256::has_low_s(&sig).unwrap(),
903            "RFC 6979 s is high here"
904        );
905
906        let mut flipped = sig;
907        EcdsaP256Sha256::normalize_s(&mut flipped).unwrap();
908        assert_ne!(flipped, sig);
909        EcdsaP256Sha256::verify(&pk, b"sample", &flipped).unwrap();
910        assert!(EcdsaP256Sha256::has_low_s(&flipped).unwrap());
911
912        let mut twice = flipped;
913        EcdsaP256Sha256::normalize_s(&mut twice).unwrap();
914        assert_eq!(twice, flipped, "normalization must be idempotent");
915    }
916
917    #[test]
918    fn ecdsa_self_test_passes() {
919        EcdsaP256Sha256::self_test().unwrap();
920    }
921
922    // -- ECDH -------------------------------------------------------------
923
924    /// NIST CAVP ECC CDH, first published case.
925    #[test]
926    fn cavp_ecc_cdh_vector() {
927        let d = unhex("7d7dc5f71eb29ddaf80d6214632eeae03d9058af1fb6d22ed80badb62bc1a534").unwrap();
928        let mut peer = vec![0x04u8];
929        peer.extend_from_slice(
930            &unhex("700c48f77f56584c5cc632ca65640db91b6bacce3a4df6b42ce7cc838833d287").unwrap(),
931        );
932        peer.extend_from_slice(
933            &unhex("db71e509e3fd9b060ddb20ba5c51dcc5948d46fbf640dfe0441782cab85fa4ac").unwrap(),
934        );
935        let mut z = [0u8; 32];
936        EcdhP256::agree(&d, &peer, &mut z).unwrap();
937        assert_eq!(
938            hex(&z),
939            "46fc62106420ff012e54a434fbdd2d25ccc5852060561e68040dd7778997bd7b"
940        );
941    }
942
943    #[test]
944    fn both_parties_derive_the_same_secret() {
945        let (alice, bob) = ([0x11u8; 32], [0x22u8; 32]);
946        let mut alice_pk = [0u8; 65];
947        let mut bob_pk = [0u8; 65];
948        EcdhP256::public_key(&alice, &mut alice_pk).unwrap();
949        EcdhP256::public_key(&bob, &mut bob_pk).unwrap();
950
951        let mut z1 = [0u8; 32];
952        let mut z2 = [0u8; 32];
953        EcdhP256::agree(&alice, &bob_pk, &mut z1).unwrap();
954        EcdhP256::agree(&bob, &alice_pk, &mut z2).unwrap();
955        assert_eq!(z1, z2);
956        assert_ne!(z1, [0u8; 32]);
957    }
958
959    #[test]
960    fn compressed_and_uncompressed_peers_agree() {
961        let (alice, bob) = ([0x33u8; 32], [0x44u8; 32]);
962        let mut unc = [0u8; 65];
963        let mut comp = [0u8; 33];
964        EcdhP256::public_key(&bob, &mut unc).unwrap();
965        EcdhP256::public_key_compressed(&bob, &mut comp).unwrap();
966
967        let mut z1 = [0u8; 32];
968        let mut z2 = [0u8; 32];
969        EcdhP256::agree(&alice, &unc, &mut z1).unwrap();
970        EcdhP256::agree(&alice, &comp, &mut z2).unwrap();
971        assert_eq!(z1, z2, "the peer key encoding must not matter");
972    }
973
974    #[test]
975    fn ecdh_rejects_invalid_inputs() {
976        let alice = [0x11u8; 32];
977        let mut z = [0u8; 32];
978        assert!(EcdhP256::agree(&alice, &[0u8; 65], &mut z).is_err());
979        assert!(EcdhP256::agree(&alice, &[], &mut z).is_err());
980
981        let mut bob_pk = [0u8; 65];
982        EcdhP256::public_key(&[0x22u8; 32], &mut bob_pk).unwrap();
983        bob_pk[64] ^= 1;
984        assert!(
985            EcdhP256::agree(&alice, &bob_pk, &mut z).is_err(),
986            "off curve"
987        );
988
989        let mut pk = [0u8; 65];
990        assert!(EcdhP256::public_key(&[0u8; 32], &mut pk).is_err());
991        assert!(EcdhP256::public_key(&[0xffu8; 32], &mut pk).is_err());
992    }
993
994    #[test]
995    fn ecdh_self_test_passes() {
996        EcdhP256::self_test().unwrap();
997    }
998}