Skip to main content

arcana/ecc/
curves.rs

1//! High-level curve API: [`Curve`] trait + per-curve unit structs.
2//!
3//! This module exposes the user-facing dispatch layer shared by ECDSA
4//! and ECDH over short Weierstrass curves. A curve key pair is
5//! primitive-agnostic -- a [`SecretKey`] is just a scalar, a
6//! [`PublicKey`] is just a point -- so the same unit struct
7//! (`P256`, `P384`, ...) dispatches ECDSA sign/verify **and** ECDH
8//! derive to the underlying LIMBS-generic implementations in
9//! [`super::ecdsa`].
10//!
11//! Supported curves:
12//!
13//! | Wrapper             | Curve              | Canonical hash (ECDSA) |
14//! |---------------------|--------------------|------------------------|
15//! | [`P256`]            | NIST P-256         | SHA-256                |
16//! | [`P384`]            | NIST P-384         | SHA-384                |
17//! | [`P521`]            | NIST P-521         | SHA-512                |
18//! | [`Secp256k1`]       | secp256k1 (SECG)   | SHA-256                |
19//! | [`BrainpoolP256r1`] | brainpoolP256r1    | SHA-256                |
20//! | [`BrainpoolP384r1`] | brainpoolP384r1    | SHA-384                |
21//! | [`BrainpoolP512r1`] | brainpoolP512r1    | SHA-512                |
22
23use super::curve::*;
24use super::ecdsa::{
25    Signature, compress_pubkey_internal, decompress_pubkey_internal, ecdh_internal, keygen_internal,
26    sign_random_internal, sign_rfc6979_internal, verify_internal,
27};
28use crate::Hasher;
29
30// ============================================================================
31// Shared ECDSA / ECDH key types
32// ============================================================================
33
34/// A simple RNG trait for randomized nonce generation and key
35/// generation.
36///
37/// Implementors fill the supplied buffer with cryptographically
38/// strong pseudo-random bytes. The trait is deliberately minimal
39/// (no `fn try_fill_bytes` returning `Result`, no error type) to
40/// keep the public surface small. Callers running on platforms
41/// where the underlying entropy source can fail should provide a
42/// wrapper that panics or aborts on failure.
43pub trait CryptoRng {
44    /// Fill `dest` with cryptographically random bytes.
45    fn fill_bytes(&mut self, dest: &mut [u8]);
46}
47
48/// Public key on a short Weierstrass curve: an SEC1-encoded point
49/// (uncompressed `0x04 || X || Y` or compressed `0x02/0x03 || X`).
50///
51/// Shared between ECDSA and ECDH on the same curve: there is no
52/// "ECDH-only" or "ECDSA-only" key type.
53#[derive(Clone, Debug)]
54pub struct PublicKey {
55    /// SEC1-encoded curve point.
56    pub bytes: Vec<u8>,
57}
58
59/// Secret key on a short Weierstrass curve: a scalar in `[1, n-1]`
60/// encoded as `felem_bytes` big-endian octets (per the curve's SEC1
61/// octet length).
62///
63/// Shared between ECDSA and ECDH on the same curve.
64#[derive(Clone)]
65pub struct SecretKey {
66    /// Big-endian encoding of the secret scalar `d`.
67    pub bytes: Vec<u8>,
68}
69
70// ============================================================================
71// Curve trait
72// ============================================================================
73//
74// Public API. Two axes are exposed orthogonally:
75//
76//   * The curve  -- one unit struct per curve, implementing `Curve`.
77//   * The hash   -- a generic type parameter on the methods that need it
78//                   (RFC 6979 internal HMAC; the message-input convenience
79//                   wrappers that hash before signing).
80//
81// `sign_random` and `verify` do **not** carry an `H` parameter: they
82// consume the digest as opaque bytes (interpreted via `bits2int`) and
83// have no algebraic dependency on which hash produced it. The asymmetry
84// reflects the actual algorithm.
85//
86// Naming convention: the **digest-input** form is the canonical, short
87// name (this is the form that maps directly to OpenSSL `ECDSA_sign`,
88// PKCS#11, HSM offload paths, X.509/CMS pipelines, ...). The **message
89// input** convenience form has a `_msg` suffix.
90//
91// Per-curve unit structs (`P256`, `P384`, ...) carry no data; they exist
92// only to dispatch the trait methods to the LIMBS-generic internals with
93// the right `params()` and the right `LIMBS` const.
94
95/// Operations on a short Weierstrass curve: keygen, ECDSA sign / verify,
96/// and ECDH key agreement.
97///
98/// Implementations are unit structs ([`P256`], [`P384`], [`P521`],
99/// [`Secp256k1`], [`BrainpoolP256r1`], [`BrainpoolP384r1`],
100/// [`BrainpoolP512r1`]) and the methods are called as e.g.
101/// `P256::sign_rfc6979::<Sha256>(&sk, &digest)` or
102/// `P256::ecdh(&sk, &peer_pk)`.
103///
104/// # Method overview
105///
106/// | Method                                       | Input         | `H` needed?                |
107/// |----------------------------------------------|---------------|----------------------------|
108/// | [`keygen`](Self::keygen)                     | --            | no                         |
109/// | [`ecdh`](Self::ecdh)                         | peer SEC1 pk  | no                         |
110/// | [`sign_rfc6979`](Self::sign_rfc6979)         | digest        | yes (HMAC inside RFC 6979) |
111/// | [`sign_random`](Self::sign_random)           | digest        | no                         |
112/// | [`verify`](Self::verify)                     | digest        | no                         |
113/// | [`sign_rfc6979_msg`](Self::sign_rfc6979_msg) | message       | yes (hash + HMAC)          |
114/// | [`sign_random_msg`](Self::sign_random_msg)   | message       | yes (hash)                 |
115/// | [`verify_msg`](Self::verify_msg)             | message       | yes (hash)                 |
116///
117/// Key pairs returned by [`keygen`](Self::keygen) are interchangeable
118/// across ECDSA and ECDH on the same curve -- a [`SecretKey`] is just a
119/// scalar, a [`PublicKey`] is just a point. There is no "ECDH-only" or
120/// "ECDSA-only" key type.
121///
122/// # Hash choice (ECDSA)
123///
124/// Any [`Hasher`] implementation may be paired with any curve.
125/// The standard pairings (P-256+SHA-256, P-384+SHA-384, brainpoolP512r1
126/// +SHA-512, etc.) are common, but **not** mandated by this crate. If you
127/// need P-256 + SHA-512 for an exotic protocol, that works too -- the
128/// `bits2int` step will truncate the digest to the curve's qlen as
129/// specified in RFC 6979 §2.3.2.
130///
131/// Per FIPS 186-5 §6.4.2, the hash output should provide at least the
132/// security level of the curve (e.g. don't pair P-384 with SHA-1). The
133/// crate does not enforce this; document and test your protocol's choice.
134pub trait Curve: Sized {
135    /// Generate a key pair on this curve.
136    fn keygen(rng: &mut dyn CryptoRng) -> (PublicKey, SecretKey);
137
138    /// Compress a public key from SEC1 uncompressed (`0x04 || X || Y`)
139    /// to SEC1 compressed (`0x02/0x03 || X`). If the input is already
140    /// compressed, returns a validated clone. Returns `None` for
141    /// malformed or off-curve input.
142    fn compress_pubkey(pk: &PublicKey) -> Option<Vec<u8>>;
143
144    /// Decompress a SEC1 compressed public key (`0x02/0x03 || X`) to
145    /// uncompressed form (`0x04 || X || Y`), recovering Y via the
146    /// field square-root. If the input is already uncompressed, acts
147    /// as a validate-and-clone. Returns `None` if the input is
148    /// malformed, if X is not a valid x-coordinate on the curve, or
149    /// if the decompressed point fails the on-curve check.
150    fn decompress_pubkey(compressed: &[u8]) -> Option<PublicKey>;
151
152    /// **ECDH** key agreement: derive the shared secret from our secret
153    /// key and the peer's SEC1 uncompressed public key.
154    ///
155    /// Returns the **raw X coordinate** of `sk * peer_pk` as `LIMBS * 8`
156    /// big-endian bytes (matches NIST SP 800-56A §5.7.1.2 "ECC CDH
157    /// Primitive" and TLS / IKE conventions).
158    ///
159    /// Returns `None` if any of the following holds:
160    /// - peer's pk has the wrong SEC1 length / tag
161    /// - peer's pk is not on the curve (defends against invalid-curve
162    ///   attacks; see [`super::curve::is_on_curve`])
163    /// - our secret scalar is not in `[1, n-1]`
164    /// - the resulting shared point is the point at infinity
165    ///
166    /// Higher-level KDFs (HKDF, X9.63 KDF, ...) are out of scope for this
167    /// layer -- callers feed the raw X bytes into their KDF of choice.
168    fn ecdh(sk: &SecretKey, peer_pk: &PublicKey) -> Option<Vec<u8>>;
169
170    /// Sign a precomputed digest with the deterministic RFC 6979 nonce.
171    ///
172    /// `H` is the hash that produced `digest`; it is required because RFC
173    /// 6979 derives the nonce via HMAC-`H` internally. Two calls with the
174    /// same `(sk, digest, H)` produce byte-identical signatures.
175    fn sign_rfc6979<H: Hasher>(sk: &SecretKey, digest: &[u8]) -> Signature;
176
177    /// Sign a precomputed digest with a uniformly random nonce drawn from
178    /// `rng`. The hash function is irrelevant -- only the digest bytes are
179    /// consumed (via `bits2int`). **Each call must consume fresh entropy**;
180    /// reusing `k` across two signatures with the same key recovers the
181    /// secret key.
182    fn sign_random(sk: &SecretKey, digest: &[u8], rng: &mut dyn CryptoRng) -> Signature;
183
184    /// Verify a signature against a precomputed digest.
185    fn verify(pk: &PublicKey, digest: &[u8], sig: &Signature) -> bool;
186
187    // ----- Convenience: hash the message in-place -----
188
189    /// Convenience: hash `msg` with `H`, then call [`Self::sign_rfc6979`].
190    fn sign_rfc6979_msg<H: Hasher>(sk: &SecretKey, msg: &[u8]) -> Signature {
191        let digest = H::hash(msg);
192        Self::sign_rfc6979::<H>(sk, &digest)
193    }
194
195    /// Convenience: hash `msg` with `H`, then call [`Self::sign_random`].
196    fn sign_random_msg<H: Hasher>(sk: &SecretKey, msg: &[u8], rng: &mut dyn CryptoRng) -> Signature {
197        let digest = H::hash(msg);
198        Self::sign_random(sk, &digest, rng)
199    }
200
201    /// Convenience: hash `msg` with `H`, then call [`Self::verify`].
202    fn verify_msg<H: Hasher>(pk: &PublicKey, msg: &[u8], sig: &Signature) -> bool {
203        let digest = H::hash(msg);
204        Self::verify(pk, &digest, sig)
205    }
206}
207
208// ============================================================================
209// Per-curve dispatch
210// ============================================================================
211
212/// Generate the per-curve unit struct + `Curve` impl for one curve. The
213/// macro pins `LIMBS` and the `params_fn`; everything else flows through
214/// the LIMBS-generic internals in [`super::ecdsa`].
215macro_rules! curve_dispatch {
216    ($name:ident, $params_fn:path, $limbs:expr, $doc:literal) => {
217        #[doc = $doc]
218        pub struct $name;
219
220        impl Curve for $name {
221            fn keygen(rng: &mut dyn CryptoRng) -> (PublicKey, SecretKey) {
222                keygen_internal::<$limbs>(&$params_fn(), rng)
223            }
224
225            fn ecdh(sk: &SecretKey, peer_pk: &PublicKey) -> Option<Vec<u8>> {
226                ecdh_internal::<$limbs>(&$params_fn(), sk, peer_pk)
227            }
228
229            fn compress_pubkey(pk: &PublicKey) -> Option<Vec<u8>> {
230                compress_pubkey_internal::<$limbs>(&$params_fn(), pk)
231            }
232
233            fn decompress_pubkey(compressed: &[u8]) -> Option<PublicKey> {
234                decompress_pubkey_internal::<$limbs>(&$params_fn(), compressed)
235            }
236
237            fn sign_rfc6979<H: Hasher>(sk: &SecretKey, digest: &[u8]) -> Signature {
238                sign_rfc6979_internal::<H, $limbs>(&$params_fn(), sk, digest)
239            }
240
241            fn sign_random(sk: &SecretKey, digest: &[u8], rng: &mut dyn CryptoRng) -> Signature {
242                sign_random_internal::<$limbs>(&$params_fn(), sk, digest, rng)
243            }
244
245            fn verify(pk: &PublicKey, digest: &[u8], sig: &Signature) -> bool {
246                verify_internal::<$limbs>(&$params_fn(), pk, digest, sig)
247            }
248        }
249    };
250}
251
252curve_dispatch!(P256, p256_params, 4, "NIST P-256 (secp256r1).");
253curve_dispatch!(P384, p384_params, 6, "NIST P-384 (secp384r1).");
254curve_dispatch!(Secp256k1, secp256k1_params, 4, "secp256k1 (SECG / Bitcoin / Ethereum).");
255curve_dispatch!(
256    BrainpoolP256r1,
257    brainpoolp256r1_params,
258    4,
259    "brainpoolP256r1 (BSI / RFC 5639)."
260);
261curve_dispatch!(
262    BrainpoolP384r1,
263    brainpoolp384r1_params,
264    6,
265    "brainpoolP384r1 (BSI / RFC 5639)."
266);
267curve_dispatch!(
268    BrainpoolP512r1,
269    brainpoolp512r1_params,
270    8,
271    "brainpoolP512r1 (BSI / RFC 5639)."
272);
273curve_dispatch!(
274    P521,
275    secp521r1_params,
276    9,
277    "NIST P-521 (secp521r1). Uses LIMBS=9; qlen=521 is not a multiple \
278     of 8, so all RFC 6979 byte-length arithmetic uses rlen_bytes=66 \
279     (not LIMBS*8=72). The canonical hash pairing is SHA-512."
280);
281
282// ============================================================================
283// Tests
284// ============================================================================
285
286#[cfg(test)]
287mod tests {
288    use super::super::ecdsa::fe_to_felem_bytes;
289    use super::super::field::FieldElement;
290    use super::*;
291    use crate::hash::sha256::Sha256;
292    use crate::hash::sha384::Sha384;
293    use crate::hash::sha512::Sha512;
294
295    fn hex_to_bytes(hex: &str) -> Vec<u8> {
296        (0..hex.len())
297            .step_by(2)
298            .map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap())
299            .collect()
300    }
301
302    /// Build (sk, pk) for d=1 on a curve, given its params. Both `sk`
303    /// and `pk` use the SEC1 `felem_bytes` external width (same as what
304    /// `keygen_internal` emits), so for P-521 this returns a 66-byte
305    /// secret key and a 133-byte uncompressed public key, not
306    /// 72 / 145.
307    fn d1_keypair<const LIMBS: usize>(params: &CurveParams<LIMBS>) -> (SecretKey, PublicKey) {
308        let felem = params.felem_bytes;
309        let mut sk_bytes = vec![0u8; felem];
310        sk_bytes[felem - 1] = 1; // d = 1 (big-endian)
311
312        let mut pk_bytes = Vec::with_capacity(1 + 2 * felem);
313        pk_bytes.push(0x04);
314        pk_bytes.extend_from_slice(&fe_to_felem_bytes(&params.gx, felem));
315        pk_bytes.extend_from_slice(&fe_to_felem_bytes(&params.gy, felem));
316
317        (SecretKey { bytes: sk_bytes }, PublicKey { bytes: pk_bytes })
318    }
319
320    /// Tiny deterministic xorshift64 RNG, for tests only. Not cryptographic --
321    /// the goal is just to feed `sign_random` a known, reproducible byte
322    /// stream so that test failures are debuggable.
323    struct TestRng {
324        state: u64,
325    }
326
327    impl TestRng {
328        fn new(seed: u64) -> Self {
329            // xorshift state must be non-zero.
330            Self {
331                state: if seed == 0 { 0xdeadbeefcafef00d } else { seed },
332            }
333        }
334    }
335
336    impl CryptoRng for TestRng {
337        fn fill_bytes(&mut self, dest: &mut [u8]) {
338            for chunk in dest.chunks_mut(8) {
339                let mut x = self.state;
340                x ^= x << 13;
341                x ^= x >> 7;
342                x ^= x << 17;
343                self.state = x;
344                for (i, b) in chunk.iter_mut().enumerate() {
345                    *b = (x >> (8 * i)) as u8;
346                }
347            }
348        }
349    }
350
351    // ----------------------------------------------------------------------
352    // ECDSA sign/verify -- P-256 canonical roundtrip
353    // ----------------------------------------------------------------------
354
355    #[test]
356    fn test_ecdsa_p256_sign_verify_rfc6979() {
357        let sk_bytes = hex_to_bytes("C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721");
358        let sk = SecretKey {
359            bytes: sk_bytes.clone(),
360        };
361
362        // Compute public key from secret key.
363        let params = p256_params();
364        let g = JacobianPoint::from_affine(params.gx, params.gy);
365        let d = FieldElement::<4>::from_bytes_be(&sk_bytes);
366        let q = scalar_mul_point(&d, &g, &params);
367        let (qx, qy) = q.to_affine(&params.p).unwrap();
368        let mut pk_bytes = vec![0x04];
369        pk_bytes.extend_from_slice(&qx.to_bytes_be());
370        pk_bytes.extend_from_slice(&qy.to_bytes_be());
371        let pk = PublicKey { bytes: pk_bytes };
372
373        let msg = b"sample";
374        let sig = P256::sign_rfc6979_msg::<Sha256>(&sk, msg);
375
376        let expected_r = hex_to_bytes("EFD48B2AACB6A8FD1140DD9CD45E81D69D2C877B56AAF991C34D0EA84EAF3716");
377        let expected_s = hex_to_bytes("F7CB1C942D657C41D436C7A1B6E29F65F3E900DBB9AFF4064DC4AB2F843ACDA8");
378
379        assert_eq!(sig.r, expected_r, "Signature r mismatch");
380        assert_eq!(sig.s, expected_s, "Signature s mismatch");
381
382        // Verify the signature.
383        assert!(
384            P256::verify_msg::<Sha256>(&pk, msg, &sig),
385            "Signature verification failed"
386        );
387    }
388
389    #[test]
390    fn test_ecdsa_p256_verify_rejects_bad_sig() {
391        let sk_bytes = hex_to_bytes("C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721");
392        let sk = SecretKey {
393            bytes: sk_bytes.clone(),
394        };
395
396        let params = p256_params();
397        let g = JacobianPoint::from_affine(params.gx, params.gy);
398        let d = FieldElement::<4>::from_bytes_be(&sk_bytes);
399        let q = scalar_mul_point(&d, &g, &params);
400        let (qx, qy) = q.to_affine(&params.p).unwrap();
401        let mut pk_bytes = vec![0x04];
402        pk_bytes.extend_from_slice(&qx.to_bytes_be());
403        pk_bytes.extend_from_slice(&qy.to_bytes_be());
404        let pk = PublicKey { bytes: pk_bytes };
405
406        let msg = b"sample";
407        let sig = P256::sign_rfc6979_msg::<Sha256>(&sk, msg);
408
409        // Modify message.
410        assert!(!P256::verify(&pk, b"tampered", &sig), "Should reject tampered message");
411
412        // Modify signature.
413        let mut bad_sig = sig.clone();
414        bad_sig.r[0] ^= 0x01;
415        assert!(!P256::verify(&pk, msg, &bad_sig), "Should reject modified signature");
416    }
417
418    #[test]
419    fn test_ecdsa_p256_sign_verify_roundtrip() {
420        let sk_bytes = hex_to_bytes("0000000000000000000000000000000000000000000000000000000000000001");
421        let sk = SecretKey {
422            bytes: sk_bytes.clone(),
423        };
424
425        // Public key for d=1 is just G.
426        let params = p256_params();
427        let mut pk_bytes = vec![0x04];
428        pk_bytes.extend_from_slice(&params.gx.to_bytes_be());
429        pk_bytes.extend_from_slice(&params.gy.to_bytes_be());
430        let pk = PublicKey { bytes: pk_bytes };
431
432        let msg = b"test message for ECDSA P-256";
433        let sig = P256::sign_rfc6979_msg::<Sha256>(&sk, msg);
434        assert!(
435            P256::verify_msg::<Sha256>(&pk, msg, &sig),
436            "Roundtrip verification failed"
437        );
438    }
439
440    // ----------------------------------------------------------------------
441    // Roundtrip tests for the new curves.
442    //
443    // Strategy: use d = 1 so the public key is exactly the generator G --
444    // avoids re-doing scalar multiplication in the test setup. Sign with
445    // RFC 6979 (deterministic, no rng needed), then verify.
446    // ----------------------------------------------------------------------
447
448    #[test]
449    fn test_ecdsa_secp256k1_sign_verify_roundtrip() {
450        let (sk, pk) = d1_keypair(&secp256k1_params());
451        let msg = b"hello secp256k1";
452        let sig = Secp256k1::sign_rfc6979_msg::<Sha256>(&sk, msg);
453        assert!(Secp256k1::verify_msg::<Sha256>(&pk, msg, &sig));
454        assert!(!Secp256k1::verify(&pk, b"tampered", &sig));
455        let mut bad = sig.clone();
456        bad.s[0] ^= 0x01;
457        assert!(!Secp256k1::verify(&pk, msg, &bad));
458    }
459
460    #[test]
461    fn test_ecdsa_brainpoolp256r1_sign_verify_roundtrip() {
462        let (sk, pk) = d1_keypair(&brainpoolp256r1_params());
463        let msg = b"hello brainpoolP256r1";
464        let sig = BrainpoolP256r1::sign_rfc6979_msg::<Sha256>(&sk, msg);
465        assert!(BrainpoolP256r1::verify_msg::<Sha256>(&pk, msg, &sig));
466    }
467
468    #[test]
469    fn test_ecdsa_brainpoolp384r1_sign_verify_roundtrip() {
470        let (sk, pk) = d1_keypair(&brainpoolp384r1_params());
471        let msg = b"hello brainpoolP384r1";
472        let sig = BrainpoolP384r1::sign_rfc6979_msg::<Sha384>(&sk, msg);
473        assert!(BrainpoolP384r1::verify_msg::<Sha384>(&pk, msg, &sig));
474    }
475
476    #[test]
477    fn test_ecdsa_brainpoolp512r1_sign_verify_roundtrip() {
478        let (sk, pk) = d1_keypair(&brainpoolp512r1_params());
479        let msg = b"hello brainpoolP512r1";
480        let sig = BrainpoolP512r1::sign_rfc6979_msg::<Sha512>(&sk, msg);
481        assert!(BrainpoolP512r1::verify_msg::<Sha512>(&pk, msg, &sig));
482    }
483
484    // ----------------------------------------------------------------------
485    // sign_random tests: exercise the random-nonce path.
486    // ----------------------------------------------------------------------
487
488    #[test]
489    fn test_ecdsa_p256_sign_random_roundtrip() {
490        let (sk, pk) = d1_keypair(&p256_params());
491        let mut rng = TestRng::new(1);
492        let msg = b"random nonce P-256";
493        let sig = P256::sign_random_msg::<Sha256>(&sk, msg, &mut rng);
494        assert!(P256::verify_msg::<Sha256>(&pk, msg, &sig));
495    }
496
497    #[test]
498    fn test_ecdsa_p384_sign_random_roundtrip() {
499        let (sk, pk) = d1_keypair(&p384_params());
500        let mut rng = TestRng::new(11);
501        let msg = b"random nonce P-384";
502        let sig = P384::sign_random_msg::<Sha384>(&sk, msg, &mut rng);
503        assert!(P384::verify_msg::<Sha384>(&pk, msg, &sig));
504    }
505
506    /// Diagnostic: sweep many seeds and confirm each random signature
507    /// verifies.
508    #[test]
509    fn test_p384_random_seed_sweep() {
510        let (sk, pk) = d1_keypair(&p384_params());
511        let msg = b"sweep";
512        for seed in 1u64..=20 {
513            let mut rng = TestRng::new(seed);
514            let sig = P384::sign_random_msg::<Sha384>(&sk, msg, &mut rng);
515            assert!(
516                P384::verify_msg::<Sha384>(&pk, msg, &sig),
517                "verify failed for P-384 seed {}",
518                seed,
519            );
520        }
521    }
522
523    #[test]
524    fn test_ecdsa_p384_sign_rfc6979_roundtrip() {
525        let (sk, pk) = d1_keypair(&p384_params());
526        let msg = b"P-384 RFC 6979 baseline";
527        let sig = P384::sign_rfc6979_msg::<Sha384>(&sk, msg);
528        assert!(P384::verify_msg::<Sha384>(&pk, msg, &sig));
529    }
530
531    #[test]
532    fn test_ecdsa_secp256k1_sign_random_roundtrip() {
533        let (sk, pk) = d1_keypair(&secp256k1_params());
534        let mut rng = TestRng::new(3);
535        let msg = b"random nonce secp256k1";
536        let sig = Secp256k1::sign_random_msg::<Sha256>(&sk, msg, &mut rng);
537        assert!(Secp256k1::verify_msg::<Sha256>(&pk, msg, &sig));
538    }
539
540    #[test]
541    fn test_ecdsa_brainpoolp256r1_sign_random_roundtrip() {
542        let (sk, pk) = d1_keypair(&brainpoolp256r1_params());
543        let mut rng = TestRng::new(4);
544        let msg = b"random nonce brainpoolP256r1";
545        let sig = BrainpoolP256r1::sign_random_msg::<Sha256>(&sk, msg, &mut rng);
546        assert!(BrainpoolP256r1::verify_msg::<Sha256>(&pk, msg, &sig));
547    }
548
549    #[test]
550    fn test_ecdsa_brainpoolp384r1_sign_random_roundtrip() {
551        let (sk, pk) = d1_keypair(&brainpoolp384r1_params());
552        let mut rng = TestRng::new(5);
553        let msg = b"random nonce brainpoolP384r1";
554        let sig = BrainpoolP384r1::sign_random_msg::<Sha384>(&sk, msg, &mut rng);
555        assert!(BrainpoolP384r1::verify_msg::<Sha384>(&pk, msg, &sig));
556    }
557
558    #[test]
559    fn test_ecdsa_brainpoolp512r1_sign_random_roundtrip() {
560        let (sk, pk) = d1_keypair(&brainpoolp512r1_params());
561        let mut rng = TestRng::new(6);
562        let msg = b"random nonce brainpoolP512r1";
563        let sig = BrainpoolP512r1::sign_random_msg::<Sha512>(&sk, msg, &mut rng);
564        assert!(BrainpoolP512r1::verify_msg::<Sha512>(&pk, msg, &sig));
565    }
566
567    /// Two `sign_random` calls on the same `(sk, msg)` with different rng
568    /// streams must produce different signatures (different `r`, since the
569    /// nonce differs). Both must still verify.
570    #[test]
571    fn test_sign_random_is_nondeterministic() {
572        let (sk, pk) = d1_keypair(&p256_params());
573        let msg = b"same message, different rng";
574
575        let mut rng1 = TestRng::new(0xAA11);
576        let mut rng2 = TestRng::new(0xBB22);
577        let sig1 = P256::sign_random_msg::<Sha256>(&sk, msg, &mut rng1);
578        let sig2 = P256::sign_random_msg::<Sha256>(&sk, msg, &mut rng2);
579
580        assert_ne!(sig1.r, sig2.r, "random sigs should differ on r");
581        assert!(P256::verify_msg::<Sha256>(&pk, msg, &sig1));
582        assert!(P256::verify_msg::<Sha256>(&pk, msg, &sig2));
583    }
584
585    /// Conversely, two `sign_rfc6979` calls on the same `(sk, msg)` MUST
586    /// produce identical signatures.
587    #[test]
588    fn test_sign_rfc6979_is_deterministic() {
589        let (sk, _pk) = d1_keypair(&p256_params());
590        let msg = b"same message, no rng";
591
592        let sig1 = P256::sign_rfc6979_msg::<Sha256>(&sk, msg);
593        let sig2 = P256::sign_rfc6979_msg::<Sha256>(&sk, msg);
594
595        assert_eq!(sig1.r, sig2.r);
596        assert_eq!(sig1.s, sig2.s);
597    }
598
599    /// The digest-input form and the message-input convenience form must
600    /// produce byte-identical signatures when the caller pre-hashes the
601    /// message themselves.
602    #[test]
603    fn test_digest_vs_msg_forms_agree() {
604        let (sk, _pk) = d1_keypair(&p256_params());
605        let msg = b"agreement";
606
607        let from_msg = P256::sign_rfc6979_msg::<Sha256>(&sk, msg);
608        let digest = Sha256::hash(msg);
609        let from_digest = P256::sign_rfc6979::<Sha256>(&sk, &digest);
610
611        assert_eq!(from_msg.r, from_digest.r);
612        assert_eq!(from_msg.s, from_digest.s);
613    }
614
615    // ----------------------------------------------------------------------
616    // Public-key validation tests for verify_internal
617    // ----------------------------------------------------------------------
618
619    fn fresh_p256_signed() -> (SecretKey, PublicKey, Vec<u8>, Signature) {
620        let (sk, pk) = d1_keypair(&p256_params());
621        let msg = b"verify hardening sample";
622        let digest = Sha256::hash(msg);
623        let sig = P256::sign_rfc6979::<Sha256>(&sk, &digest);
624        assert!(P256::verify(&pk, &digest, &sig));
625        (sk, pk, digest, sig)
626    }
627
628    #[test]
629    fn test_verify_rejects_wrong_length_pubkey() {
630        let (_sk, mut pk, digest, sig) = fresh_p256_signed();
631        pk.bytes.pop();
632        assert!(!P256::verify(&pk, &digest, &sig));
633    }
634
635    #[test]
636    fn test_verify_rejects_wrong_tag_pubkey() {
637        let (_sk, mut pk, digest, sig) = fresh_p256_signed();
638        pk.bytes[0] = 0x02;
639        assert!(!P256::verify(&pk, &digest, &sig));
640    }
641
642    #[test]
643    fn test_verify_rejects_off_curve_pubkey() {
644        let (_sk, mut pk, digest, sig) = fresh_p256_signed();
645        let last = pk.bytes.len() - 1;
646        pk.bytes[last] ^= 0x01;
647        assert!(!P256::verify(&pk, &digest, &sig), "off-curve pubkey must be rejected");
648    }
649
650    #[test]
651    fn test_verify_rejects_infinity_pubkey() {
652        let (_sk, _real_pk, digest, sig) = fresh_p256_signed();
653        let mut bytes = vec![0u8; 65];
654        bytes[0] = 0x04;
655        let bad_pk = PublicKey { bytes };
656        assert!(!P256::verify(&bad_pk, &digest, &sig));
657    }
658
659    #[test]
660    fn test_verify_rejects_off_curve_pubkey_brainpoolp384r1() {
661        let (sk, mut pk) = d1_keypair(&brainpoolp384r1_params());
662        let msg = b"bp384 hardening";
663        let sig = BrainpoolP384r1::sign_rfc6979_msg::<Sha384>(&sk, msg);
664        assert!(BrainpoolP384r1::verify_msg::<Sha384>(&pk, msg, &sig));
665        let last = pk.bytes.len() - 1;
666        pk.bytes[last] ^= 0x01;
667        assert!(!BrainpoolP384r1::verify_msg::<Sha384>(&pk, msg, &sig));
668    }
669
670    // ----------------------------------------------------------------------
671    // Non-canonical (curve, hash) pairings
672    // ----------------------------------------------------------------------
673
674    #[test]
675    fn test_p256_sha512_pairing_roundtrip() {
676        let (sk, pk) = d1_keypair(&p256_params());
677        let msg = b"P-256 paired with SHA-512";
678
679        let sig_det = P256::sign_rfc6979_msg::<Sha512>(&sk, msg);
680        assert!(P256::verify_msg::<Sha512>(&pk, msg, &sig_det));
681
682        let mut rng = TestRng::new(0xC001);
683        let sig_rand = P256::sign_random_msg::<Sha512>(&sk, msg, &mut rng);
684        assert!(P256::verify_msg::<Sha512>(&pk, msg, &sig_rand));
685
686        let sig_det2 = P256::sign_rfc6979_msg::<Sha512>(&sk, msg);
687        assert_eq!(sig_det.r, sig_det2.r);
688        assert_eq!(sig_det.s, sig_det2.s);
689
690        let sig_sha256 = P256::sign_rfc6979_msg::<Sha256>(&sk, msg);
691        assert_ne!(
692            sig_det.r, sig_sha256.r,
693            "P-256+SHA-512 and P-256+SHA-256 must differ on r"
694        );
695    }
696
697    #[test]
698    fn test_brainpoolp256r1_sha512_pairing_roundtrip() {
699        let (sk, pk) = d1_keypair(&brainpoolp256r1_params());
700        let msg = b"brainpoolP256r1 + SHA-512";
701        let sig = BrainpoolP256r1::sign_rfc6979_msg::<Sha512>(&sk, msg);
702        assert!(BrainpoolP256r1::verify_msg::<Sha512>(&pk, msg, &sig));
703    }
704
705    #[test]
706    fn test_secp256k1_sha512_pairing_roundtrip() {
707        let (sk, pk) = d1_keypair(&secp256k1_params());
708        let msg = b"secp256k1 + SHA-512";
709        let sig = Secp256k1::sign_rfc6979_msg::<Sha512>(&sk, msg);
710        assert!(Secp256k1::verify_msg::<Sha512>(&pk, msg, &sig));
711    }
712
713    // ----------------------------------------------------------------------
714    // P-521 (secp521r1) end-to-end
715    // ----------------------------------------------------------------------
716
717    #[test]
718    fn test_p521_sign_rfc6979_roundtrip() {
719        let (sk, pk) = d1_keypair(&secp521r1_params());
720        assert_eq!(sk.bytes.len(), 66);
721        assert_eq!(pk.bytes.len(), 1 + 2 * 66);
722
723        let msg = b"P-521 RFC 6979 baseline";
724        let sig = P521::sign_rfc6979_msg::<Sha512>(&sk, msg);
725        assert_eq!(sig.r.len(), 66);
726        assert_eq!(sig.s.len(), 66);
727        assert!(P521::verify_msg::<Sha512>(&pk, msg, &sig));
728    }
729
730    #[test]
731    fn test_p521_sign_random_roundtrip() {
732        let (sk, pk) = d1_keypair(&secp521r1_params());
733        let mut rng = TestRng::new(0x521);
734        let msg = b"P-521 random nonce";
735        let sig = P521::sign_random_msg::<Sha512>(&sk, msg, &mut rng);
736        assert!(P521::verify_msg::<Sha512>(&pk, msg, &sig));
737    }
738
739    #[test]
740    fn test_p521_rfc6979_is_deterministic() {
741        let (sk, _pk) = d1_keypair(&secp521r1_params());
742        let msg = b"determinism";
743        let sig1 = P521::sign_rfc6979_msg::<Sha512>(&sk, msg);
744        let sig2 = P521::sign_rfc6979_msg::<Sha512>(&sk, msg);
745        assert_eq!(sig1.r, sig2.r);
746        assert_eq!(sig1.s, sig2.s);
747    }
748
749    #[test]
750    fn test_p521_sha512_der_roundtrip() {
751        let (sk, pk) = d1_keypair(&secp521r1_params());
752        let msg = b"P-521 DER";
753        let sig = P521::sign_rfc6979_msg::<Sha512>(&sk, msg);
754        let der = sig.to_der();
755        let parsed = Signature::from_der(&der).expect("from_der");
756        assert!(P521::verify_msg::<Sha512>(&pk, msg, &parsed));
757    }
758
759    #[test]
760    fn test_p521_sec1_compressed_roundtrip() {
761        let (_sk, pk) = d1_keypair(&secp521r1_params());
762        assert_eq!(pk.bytes.len(), 133);
763
764        let compressed = P521::compress_pubkey(&pk).expect("compress");
765        assert_eq!(compressed.len(), 67);
766        assert!(compressed[0] == 0x02 || compressed[0] == 0x03);
767
768        let decompressed = P521::decompress_pubkey(&compressed).expect("decompress");
769        assert_eq!(decompressed.bytes.len(), 133);
770        assert_eq!(decompressed.bytes, pk.bytes);
771    }
772
773    #[test]
774    fn test_p521_ecdh_roundtrip() {
775        let mut rng = TestRng::new(0x5EC5);
776        let (pk_a, sk_a) = P521::keygen(&mut rng);
777        let (pk_b, sk_b) = P521::keygen(&mut rng);
778        assert_eq!(pk_a.bytes.len(), 133);
779        assert_eq!(sk_a.bytes.len(), 66);
780        let s_ab = P521::ecdh(&sk_a, &pk_b).expect("alice ecdh");
781        let s_ba = P521::ecdh(&sk_b, &pk_a).expect("bob ecdh");
782        assert_eq!(s_ab, s_ba);
783        assert_eq!(s_ab.len(), 66);
784    }
785
786    /// **SEC1 interop pin**: decompress the known SEC1 compressed
787    /// encoding of the P-521 generator G and verify it matches the
788    /// official (Gx, Gy) from FIPS 186-5 Appendix C.2.3 byte-for-byte.
789    #[test]
790    fn test_p521_sec1_pinned_interop() {
791        let compressed_g = hex_to_bytes(
792            "0200C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66",
793        );
794        assert_eq!(compressed_g.len(), 67);
795
796        let uncompressed_g = hex_to_bytes(
797            "04\
798             00C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66\
799             011839296A789A3BC0045C8A5FB42C7D1BD998F54449579B446817AFBD17273E662C97EE72995EF42640C550B9013FAD0761353C7086A272C24088BE94769FD16650",
800        );
801        assert_eq!(uncompressed_g.len(), 133);
802
803        let decompressed = P521::decompress_pubkey(&compressed_g).expect("decompress");
804        assert_eq!(decompressed.bytes, uncompressed_g);
805
806        let pk = PublicKey {
807            bytes: uncompressed_g.clone(),
808        };
809        let recompressed = P521::compress_pubkey(&pk).expect("compress");
810        assert_eq!(recompressed, compressed_g);
811
812        let dummy_digest = [0u8; 64];
813        let bogus_sig = Signature {
814            r: vec![0x01],
815            s: vec![0x01],
816        };
817        let _ = P521::verify(&pk, &dummy_digest, &bogus_sig);
818    }
819
820    #[test]
821    fn test_p521_verify_rejects_tampered() {
822        let (sk, pk) = d1_keypair(&secp521r1_params());
823        let msg = b"tamper";
824        let mut sig = P521::sign_rfc6979_msg::<Sha512>(&sk, msg);
825        sig.r[0] ^= 0x01;
826        assert!(!P521::verify_msg::<Sha512>(&pk, msg, &sig));
827    }
828
829    // ----------------------------------------------------------------------
830    // SEC1 compressed public keys
831    // ----------------------------------------------------------------------
832
833    #[test]
834    fn test_sec1_compressed_roundtrip_p256() {
835        let (_sk, pk) = d1_keypair(&p256_params());
836        assert_eq!(pk.bytes.len(), 65);
837        assert_eq!(pk.bytes[0], 0x04);
838
839        let compressed = P256::compress_pubkey(&pk).expect("compress");
840        assert_eq!(compressed.len(), 33);
841        assert!(compressed[0] == 0x02 || compressed[0] == 0x03);
842
843        let decompressed = P256::decompress_pubkey(&compressed).expect("decompress");
844        assert_eq!(decompressed.bytes, pk.bytes);
845    }
846
847    #[test]
848    fn test_sec1_compressed_roundtrip_all_curves() {
849        fn rt<C: Curve>() -> Option<()> {
850            let mut rng = TestRng::new(0x5EC1);
851            let (pk, _sk) = C::keygen(&mut rng);
852            let compressed = C::compress_pubkey(&pk)?;
853            let decompressed = C::decompress_pubkey(&compressed)?;
854            assert_eq!(decompressed.bytes, pk.bytes);
855            Some(())
856        }
857        assert!(rt::<P256>().is_some());
858        assert!(rt::<P384>().is_some());
859        assert!(rt::<Secp256k1>().is_some());
860        assert!(rt::<BrainpoolP256r1>().is_some());
861        assert!(rt::<BrainpoolP384r1>().is_some());
862        assert!(rt::<BrainpoolP512r1>().is_some());
863        assert!(rt::<P521>().is_some());
864    }
865
866    #[test]
867    fn test_verify_accepts_compressed_pubkey() {
868        let (sk, pk) = d1_keypair(&p256_params());
869        let msg = b"compressed pk end-to-end";
870        let sig = P256::sign_rfc6979_msg::<Sha256>(&sk, msg);
871
872        let compressed = P256::compress_pubkey(&pk).unwrap();
873        let compressed_pk = PublicKey { bytes: compressed };
874        assert!(P256::verify_msg::<Sha256>(&compressed_pk, msg, &sig));
875    }
876
877    #[test]
878    fn test_ecdh_accepts_compressed_pubkey() {
879        let mut rng = TestRng::new(0x5EC2);
880        let (pk_a, sk_a) = P256::keygen(&mut rng);
881        let (pk_b, sk_b) = P256::keygen(&mut rng);
882
883        let pk_a_c = PublicKey {
884            bytes: P256::compress_pubkey(&pk_a).unwrap(),
885        };
886        let pk_b_c = PublicKey {
887            bytes: P256::compress_pubkey(&pk_b).unwrap(),
888        };
889
890        let s_ab = P256::ecdh(&sk_a, &pk_b_c).unwrap();
891        let s_ba = P256::ecdh(&sk_b, &pk_a_c).unwrap();
892        assert_eq!(s_ab, s_ba);
893    }
894
895    #[test]
896    fn test_sec1_compressed_parity_bit() {
897        let mut rng = TestRng::new(0xBEEF);
898        for _ in 0..20 {
899            let (pk, _sk) = P256::keygen(&mut rng);
900            let compressed = P256::compress_pubkey(&pk).unwrap();
901            let y_lsb = pk.bytes[64] & 1;
902            let tag = compressed[0];
903            assert!(tag == 0x02 || tag == 0x03);
904            assert_eq!((tag & 1), y_lsb, "compressed tag parity must match Y LSB");
905        }
906    }
907
908    #[test]
909    fn test_decompress_rejects_wrong_length() {
910        assert!(P256::decompress_pubkey(&[0x02; 32]).is_none());
911        assert!(P256::decompress_pubkey(&[0x02; 34]).is_none());
912        assert!(P256::decompress_pubkey(&[]).is_none());
913    }
914
915    #[test]
916    fn test_decompress_rejects_unknown_tag() {
917        let mut bytes = vec![0u8; 33];
918        bytes[0] = 0x05;
919        assert!(P256::decompress_pubkey(&bytes).is_none());
920    }
921
922    // ----------------------------------------------------------------------
923    // DER roundtrip via Curve::sign_rfc6979 / verify
924    // ----------------------------------------------------------------------
925
926    /// DER round-trip on a freshly-signed signature: sig -> DER -> sig ->
927    /// verify. Value-level round-trip.
928    #[test]
929    fn test_der_sig_roundtrip_verifies() {
930        let (sk, pk) = d1_keypair(&p256_params());
931        let msg = b"DER round-trip";
932        let sig = P256::sign_rfc6979_msg::<Sha256>(&sk, msg);
933
934        let der = sig.to_der();
935        let parsed = Signature::from_der(&der).expect("from_der");
936        assert!(P256::verify_msg::<Sha256>(&pk, msg, &parsed));
937    }
938
939    /// DER bytes round-trip: from_der(to_der(sig)) gives the same bytes
940    /// when re-encoded. Idempotence on the DER side.
941    #[test]
942    fn test_der_idempotent_on_der_side() {
943        let (sk, _pk) = d1_keypair(&p256_params());
944        let msg = b"idempotent";
945        let sig = P256::sign_rfc6979_msg::<Sha256>(&sk, msg);
946
947        let der1 = sig.to_der();
948        let parsed = Signature::from_der(&der1).unwrap();
949        let der2 = parsed.to_der();
950        assert_eq!(der1, der2);
951    }
952}