Skip to main content

arcana/ecc/
ecdsa.rs

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