Skip to main content

arcana/ecc/
ecdsa.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Cédric Mesnil <cslashm@pm.me>
3
4// Index-based loops in this module deliberately mirror the reference
5// algorithm (parallel-array access `a[i]`/`b[i]`, or a position-indexed
6// packing/reduction state) and keep the constant-time data layout explicit;
7// the `iter().enumerate()` rewrite would obscure it. Documented, single-lint
8// allow per CLAUDE.md §6 (scoped to this module, not a blanket suppression).
9#![allow(clippy::needless_range_loop)]
10
11//! ECDSA signing / verifying primitives (FIPS 186-5) with deterministic
12//! nonces (RFC 6979).
13//!
14//! This module hosts the LIMBS-generic internals that implement the
15//! per-curve ECDSA (and ECDH) operations. The user-facing API -- the
16//! [`Curve`](super::curves::Curve) trait and the per-curve unit structs
17//! ([`P256`](super::curves::P256), [`P384`](super::curves::P384), ...)
18//! lives in [`super::curves`].
19//!
20//! The [`Signature`] type + DER encoding also lives here because it is
21//! ECDSA-specific (ECDH has no signature value).
22
23use super::curve::*;
24use super::curves::{CryptoRng, PublicKey, SecretKey};
25use super::field::*;
26use crate::Hasher;
27
28// ============================================================================
29// Signature type + DER
30// ============================================================================
31
32/// ECDSA signature `(r, s)` as big-endian byte arrays.
33///
34/// Each component is `felem_bytes` octets long for the curve
35/// (32 / 48 / 64 / 66 depending on the curve).
36#[derive(Clone, Debug)]
37pub struct Signature {
38    /// `r` component of the signature, big-endian.
39    pub r: Vec<u8>,
40    /// `s` component of the signature, big-endian.
41    pub s: Vec<u8>,
42}
43
44impl Signature {
45    /// Encode as ASN.1 DER (RFC 5480 / X.509 standard form):
46    ///
47    /// ```asn1
48    /// ECDSA-Sig-Value ::= SEQUENCE {
49    ///     r  INTEGER,
50    ///     s  INTEGER
51    /// }
52    /// ```
53    ///
54    /// Uses the strict canonical DER encoding:
55    /// - Lengths use the shortest form (single byte < 128, `81 xx` up
56    ///   to 255, `82 hi lo` above).
57    /// - INTEGERs strip leading zero octets, and prepend one `00` octet
58    ///   if the high bit of the first octet would otherwise be set
59    ///   (which would make the number look negative in two's complement).
60    ///
61    /// This is the format used by X.509 certificates, TLS, S/MIME, CMS,
62    /// JWS `ES256-DER`, and virtually every OpenSSL-derived tool.
63    pub fn to_der(&self) -> Vec<u8> {
64        let r_der = encode_der_integer(&self.r);
65        let s_der = encode_der_integer(&self.s);
66        let payload_len = r_der.len() + s_der.len();
67
68        let mut out = Vec::with_capacity(4 + payload_len);
69        out.push(0x30); // SEQUENCE
70        encode_der_len(&mut out, payload_len);
71        out.extend_from_slice(&r_der);
72        out.extend_from_slice(&s_der);
73        out
74    }
75
76    /// Parse an ASN.1 DER encoding (strict). Returns `None` if the input
77    /// is not a valid canonical DER encoding of an ECDSA signature.
78    ///
79    /// Rejected inputs include (for defence against signature malleability
80    /// and parser-differential attacks):
81    /// - Any input whose length does not exactly match the advertised
82    ///   SEQUENCE length.
83    /// - Non-minimal length encodings (e.g. `81 10` where `10` would do,
84    ///   or `82 00 10` where `10` would do).
85    /// - INTEGERs with superfluous leading zero octets, or with the high
86    ///   bit set (which would be a negative number).
87    /// - r or s equal to zero.
88    ///
89    /// The returned `Signature` has `r` and `s` stripped of their DER
90    /// padding (so they may be shorter than `LIMBS * 8` bytes). This is
91    /// fine for `Curve::verify`, which interprets them via `bits2int` and
92    /// thus handles variable widths correctly.
93    pub fn from_der(der: &[u8]) -> Option<Self> {
94        let (seq_tag, rest) = (*der.first()?, &der[1..]);
95        if seq_tag != 0x30 {
96            return None;
97        }
98        let (payload_len, rest) = decode_der_len(rest)?;
99        if rest.len() != payload_len {
100            return None;
101        }
102
103        let (r, rest) = decode_der_integer(rest)?;
104        let (s, tail) = decode_der_integer(rest)?;
105        if !tail.is_empty() {
106            return None;
107        }
108
109        // Reject r == 0 or s == 0 (the signing algorithm never produces
110        // them either).
111        if r.iter().all(|&b| b == 0) || s.iter().all(|&b| b == 0) {
112            return None;
113        }
114
115        Some(Signature { r, s })
116    }
117}
118
119// ============================================================================
120// Tiny ASN.1 DER helpers (SEQUENCE, INTEGER) -- strict canonical encoding
121// ============================================================================
122//
123// We implement only the two primitives ECDSA needs, deliberately. No
124// generic ASN.1 parser: the attack surface on full ASN.1 is notorious.
125
126/// Encode a DER length into `out` using the shortest valid form.
127fn encode_der_len(out: &mut Vec<u8>, len: usize) {
128    if len < 0x80 {
129        out.push(len as u8);
130    } else if len < 0x100 {
131        out.push(0x81);
132        out.push(len as u8);
133    } else if len < 0x10000 {
134        out.push(0x82);
135        out.push((len >> 8) as u8);
136        out.push((len & 0xff) as u8);
137    } else {
138        // No ECDSA signature on the curves we support comes anywhere
139        // close to 64 kiB; if we ever see one, the caller has a much
140        // bigger problem.
141        panic!("DER length > 65535 is not supported by this encoder");
142    }
143}
144
145/// Decode a DER length. Returns `Some((len, remaining_slice))` on
146/// success, or `None` if the encoding is malformed or not in
147/// strict-canonical form.
148fn decode_der_len(bytes: &[u8]) -> Option<(usize, &[u8])> {
149    let first = *bytes.first()?;
150    if first < 0x80 {
151        Some((first as usize, &bytes[1..]))
152    } else if first == 0x81 {
153        let b = *bytes.get(1)?;
154        if b < 0x80 {
155            // Must have used the single-byte form.
156            return None;
157        }
158        Some((b as usize, &bytes[2..]))
159    } else if first == 0x82 {
160        let b1 = *bytes.get(1)?;
161        let b2 = *bytes.get(2)?;
162        let len = ((b1 as usize) << 8) | (b2 as usize);
163        if len < 0x100 {
164            // Must have used the `81 xx` form.
165            return None;
166        }
167        Some((len, &bytes[3..]))
168    } else {
169        // Longer length forms are legal ASN.1 but no ECDSA signature
170        // we produce needs them.
171        None
172    }
173}
174
175/// Encode a big-endian non-negative integer as a DER INTEGER (tag 0x02).
176/// Strips leading zero octets, and prepends one 0x00 if the first octet
177/// of the stripped representation has its high bit set.
178fn encode_der_integer(be: &[u8]) -> Vec<u8> {
179    // Strip leading zero octets.
180    let mut i = 0;
181    while i < be.len() && be[i] == 0 {
182        i += 1;
183    }
184    let stripped: &[u8] = if i == be.len() { &[0u8] } else { &be[i..] };
185
186    // If the high bit of the first octet is set, prepend 0x00 so that
187    // the integer is interpreted as positive.
188    let needs_pad = !stripped.is_empty() && (stripped[0] & 0x80) != 0;
189    let body_len = stripped.len() + if needs_pad { 1 } else { 0 };
190
191    let mut out = Vec::with_capacity(2 + body_len);
192    out.push(0x02); // INTEGER tag
193    encode_der_len(&mut out, body_len);
194    if needs_pad {
195        out.push(0x00);
196    }
197    out.extend_from_slice(stripped);
198    out
199}
200
201/// Decode a DER INTEGER. Returns `Some((big_endian_bytes, rest))` on
202/// success. Rejects non-canonical encodings:
203/// - superfluous leading zero octets
204/// - negative numbers (high bit of first octet set)
205/// - empty contents
206fn decode_der_integer(bytes: &[u8]) -> Option<(Vec<u8>, &[u8])> {
207    let tag = *bytes.first()?;
208    if tag != 0x02 {
209        return None;
210    }
211    let (len, rest) = decode_der_len(&bytes[1..])?;
212    if len == 0 || rest.len() < len {
213        return None;
214    }
215    let (content, tail) = rest.split_at(len);
216
217    // Strict checks.
218    if content[0] & 0x80 != 0 {
219        // High bit set on a positive integer.
220        return None;
221    }
222    if content.len() > 1 && content[0] == 0x00 && (content[1] & 0x80) == 0 {
223        // Non-minimal: leading 0x00 was not needed.
224        return None;
225    }
226
227    // Strip the leading 0x00 padding byte if present (it was there only
228    // to disambiguate the sign). The result is the canonical big-endian
229    // representation.
230    let out = if content[0] == 0x00 && content.len() > 1 {
231        content[1..].to_vec()
232    } else {
233        content.to_vec()
234    };
235
236    Some((out, tail))
237}
238
239// ============================================================================
240// HMAC (used by RFC 6979)
241// ============================================================================
242
243/// HMAC using a Hasher H. HMAC(K, text) = H((K ^ opad) || H((K ^ ipad) || text))
244fn hmac<H: Hasher>(key: &[u8], data: &[u8]) -> Vec<u8> {
245    let block_len = H::BLOCK_LEN;
246
247    // If key > block_len, hash it.
248    let k = if key.len() > block_len {
249        H::hash(key)
250    } else {
251        key.to_vec()
252    };
253
254    // Pad key to block_len.
255    let mut k_padded = vec![0u8; block_len];
256    k_padded[..k.len()].copy_from_slice(&k);
257
258    // ipad = k_padded XOR 0x36
259    let mut ipad = vec![0u8; block_len];
260    for i in 0..block_len {
261        ipad[i] = k_padded[i] ^ 0x36;
262    }
263
264    // opad = k_padded XOR 0x5C
265    let mut opad = vec![0u8; block_len];
266    for i in 0..block_len {
267        opad[i] = k_padded[i] ^ 0x5c;
268    }
269
270    // inner = H(ipad || data)
271    let mut inner_hasher = H::new();
272    inner_hasher.update(&ipad);
273    inner_hasher.update(data);
274    let inner = inner_hasher.finalize();
275
276    // outer = H(opad || inner)
277    let mut outer_hasher = H::new();
278    outer_hasher.update(&opad);
279    outer_hasher.update(&inner);
280    outer_hasher.finalize()
281}
282
283/// HMAC with multiple data segments (concatenated).
284fn hmac_multi<H: Hasher>(key: &[u8], parts: &[&[u8]]) -> Vec<u8> {
285    let block_len = H::BLOCK_LEN;
286
287    let k = if key.len() > block_len {
288        H::hash(key)
289    } else {
290        key.to_vec()
291    };
292
293    let mut k_padded = vec![0u8; block_len];
294    k_padded[..k.len()].copy_from_slice(&k);
295
296    let mut ipad = vec![0u8; block_len];
297    let mut opad = vec![0u8; block_len];
298    for i in 0..block_len {
299        ipad[i] = k_padded[i] ^ 0x36;
300        opad[i] = k_padded[i] ^ 0x5c;
301    }
302
303    let mut inner_hasher = H::new();
304    inner_hasher.update(&ipad);
305    for part in parts {
306        inner_hasher.update(part);
307    }
308    let inner = inner_hasher.finalize();
309
310    let mut outer_hasher = H::new();
311    outer_hasher.update(&opad);
312    outer_hasher.update(&inner);
313    outer_hasher.finalize()
314}
315
316// ============================================================================
317// RFC 6979 §2.3.2 bits2int
318// ============================================================================
319
320/// Convert a digest (or any byte string) into a field element, per the
321/// `bits2int` operation of RFC 6979 §2.3.2.
322///
323/// Given an input of `blen = 8 * input.len()` bits and a curve order of
324/// `qlen` bits, `bits2int` returns the leftmost `min(blen, qlen)` bits of
325/// `input` interpreted as a big-endian non-negative integer.
326///
327/// Byte-level implementation:
328///
329/// 1. Compute `rlen_bytes = (qlen_bits + 7) / 8`, the ceil-byte length of
330///    the curve order.
331/// 2. Take `take = min(input.len(), rlen_bytes)` leftmost bytes of `input`.
332/// 3. Interpret those bytes as a big-endian integer.
333/// 4. If `take * 8 > qlen_bits`, right-shift by `take * 8 - qlen_bits`
334///    bits to drop the excess low bits (sub-byte shift).
335///
336/// For every curve we ship **except P-521**, `qlen_bits` is a multiple
337/// of 8 and step 4 is a no-op. For P-521 (`qlen_bits = 521`,
338/// `rlen_bytes = 66`), the shift is non-zero only when the input is
339/// exactly 66+ bytes long -- e.g. P-521 + SHA-512 (64 bytes) needs no
340/// shift, but a hypothetical P-521 + 528-bit hash would need a 7-bit
341/// right-shift.
342///
343/// **This is the operation that the original code was missing**: the
344/// first version did `from_bytes_be(input)` directly, which reads bytes
345/// starting at the LSB and therefore takes the **rightmost** bytes when
346/// `input.len() > LIMBS*8` -- the opposite of what RFC 6979 specifies.
347/// The bug never surfaced until the flexible (curve, hash) API exposed
348/// pairings where `hlen > qlen`.
349fn bits2int<const LIMBS: usize>(input: &[u8], qlen_bits: usize) -> FieldElement<LIMBS> {
350    let rlen_bytes = qlen_bits.div_ceil(8);
351    let take = input.len().min(rlen_bytes);
352
353    // Interpret the leftmost `take` bytes as a big-endian integer.
354    let mut fe = FieldElement::<LIMBS>::from_bytes_be(&input[..take]);
355
356    // Sub-byte right shift if the taken bytes cover more bits than qlen
357    // allows. For curves with qlen_bits % 8 == 0 this is always a no-op.
358    // For P-521 (qlen_bits = 521, rlen_bytes = 66) it fires only when
359    // the input is >= 66 bytes, shifting by 528 - 521 = 7.
360    let take_bits = take * 8;
361    if take_bits > qlen_bits {
362        shr_limbs(&mut fe, (take_bits - qlen_bits) as u32);
363    }
364    fe
365}
366
367/// In-place right shift of a little-endian-limb FieldElement by `n` bits,
368/// where `0 < n < 64`. Used by [`bits2int`] for the sub-byte truncation
369/// step on curves whose qlen is not a multiple of 8 (i.e. P-521).
370fn shr_limbs<const LIMBS: usize>(x: &mut FieldElement<LIMBS>, n: u32) {
371    debug_assert!(n > 0 && n < 64, "shr_limbs supports 1..=63");
372    let lo = n;
373    let hi = 64 - n;
374    for i in 0..LIMBS - 1 {
375        x.limbs[i] = (x.limbs[i] >> lo) | (x.limbs[i + 1] << hi);
376    }
377    x.limbs[LIMBS - 1] >>= lo;
378}
379
380// ============================================================================
381// RFC 6979 deterministic nonce generation
382// ============================================================================
383
384/// RFC 6979 §2.3.3 `int2octets(x)`: produce the canonical `rlen_bytes`
385/// big-endian encoding of the non-negative integer given by `x_bytes`
386/// (which may itself be any length).
387///
388/// If `x_bytes` is longer than `rlen_bytes`, the leading excess bytes
389/// are sliced off (in our use, those bytes are always zero since
390/// `x < n < 2^qlen`). If it is shorter, it is left-padded with zeros.
391fn int2octets(x_bytes: &[u8], rlen_bytes: usize) -> Vec<u8> {
392    use core::cmp::Ordering;
393    match x_bytes.len().cmp(&rlen_bytes) {
394        Ordering::Equal => x_bytes.to_vec(),
395        Ordering::Greater => x_bytes[x_bytes.len() - rlen_bytes..].to_vec(),
396        Ordering::Less => {
397            let mut out = vec![0u8; rlen_bytes];
398            out[rlen_bytes - x_bytes.len()..].copy_from_slice(x_bytes);
399            out
400        }
401    }
402}
403
404/// Generate deterministic nonce k per RFC 6979 using hash H.
405///
406/// `x_bytes` is the private key as big-endian bytes, `h1` is the message
407/// digest (already produced by `H`), `n` is the curve order, and
408/// `qlen_bits` is the bit length of `n`.
409///
410/// The byte lengths used internally are driven by
411/// `rlen_bytes = (qlen_bits + 7) / 8`, NOT `LIMBS * 8`. For P-521
412/// (qlen_bits = 521, LIMBS = 9) these values disagree: rlen_bytes = 66,
413/// LIMBS*8 = 72. Using the wrong one breaks the HMAC chain and produces
414/// a different nonce than the RFC 6979 reference.
415fn rfc6979_k<H: Hasher, const LIMBS: usize>(
416    x_bytes: &[u8],
417    h1: &[u8],
418    n: &[u64; LIMBS],
419    qlen_bits: usize,
420) -> FieldElement<LIMBS> {
421    let hlen = H::OUTPUT_LEN;
422    let rlen_bytes = qlen_bits.div_ceil(8);
423
424    // int2octets(x): canonical rlen_bytes big-endian encoding of the
425    // secret scalar. For P-521 this strips 6 leading zero bytes from the
426    // 72-byte internal storage; for every other curve it is a no-op.
427    let x_octets = int2octets(x_bytes, rlen_bytes);
428
429    // bits2octets(h1) = int2octets(bits2int(h1) mod q)
430    // We use the full bits2int (with sub-byte shift support) so this is
431    // correct even for non-byte-aligned qlen.
432    let z1 = bits2int::<LIMBS>(h1, qlen_bits);
433    let z1_reduced = reduce_mod_n(&z1, n);
434    let z1_octets = int2octets(&z1_reduced.to_bytes_be(), rlen_bytes);
435
436    // Step a: h1 is already computed (passed in).
437    // Step b: V = 0x01 0x01 ... 0x01 (hlen bytes).
438    let mut v = vec![0x01u8; hlen];
439    // Step c: K = 0x00 0x00 ... 0x00 (hlen bytes).
440    let mut k = vec![0x00u8; hlen];
441
442    // Step d: K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1))
443    k = hmac_multi::<H>(&k, &[&v, &[0x00], &x_octets, &z1_octets]);
444
445    // Step e: V = HMAC_K(V)
446    v = hmac::<H>(&k, &v);
447
448    // Step f: K = HMAC_K(V || 0x01 || int2octets(x) || bits2octets(h1))
449    k = hmac_multi::<H>(&k, &[&v, &[0x01], &x_octets, &z1_octets]);
450
451    // Step g: V = HMAC_K(V)
452    v = hmac::<H>(&k, &v);
453
454    // Step h: generate k.
455    loop {
456        // h.1: T = empty
457        let mut t = Vec::new();
458
459        // h.2: while tlen < rlen_bytes, T = T || HMAC_K(V), V = HMAC_K(V)
460        while t.len() < rlen_bytes {
461            v = hmac::<H>(&k, &v);
462            t.extend_from_slice(&v);
463        }
464
465        // h.3: k = bits2int(T). This applies the sub-byte shift for
466        // curves with qlen_bits not a multiple of 8 (i.e. P-521).
467        let candidate = bits2int::<LIMBS>(&t[..rlen_bytes], qlen_bits);
468        // Check 1 <= k < n.
469        if !candidate.is_zero() && scalar_is_valid(&candidate, n) {
470            return candidate;
471        }
472
473        // If not valid, update K and V and try again.
474        k = hmac_multi::<H>(&k, &[&v, &[0x00]]);
475        v = hmac::<H>(&k, &v);
476    }
477}
478
479/// Reduce a value mod n by conditional subtraction.
480fn reduce_mod_n<const LIMBS: usize>(a: &FieldElement<LIMBS>, n: &[u64; LIMBS]) -> FieldElement<LIMBS> {
481    let mut result = *a;
482    // Up to two subtractions (hash might be up to 2n).
483    for _ in 0..2 {
484        let mut borrow: u64 = 0;
485        let mut sub = [0u64; LIMBS];
486        for i in 0..LIMBS {
487            let diff = (result.limbs[i] as u128)
488                .wrapping_sub(n[i] as u128)
489                .wrapping_sub(borrow as u128);
490            sub[i] = diff as u64;
491            borrow = ((diff >> 64) as u64) & 1;
492        }
493        let mask = 0u64.wrapping_sub(1 - borrow); // all-ones if result >= n
494        let inv_mask = !mask;
495        for i in 0..LIMBS {
496            result.limbs[i] = (sub[i] & mask) | (result.limbs[i] & inv_mask);
497        }
498    }
499    result
500}
501
502// ============================================================================
503// Generic ECDSA implementation
504// ============================================================================
505//
506// keygen / sign / verify are written once, generic over both the curve
507// (`CurveParams<LIMBS>`) and the hash function (`H: Hasher`). The per-curve
508// wrappers below (P256, Secp256k1, ...) are thin glue that pin the
509// LIMBS const, the curve params constructor, and the canonical hash.
510
511/// Parse an SEC1 public key -- **either** uncompressed or compressed --
512/// and validate that the encoded point lies on the curve.
513///
514/// Accepted encodings (where `F = params.felem_bytes` is the **SEC1
515/// field element octet length**, not the internal storage width):
516///
517/// | Tag  | Length    | Form                           |
518/// |------|-----------|--------------------------------|
519/// | 0x04 | 1 + 2*F   | Uncompressed: `04 \|\| X \|\| Y` |
520/// | 0x02 | 1 + F     | Compressed, y is even          |
521/// | 0x03 | 1 + F     | Compressed, y is odd           |
522///
523/// For every curve we ship except P-521, `F == LIMBS*8`. For P-521
524/// `F = 66` while `LIMBS*8 = 72`, so the parser accepts 133-byte
525/// uncompressed / 67-byte compressed inputs as per SEC1.
526///
527/// Returns `Some(point)` only if the length, the tag, and the on-curve
528/// check all pass. For compressed input, the Y coordinate is recovered
529/// via `field_sqrt_p3mod4(x^3 + a*x + b)`: if that value squared does
530/// not equal the RHS of the curve equation, X is not a valid
531/// x-coordinate and the on-curve safety-net rejects the point.
532///
533/// **All callers that consume an externally-provided public key must
534/// use this function.** Skipping the on-curve check enables invalid-curve
535/// attacks. The check is mandatory in ECDH and is the recommended
536/// hardening for ECDSA verify (where it defends against attacker-
537/// controlled keys fed to a verifier).
538///
539/// Shared between [`verify_internal`], [`ecdh_internal`], and the
540/// `Curve::decompress_pubkey` trait method so that there is exactly
541/// one entry point for external public keys.
542pub(super) fn parse_and_validate_pubkey<const LIMBS: usize>(
543    params: &CurveParams<LIMBS>,
544    pk: &PublicKey,
545) -> Option<JacobianPoint<LIMBS>> {
546    let felem = params.felem_bytes;
547    let bytes = pk.bytes.as_slice();
548
549    let (qx, qy) = match bytes.first().copied()? {
550        // Uncompressed: 04 || X || Y
551        0x04 => {
552            if bytes.len() != 1 + 2 * felem {
553                return None;
554            }
555            // `from_bytes_be` transparently left-pads short inputs into
556            // the internal LIMBS*8 storage, so passing the `felem`-wide
557            // slice directly is correct for P-521 (66 -> 72 with 6
558            // leading zero bytes).
559            let qx = FieldElement::<LIMBS>::from_bytes_be(&bytes[1..1 + felem]);
560            let qy = FieldElement::<LIMBS>::from_bytes_be(&bytes[1 + felem..1 + 2 * felem]);
561            (qx, qy)
562        }
563
564        // Compressed: 02 || X (y even) or 03 || X (y odd)
565        tag @ (0x02 | 0x03) => {
566            if bytes.len() != 1 + felem {
567                return None;
568            }
569            let qx = FieldElement::<LIMBS>::from_bytes_be(&bytes[1..1 + felem]);
570
571            // Compute RHS = x^3 + a*x + b  mod p
572            let p = &params.p;
573            let x2 = field_sqr(&qx, p);
574            let x3 = field_mul(&x2, &qx, p);
575            let ax = field_mul(&params.a, &qx, p);
576            let rhs = field_add(&field_add(&x3, &ax, p), &params.b, p);
577
578            // Candidate y = sqrt(RHS) under the assumption p ≡ 3 (mod 4).
579            // This is correct for all six curves we currently support.
580            let mut qy = field_sqrt_p3mod4(&rhs, p);
581
582            // If the computed parity doesn't match the requested parity,
583            // use -y = p - y instead. Not constant-time (parity of a public
584            // point is public information).
585            let want_odd = (tag & 1) == 1;
586            let have_odd = (qy.limbs[0] & 1) == 1;
587            if have_odd != want_odd {
588                qy = field_neg(&qy, p);
589            }
590
591            (qx, qy)
592        }
593
594        _ => return None,
595    };
596
597    // On-curve check: for uncompressed input this is the main defence
598    // against attacker-supplied off-curve keys; for compressed input it
599    // also doubles as the non-residue detector (a bogus `y` produced by
600    // field_sqrt_p3mod4 on a non-QR X will fail here).
601    if !is_on_curve(&qx, &qy, params) {
602        return None;
603    }
604
605    Some(JacobianPoint::from_affine(qx, qy))
606}
607
608/// Serialize a field element as `felem_bytes` big-endian octets.
609///
610/// Converts from the internal `LIMBS*8`-wide representation produced by
611/// `FieldElement::to_bytes_be()` to the SEC1 §2.3.5 external width by
612/// stripping the leading zero bytes. Returns a fresh `Vec<u8>` of
613/// exactly `felem_bytes` length.
614///
615/// For curves with `felem_bytes == LIMBS*8` (i.e. every curve we ship
616/// except P-521) this is a straight copy of `to_bytes_be()`. For P-521
617/// it drops the 6 leading zero bytes of the 72-byte internal encoding
618/// to produce the standard 66-byte SEC1 field element.
619///
620/// **Invariant**: the skipped bytes are always zero when the field
621/// element value is less than `p`, which is enforced everywhere that
622/// builds a FieldElement (field_add, field_mul, etc.) -- this function
623/// relies on that invariant rather than checking it at runtime.
624pub(super) fn fe_to_felem_bytes<const LIMBS: usize>(fe: &FieldElement<LIMBS>, felem_bytes: usize) -> Vec<u8> {
625    let full = fe.to_bytes_be();
626    debug_assert!(felem_bytes <= full.len());
627    full[full.len() - felem_bytes..].to_vec()
628}
629
630/// SEC1 compress: encode a public key as
631/// `0x02 || X`  (y even)  or  `0x03 || X`  (y odd).
632///
633/// Accepts either:
634/// - SEC1 **uncompressed** input (`0x04 || X || Y`): slices out X and the
635///   last byte of Y directly, after validating that the point is on the
636///   curve (defence in depth: we don't emit a compressed encoding for a
637///   pk we wouldn't accept as input).
638/// - SEC1 **compressed** input (`0x02/0x03 || X`): re-validates and
639///   returns a clone (idempotent).
640///
641/// Returns `None` if the input is malformed or off-curve.
642pub fn compress_pubkey<const LIMBS: usize>(params: &CurveParams<LIMBS>, pk: &PublicKey) -> Option<Vec<u8>> {
643    let felem = params.felem_bytes;
644    let bytes = pk.bytes.as_slice();
645    let tag = bytes.first().copied()?;
646
647    match tag {
648        0x04 => {
649            if bytes.len() != 1 + 2 * felem {
650                return None;
651            }
652            // Validate on-curve before emitting a compressed encoding.
653            parse_and_validate_pubkey::<LIMBS>(params, pk)?;
654            // Y's last byte is at index `2*felem` in the uncompressed
655            // encoding (1 byte tag + felem bytes of X + felem bytes of Y,
656            // so Y spans indices [1+felem, 1+2*felem)).
657            let y_last = 2 * felem;
658            let tag_out = 0x02 | (bytes[y_last] & 1);
659            let mut out = Vec::with_capacity(1 + felem);
660            out.push(tag_out);
661            out.extend_from_slice(&bytes[1..1 + felem]);
662            Some(out)
663        }
664        0x02 | 0x03 => {
665            if bytes.len() != 1 + felem {
666                return None;
667            }
668            // Already compressed -- validate and return a clone.
669            parse_and_validate_pubkey::<LIMBS>(params, pk)?;
670            Some(bytes.to_vec())
671        }
672        _ => None,
673    }
674}
675
676/// SEC1 decompress: take a `0x02/0x03 || X` encoding and return the
677/// `0x04 || X || Y` uncompressed form, recovering Y via
678/// `field_sqrt_p3mod4`.
679///
680/// Returns `None` if the input is malformed, if X is not a valid
681/// x-coordinate on the curve (i.e. `RHS(X)` is a non-residue), or if
682/// the decompressed point fails the on-curve safety check.
683///
684/// Also accepts an *already uncompressed* input as a no-op, so callers
685/// can use `decompress_pubkey(bytes)` as a "normalise to uncompressed"
686/// entry point regardless of which form they start from.
687pub fn decompress_pubkey<const LIMBS: usize>(params: &CurveParams<LIMBS>, compressed: &[u8]) -> Option<PublicKey> {
688    let felem = params.felem_bytes;
689    let pk = PublicKey {
690        bytes: compressed.to_vec(),
691    };
692    let point = parse_and_validate_pubkey::<LIMBS>(params, &pk)?;
693    // `from_affine` gives us Z=1, so `to_affine` is effectively the
694    // identity (plus a wasted modular inverse that is negligible here
695    // and keeps the code free of special cases).
696    let (qx, qy) = point.to_affine(&params.p)?;
697    let mut out = Vec::with_capacity(1 + 2 * felem);
698    out.push(0x04);
699    out.extend_from_slice(&fe_to_felem_bytes(&qx, felem));
700    out.extend_from_slice(&fe_to_felem_bytes(&qy, felem));
701    Some(PublicKey { bytes: out })
702}
703
704/// ECDH shared-secret derivation, generic over the curve.
705///
706/// Computes `sk * peer_pk` and returns the X coordinate of the resulting
707/// affine point as **`felem_bytes`** big-endian bytes (= 32 / 48 / 64
708/// for the byte-aligned curves, 66 for P-521 per SEC1 §2.3.5). Returns
709/// `None` if any of the validation steps fails:
710///
711/// 1. SEC1 parsing + on-curve validation of `peer_pk` (delegated to
712///    [`parse_and_validate_pubkey`])
713/// 2. Our secret scalar lies in `[1, n-1]`
714/// 3. The resulting shared point is not the point at infinity (small
715///    subgroup defence in depth)
716///
717/// Sits next to the ECDSA helpers because all the LIMBS-generic curve
718/// operations live here, and is wired into the same per-curve dispatch
719/// macro so the public API is uniform: `P256::ecdh(...)`,
720/// `P256::sign_rfc6979(...)`, etc. all dispatch through the same trait.
721pub(super) fn ecdh_internal<const LIMBS: usize>(
722    params: &CurveParams<LIMBS>,
723    sk: &SecretKey,
724    peer_pk: &PublicKey,
725) -> Option<Vec<u8>> {
726    // 1. Validate the peer's public key.
727    let peer_point = parse_and_validate_pubkey::<LIMBS>(params, peer_pk)?;
728
729    // 2. Decode our secret scalar and re-check that it is in [1, n-1].
730    let d = FieldElement::<LIMBS>::from_bytes_be(&sk.bytes);
731    if d.is_zero() || !scalar_is_valid(&d, &params.n) {
732        return None;
733    }
734
735    // 3. Compute the shared point d * peer_pk via the constant-time
736    //    Montgomery ladder.
737    let shared = scalar_mul_point(&d, &peer_point, params);
738
739    // 4. Reject the point at infinity.
740    let (sx, _sy) = shared.to_affine(&params.p)?;
741
742    // 5. Output the X coordinate as SEC1 felem_bytes BE octets.
743    Some(fe_to_felem_bytes(&sx, params.felem_bytes))
744}
745
746/// Fill `buf` with fresh random bytes and clear any bits above
747/// `qlen_bits` in the high (big-endian first) byte. Used by the
748/// rejection-sampling loops below to avoid a near-infinite rejection
749/// rate on curves like P-521 where `qlen_bits` is significantly less
750/// than `buf.len() * 8`.
751///
752/// For byte-aligned curves (`qlen_bits % 8 == 0`) this is just a plain
753/// `fill_bytes` with no masking.
754fn fill_bytes_masked(buf: &mut [u8], qlen_bits: usize, rng: &mut dyn CryptoRng) {
755    rng.fill_bytes(buf);
756    let excess = buf.len() * 8 - qlen_bits;
757    if excess > 0 && !buf.is_empty() {
758        // Keep only the low (8 - excess) bits of the top byte.
759        buf[0] &= (1u8 << (8 - excess)) - 1;
760    }
761}
762
763/// ECDSA key generation, generic over the curve. Same operation as ECDH
764/// keygen (a curve key pair is curve-key-pair, regardless of how it will be
765/// used downstream), so this is exposed to sibling modules under `ecc::`.
766///
767/// Uses `rlen_bytes = (qlen_bits + 7) / 8` as the sampling width with
768/// the high byte masked to the curve's qlen, so the rejection rate of
769/// the `< n` check stays ~50 % instead of collapsing to ~2^-55 on
770/// curves where `qlen_bits` is significantly below `LIMBS*8*8`
771/// (i.e. P-521: qlen=521, LIMBS*8*8=576).
772///
773/// The returned `SecretKey.bytes` has the **SEC1 felem_bytes** width
774/// (i.e. rlen_bytes for every curve we ship, because rlen_bytes ==
775/// felem_bytes for all of them). The returned `PublicKey.bytes` is
776/// the SEC1 uncompressed encoding `04 || X || Y` with `felem_bytes`
777/// per coordinate.
778pub(super) fn keygen_internal<const LIMBS: usize>(
779    params: &CurveParams<LIMBS>,
780    rng: &mut dyn CryptoRng,
781) -> (PublicKey, SecretKey) {
782    let g = JacobianPoint::from_affine(params.gx, params.gy);
783    let felem = params.felem_bytes;
784    let rlen_bytes = params.qlen_bits.div_ceil(8);
785
786    loop {
787        // Draw `rlen_bytes` masked to qlen_bits (see fill_bytes_masked
788        // for why this matters on curves where rlen_bytes < LIMBS*8*8).
789        let mut sk_bytes = vec![0u8; rlen_bytes];
790        fill_bytes_masked(&mut sk_bytes, params.qlen_bits, rng);
791
792        // For every curve we ship, `rlen_bytes == felem_bytes` -- the
793        // secret key's external width equals the sampling width. (If a
794        // future curve broke that invariant, we would need to either
795        // pad or truncate `sk_bytes` to felem_bytes here.)
796        debug_assert_eq!(rlen_bytes, felem);
797
798        // Decode into a FieldElement<LIMBS> for the scalar check and
799        // the scalar-mul. `from_bytes_be` left-pads automatically.
800        let d = FieldElement::<LIMBS>::from_bytes_be(&sk_bytes);
801
802        // Ensure 1 <= d < n.
803        if d.is_zero() || !scalar_is_valid(&d, &params.n) {
804            continue;
805        }
806
807        let q = scalar_mul_point(&d, &g, params);
808        let (qx, qy) = q.to_affine(&params.p).unwrap();
809
810        // SEC1 uncompressed: 04 || X (felem_bytes) || Y (felem_bytes).
811        let mut pk_bytes = Vec::with_capacity(1 + 2 * felem);
812        pk_bytes.push(0x04);
813        pk_bytes.extend_from_slice(&fe_to_felem_bytes(&qx, felem));
814        pk_bytes.extend_from_slice(&fe_to_felem_bytes(&qy, felem));
815
816        return (PublicKey { bytes: pk_bytes }, SecretKey { bytes: sk_bytes });
817    }
818}
819
820/// Compute the (r, s) signature pair from a chosen nonce `k`. Returns `None`
821/// if this particular `k` would yield `r == 0` or `s == 0` (probability
822/// ~2^-256, but the spec mandates rejecting the value and trying another).
823///
824/// `r` and `s` are emitted at SEC1 `felem_bytes` width (32 / 48 / 64
825/// for the byte-aligned curves, 66 for P-521). This matches what
826/// OpenSSL and every other standards-compliant library produces on
827/// the wire, and it is what the DER encoder expects on input.
828///
829/// Shared between the random-nonce and RFC 6979 sign paths.
830fn try_sign_with_k<const LIMBS: usize>(
831    params: &CurveParams<LIMBS>,
832    g: &JacobianPoint<LIMBS>,
833    d: &FieldElement<LIMBS>,
834    e: &FieldElement<LIMBS>,
835    k: &FieldElement<LIMBS>,
836) -> Option<Signature> {
837    let n = &params.n;
838
839    // (x1, _) = k*G
840    let kg = scalar_mul_point(k, g, params);
841    let (x1, _y1) = kg.to_affine(&params.p)?;
842
843    // r = x1 mod n; reject if zero.
844    let r = reduce_mod_n(&x1, n);
845    if r.is_zero() {
846        return None;
847    }
848
849    // s = k^{-1} * (e + r*d) mod n; reject if zero.
850    let k_inv = scalar_inv(k, n);
851    let rd = scalar_mul(&r, d, n);
852    let e_plus_rd = scalar_add(e, &rd, n);
853    let s = scalar_mul(&k_inv, &e_plus_rd, n);
854    if s.is_zero() {
855        return None;
856    }
857
858    Some(Signature {
859        r: fe_to_felem_bytes(&r, params.felem_bytes),
860        s: fe_to_felem_bytes(&s, params.felem_bytes),
861    })
862}
863
864/// Sample a uniformly random scalar `k` in `[1, n-1]` from `rng`, by
865/// rejection sampling on `rlen_bytes = (qlen_bits + 7) / 8` bytes with
866/// the top byte masked to the curve's qlen.
867///
868/// Sampling directly into a `LIMBS*8`-wide buffer (the field element
869/// storage width) would be catastrophic on curves where `qlen_bits`
870/// is significantly below `LIMBS*8*8` -- e.g. P-521 has qlen=521 and
871/// LIMBS*8*8=576, so a naive sample has rejection rate 2^-55 and
872/// effectively loops forever.
873fn sample_random_scalar<const LIMBS: usize>(
874    n: &[u64; LIMBS],
875    qlen_bits: usize,
876    rng: &mut dyn CryptoRng,
877) -> FieldElement<LIMBS> {
878    let rlen_bytes = qlen_bits.div_ceil(8);
879    let mut buf = vec![0u8; rlen_bytes];
880    loop {
881        fill_bytes_masked(&mut buf, qlen_bits, rng);
882        let candidate = FieldElement::<LIMBS>::from_bytes_be(&buf);
883        if !candidate.is_zero() && scalar_is_valid(&candidate, n) {
884            return candidate;
885        }
886    }
887}
888
889/// ECDSA sign with **random** nonce (classical ECDSA / FIPS 186-5).
890///
891/// Takes a precomputed `digest`. The hash function used to produce the
892/// digest is irrelevant to the random-nonce path; only its byte length
893/// matters (it is interpreted via `bits2int`). The verifier consumes the
894/// same digest bytes.
895///
896/// Each call to `rng` must produce fresh, unpredictable bytes. Reusing `k`
897/// across two signatures with the same key recovers the secret key.
898pub(super) fn sign_random_internal<const LIMBS: usize>(
899    params: &CurveParams<LIMBS>,
900    sk: &SecretKey,
901    digest: &[u8],
902    rng: &mut dyn CryptoRng,
903) -> Signature {
904    let g = JacobianPoint::from_affine(params.gx, params.gy);
905
906    let e = bits2int::<LIMBS>(digest, params.qlen_bits);
907    let e = reduce_mod_n(&e, &params.n);
908
909    let d = FieldElement::<LIMBS>::from_bytes_be(&sk.bytes);
910
911    loop {
912        let k = sample_random_scalar::<LIMBS>(&params.n, params.qlen_bits, rng);
913        if let Some(sig) = try_sign_with_k::<LIMBS>(params, &g, &d, &e, &k) {
914            return sig;
915        }
916        // Otherwise: r or s was zero, draw a fresh k and retry.
917    }
918}
919
920/// ECDSA sign with **deterministic** nonce per RFC 6979.
921///
922/// Takes a precomputed `digest`. The generic `H` is the hash function used
923/// to produce the digest -- it is required here because RFC 6979 derives
924/// the nonce via HMAC-`H` internally and the choice of `H` materially
925/// changes the nonce. **The caller MUST pass the same `H` that produced
926/// the digest**, otherwise determinism is broken (and verifying the same
927/// `(sk, msg)` from a different binding will yield a different signature).
928pub(super) fn sign_rfc6979_internal<H: Hasher, const LIMBS: usize>(
929    params: &CurveParams<LIMBS>,
930    sk: &SecretKey,
931    digest: &[u8],
932) -> Signature {
933    let g = JacobianPoint::from_affine(params.gx, params.gy);
934
935    let e = bits2int::<LIMBS>(digest, params.qlen_bits);
936    let e = reduce_mod_n(&e, &params.n);
937
938    let d = FieldElement::<LIMBS>::from_bytes_be(&sk.bytes);
939
940    // RFC 6979 §3.2 produces a valid k on the first call. The outer loop
941    // exists for the (~2^-256) edge case where r or s comes out zero -- the
942    // current `rfc6979_k` doesn't expose a "retry counter", so this loop
943    // would spin forever in that pathological case. In practice it never
944    // triggers, and a future change can thread a counter into rfc6979_k.
945    loop {
946        let k = rfc6979_k::<H, LIMBS>(&sk.bytes, digest, &params.n, params.qlen_bits);
947        if let Some(sig) = try_sign_with_k::<LIMBS>(params, &g, &d, &e, &k) {
948            return sig;
949        }
950    }
951}
952
953/// ECDSA verify, generic over the curve. Takes a precomputed `digest`.
954///
955/// The hash function used to produce the digest is irrelevant to the
956/// verifier (the verifier only knows the digest as bytes, interprets them
957/// via `bits2int`, and checks the algebraic relation). The caller is
958/// responsible for using the same digest length the signer used.
959///
960/// Public-key validation:
961///
962/// - SEC1 uncompressed length and format byte
963/// - Decoded `(qx, qy)` lies on the curve (defends against attacker-supplied
964///   off-curve keys, see [`parse_and_validate_pubkey`])
965///
966/// All curves we ship are prime-order (cofactor 1), so once `Q` is on the
967/// curve and not the encoded point at infinity (which the `0x04` tag rules
968/// out), it automatically has full order `n` -- no extra subgroup test is
969/// required.
970pub(super) fn verify_internal<const LIMBS: usize>(
971    params: &CurveParams<LIMBS>,
972    pk: &PublicKey,
973    digest: &[u8],
974    sig: &Signature,
975) -> bool {
976    let n = &params.n;
977
978    // Parse + on-curve validate the public key. Same entry point used by
979    // ECDH derive, so the validation rules cannot drift between the two.
980    let q = match parse_and_validate_pubkey::<LIMBS>(params, pk) {
981        Some(q) => q,
982        None => return false,
983    };
984
985    // Parse signature components. Reject if r or s is wider than the
986    // field element size (LIMBS * 8 bytes). Without this check, an
987    // oversized DER INTEGER (e.g. 33 bytes for P-256) would be silently
988    // truncated by from_bytes_be, and the truncated value might
989    // accidentally verify against the public key.
990    let felem_bytes = LIMBS * 8;
991    if sig.r.len() > felem_bytes || sig.s.len() > felem_bytes {
992        return false;
993    }
994    let r = FieldElement::<LIMBS>::from_bytes_be(&sig.r);
995    let s = FieldElement::<LIMBS>::from_bytes_be(&sig.s);
996
997    if r.is_zero() || s.is_zero() {
998        return false;
999    }
1000    if !scalar_is_valid(&r, n) || !scalar_is_valid(&s, n) {
1001        return false;
1002    }
1003
1004    let e = bits2int::<LIMBS>(digest, params.qlen_bits);
1005    let e = reduce_mod_n(&e, n);
1006
1007    let w = scalar_inv(&s, n);
1008    let u1 = scalar_mul(&e, &w, n);
1009    let u2 = scalar_mul(&r, &w, n);
1010
1011    let g = JacobianPoint::from_affine(params.gx, params.gy);
1012    let point = double_scalar_mul(&u1, &g, &u2, &q, params);
1013
1014    let (x1, _y1) = match point.to_affine(&params.p) {
1015        Some(pt) => pt,
1016        None => return false,
1017    };
1018    let v = reduce_mod_n(&x1, n);
1019    r == v
1020}
1021
1022// The user-facing [`Curve`](super::curves::Curve) trait, the per-curve
1023// unit structs (`P256`, `P384`, ...), and the dispatch macro are defined
1024// in [`super::curves`]; they dispatch through the `*_internal` functions
1025// above.
1026
1027// ============================================================================
1028// Tests
1029// ============================================================================
1030
1031#[cfg(test)]
1032mod tests {
1033    use super::*;
1034    use crate::hash::sha256::Sha256;
1035
1036    fn hex_to_bytes(hex: &str) -> Vec<u8> {
1037        (0..hex.len())
1038            .step_by(2)
1039            .map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap())
1040            .collect()
1041    }
1042
1043    /// RFC 6979 test vector for P-256 with SHA-256.
1044    /// Private key: C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721
1045    /// Message: "sample"
1046    /// Expected k: A6E3C57DD01ABE90086538398355DD4C3B17AA873382B0F24D6129493D8AAD60
1047    /// Expected r: EFD48B2AACB6A8FD1140DD9CD45E81D69D2C877B56AAF991C34D0EA84EAF3716
1048    /// Expected s: F7CB1C942D657C41D436C7A1B6E29F65F3E900DBB9AFF4064DC4AB2F843ACDA8
1049    #[test]
1050    fn test_rfc6979_p256_nonce() {
1051        let sk_bytes = hex_to_bytes("C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721");
1052        let msg = b"sample";
1053        let e_hash = Sha256::hash(msg);
1054
1055        let k = rfc6979_k::<Sha256, 4>(&sk_bytes, &e_hash, &P256_N, 256);
1056        let k_bytes = k.to_bytes_be();
1057
1058        let expected_k = hex_to_bytes("A6E3C57DD01ABE90086538398355DD4C3B17AA873382B0F24D6129493D8AAD60");
1059        assert_eq!(k_bytes, expected_k, "RFC 6979 nonce k mismatch");
1060    }
1061
1062    /// Known DER encoding of the RFC 6979 P-256 / SHA-256 "sample" vector.
1063    /// Pins the DER output format against an external reference value
1064    /// (computed from the RFC 6979 r and s by hand).
1065    #[test]
1066    fn test_der_rfc6979_p256_sample_vector() {
1067        let r = hex_to_bytes("EFD48B2AACB6A8FD1140DD9CD45E81D69D2C877B56AAF991C34D0EA84EAF3716");
1068        let s = hex_to_bytes("F7CB1C942D657C41D436C7A1B6E29F65F3E900DBB9AFF4064DC4AB2F843ACDA8");
1069        let sig = Signature { r, s };
1070
1071        // Both r and s have MSB set (0xEF, 0xF7), so each INTEGER needs a
1072        // leading 0x00 padding byte to stay positive. Each INTEGER body
1073        // is therefore 33 bytes; with the 0x02+0x21 header, each INTEGER
1074        // is 35 bytes; the SEQUENCE payload is 70 bytes; the SEQUENCE
1075        // header is 0x30 0x46; total DER length is 72 bytes.
1076        let expected = hex_to_bytes(
1077            "3046\
1078             02210\
1079             0EFD48B2AACB6A8FD1140DD9CD45E81D69D2C877B56AAF991C34D0EA84EAF3716\
1080             0221\
1081             00F7CB1C942D657C41D436C7A1B6E29F65F3E900DBB9AFF4064DC4AB2F843ACDA8",
1082        );
1083        assert_eq!(sig.to_der(), expected);
1084
1085        // Round-trip back.
1086        let parsed = Signature::from_der(&expected).unwrap();
1087        assert_eq!(parsed.r, sig.r);
1088        assert_eq!(parsed.s, sig.s);
1089    }
1090
1091    /// from_der rejects malformed / non-canonical encodings.
1092    #[test]
1093    fn test_der_rejects_malformed() {
1094        // Empty
1095        assert!(Signature::from_der(&[]).is_none());
1096        // Wrong top tag (SET instead of SEQUENCE)
1097        assert!(Signature::from_der(&[0x31, 0x00]).is_none());
1098        // SEQUENCE of nothing (we require two INTEGERs inside)
1099        assert!(Signature::from_der(&[0x30, 0x00]).is_none());
1100        // Advertised length longer than the actual content
1101        assert!(Signature::from_der(&[0x30, 0x06, 0x02, 0x01, 0x01]).is_none());
1102        // Truncated after r, no s
1103        assert!(Signature::from_der(&[0x30, 0x03, 0x02, 0x01, 0x01]).is_none());
1104        // r = 0 must be rejected
1105        assert!(Signature::from_der(&[0x30, 0x06, 0x02, 0x01, 0x00, 0x02, 0x01, 0x01]).is_none());
1106        // Non-canonical length (uses 0x81 for a length < 128)
1107        assert!(Signature::from_der(&[0x30, 0x81, 0x06, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01]).is_none());
1108        // Non-minimal INTEGER: leading 00 that isn't needed (0x01 is
1109        // positive already)
1110        assert!(Signature::from_der(&[0x30, 0x08, 0x02, 0x02, 0x00, 0x01, 0x02, 0x01, 0x01]).is_none());
1111        // Negative INTEGER (high bit set, no leading 00)
1112        assert!(Signature::from_der(&[0x30, 0x06, 0x02, 0x01, 0x80, 0x02, 0x01, 0x01]).is_none());
1113        // Trailing garbage after the SEQUENCE payload
1114        assert!(Signature::from_der(&[0x30, 0x06, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0xAB]).is_none());
1115    }
1116
1117    /// A tiny DER encoding of r=1, s=1 round-trips cleanly.
1118    #[test]
1119    fn test_der_small_integers_roundtrip() {
1120        let sig = Signature {
1121            r: vec![0x01],
1122            s: vec![0x01],
1123        };
1124        let der = sig.to_der();
1125        // SEQUENCE { 0x02 01 01, 0x02 01 01 }  -> 0x30 06 02 01 01 02 01 01
1126        assert_eq!(der, vec![0x30, 0x06, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01]);
1127        let back = Signature::from_der(&der).unwrap();
1128        assert_eq!(back.r, vec![0x01]);
1129        assert_eq!(back.s, vec![0x01]);
1130    }
1131}