Skip to main content

aptos_sdk/crypto/
secp256r1.rs

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