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 = ¶ms.p;
563 let x2 = field_sqr(&qx, p);
564 let x3 = field_mul(&x2, &qx, p);
565 let ax = field_mul(¶ms.a, &qx, p);
566 let rhs = field_add(&field_add(&x3, &ax, p), ¶ms.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(¶ms.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, ¶ms.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(¶ms.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, ¶ms.n) {
800 continue;
801 }
802
803 let q = scalar_mul_point(&d, &g, params);
804 let (qx, qy) = q.to_affine(¶ms.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 = ¶ms.n;
834
835 // (x1, _) = k*G
836 let kg = scalar_mul_point(k, g, params);
837 let (x1, _y1) = kg.to_affine(¶ms.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, ¶ms.n);
904
905 let d = FieldElement::<LIMBS>::from_bytes_be(&sk.bytes);
906
907 loop {
908 let k = sample_random_scalar::<LIMBS>(¶ms.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, ¶ms.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, ¶ms.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 = ¶ms.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(¶ms.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}