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 = ¶ms.p;
573 let x2 = field_sqr(&qx, p);
574 let x3 = field_mul(&x2, &qx, p);
575 let ax = field_mul(¶ms.a, &qx, p);
576 let rhs = field_add(&field_add(&x3, &ax, p), ¶ms.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(¶ms.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, ¶ms.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(¶ms.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, ¶ms.n) {
804 continue;
805 }
806
807 let q = scalar_mul_point(&d, &g, params);
808 let (qx, qy) = q.to_affine(¶ms.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 = ¶ms.n;
838
839 // (x1, _) = k*G
840 let kg = scalar_mul_point(k, g, params);
841 let (x1, _y1) = kg.to_affine(¶ms.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, ¶ms.n);
908
909 let d = FieldElement::<LIMBS>::from_bytes_be(&sk.bytes);
910
911 loop {
912 let k = sample_random_scalar::<LIMBS>(¶ms.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, ¶ms.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, ¶ms.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 = ¶ms.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(¶ms.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}