Skip to main content

aptos_sdk/crypto/
secp256k1.rs

1//! Secp256k1 ECDSA signature scheme implementation.
2//!
3//! Secp256k1 is the same elliptic curve used by Bitcoin and Ethereum.
4//!
5//! # Security: Signature Malleability
6//!
7//! This implementation enforces **low-S only** signatures to match the Aptos
8//! blockchain's on-chain verification, which rejects high-S signatures to
9//! prevent signature malleability attacks.
10//!
11//! - **Signing** always produces low-S signatures (the `k256` crate normalizes
12//!   by default).
13//! - **Parsing** (`from_bytes`, `from_hex`) rejects high-S signatures.
14//! - **Verification** also rejects high-S signatures as a defense-in-depth
15//!   measure.
16
17use crate::crypto::traits::{PublicKey, Signature, Signer, Verifier};
18use crate::error::{AptosError, AptosResult};
19use k256::ecdsa::{
20    Signature as K256Signature, SigningKey, VerifyingKey,
21    signature::hazmat::{PrehashSigner, PrehashVerifier},
22};
23use serde::{Deserialize, Serialize};
24use std::fmt;
25use zeroize::Zeroize;
26
27/// Secp256k1 private key length in bytes.
28pub const SECP256K1_PRIVATE_KEY_LENGTH: usize = 32;
29/// Secp256k1 public key length in bytes (compressed).
30pub const SECP256K1_PUBLIC_KEY_LENGTH: usize = 33;
31/// Secp256k1 uncompressed public key length in bytes.
32#[allow(dead_code)] // Public API constant
33pub const SECP256K1_PUBLIC_KEY_UNCOMPRESSED_LENGTH: usize = 65;
34/// Secp256k1 signature length in bytes (raw fixed-width `r || s`, 32 bytes each).
35pub const SECP256K1_SIGNATURE_LENGTH: usize = 64;
36
37/// A Secp256k1 ECDSA private key.
38///
39/// The private key is zeroized when dropped.
40#[derive(Clone, Zeroize)]
41#[zeroize(drop)]
42pub struct Secp256k1PrivateKey {
43    #[zeroize(skip)]
44    #[allow(unused)] // Field is used; lint false positive from Zeroize derive
45    inner: SigningKey,
46}
47
48impl Secp256k1PrivateKey {
49    /// Generates a new random Secp256k1 private key.
50    pub fn generate() -> Self {
51        let signing_key = SigningKey::random(&mut rand::rngs::OsRng);
52        Self { inner: signing_key }
53    }
54
55    /// Creates a private key from raw bytes.
56    ///
57    /// # Errors
58    ///
59    /// Returns [`AptosError::InvalidPrivateKey`] if:
60    /// - The byte slice length is not exactly 32 bytes
61    /// - The bytes do not represent a valid Secp256k1 private key
62    pub fn from_bytes(bytes: &[u8]) -> AptosResult<Self> {
63        if bytes.len() != SECP256K1_PRIVATE_KEY_LENGTH {
64            return Err(AptosError::InvalidPrivateKey(format!(
65                "expected {} bytes, got {}",
66                SECP256K1_PRIVATE_KEY_LENGTH,
67                bytes.len()
68            )));
69        }
70        let signing_key = SigningKey::from_slice(bytes)
71            .map_err(|e| AptosError::InvalidPrivateKey(e.to_string()))?;
72        Ok(Self { inner: signing_key })
73    }
74
75    /// Creates a private key from a hex string.
76    ///
77    /// # Errors
78    ///
79    /// Returns [`AptosError::Hex`] if the hex string is invalid.
80    /// Returns [`AptosError::InvalidPrivateKey`] if the decoded bytes are not exactly 32 bytes or do not represent a valid Secp256k1 private key.
81    pub fn from_hex(hex_str: &str) -> AptosResult<Self> {
82        let bytes = const_hex::decode(hex_str)?;
83        Self::from_bytes(&bytes)
84    }
85
86    /// Creates a private key from AIP-80 format string.
87    ///
88    /// AIP-80 format: `secp256k1-priv-0x{hex_bytes}`
89    ///
90    /// # Errors
91    ///
92    /// Returns an error if the format is invalid or the key bytes are invalid.
93    pub fn from_aip80(s: &str) -> AptosResult<Self> {
94        const PREFIX: &str = "secp256k1-priv-";
95        if let Some(hex_part) = s.strip_prefix(PREFIX) {
96            Self::from_hex(hex_part)
97        } else {
98            Err(AptosError::InvalidPrivateKey(format!(
99                "invalid AIP-80 format: expected prefix '{PREFIX}'"
100            )))
101        }
102    }
103
104    /// Returns the private key as bytes.
105    pub fn to_bytes(&self) -> [u8; SECP256K1_PRIVATE_KEY_LENGTH] {
106        self.inner.to_bytes().into()
107    }
108
109    /// Returns the private key as a hex string.
110    pub fn to_hex(&self) -> String {
111        const_hex::encode_prefixed(self.inner.to_bytes())
112    }
113
114    /// Returns the private key in AIP-80 format.
115    ///
116    /// AIP-80 format: `secp256k1-priv-0x{hex_bytes}`
117    pub fn to_aip80(&self) -> String {
118        format!("secp256k1-priv-{}", self.to_hex())
119    }
120
121    /// Returns the corresponding public key.
122    pub fn public_key(&self) -> Secp256k1PublicKey {
123        Secp256k1PublicKey {
124            inner: *self.inner.verifying_key(),
125        }
126    }
127
128    /// Signs `message` and returns a low-S ECDSA signature over the
129    /// `SHA3-256` digest of `message`.
130    ///
131    /// Aptos on-chain verification (see `aptos-crypto::secp256k1_ecdsa::Signature::verify`)
132    /// hashes the signing-message preimage with **SHA-3-256** before invoking
133    /// ECDSA on the 32-byte digest. We mirror that flow exactly: compute the
134    /// SHA-3-256 digest first, then sign it via `PrehashSigner` so the k256
135    /// crate does not apply its default SHA-256 internally.
136    ///
137    /// Historical note: prior versions of this SDK relied on `Signer::sign`
138    /// which does an internal SHA-256, producing a signature over
139    /// `SHA-256(message)` (or, with a SHA-256 pre-hash, over
140    /// `SHA-256(SHA-256(message))`). Both forms verified internally but
141    /// were rejected by the chain because the chain hashes with SHA-3-256.
142    ///
143    /// # Panics
144    ///
145    /// Panics only in the (mathematically impossible) case that
146    /// `k256::ecdsa::SigningKey::sign_prehash` returns `Err` for a 32-byte
147    /// prehash, which the `signature::hazmat::PrehashSigner` contract for
148    /// `secp256k1` does not produce.
149    pub fn sign(&self, message: &[u8]) -> Secp256k1Signature {
150        let digest = crate::crypto::sha3_256(message);
151        let signature: K256Signature = self
152            .inner
153            .sign_prehash(&digest)
154            .expect("32-byte SHA3-256 digest is a valid ECDSA prehash");
155        // SECURITY: Ensure low-S (defense-in-depth; k256 should already do this)
156        let normalized = signature.normalize_s().unwrap_or(signature);
157        Secp256k1Signature { inner: normalized }
158    }
159
160    /// Signs a 32-byte pre-computed digest directly, with NO further hashing.
161    ///
162    /// The caller is responsible for ensuring `digest` is the correct hash
163    /// for the on-chain verification scheme (Aptos uses SHA-3-256).
164    ///
165    /// # Panics
166    ///
167    /// Panics only if `k256::ecdsa::SigningKey::sign_prehash` returns `Err`
168    /// for the supplied 32-byte digest, which the
169    /// `signature::hazmat::PrehashSigner` contract for `secp256k1` does not
170    /// produce.
171    pub fn sign_prehashed(&self, digest: &[u8; 32]) -> Secp256k1Signature {
172        let signature: K256Signature = self
173            .inner
174            .sign_prehash(digest)
175            .expect("32-byte digest is a valid ECDSA prehash");
176        let normalized = signature.normalize_s().unwrap_or(signature);
177        Secp256k1Signature { inner: normalized }
178    }
179}
180
181impl Signer for Secp256k1PrivateKey {
182    type Signature = Secp256k1Signature;
183
184    fn sign(&self, message: &[u8]) -> Secp256k1Signature {
185        Secp256k1PrivateKey::sign(self, message)
186    }
187
188    fn public_key(&self) -> Secp256k1PublicKey {
189        Secp256k1PrivateKey::public_key(self)
190    }
191}
192
193impl fmt::Debug for Secp256k1PrivateKey {
194    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195        write!(f, "Secp256k1PrivateKey([REDACTED])")
196    }
197}
198
199/// A Secp256k1 ECDSA public key.
200#[derive(Clone, Copy, PartialEq, Eq)]
201pub struct Secp256k1PublicKey {
202    inner: VerifyingKey,
203}
204
205impl Secp256k1PublicKey {
206    /// Creates a public key from bytes in any accepted encoding.
207    ///
208    /// Accepts all of the following:
209    /// - 33-byte SEC1 compressed (`0x02`/`0x03` prefix)
210    /// - 65-byte SEC1 uncompressed (`0x04` prefix)
211    /// - 64-byte raw `X || Y` affine coordinates (the Aptos on-chain encoding,
212    ///   with the leading `0x04` marker dropped)
213    ///
214    /// # Errors
215    ///
216    /// Returns [`AptosError::InvalidPublicKey`] if the bytes do not decode to a
217    /// valid Secp256k1 public key in one of the accepted encodings.
218    pub fn from_bytes(bytes: &[u8]) -> AptosResult<Self> {
219        // Accept any SEC1-style encoding (33 compressed, 65 uncompressed) as
220        // before; *also* accept the raw 64-byte `(X || Y)` Aptos on-chain
221        // format by reconstructing the SEC1 uncompressed form (a leading 0x04
222        // followed by the 64 raw bytes).
223        if bytes.len() == 64 {
224            let mut sec1 = Vec::with_capacity(65);
225            sec1.push(0x04);
226            sec1.extend_from_slice(bytes);
227            return VerifyingKey::from_sec1_bytes(&sec1)
228                .map(|inner| Self { inner })
229                .map_err(|e| AptosError::InvalidPublicKey(e.to_string()));
230        }
231        let verifying_key = VerifyingKey::from_sec1_bytes(bytes)
232            .map_err(|e| AptosError::InvalidPublicKey(e.to_string()))?;
233        Ok(Self {
234            inner: verifying_key,
235        })
236    }
237
238    /// Creates a public key from a hex string.
239    ///
240    /// # Errors
241    ///
242    /// Returns [`AptosError::Hex`] if the hex string is invalid.
243    /// Returns [`AptosError::InvalidPublicKey`] if the decoded bytes do not represent a valid
244    /// Secp256k1 public key in one of the encodings accepted by [`Self::from_bytes`]
245    /// (33-byte compressed, 65-byte SEC1 uncompressed, or 64-byte raw `X || Y`).
246    pub fn from_hex(hex_str: &str) -> AptosResult<Self> {
247        let bytes = const_hex::decode(hex_str)?;
248        Self::from_bytes(&bytes)
249    }
250
251    /// Creates a public key from AIP-80 format string.
252    ///
253    /// AIP-80 format: `secp256k1-pub-0x{hex_bytes}`
254    ///
255    /// # Errors
256    ///
257    /// Returns an error if the format is invalid or the key bytes are invalid.
258    pub fn from_aip80(s: &str) -> AptosResult<Self> {
259        const PREFIX: &str = "secp256k1-pub-";
260        if let Some(hex_part) = s.strip_prefix(PREFIX) {
261            Self::from_hex(hex_part)
262        } else {
263            Err(AptosError::InvalidPublicKey(format!(
264                "invalid AIP-80 format: expected prefix '{PREFIX}'"
265            )))
266        }
267    }
268
269    /// Returns the public key as compressed bytes (33 bytes).
270    pub fn to_bytes(&self) -> Vec<u8> {
271        self.inner.to_sec1_bytes().to_vec()
272    }
273
274    /// Returns the public key in SEC1 uncompressed encoding (65 bytes, leading 0x04 marker).
275    ///
276    /// Most Aptos on-chain APIs use the *raw* 64-byte encoding (the `X || Y`
277    /// coordinates with the 0x04 marker dropped) -- see [`Self::to_raw_bytes`].
278    /// This method is retained for callers that need the SEC1 form.
279    pub fn to_uncompressed_bytes(&self) -> Vec<u8> {
280        #[allow(unused_imports)]
281        use k256::elliptic_curve::sec1::ToEncodedPoint;
282        self.inner.to_encoded_point(false).as_bytes().to_vec()
283    }
284
285    /// Returns the public key as the raw 64 bytes of the `X || Y` affine
286    /// coordinates, without the leading SEC1 0x04 uncompressed-point marker.
287    ///
288    /// This is the encoding expected by `aptos-stdlib`'s
289    /// `secp256k1::ecdsa_raw_public_key_from_64_bytes` and by the
290    /// `AnyPublicKey::Secp256k1Ecdsa` variant on-chain (each variant carries a
291    /// `vector<u8>` of length 64).
292    pub fn to_raw_bytes(&self) -> [u8; 64] {
293        let uncompressed = self.to_uncompressed_bytes();
294        // The encoded form is always `0x04 || X(32) || Y(32)` so we can safely
295        // drop the first byte here.
296        debug_assert_eq!(uncompressed.len(), 65);
297        debug_assert_eq!(uncompressed[0], 0x04);
298        let mut out = [0u8; 64];
299        out.copy_from_slice(&uncompressed[1..]);
300        out
301    }
302
303    /// Returns the public key as a hex string (compressed format).
304    pub fn to_hex(&self) -> String {
305        const_hex::encode_prefixed(self.to_bytes())
306    }
307
308    /// Returns the public key in AIP-80 format (compressed).
309    ///
310    /// AIP-80 format: `secp256k1-pub-0x{hex_bytes}`
311    pub fn to_aip80(&self) -> String {
312        format!("secp256k1-pub-{}", self.to_hex())
313    }
314
315    /// Verifies a signature against a message.
316    ///
317    /// # Security
318    ///
319    /// Rejects high-S signatures before verification, matching Aptos on-chain
320    /// behavior. This is a defense-in-depth check; signatures created through
321    /// this SDK's `from_bytes` are already guaranteed to be low-S.
322    ///
323    /// # Errors
324    ///
325    /// Returns [`AptosError::SignatureVerificationFailed`] if the signature has
326    /// a high-S value, is invalid, or does not match the message.
327    pub fn verify(&self, message: &[u8], signature: &Secp256k1Signature) -> AptosResult<()> {
328        // SECURITY: Reject high-S signatures (matches aptos-core behavior)
329        if signature.inner.normalize_s().is_some() {
330            return Err(AptosError::SignatureVerificationFailed);
331        }
332        // Aptos hashes secp256k1 signing messages with SHA-3-256 -- not SHA-256.
333        // Match that exactly via `verify_prehash` so k256 does not apply its
334        // default SHA-256 hashing.
335        let digest = crate::crypto::sha3_256(message);
336        self.inner
337            .verify_prehash(&digest, &signature.inner)
338            .map_err(|_| AptosError::SignatureVerificationFailed)
339    }
340
341    /// Verifies a signature against a pre-hashed message.
342    ///
343    /// # Security
344    ///
345    /// Rejects high-S signatures before verification, matching Aptos on-chain
346    /// behavior.
347    ///
348    /// # Errors
349    ///
350    /// Returns [`AptosError::SignatureVerificationFailed`] if the signature has
351    /// a high-S value, is invalid, or does not match the hash.
352    pub fn verify_prehashed(
353        &self,
354        hash: &[u8; 32],
355        signature: &Secp256k1Signature,
356    ) -> AptosResult<()> {
357        // SECURITY: Reject high-S signatures (matches aptos-core behavior)
358        if signature.inner.normalize_s().is_some() {
359            return Err(AptosError::SignatureVerificationFailed);
360        }
361        // Verify against the 32-byte digest directly (no additional hashing).
362        self.inner
363            .verify_prehash(hash, &signature.inner)
364            .map_err(|_| AptosError::SignatureVerificationFailed)
365    }
366
367    /// Derives the account address for this public key.
368    ///
369    /// Uses the `SingleKey` authentication scheme (`scheme_id` = 2):
370    /// `auth_key = SHA3-256(BCS(AnyPublicKey::Secp256k1Ecdsa) || 0x02)`
371    ///
372    /// Where `BCS(AnyPublicKey::Secp256k1Ecdsa)` = `0x01 || ULEB128(65) || uncompressed_public_key`
373    pub fn to_address(&self) -> crate::types::AccountAddress {
374        // BCS format: variant_byte || ULEB128(length) || pubkey_bytes.
375        //
376        // The chain's `AccountAuthenticator::SingleKey` authentication-key
377        // derivation goes through `bcs::to_bytes(&AnyPublicKey)`, which for
378        // the `Secp256k1Ecdsa` variant re-serializes the underlying
379        // `secp256k1_ecdsa::PublicKey` via `libsecp256k1::PublicKey::serialize()`
380        // -- always producing 65 bytes of SEC1 uncompressed encoding
381        // (0x04 || X || Y), regardless of which form (33/64/65) was on the
382        // wire. We must therefore derive the address using the same 65-byte
383        // canonical form to obtain an address the chain will agree with.
384        let uncompressed = self.to_uncompressed_bytes();
385        let mut bcs_bytes = Vec::with_capacity(1 + 1 + uncompressed.len());
386        bcs_bytes.push(0x01); // AnyPublicKey::Secp256k1Ecdsa variant
387        bcs_bytes.push(65); // ULEB128(65)
388        bcs_bytes.extend_from_slice(&uncompressed);
389        crate::crypto::derive_address(&bcs_bytes, crate::crypto::SINGLE_KEY_SCHEME)
390    }
391}
392
393impl PublicKey for Secp256k1PublicKey {
394    const LENGTH: usize = SECP256K1_PUBLIC_KEY_LENGTH;
395
396    fn from_bytes(bytes: &[u8]) -> AptosResult<Self> {
397        Secp256k1PublicKey::from_bytes(bytes)
398    }
399
400    fn to_bytes(&self) -> Vec<u8> {
401        Secp256k1PublicKey::to_bytes(self)
402    }
403}
404
405impl Verifier for Secp256k1PublicKey {
406    type Signature = Secp256k1Signature;
407
408    fn verify(&self, message: &[u8], signature: &Secp256k1Signature) -> AptosResult<()> {
409        Secp256k1PublicKey::verify(self, message, signature)
410    }
411}
412
413impl fmt::Debug for Secp256k1PublicKey {
414    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
415        write!(f, "Secp256k1PublicKey({})", self.to_hex())
416    }
417}
418
419impl fmt::Display for Secp256k1PublicKey {
420    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
421        write!(f, "{}", self.to_hex())
422    }
423}
424
425impl Serialize for Secp256k1PublicKey {
426    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
427    where
428        S: serde::Serializer,
429    {
430        if serializer.is_human_readable() {
431            serializer.serialize_str(&self.to_hex())
432        } else {
433            serializer.serialize_bytes(&self.to_bytes())
434        }
435    }
436}
437
438impl<'de> Deserialize<'de> for Secp256k1PublicKey {
439    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
440    where
441        D: serde::Deserializer<'de>,
442    {
443        if deserializer.is_human_readable() {
444            let s = String::deserialize(deserializer)?;
445            Self::from_hex(&s).map_err(serde::de::Error::custom)
446        } else {
447            let bytes = Vec::<u8>::deserialize(deserializer)?;
448            Self::from_bytes(&bytes).map_err(serde::de::Error::custom)
449        }
450    }
451}
452
453/// A Secp256k1 ECDSA signature.
454#[derive(Clone, Copy, PartialEq, Eq)]
455pub struct Secp256k1Signature {
456    inner: K256Signature,
457}
458
459impl Secp256k1Signature {
460    /// Creates a signature from raw bytes (64 bytes, r || s).
461    ///
462    /// # Security
463    ///
464    /// Rejects high-S signatures to match Aptos on-chain verification behavior.
465    /// The Aptos VM only accepts low-S (canonical) ECDSA signatures to prevent
466    /// signature malleability attacks.
467    ///
468    /// # Errors
469    ///
470    /// Returns [`AptosError::InvalidSignature`] if:
471    /// - The byte slice length is not exactly 64 bytes
472    /// - The bytes do not represent a valid Secp256k1 signature
473    /// - The signature has a high-S value (not canonical)
474    pub fn from_bytes(bytes: &[u8]) -> AptosResult<Self> {
475        if bytes.len() != SECP256K1_SIGNATURE_LENGTH {
476            return Err(AptosError::InvalidSignature(format!(
477                "expected {} bytes, got {}",
478                SECP256K1_SIGNATURE_LENGTH,
479                bytes.len()
480            )));
481        }
482        let signature = K256Signature::from_slice(bytes)
483            .map_err(|e| AptosError::InvalidSignature(e.to_string()))?;
484        // SECURITY: Reject high-S signatures. Aptos on-chain verification only
485        // accepts low-S (canonical) signatures to prevent malleability.
486        // normalize_s() returns Some(_) if the signature was high-S.
487        if signature.normalize_s().is_some() {
488            return Err(AptosError::InvalidSignature(
489                "high-S signature rejected: Aptos requires low-S (canonical) ECDSA signatures"
490                    .into(),
491            ));
492        }
493        Ok(Self { inner: signature })
494    }
495
496    /// Creates a signature from a hex string.
497    ///
498    /// # Errors
499    ///
500    /// Returns [`AptosError::Hex`] if the hex string is invalid.
501    /// Returns [`AptosError::InvalidSignature`] if the decoded bytes are not exactly 64 bytes or do not represent a valid Secp256k1 signature.
502    pub fn from_hex(hex_str: &str) -> AptosResult<Self> {
503        let bytes = const_hex::decode(hex_str)?;
504        Self::from_bytes(&bytes)
505    }
506
507    /// Returns the signature as bytes (64 bytes, r || s).
508    pub fn to_bytes(&self) -> [u8; SECP256K1_SIGNATURE_LENGTH] {
509        self.inner.to_bytes().into()
510    }
511
512    /// Returns the signature as a hex string.
513    pub fn to_hex(&self) -> String {
514        const_hex::encode_prefixed(self.to_bytes())
515    }
516}
517
518impl Signature for Secp256k1Signature {
519    type PublicKey = Secp256k1PublicKey;
520    const LENGTH: usize = SECP256K1_SIGNATURE_LENGTH;
521
522    fn from_bytes(bytes: &[u8]) -> AptosResult<Self> {
523        Secp256k1Signature::from_bytes(bytes)
524    }
525
526    fn to_bytes(&self) -> Vec<u8> {
527        self.inner.to_bytes().to_vec()
528    }
529}
530
531impl fmt::Debug for Secp256k1Signature {
532    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
533        write!(f, "Secp256k1Signature({})", self.to_hex())
534    }
535}
536
537impl fmt::Display for Secp256k1Signature {
538    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
539        write!(f, "{}", self.to_hex())
540    }
541}
542
543impl Serialize for Secp256k1Signature {
544    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
545    where
546        S: serde::Serializer,
547    {
548        if serializer.is_human_readable() {
549            serializer.serialize_str(&self.to_hex())
550        } else {
551            serializer.serialize_bytes(&self.to_bytes())
552        }
553    }
554}
555
556impl<'de> Deserialize<'de> for Secp256k1Signature {
557    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
558    where
559        D: serde::Deserializer<'de>,
560    {
561        if deserializer.is_human_readable() {
562            let s = String::deserialize(deserializer)?;
563            Self::from_hex(&s).map_err(serde::de::Error::custom)
564        } else {
565            let bytes = Vec::<u8>::deserialize(deserializer)?;
566            Self::from_bytes(&bytes).map_err(serde::de::Error::custom)
567        }
568    }
569}
570
571#[cfg(test)]
572mod tests {
573    use super::*;
574
575    #[test]
576    fn test_generate_and_sign() {
577        let private_key = Secp256k1PrivateKey::generate();
578        let message = b"hello world";
579        let signature = private_key.sign(message);
580
581        let public_key = private_key.public_key();
582        assert!(public_key.verify(message, &signature).is_ok());
583    }
584
585    #[test]
586    fn test_wrong_message_fails() {
587        let private_key = Secp256k1PrivateKey::generate();
588        let message = b"hello world";
589        let wrong_message = b"hello world!";
590        let signature = private_key.sign(message);
591
592        let public_key = private_key.public_key();
593        assert!(public_key.verify(wrong_message, &signature).is_err());
594    }
595
596    #[test]
597    fn test_from_bytes_roundtrip() {
598        let private_key = Secp256k1PrivateKey::generate();
599        let bytes = private_key.to_bytes();
600        let restored = Secp256k1PrivateKey::from_bytes(&bytes).unwrap();
601        assert_eq!(private_key.to_bytes(), restored.to_bytes());
602    }
603
604    #[test]
605    fn test_public_key_compressed() {
606        let private_key = Secp256k1PrivateKey::generate();
607        let public_key = private_key.public_key();
608
609        // Compressed should be 33 bytes
610        assert_eq!(public_key.to_bytes().len(), 33);
611
612        // Uncompressed should be 65 bytes
613        assert_eq!(public_key.to_uncompressed_bytes().len(), 65);
614    }
615
616    #[test]
617    fn test_public_key_from_bytes_roundtrip() {
618        let private_key = Secp256k1PrivateKey::generate();
619        let public_key = private_key.public_key();
620        let bytes = public_key.to_bytes();
621        let restored = Secp256k1PublicKey::from_bytes(&bytes).unwrap();
622        assert_eq!(public_key.to_bytes(), restored.to_bytes());
623    }
624
625    #[test]
626    fn test_signature_from_bytes_roundtrip() {
627        let private_key = Secp256k1PrivateKey::generate();
628        let signature = private_key.sign(b"test");
629        let bytes = signature.to_bytes();
630        let restored = Secp256k1Signature::from_bytes(&bytes).unwrap();
631        assert_eq!(signature.to_bytes(), restored.to_bytes());
632    }
633
634    #[test]
635    fn test_hex_roundtrip() {
636        let private_key = Secp256k1PrivateKey::generate();
637        let hex = private_key.to_hex();
638        let restored = Secp256k1PrivateKey::from_hex(&hex).unwrap();
639        assert_eq!(private_key.to_bytes(), restored.to_bytes());
640    }
641
642    #[test]
643    fn test_public_key_hex_roundtrip() {
644        let private_key = Secp256k1PrivateKey::generate();
645        let public_key = private_key.public_key();
646        let hex = public_key.to_hex();
647        let restored = Secp256k1PublicKey::from_hex(&hex).unwrap();
648        assert_eq!(public_key.to_bytes(), restored.to_bytes());
649    }
650
651    #[test]
652    fn test_signature_hex_roundtrip() {
653        let private_key = Secp256k1PrivateKey::generate();
654        let signature = private_key.sign(b"test");
655        let hex = signature.to_hex();
656        let restored = Secp256k1Signature::from_hex(&hex).unwrap();
657        assert_eq!(signature.to_bytes(), restored.to_bytes());
658    }
659
660    #[test]
661    fn test_invalid_private_key_bytes() {
662        let bytes = vec![0u8; 16]; // Wrong length
663        let result = Secp256k1PrivateKey::from_bytes(&bytes);
664        assert!(result.is_err());
665    }
666
667    #[test]
668    fn test_invalid_public_key_bytes() {
669        let bytes = vec![0u8; 16]; // Wrong length
670        let result = Secp256k1PublicKey::from_bytes(&bytes);
671        assert!(result.is_err());
672    }
673
674    #[test]
675    fn test_invalid_signature_bytes() {
676        let bytes = vec![0u8; 16]; // Wrong length
677        let result = Secp256k1Signature::from_bytes(&bytes);
678        assert!(result.is_err());
679    }
680
681    #[test]
682    fn test_high_s_signature_rejected() {
683        use k256::elliptic_curve::ops::Neg;
684
685        // Sign a message (produces low-S)
686        let private_key = Secp256k1PrivateKey::generate();
687        let signature = private_key.sign(b"test message");
688
689        // Construct high-S by negating the S component: S' = n - S
690        let low_s_sig = k256::ecdsa::Signature::from_slice(&signature.to_bytes()).unwrap();
691        let (r, s) = low_s_sig.split_scalars();
692        let neg_s = s.neg();
693        let high_s_sig = k256::ecdsa::Signature::from_scalars(r, neg_s).unwrap();
694        // Confirm it really is high-S (normalize_s returns Some for high-S)
695        assert!(
696            high_s_sig.normalize_s().is_some(),
697            "constructed signature should be high-S"
698        );
699        let high_s_bytes = high_s_sig.to_bytes();
700
701        // from_bytes should reject high-S
702        let result = Secp256k1Signature::from_bytes(&high_s_bytes);
703        assert!(result.is_err(), "high-S signature should be rejected");
704        assert!(
705            result
706                .unwrap_err()
707                .to_string()
708                .contains("high-S signature rejected"),
709            "error message should mention high-S rejection"
710        );
711
712        // Verify should also reject high-S (defense-in-depth via inner field)
713        let sig_with_high_s = Secp256k1Signature { inner: high_s_sig };
714        let public_key = private_key.public_key();
715        let result = public_key.verify(b"test message", &sig_with_high_s);
716        assert!(result.is_err(), "verify should reject high-S signature");
717    }
718
719    #[test]
720    fn test_signing_always_produces_low_s() {
721        // Run multiple iterations to increase confidence
722        for _ in 0..20 {
723            let private_key = Secp256k1PrivateKey::generate();
724            let signature = private_key.sign(b"test low-s");
725            // normalize_s returns None if already low-S
726            assert!(
727                signature.inner.normalize_s().is_none(),
728                "signing must always produce low-S signatures"
729            );
730        }
731    }
732
733    #[test]
734    fn test_sign_prehashed_and_verify_prehashed_roundtrip() {
735        let private_key = Secp256k1PrivateKey::generate();
736        let public_key = private_key.public_key();
737        let hash = crate::crypto::sha3_256(b"prehash roundtrip");
738
739        let signature = private_key.sign_prehashed(&hash);
740        public_key.verify_prehashed(&hash, &signature).unwrap();
741    }
742
743    #[test]
744    fn test_verify_prehashed_wrong_hash_fails() {
745        let private_key = Secp256k1PrivateKey::generate();
746        let public_key = private_key.public_key();
747        let hash = crate::crypto::sha3_256(b"prehash correct");
748        let wrong_hash = crate::crypto::sha3_256(b"prehash wrong");
749        let signature = private_key.sign_prehashed(&hash);
750
751        let result = public_key.verify_prehashed(&wrong_hash, &signature);
752        assert!(result.is_err());
753    }
754
755    #[test]
756    fn test_json_serialization_public_key() {
757        let private_key = Secp256k1PrivateKey::generate();
758        let public_key = private_key.public_key();
759        let json = serde_json::to_string(&public_key).unwrap();
760        let restored: Secp256k1PublicKey = serde_json::from_str(&json).unwrap();
761        assert_eq!(public_key.to_bytes(), restored.to_bytes());
762    }
763
764    #[test]
765    fn test_json_serialization_signature() {
766        let private_key = Secp256k1PrivateKey::generate();
767        let signature = private_key.sign(b"test");
768        let json = serde_json::to_string(&signature).unwrap();
769        let restored: Secp256k1Signature = serde_json::from_str(&json).unwrap();
770        assert_eq!(signature.to_bytes(), restored.to_bytes());
771    }
772
773    #[test]
774    fn test_key_lengths() {
775        assert_eq!(Secp256k1PublicKey::LENGTH, SECP256K1_PUBLIC_KEY_LENGTH);
776        assert_eq!(Secp256k1Signature::LENGTH, SECP256K1_SIGNATURE_LENGTH);
777    }
778
779    #[test]
780    fn test_display_debug() {
781        let private_key = Secp256k1PrivateKey::generate();
782        let public_key = private_key.public_key();
783        let signature = private_key.sign(b"test");
784
785        // Debug should contain type name
786        assert!(format!("{public_key:?}").contains("Secp256k1PublicKey"));
787        assert!(format!("{signature:?}").contains("Secp256k1Signature"));
788
789        // Display should show hex
790        assert!(format!("{public_key}").starts_with("0x"));
791        assert!(format!("{signature}").starts_with("0x"));
792    }
793
794    #[test]
795    fn test_private_key_aip80_roundtrip() {
796        let private_key = Secp256k1PrivateKey::generate();
797        let aip80 = private_key.to_aip80();
798
799        // Should have correct prefix
800        assert!(aip80.starts_with("secp256k1-priv-0x"));
801
802        // Should roundtrip correctly
803        let restored = Secp256k1PrivateKey::from_aip80(&aip80).unwrap();
804        assert_eq!(private_key.to_bytes(), restored.to_bytes());
805    }
806
807    #[test]
808    fn test_private_key_aip80_format() {
809        let bytes = [0x01; 32];
810        let private_key = Secp256k1PrivateKey::from_bytes(&bytes).unwrap();
811        let aip80 = private_key.to_aip80();
812
813        // Expected format: secp256k1-priv-0x0101...01
814        let expected = format!("secp256k1-priv-0x{}", "01".repeat(32));
815        assert_eq!(aip80, expected);
816    }
817
818    #[test]
819    fn test_private_key_aip80_invalid_prefix() {
820        let result = Secp256k1PrivateKey::from_aip80("ed25519-priv-0x01");
821        assert!(result.is_err());
822    }
823
824    #[test]
825    fn test_public_key_aip80_roundtrip() {
826        let private_key = Secp256k1PrivateKey::generate();
827        let public_key = private_key.public_key();
828        let aip80 = public_key.to_aip80();
829
830        // Should have correct prefix
831        assert!(aip80.starts_with("secp256k1-pub-0x"));
832
833        // Should roundtrip correctly
834        let restored = Secp256k1PublicKey::from_aip80(&aip80).unwrap();
835        assert_eq!(public_key.to_bytes(), restored.to_bytes());
836    }
837
838    #[test]
839    fn test_public_key_aip80_invalid_prefix() {
840        let result = Secp256k1PublicKey::from_aip80("ed25519-pub-0x01");
841        assert!(result.is_err());
842    }
843
844    #[test]
845    fn test_signer_trait() {
846        use crate::crypto::traits::Signer;
847
848        let private_key = Secp256k1PrivateKey::generate();
849        let message = b"trait test";
850
851        let signature = Signer::sign(&private_key, message);
852        let public_key = Signer::public_key(&private_key);
853
854        assert!(public_key.verify(message, &signature).is_ok());
855    }
856
857    #[test]
858    fn test_verifier_trait() {
859        use crate::crypto::traits::Verifier;
860
861        let private_key = Secp256k1PrivateKey::generate();
862        let public_key = private_key.public_key();
863        let message = b"verifier test";
864        let signature = private_key.sign(message);
865
866        assert!(Verifier::verify(&public_key, message, &signature).is_ok());
867    }
868
869    #[test]
870    fn test_public_key_trait() {
871        use crate::crypto::traits::PublicKey;
872
873        let private_key = Secp256k1PrivateKey::generate();
874        let public_key = private_key.public_key();
875        let bytes = PublicKey::to_bytes(&public_key);
876        let restored = Secp256k1PublicKey::from_bytes(&bytes).unwrap();
877        assert_eq!(public_key.to_bytes(), restored.to_bytes());
878    }
879
880    #[test]
881    fn test_signature_trait() {
882        use crate::crypto::traits::Signature;
883
884        let private_key = Secp256k1PrivateKey::generate();
885        let signature = private_key.sign(b"test");
886        let bytes = Signature::to_bytes(&signature);
887        let restored = Secp256k1Signature::from_bytes(&bytes).unwrap();
888        assert_eq!(signature.to_bytes(), restored.to_bytes());
889    }
890
891    #[test]
892    fn test_private_key_debug() {
893        let private_key = Secp256k1PrivateKey::generate();
894        let debug = format!("{private_key:?}");
895        assert!(debug.contains("REDACTED"));
896        assert!(!debug.contains(&private_key.to_hex()));
897    }
898
899    #[test]
900    fn test_address_derivation() {
901        let private_key = Secp256k1PrivateKey::generate();
902        let public_key = private_key.public_key();
903        let address = public_key.to_address();
904
905        // Address should not be zero
906        assert!(!address.is_zero());
907
908        // Same public key should derive same address
909        let address2 = public_key.to_address();
910        assert_eq!(address, address2);
911    }
912
913    #[test]
914    fn test_uncompressed_bytes() {
915        let private_key = Secp256k1PrivateKey::generate();
916        let public_key = private_key.public_key();
917
918        // Uncompressed should be 65 bytes (0x04 prefix + 64 bytes)
919        let uncompressed = public_key.to_uncompressed_bytes();
920        assert_eq!(uncompressed.len(), 65);
921        assert_eq!(uncompressed[0], 0x04); // Uncompressed point prefix
922    }
923
924    #[test]
925    fn test_private_key_clone() {
926        let private_key = Secp256k1PrivateKey::generate();
927        let cloned = private_key.clone();
928        assert_eq!(private_key.to_bytes(), cloned.to_bytes());
929    }
930
931    #[test]
932    fn test_public_key_equality() {
933        let private_key = Secp256k1PrivateKey::generate();
934        let pk1 = private_key.public_key();
935        let pk2 = private_key.public_key();
936        assert_eq!(pk1, pk2);
937
938        let different = Secp256k1PrivateKey::generate().public_key();
939        assert_ne!(pk1, different);
940    }
941
942    #[test]
943    fn test_signature_equality() {
944        let private_key = Secp256k1PrivateKey::generate();
945        let sig1 = private_key.sign(b"test");
946        let sig2 = private_key.sign(b"test");
947        // Note: ECDSA signatures may have randomness, so they might not be equal
948        // But they should both verify
949        let public_key = private_key.public_key();
950        assert!(public_key.verify(b"test", &sig1).is_ok());
951        assert!(public_key.verify(b"test", &sig2).is_ok());
952    }
953
954    /// The documented drop-clearing guarantee relies on the inner `k256`
955    /// `SigningKey` zeroizing its secret on drop. This compile-time assertion
956    /// pins that contract so the `crypto/mod.rs` docs stay truthful.
957    #[test]
958    fn test_inner_key_zeroizes_on_drop() {
959        fn assert_zeroize_on_drop<T: zeroize::ZeroizeOnDrop>() {}
960        assert_zeroize_on_drop::<SigningKey>();
961    }
962}