Skip to main content

paralock_core/
keys.rs

1//! secp256k1 keys, ECDSA signatures and 32-byte secrets.
2//!
3//! - [`PrivateKey`]: a scalar in `[1, n)`, zeroized on drop, never printed.
4//! - [`PublicKey`]: a validated curve point, always stored in 33-byte compressed form (Zcash scripts, adaptor points
5//!   and EVM signers all use it).
6//! - [`Signature`]: an ECDSA `(r, s)` pair with both scalars in `[1, n)`, with strict DER (BIP 66) and 64-byte compact
7//!   encodings.
8//! - [`Secret`]: a 32-byte secret (an HTLC preimage), zeroized on drop and compared in constant time.
9//!
10//! Signing is RFC 6979 deterministic with the digest reduced modulo `n` before nonce derivation (exactly what
11//! `@noble/curves` does, so Rust and TypeScript produce identical signatures) and always returns low-S.
12
13use core::cmp::Ordering;
14use core::fmt;
15use core::hash::{Hash, Hasher};
16
17use k256::ecdsa::hazmat::SignPrimitive;
18use k256::elliptic_curve::PrimeField;
19use k256::elliptic_curve::group::Group;
20use k256::elliptic_curve::ops::Reduce;
21use k256::elliptic_curve::point::AffineCoordinates;
22use k256::elliptic_curve::sec1::ToEncodedPoint;
23use k256::elliptic_curve::subtle::Choice;
24use k256::{AffinePoint, FieldBytes, NonZeroScalar, ProjectivePoint, Scalar, U256};
25use rand_core::CryptoRngCore;
26use sha2::{Digest, Sha256};
27use subtle::ConstantTimeEq;
28use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
29
30use crate::encoding::{self, EncodingError};
31
32/// secp256k1 group order `n`, big-endian.
33pub const CURVE_ORDER: [u8; 32] = [
34    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xba, 0xae, 0xdc,
35    0xe6, 0xaf, 0x48, 0xa0, 0x3b, 0xbf, 0xd2, 0x5e, 0x8c, 0xd0, 0x36, 0x41, 0x41,
36];
37
38/// `n / 2`, big-endian: the largest low `s`.
39pub const CURVE_ORDER_HALF: [u8; 32] = [
40    0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x5d, 0x57, 0x6e,
41    0x73, 0x57, 0xa4, 0x50, 0x1d, 0xdf, 0xe9, 0x2f, 0x46, 0x68, 0x1b, 0x20, 0xa0,
42];
43
44/// An invalid key, point, scalar or signature.
45#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
46#[non_exhaustive]
47pub enum KeyError {
48    /// Malformed hex or wrong length.
49    #[error(transparent)]
50    Encoding(#[from] EncodingError),
51    /// The private key is zero or not below the curve order.
52    #[error("invalid secp256k1 private key")]
53    InvalidPrivateKey,
54    /// The bytes are not a valid secp256k1 point in the accepted encoding.
55    #[error("{what} is not a valid secp256k1 public key")]
56    InvalidPublicKey {
57        /// What was being parsed.
58        what: &'static str,
59    },
60    /// A signature scalar is zero or not below the curve order.
61    #[error("signature {which} is out of range")]
62    SignatureOutOfRange {
63        /// `"r"` or `"s"`.
64        which: &'static str,
65    },
66    /// The signature is not strict DER (BIP 66).
67    #[error("signature is not strict DER")]
68    InvalidDer,
69    /// Signing produced a zero `r` or `s` (probability ~2⁻²⁵⁶).
70    #[error("signing failed")]
71    SigningFailed,
72    /// The random number generator failed (`try_fill_bytes` returned an error) or kept producing out-of-range values.
73    #[error("the random number generator failed")]
74    RandomnessUnavailable,
75}
76
77// ---------------------------------------------------------------------------------------------------------------
78// Scalars
79
80/// Parses a 32-byte big-endian scalar in `[0, n)`.
81pub(crate) fn scalar_from_bytes(bytes: &[u8; 32]) -> Option<Scalar> {
82    Option::from(Scalar::from_repr(FieldBytes::from(*bytes)))
83}
84
85/// Parses a 32-byte big-endian scalar in `[1, n)`. The temporary copy of the bytes is wiped (they may be a key).
86pub(crate) fn nonzero_scalar_from_bytes(bytes: &[u8; 32]) -> Option<NonZeroScalar> {
87    let mut repr = FieldBytes::from(*bytes);
88    let scalar = Option::from(NonZeroScalar::from_repr(repr));
89    repr[..].zeroize();
90    scalar
91}
92
93/// A 32-byte big-endian value reduced modulo `n` (`Fn.create(bytesToNumber(b))` in noble).
94pub(crate) fn reduce32(bytes: &[u8; 32]) -> Scalar {
95    <Scalar as Reduce<U256>>::reduce_bytes(&FieldBytes::from(*bytes))
96}
97
98/// Finalizes `hasher` in place and wipes it: the chaining state is reset to the initial value and the block buffer
99/// is overwritten with zeros, so neither the secret inputs nor a midstate derived from them stays in memory after the
100/// hasher is dropped (the RustCrypto hashers don't zeroize on drop). Pass the hasher by reference: moving it would
101/// leave an unwiped copy behind.
102pub(crate) fn finalize_wiped<D>(hasher: &mut D) -> sha2::digest::Output<D>
103where
104    D: Digest + sha2::digest::FixedOutputReset + sha2::digest::core_api::BlockSizeUser,
105{
106    let out = Digest::finalize_reset(hasher);
107    // After the reset the buffer position is 0; one byte short of a full block is buffered without compressing,
108    // overwriting everything a previous message left there.
109    let zeros = [0u8; 256];
110    let fill = (<D as sha2::digest::core_api::BlockSizeUser>::block_size()).saturating_sub(1).min(zeros.len());
111    Digest::update(hasher, &zeros[..fill]);
112    out
113}
114
115/// Big-endian bytes of a scalar.
116pub(crate) fn scalar_to_bytes(s: &Scalar) -> [u8; 32] {
117    s.to_bytes().into()
118}
119
120/// The x coordinate of a point reduced modulo `n` (an ECDSA `r`).
121pub(crate) fn x_mod_n(point: &AffinePoint) -> Scalar {
122    <Scalar as Reduce<U256>>::reduce_bytes(&point.x())
123}
124
125/// True for a scalar `s ≤ n/2`.
126pub(crate) fn is_low(s: &[u8; 32]) -> bool {
127    s.as_slice() <= CURVE_ORDER_HALF.as_slice()
128}
129
130// ---------------------------------------------------------------------------------------------------------------
131// Private keys
132
133/// A secp256k1 private key: a scalar in `[1, n)`. Zeroized on drop; `Debug` never prints it.
134#[derive(Clone)]
135pub struct PrivateKey(k256::SecretKey);
136
137/// Bound on rejection-sampling draws in `try_random` (each draw is out of range with probability ~2⁻¹²⁸).
138const MAX_KEY_DRAWS: usize = 8;
139
140impl PrivateKey {
141    /// A fresh random key from a cryptographically secure RNG. Panics if the RNG itself fails (as
142    /// `RngCore::fill_bytes` does); use [`Self::try_random`] to get an error instead.
143    pub fn random<R: CryptoRngCore + ?Sized>(rng: &mut R) -> Self {
144        let mut rng = rng;
145        Self(k256::SecretKey::from(NonZeroScalar::random(&mut rng)))
146    }
147
148    /// A fresh random key, drawn with `try_fill_bytes`: fails with [`KeyError::RandomnessUnavailable`] instead of
149    /// panicking when the RNG fails (e.g. `getrandom` in a sandbox).
150    pub fn try_random<R: CryptoRngCore + ?Sized>(rng: &mut R) -> Result<Self, KeyError> {
151        let mut bytes = Zeroizing::new([0u8; 32]);
152        for _ in 0..MAX_KEY_DRAWS {
153            rng.try_fill_bytes(&mut *bytes).map_err(|_| KeyError::RandomnessUnavailable)?;
154            if let Ok(key) = Self::from_bytes(&bytes) {
155                return Ok(key);
156            }
157        }
158        Err(KeyError::RandomnessUnavailable)
159    }
160
161    /// A key from its 32-byte big-endian encoding. Fails unless `1 ≤ key < n`.
162    pub fn from_bytes(bytes: &[u8; 32]) -> Result<Self, KeyError> {
163        let scalar = nonzero_scalar_from_bytes(bytes).ok_or(KeyError::InvalidPrivateKey)?;
164        Ok(Self(k256::SecretKey::from(scalar)))
165    }
166
167    /// A key from a 32-byte slice.
168    pub fn from_slice(bytes: &[u8]) -> Result<Self, KeyError> {
169        let array = Zeroizing::new(encoding::to_array::<32>(bytes, "private key")?);
170        Self::from_bytes(&array)
171    }
172
173    /// A key from 64 hex digits (with or without `0x`).
174    pub fn from_hex(s: &str) -> Result<Self, KeyError> {
175        let bytes = Zeroizing::new(encoding::decode_hex(s)?);
176        Self::from_slice(&bytes)
177    }
178
179    /// The 32-byte big-endian encoding, zeroized when dropped (the intermediate copy is wiped too).
180    pub fn to_bytes(&self) -> Zeroizing<[u8; 32]> {
181        let mut field = self.0.to_bytes();
182        let mut out = Zeroizing::new([0u8; 32]);
183        out.copy_from_slice(&field);
184        field[..].zeroize();
185        out
186    }
187
188    /// The matching public key.
189    pub fn public_key(&self) -> PublicKey {
190        PublicKey::from_affine(*self.0.public_key().as_affine())
191    }
192
193    pub(crate) fn scalar(&self) -> NonZeroScalar {
194        self.0.to_nonzero_scalar()
195    }
196
197    /// A low-S ECDSA signature over a 32-byte digest (no hashing). RFC 6979 deterministic nonces, with the digest
198    /// reduced modulo `n` first, as `@noble/curves` does: identical inputs give identical signatures in Rust and
199    /// TypeScript.
200    pub fn sign_prehash(&self, digest: &[u8; 32]) -> Result<Signature, KeyError> {
201        let z = FieldBytes::from(scalar_to_bytes(&reduce32(digest)));
202        let secret = Zeroizing::new(self.scalar());
203        let (sig, _) = (**secret).try_sign_prehashed_rfc6979::<Sha256>(&z, &[]).map_err(|_| KeyError::SigningFailed)?;
204        // k256 normalizes to low-S; normalize again defensively (a no-op when already low).
205        let sig = sig.normalize_s().unwrap_or(sig);
206        let (r, s) = sig.split_bytes();
207        Signature::from_scalars(r.into(), s.into())
208    }
209}
210
211impl fmt::Debug for PrivateKey {
212    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213        f.write_str("PrivateKey(<redacted>)")
214    }
215}
216
217impl PartialEq for PrivateKey {
218    fn eq(&self, other: &Self) -> bool {
219        self.to_bytes().ct_eq(&*other.to_bytes()).into()
220    }
221}
222
223impl Eq for PrivateKey {}
224
225// ---------------------------------------------------------------------------------------------------------------
226// Public keys
227
228/// A secp256k1 public key (a non-identity point on the curve), stored compressed. Ordered by its compressed bytes,
229/// which is the lexicographic order of its hex (BIP 67).
230#[derive(Clone, Copy)]
231pub struct PublicKey {
232    bytes: [u8; 33],
233    point: AffinePoint,
234}
235
236impl PublicKey {
237    fn from_affine(point: AffinePoint) -> Self {
238        let encoded = point.to_encoded_point(true);
239        let mut bytes = [0u8; 33];
240        // A non-identity point always has a 33-byte compressed encoding.
241        if let Some(slice) = encoded.as_bytes().get(..33) {
242            bytes.copy_from_slice(slice);
243        }
244        Self { bytes, point }
245    }
246
247    /// Parses a 33-byte compressed key (`02`/`03` prefix) that lies on the curve. This is the only form Zcash scripts
248    /// accept (`isValidPublicKey`).
249    pub fn from_compressed(bytes: &[u8]) -> Result<Self, KeyError> {
250        Self::parse(bytes, false, "public key")
251    }
252
253    /// Parses a compressed (33-byte) or uncompressed (65-byte, `04` prefix) SEC1 key, like noble's
254    /// `Point.fromBytes`. Hybrid, compact and identity encodings are rejected.
255    pub fn from_sec1(bytes: &[u8]) -> Result<Self, KeyError> {
256        Self::parse(bytes, true, "public key")
257    }
258
259    /// Parses a compressed key from hex (with or without `0x`).
260    pub fn from_hex(s: &str) -> Result<Self, KeyError> {
261        Self::from_compressed(&encoding::decode_hex(s)?)
262    }
263
264    pub(crate) fn parse(bytes: &[u8], allow_uncompressed: bool, what: &'static str) -> Result<Self, KeyError> {
265        let well_formed = match bytes.first() {
266            Some(0x02 | 0x03) => bytes.len() == 33,
267            Some(0x04) => allow_uncompressed && bytes.len() == 65,
268            _ => false,
269        };
270        if !well_formed {
271            return Err(KeyError::InvalidPublicKey { what });
272        }
273        let key = k256::PublicKey::from_sec1_bytes(bytes).map_err(|_| KeyError::InvalidPublicKey { what })?;
274        Ok(Self::from_affine(*key.as_affine()))
275    }
276
277    /// True if `bytes` is a valid compressed key (`isValidPublicKey`).
278    pub fn is_valid_compressed(bytes: &[u8]) -> bool {
279        Self::from_compressed(bytes).is_ok()
280    }
281
282    /// The 33-byte compressed encoding.
283    pub const fn to_bytes(&self) -> [u8; 33] {
284        self.bytes
285    }
286
287    /// The 33-byte compressed encoding, borrowed.
288    pub const fn as_bytes(&self) -> &[u8; 33] {
289        &self.bytes
290    }
291
292    /// The 65-byte uncompressed encoding (`04 ‖ x ‖ y`).
293    pub fn to_uncompressed(&self) -> [u8; 65] {
294        let encoded = self.point.to_encoded_point(false);
295        let mut out = [0u8; 65];
296        if let Some(slice) = encoded.as_bytes().get(..65) {
297            out.copy_from_slice(slice);
298        }
299        out
300    }
301
302    /// Lowercase hex of the compressed encoding (no `0x`).
303    pub fn to_hex(&self) -> String {
304        encoding::to_hex(&self.bytes)
305    }
306
307    /// `ripemd160(sha256(compressed))`: the P2PKH hash.
308    pub fn hash160(&self) -> [u8; 20] {
309        crate::zcash::hash160(&self.bytes)
310    }
311
312    pub(crate) const fn affine(&self) -> &AffinePoint {
313        &self.point
314    }
315
316    pub(crate) fn projective(&self) -> ProjectivePoint {
317        ProjectivePoint::from(self.point)
318    }
319
320    pub(crate) fn from_projective(point: &ProjectivePoint) -> Option<Self> {
321        let affine = point.to_affine();
322        if bool::from(point.is_identity()) { None } else { Some(Self::from_affine(affine)) }
323    }
324
325    /// Verifies a low-S ECDSA signature over a 32-byte digest. High-S signatures are rejected.
326    pub fn verify_prehash(&self, digest: &[u8; 32], signature: &Signature) -> bool {
327        if !signature.is_low_s() {
328            return false;
329        }
330        let Ok(sig) = k256::ecdsa::Signature::from_scalars(signature.r, signature.s) else {
331            return false;
332        };
333        let Ok(key) = k256::ecdsa::VerifyingKey::from_affine(self.point) else {
334            return false;
335        };
336        use k256::ecdsa::signature::hazmat::PrehashVerifier;
337        key.verify_prehash(digest, &sig).is_ok()
338    }
339}
340
341impl PartialEq for PublicKey {
342    fn eq(&self, other: &Self) -> bool {
343        self.bytes == other.bytes
344    }
345}
346
347impl Eq for PublicKey {}
348
349impl Hash for PublicKey {
350    fn hash<H: Hasher>(&self, state: &mut H) {
351        self.bytes.hash(state);
352    }
353}
354
355impl PartialOrd for PublicKey {
356    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
357        Some(self.cmp(other))
358    }
359}
360
361impl Ord for PublicKey {
362    fn cmp(&self, other: &Self) -> Ordering {
363        self.bytes.cmp(&other.bytes)
364    }
365}
366
367impl fmt::Debug for PublicKey {
368    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
369        write!(f, "PublicKey({})", self.to_hex())
370    }
371}
372
373impl fmt::Display for PublicKey {
374    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
375        f.write_str(&self.to_hex())
376    }
377}
378
379// ---------------------------------------------------------------------------------------------------------------
380// Key pairs
381
382/// A private key and its public key. The fields are private and the public key is always derived from the private
383/// key, so a pair whose halves don't match can't be built (the builders sign with one half and put the other in
384/// scripts).
385#[derive(Clone, PartialEq, Eq)]
386pub struct KeyPair {
387    private_key: PrivateKey,
388    public_key: PublicKey,
389}
390
391impl KeyPair {
392    /// A fresh random key pair (panics if the RNG fails; see [`Self::try_random`]).
393    pub fn random<R: CryptoRngCore + ?Sized>(rng: &mut R) -> Self {
394        Self::from_private(PrivateKey::random(rng))
395    }
396
397    /// A fresh random key pair, or [`KeyError::RandomnessUnavailable`] if the RNG fails.
398    pub fn try_random<R: CryptoRngCore + ?Sized>(rng: &mut R) -> Result<Self, KeyError> {
399        Ok(Self::from_private(PrivateKey::try_random(rng)?))
400    }
401
402    /// The key pair of a private key.
403    pub fn from_private(private_key: PrivateKey) -> Self {
404        let public_key = private_key.public_key();
405        Self { private_key, public_key }
406    }
407
408    /// The key pair of a 32-byte private key in hex (`keyPairFromPrivate`).
409    pub fn from_private_hex(s: &str) -> Result<Self, KeyError> {
410        Ok(Self::from_private(PrivateKey::from_hex(s)?))
411    }
412
413    /// The 32-byte secret scalar. Keep it secret: it spends everything locked to this key.
414    pub const fn private_key(&self) -> &PrivateKey {
415        &self.private_key
416    }
417
418    /// The matching public key.
419    pub const fn public_key(&self) -> PublicKey {
420        self.public_key
421    }
422
423    /// The private key, consuming the pair.
424    pub fn into_private_key(self) -> PrivateKey {
425        self.private_key
426    }
427}
428
429impl From<PrivateKey> for KeyPair {
430    fn from(private_key: PrivateKey) -> Self {
431        Self::from_private(private_key)
432    }
433}
434
435impl fmt::Debug for KeyPair {
436    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
437        f.debug_struct("KeyPair").field("public_key", &self.public_key).finish_non_exhaustive()
438    }
439}
440
441// ---------------------------------------------------------------------------------------------------------------
442// Signatures
443
444/// An ECDSA signature `(r, s)` with `1 ≤ r, s < n`. It may be high-S when parsed; everything this crate produces is
445/// low-S, and every verifier here rejects high-S.
446#[derive(Clone, Copy, PartialEq, Eq, Hash)]
447pub struct Signature {
448    r: [u8; 32],
449    s: [u8; 32],
450}
451
452impl Signature {
453    /// A signature from big-endian scalars. Fails unless both are in `[1, n)`.
454    pub fn from_scalars(r: [u8; 32], s: [u8; 32]) -> Result<Self, KeyError> {
455        if nonzero_scalar_from_bytes(&r).is_none() {
456            return Err(KeyError::SignatureOutOfRange { which: "r" });
457        }
458        if nonzero_scalar_from_bytes(&s).is_none() {
459            return Err(KeyError::SignatureOutOfRange { which: "s" });
460        }
461        Ok(Self { r, s })
462    }
463
464    /// Parses the 64-byte `r ‖ s` encoding.
465    pub fn from_compact(bytes: &[u8]) -> Result<Self, KeyError> {
466        let array: [u8; 64] = encoding::to_array(bytes, "compact signature")?;
467        let mut r = [0u8; 32];
468        let mut s = [0u8; 32];
469        r.copy_from_slice(&array[..32]);
470        s.copy_from_slice(&array[32..]);
471        Self::from_scalars(r, s)
472    }
473
474    /// Parses a strict DER signature (BIP 66 rules, without a trailing sighash byte). Non-minimal lengths or
475    /// integers, negative integers, trailing data and out-of-range scalars are rejected.
476    pub fn from_der(der: &[u8]) -> Result<Self, KeyError> {
477        let (r, s) = parse_strict_der(der).ok_or(KeyError::InvalidDer)?;
478        Self::from_scalars(r, s)
479    }
480
481    /// `r`, big-endian.
482    pub const fn r(&self) -> [u8; 32] {
483        self.r
484    }
485
486    /// `s`, big-endian.
487    pub const fn s(&self) -> [u8; 32] {
488        self.s
489    }
490
491    /// True if `s ≤ n/2`.
492    pub fn is_low_s(&self) -> bool {
493        is_low(&self.s)
494    }
495
496    /// The low-S form of this signature (`s` replaced by `n − s` when high). Same signature, other encoding.
497    pub fn normalize_s(&self) -> Self {
498        if self.is_low_s() {
499            return *self;
500        }
501        match scalar_from_bytes(&self.s) {
502            Some(s) => Self { r: self.r, s: scalar_to_bytes(&-s) },
503            None => *self,
504        }
505    }
506
507    /// 64-byte `r ‖ s`.
508    pub fn to_compact(&self) -> [u8; 64] {
509        let mut out = [0u8; 64];
510        out[..32].copy_from_slice(&self.r);
511        out[32..].copy_from_slice(&self.s);
512        out
513    }
514
515    /// Minimal DER encoding, as in Bitcoin/Zcash scriptSigs (append the sighash type byte yourself).
516    pub fn to_der(&self) -> Vec<u8> {
517        let r = der_integer(&self.r);
518        let s = der_integer(&self.s);
519        let mut out = Vec::with_capacity(6 + r.len() + s.len());
520        out.push(0x30);
521        out.push((4 + r.len() + s.len()) as u8);
522        out.push(0x02);
523        out.push(r.len() as u8);
524        out.extend_from_slice(&r);
525        out.push(0x02);
526        out.push(s.len() as u8);
527        out.extend_from_slice(&s);
528        out
529    }
530
531    pub(crate) fn r_scalar(&self) -> Scalar {
532        scalar_from_bytes(&self.r).unwrap_or(Scalar::ZERO)
533    }
534
535    pub(crate) fn s_scalar(&self) -> Scalar {
536        scalar_from_bytes(&self.s).unwrap_or(Scalar::ZERO)
537    }
538}
539
540impl fmt::Debug for Signature {
541    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
542        write!(f, "Signature(r: {}, s: {})", encoding::to_hex(&self.r), encoding::to_hex(&self.s))
543    }
544}
545
546/// A DER INTEGER body for a 32-byte unsigned big-endian value: leading zeros stripped, `00` prepended if the high bit
547/// is set.
548fn der_integer(value: &[u8; 32]) -> Vec<u8> {
549    let first = value.iter().position(|&b| b != 0).unwrap_or(31);
550    let digits = value.get(first..).unwrap_or(&[0]);
551    let mut out = Vec::with_capacity(33);
552    if digits.first().is_some_and(|b| b & 0x80 != 0) {
553        out.push(0);
554    }
555    out.extend_from_slice(digits);
556    out
557}
558
559/// BIP 66 `IsValidSignatureEncoding` (minus the sighash byte), returning `(r, s)` left-padded to 32 bytes.
560fn parse_strict_der(sig: &[u8]) -> Option<([u8; 32], [u8; 32])> {
561    let len = sig.len();
562    if !(8..=72).contains(&len) {
563        return None;
564    }
565    if *sig.first()? != 0x30 || usize::from(*sig.get(1)?) != len - 2 {
566        return None;
567    }
568    let len_r = usize::from(*sig.get(3)?);
569    if 5 + len_r >= len {
570        return None;
571    }
572    let len_s = usize::from(*sig.get(5 + len_r)?);
573    if len_r + len_s + 6 != len {
574        return None;
575    }
576    let r = der_body(sig, 2, len_r)?;
577    let s = der_body(sig, 4 + len_r, len_s)?;
578    Some((r, s))
579}
580
581/// Checks the INTEGER at `tag_at` (tag, length, body of `body_len` bytes) and returns its value as 32 bytes.
582fn der_body(sig: &[u8], tag_at: usize, body_len: usize) -> Option<[u8; 32]> {
583    if *sig.get(tag_at)? != 0x02 || body_len == 0 {
584        return None;
585    }
586    let body = sig.get(tag_at + 2..tag_at + 2 + body_len)?;
587    let first = *body.first()?;
588    if first & 0x80 != 0 {
589        return None; // negative
590    }
591    if body_len > 1 && first == 0 && body.get(1).is_some_and(|b| b & 0x80 == 0) {
592        return None; // unnecessary leading zero
593    }
594    let digits = if first == 0 && body_len > 1 { body.get(1..)? } else { body };
595    if digits.len() > 32 {
596        return None;
597    }
598    let mut out = [0u8; 32];
599    out.get_mut(32 - digits.len()..)?.copy_from_slice(digits);
600    Some(out)
601}
602
603// ---------------------------------------------------------------------------------------------------------------
604// Secrets
605
606/// A 32-byte secret: an HTLC preimage (its sha256 is the hashlock on both chains). Zeroized on drop, compared in
607/// constant time, never printed.
608#[derive(Clone, Zeroize, ZeroizeOnDrop)]
609pub struct Secret([u8; 32]);
610
611impl Secret {
612    /// A fresh random secret (panics if the RNG fails, as `RngCore::fill_bytes` does; see [`Self::try_random`]).
613    pub fn random<R: CryptoRngCore + ?Sized>(rng: &mut R) -> Self {
614        let mut secret = Self([0u8; 32]);
615        rng.fill_bytes(&mut secret.0);
616        secret
617    }
618
619    /// A fresh random secret, or [`KeyError::RandomnessUnavailable`] if the RNG fails.
620    pub fn try_random<R: CryptoRngCore + ?Sized>(rng: &mut R) -> Result<Self, KeyError> {
621        let mut secret = Self([0u8; 32]);
622        rng.try_fill_bytes(&mut secret.0).map_err(|_| KeyError::RandomnessUnavailable)?;
623        Ok(secret)
624    }
625
626    /// Wraps 32 bytes.
627    pub const fn from_bytes(bytes: [u8; 32]) -> Self {
628        Self(bytes)
629    }
630
631    /// A secret from a 32-byte slice (the intermediate array is wiped).
632    pub fn from_slice(bytes: &[u8]) -> Result<Self, EncodingError> {
633        let mut secret = Self([0u8; 32]);
634        if bytes.len() != 32 {
635            return Err(EncodingError::InvalidLength { what: "secret", expected: 32, actual: bytes.len() });
636        }
637        secret.0.copy_from_slice(bytes);
638        Ok(secret)
639    }
640
641    /// A secret from 64 hex digits (with or without `0x`).
642    pub fn from_hex(s: &str) -> Result<Self, EncodingError> {
643        let bytes = Zeroizing::new(encoding::decode_hex(s)?);
644        Self::from_slice(&bytes)
645    }
646
647    /// The secret bytes. Handle with care.
648    pub const fn expose_secret(&self) -> &[u8; 32] {
649        &self.0
650    }
651
652    /// Lowercase hex (no `0x`), zeroized when dropped.
653    pub fn to_hex(&self) -> Zeroizing<String> {
654        Zeroizing::new(encoding::to_hex(&self.0))
655    }
656
657    /// `sha256(secret)`: the htlc-mode hashlock (`hashlockOf`). The hasher, which buffers the secret, is wiped.
658    pub fn hashlock(&self) -> [u8; 32] {
659        let mut hasher = Sha256::new();
660        hasher.update(self.0);
661        finalize_wiped(&mut hasher).into()
662    }
663
664    /// Constant-time check that `sha256(self) == hashlock`.
665    pub fn matches_hashlock(&self, hashlock: &[u8; 32]) -> bool {
666        self.hashlock().ct_eq(hashlock).into()
667    }
668}
669
670impl ConstantTimeEq for Secret {
671    fn ct_eq(&self, other: &Self) -> subtle::Choice {
672        self.0.ct_eq(&other.0)
673    }
674}
675
676impl PartialEq for Secret {
677    fn eq(&self, other: &Self) -> bool {
678        self.ct_eq(other).into()
679    }
680}
681
682impl Eq for Secret {}
683
684impl fmt::Debug for Secret {
685    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
686        f.write_str("Secret(<redacted>)")
687    }
688}
689
690/// Lifts an x coordinate (big-endian, `< p`) with the given y parity to a curve point.
691pub(crate) fn lift_x(x: &[u8; 32], y_odd: bool) -> Option<AffinePoint> {
692    use k256::elliptic_curve::point::DecompressPoint;
693    Option::from(AffinePoint::decompress(&FieldBytes::from(*x), Choice::from(u8::from(y_odd))))
694}
695
696#[cfg(test)]
697mod tests {
698    #![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
699    use super::*;
700
701    #[test]
702    fn public_keys_must_be_on_the_curve_and_compressed() {
703        let key = KeyPair::random(&mut crate::testutil::TestRng);
704        assert!(PublicKey::is_valid_compressed(key.public_key.as_bytes()));
705        let mut off_curve = [0u8; 33];
706        off_curve[0] = 2;
707        assert!(!PublicKey::is_valid_compressed(&off_curve));
708        assert!(!PublicKey::is_valid_compressed(&key.public_key.as_bytes()[1..]));
709        assert!(PublicKey::from_compressed(&key.public_key.to_uncompressed()).is_err());
710        assert_eq!(PublicKey::from_sec1(&key.public_key.to_uncompressed()).unwrap(), key.public_key);
711        let mut compact = key.public_key.to_bytes();
712        compact[0] = 0x05;
713        assert!(PublicKey::from_sec1(&compact).is_err());
714        assert!(PublicKey::from_sec1(&[0]).is_err());
715    }
716
717    #[test]
718    fn private_keys_are_range_checked() {
719        assert_eq!(PrivateKey::from_bytes(&[0u8; 32]), Err(KeyError::InvalidPrivateKey));
720        assert_eq!(PrivateKey::from_bytes(&CURVE_ORDER), Err(KeyError::InvalidPrivateKey));
721        let mut max = CURVE_ORDER;
722        max[31] -= 1;
723        assert!(PrivateKey::from_bytes(&max).is_ok());
724        assert!(format!("{:?}", PrivateKey::from_bytes(&max).unwrap()).contains("redacted"));
725    }
726
727    #[test]
728    fn signs_low_s_and_round_trips_der() {
729        let key = KeyPair::random(&mut crate::testutil::TestRng);
730        for i in 0..20u8 {
731            let digest: [u8; 32] = Sha256::digest([i]).into();
732            let sig = key.private_key.sign_prehash(&digest).unwrap();
733            assert!(sig.is_low_s());
734            assert!(key.public_key.verify_prehash(&digest, &sig));
735            assert_eq!(Signature::from_der(&sig.to_der()).unwrap(), sig);
736            assert_eq!(Signature::from_compact(&sig.to_compact()).unwrap(), sig);
737            // The high-S twin is the same signature but is rejected.
738            let s = scalar_from_bytes(&sig.s()).unwrap();
739            let high = Signature::from_scalars(sig.r(), scalar_to_bytes(&-s)).unwrap();
740            assert!(!high.is_low_s());
741            assert!(!key.public_key.verify_prehash(&digest, &high));
742            assert_eq!(high.normalize_s(), sig);
743        }
744    }
745
746    #[test]
747    fn rejects_non_strict_der() {
748        let key = KeyPair::random(&mut crate::testutil::TestRng);
749        let sig = key.private_key.sign_prehash(&[7u8; 32]).unwrap();
750        let der = sig.to_der();
751        let mut trailing = der.clone();
752        trailing.push(0);
753        assert_eq!(Signature::from_der(&trailing), Err(KeyError::InvalidDer));
754        // Padding r with an unnecessary zero byte.
755        let r_len = usize::from(der[3]);
756        let mut padded = vec![0x30, der[1] + 1, 0x02, der[3] + 1, 0x00];
757        padded.extend_from_slice(&der[4..]);
758        assert_eq!(Signature::from_der(&padded), Err(KeyError::InvalidDer));
759        assert_eq!(r_len + 6 + usize::from(der[5 + r_len]), der.len());
760        assert_eq!(Signature::from_der(&[]), Err(KeyError::InvalidDer));
761        // A negative r.
762        let negative = [0x30, 0x06, 0x02, 0x01, 0x80, 0x02, 0x01, 0x01];
763        assert_eq!(Signature::from_der(&negative), Err(KeyError::InvalidDer));
764        // A zero r is well-formed DER but out of range.
765        let zero = [0x30, 0x06, 0x02, 0x01, 0x00, 0x02, 0x01, 0x01];
766        assert_eq!(Signature::from_der(&zero), Err(KeyError::SignatureOutOfRange { which: "r" }));
767    }
768
769    #[test]
770    fn finalize_wiped_leaves_no_trace_of_the_message() {
771        use sha2::Sha512;
772        let secret = [0x5a; 57];
773        let mut hasher = Sha512::new();
774        hasher.update(secret);
775        let out = finalize_wiped(&mut hasher);
776        assert_eq!(out, Sha512::digest(secret));
777        // The state is back to the initial value and the buffer holds 127 zero bytes: hashing nothing more gives the
778        // digest of those zeros, whatever the message was.
779        assert_eq!(hasher.clone().finalize(), Sha512::digest([0u8; 127]));
780        let mut sha256 = Sha256::new();
781        sha256.update(secret);
782        assert_eq!(finalize_wiped(&mut sha256), Sha256::digest(secret));
783        assert_eq!(sha256.finalize(), Sha256::digest([0u8; 63]));
784    }
785
786    #[test]
787    fn key_pairs_derive_their_public_key() {
788        let pair = KeyPair::from_private(PrivateKey::from_bytes(&[3; 32]).unwrap());
789        assert_eq!(pair.public_key(), pair.private_key().public_key());
790        assert_eq!(KeyPair::from(PrivateKey::from_bytes(&[3; 32]).unwrap()), pair);
791        assert_eq!(pair.clone().into_private_key(), *pair.private_key());
792        let secret = Secret::from_slice(&[9; 32]).unwrap();
793        assert_eq!(secret.expose_secret(), &[9; 32]);
794        assert!(Secret::from_slice(&[9; 31]).is_err());
795    }
796
797    #[test]
798    fn secrets_are_redacted_and_hash_to_the_hashlock() {
799        let secret = Secret::from_bytes([1u8; 32]);
800        assert_eq!(format!("{secret:?}"), "Secret(<redacted>)");
801        let lock = secret.hashlock();
802        assert!(secret.matches_hashlock(&lock));
803        assert!(!Secret::from_bytes([2u8; 32]).matches_hashlock(&lock));
804    }
805}