Skip to main content

txgate_crypto/
keypair.rs

1//! Cryptographic key pair traits and implementations.
2//!
3//! This module provides the [`KeyPair`] trait for abstracting over different
4//! elliptic curve key pairs, and implementations for specific curves.
5//!
6//! # Supported Curves
7//!
8//! - [`Secp256k1KeyPair`] - For Ethereum, Bitcoin, Tron, and Ripple
9//! - [`Ed25519KeyPair`] - For Solana and other ed25519-based chains
10//!
11//! # Example
12//!
13//! ```rust
14//! use txgate_crypto::keypair::{KeyPair, Secp256k1KeyPair};
15//!
16//! // Generate a new random key pair
17//! let keypair = Secp256k1KeyPair::generate();
18//!
19//! // Get the public key
20//! let pubkey = keypair.public_key();
21//! println!("Compressed: {} bytes", pubkey.compressed().len());
22//!
23//! // Get Ethereum address
24//! let eth_address = pubkey.ethereum_address();
25//! println!("Ethereum address: 0x{}", hex::encode(eth_address));
26//!
27//! // Sign a message hash
28//! let hash = [0u8; 32]; // In practice, this would be a real hash
29//! let signature = keypair.sign(&hash).expect("signing failed");
30//! println!("Signature: {} bytes", signature.as_ref().len());
31//! ```
32
33use k256::ecdsa::{RecoveryId, Signature as K256Signature, SigningKey, VerifyingKey};
34use sha3::{Digest, Keccak256};
35
36use crate::keys::SecretKey;
37use txgate_core::error::SignError;
38
39// ============================================================================
40// KeyPair Trait
41// ============================================================================
42
43/// Trait for cryptographic key pairs.
44///
45/// This trait abstracts over different elliptic curve key pairs,
46/// allowing the signing service to work with multiple curves.
47///
48/// # Thread Safety
49///
50/// All implementations must be `Send + Sync` to support multi-threaded
51/// signing operations.
52///
53/// # Example
54///
55/// ```rust
56/// use txgate_crypto::keypair::{KeyPair, Secp256k1KeyPair};
57///
58/// fn sign_message<K: KeyPair>(keypair: &K, hash: &[u8; 32]) -> Vec<u8> {
59///     keypair.sign(hash)
60///         .expect("signing failed")
61///         .as_ref()
62///         .to_vec()
63/// }
64///
65/// let keypair = Secp256k1KeyPair::generate();
66/// let hash = [0u8; 32];
67/// let sig = sign_message(&keypair, &hash);
68/// ```
69pub trait KeyPair: Send + Sync {
70    /// The signature type produced by this key pair.
71    type Signature: AsRef<[u8]>;
72
73    /// The public key type for this key pair.
74    type PublicKey: AsRef<[u8]>;
75
76    /// Generate a new random key pair.
77    ///
78    /// Uses a cryptographically secure random number generator.
79    fn generate() -> Self
80    where
81        Self: Sized;
82
83    /// Create a key pair from raw secret key bytes.
84    ///
85    /// # Arguments
86    /// * `bytes` - The 32-byte secret key material
87    ///
88    /// # Errors
89    /// Returns an error if the bytes don't represent a valid secret key
90    /// for this curve.
91    fn from_bytes(bytes: [u8; 32]) -> Result<Self, SignError>
92    where
93        Self: Sized;
94
95    /// Get the public key.
96    fn public_key(&self) -> &Self::PublicKey;
97
98    /// Sign a 32-byte message hash.
99    ///
100    /// # Arguments
101    /// * `hash` - The 32-byte hash to sign (NOT the raw message)
102    ///
103    /// # Returns
104    /// The signature bytes.
105    ///
106    /// # Errors
107    /// Returns an error if signing fails.
108    ///
109    /// # Important
110    /// The `hash` parameter should be a cryptographic hash of the message
111    /// (e.g., SHA-256 or Keccak-256), NOT the raw message itself.
112    fn sign(&self, hash: &[u8; 32]) -> Result<Self::Signature, SignError>;
113}
114
115// ============================================================================
116// Secp256k1 Public Key
117// ============================================================================
118
119/// Wrapper for secp256k1 public keys.
120///
121/// Stores both compressed (33 bytes) and uncompressed (65 bytes) formats
122/// to avoid recomputation.
123///
124/// # Formats
125///
126/// - **Compressed**: 33 bytes, prefix `0x02` or `0x03` + 32-byte X coordinate
127/// - **Uncompressed**: 65 bytes, prefix `0x04` + 32-byte X + 32-byte Y
128///
129/// # Example
130///
131/// ```rust
132/// use txgate_crypto::keypair::{KeyPair, Secp256k1KeyPair};
133///
134/// let keypair = Secp256k1KeyPair::generate();
135/// let pubkey = keypair.public_key();
136///
137/// // Get compressed format (33 bytes)
138/// assert_eq!(pubkey.compressed().len(), 33);
139///
140/// // Get uncompressed format (65 bytes)
141/// assert_eq!(pubkey.uncompressed().len(), 65);
142///
143/// // Get Ethereum address (20 bytes)
144/// let address = pubkey.ethereum_address();
145/// assert_eq!(address.len(), 20);
146/// ```
147#[derive(Clone)]
148pub struct Secp256k1PublicKey {
149    /// Compressed public key (33 bytes: prefix + X coordinate)
150    compressed: [u8; 33],
151    /// Uncompressed public key (65 bytes: prefix + X + Y coordinates)
152    uncompressed: [u8; 65],
153}
154
155impl Secp256k1PublicKey {
156    /// Create a new public key from a k256 `VerifyingKey`.
157    fn from_verifying_key(verifying: &VerifyingKey) -> Self {
158        let point = verifying.to_encoded_point(false);
159        let uncompressed_bytes = point.as_bytes();
160
161        let mut uncompressed = [0u8; 65];
162        uncompressed.copy_from_slice(uncompressed_bytes);
163
164        let point_compressed = verifying.to_encoded_point(true);
165        let compressed_bytes = point_compressed.as_bytes();
166
167        let mut compressed = [0u8; 33];
168        compressed.copy_from_slice(compressed_bytes);
169
170        Self {
171            compressed,
172            uncompressed,
173        }
174    }
175
176    /// Get the compressed public key (33 bytes).
177    ///
178    /// Format: `prefix (1 byte) || X (32 bytes)`
179    /// - Prefix is `0x02` if Y is even, `0x03` if Y is odd
180    #[must_use]
181    pub const fn compressed(&self) -> &[u8; 33] {
182        &self.compressed
183    }
184
185    /// Get the uncompressed public key (65 bytes).
186    ///
187    /// Format: `0x04 || X (32 bytes) || Y (32 bytes)`
188    #[must_use]
189    pub const fn uncompressed(&self) -> &[u8; 65] {
190        &self.uncompressed
191    }
192
193    /// Derive the Ethereum address from this public key.
194    ///
195    /// The Ethereum address is the last 20 bytes of the Keccak-256 hash
196    /// of the uncompressed public key (without the `0x04` prefix).
197    ///
198    /// # Example
199    ///
200    /// ```rust
201    /// use txgate_crypto::keypair::{KeyPair, Secp256k1KeyPair};
202    ///
203    /// let keypair = Secp256k1KeyPair::generate();
204    /// let address = keypair.public_key().ethereum_address();
205    /// assert_eq!(address.len(), 20);
206    /// ```
207    #[must_use]
208    pub fn ethereum_address(&self) -> [u8; 20] {
209        // Hash the uncompressed public key without the 0x04 prefix
210        // The uncompressed key is always 65 bytes, so [1..] is 64 bytes
211        let hash = Keccak256::digest(&self.uncompressed[1..]);
212
213        // Take the last 20 bytes of the hash
214        // Keccak256 always produces 32 bytes
215        let mut address = [0u8; 20];
216
217        // Use get() to safely extract the slice, though this should never fail
218        // since Keccak256 always produces exactly 32 bytes
219        if let Some(hash_tail) = hash.get(12..32) {
220            address.copy_from_slice(hash_tail);
221        }
222
223        address
224    }
225}
226
227impl AsRef<[u8]> for Secp256k1PublicKey {
228    fn as_ref(&self) -> &[u8] {
229        &self.compressed
230    }
231}
232
233impl std::fmt::Debug for Secp256k1PublicKey {
234    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
235        write!(f, "Secp256k1PublicKey({})", hex::encode(self.compressed))
236    }
237}
238
239// ============================================================================
240// Secp256k1 Signature
241// ============================================================================
242
243/// Wrapper for secp256k1 ECDSA signatures.
244///
245/// Contains the 64-byte signature (r || s) and the recovery ID for
246/// Ethereum compatibility.
247///
248/// # Signature Formats
249///
250/// - **Standard**: 64 bytes (r: 32 bytes || s: 32 bytes)
251/// - **Recoverable**: 65 bytes (r || s || v) where v is the recovery ID
252///
253/// # Security
254///
255/// The S value is normalized to the lower half of the curve order to
256/// prevent signature malleability attacks.
257///
258/// # Example
259///
260/// ```rust
261/// use txgate_crypto::keypair::{KeyPair, Secp256k1KeyPair};
262///
263/// let keypair = Secp256k1KeyPair::generate();
264/// let hash = [0u8; 32];
265/// let signature = keypair.sign(&hash).expect("signing failed");
266///
267/// // Get standard 64-byte signature
268/// assert_eq!(signature.as_ref().len(), 64);
269///
270/// // Get recoverable signature with recovery ID
271/// let recoverable = signature.to_recoverable_bytes();
272/// assert_eq!(recoverable.len(), 65);
273///
274/// // Get recovery ID for ecrecover
275/// let v = signature.recovery_id();
276/// assert!(v == 0 || v == 1);
277/// ```
278#[derive(Clone)]
279pub struct Secp256k1Signature {
280    /// The 64-byte signature (r || s)
281    bytes: [u8; 64],
282    /// Recovery ID for Ethereum compatibility (0 or 1)
283    recovery_id: u8,
284}
285
286impl Secp256k1Signature {
287    /// Create a signature from raw bytes and a recovery ID.
288    ///
289    /// This is useful for reconstructing a signature from its components.
290    ///
291    /// # Arguments
292    /// * `bytes` - The 64-byte signature (r || s)
293    /// * `recovery_id` - The recovery ID (0 or 1)
294    ///
295    /// # Example
296    ///
297    /// ```rust
298    /// use txgate_crypto::keypair::{KeyPair, Secp256k1KeyPair, Secp256k1Signature};
299    ///
300    /// let keypair = Secp256k1KeyPair::generate();
301    /// let hash = [0u8; 32];
302    /// let signature = keypair.sign(&hash).expect("signing failed");
303    ///
304    /// // Get the recoverable bytes
305    /// let recoverable = signature.to_recoverable_bytes();
306    ///
307    /// // Reconstruct the signature
308    /// let bytes: [u8; 64] = recoverable[..64].try_into().unwrap();
309    /// let recovery_id = recoverable[64];
310    /// let reconstructed = Secp256k1Signature::from_bytes_and_recovery_id(bytes, recovery_id);
311    ///
312    /// assert_eq!(signature.as_ref(), reconstructed.as_ref());
313    /// ```
314    #[must_use]
315    pub const fn from_bytes_and_recovery_id(bytes: [u8; 64], recovery_id: u8) -> Self {
316        Self { bytes, recovery_id }
317    }
318
319    /// Get the recovery ID.
320    ///
321    /// This is 0 or 1, which can be used with Ethereum's `ecrecover`:
322    /// - For EIP-155 transactions: `v = recovery_id + 27`
323    /// - For EIP-2930/EIP-1559: `v = recovery_id`
324    #[must_use]
325    pub const fn recovery_id(&self) -> u8 {
326        self.recovery_id
327    }
328
329    /// Return signature as 65 bytes: r (32) || s (32) || v (1).
330    ///
331    /// The `v` byte is the raw recovery ID (0 or 1). For Ethereum
332    /// transactions, you may need to add 27 or use chain-specific
333    /// calculations for EIP-155.
334    #[must_use]
335    pub fn to_recoverable_bytes(&self) -> [u8; 65] {
336        let mut result = [0u8; 65];
337        result[..64].copy_from_slice(&self.bytes);
338        result[64] = self.recovery_id;
339        result
340    }
341
342    /// Get the r component of the signature (first 32 bytes).
343    ///
344    /// This always succeeds because the signature is always exactly 64 bytes.
345    ///
346    /// # Panics
347    ///
348    /// This method cannot panic in practice because the internal storage
349    /// is always exactly 64 bytes, but the conversion is technically fallible.
350    #[must_use]
351    #[allow(clippy::missing_panics_doc)]
352    pub fn r(&self) -> &[u8; 32] {
353        // Split the array at index 32 to get the first half
354        // This is safe because bytes is always [u8; 64]
355        let (r_part, _) = self.bytes.split_at(32);
356        // Convert to fixed-size array reference
357        // SAFETY: split_at(32) on a [u8; 64] always returns exactly 32 bytes in first half
358        #[allow(clippy::expect_used)]
359        r_part
360            .try_into()
361            .expect("split_at(32) always produces 32 bytes")
362    }
363
364    /// Get the s component of the signature (last 32 bytes).
365    ///
366    /// This always succeeds because the signature is always exactly 64 bytes.
367    ///
368    /// # Panics
369    ///
370    /// This method cannot panic in practice because the internal storage
371    /// is always exactly 64 bytes, but the conversion is technically fallible.
372    #[must_use]
373    #[allow(clippy::missing_panics_doc)]
374    pub fn s(&self) -> &[u8; 32] {
375        // Split the array at index 32 to get the second half
376        // This is safe because bytes is always [u8; 64]
377        let (_, s_part) = self.bytes.split_at(32);
378        // Convert to fixed-size array reference
379        // SAFETY: split_at(32) on a [u8; 64] always returns exactly 32 bytes in second half
380        #[allow(clippy::expect_used)]
381        s_part
382            .try_into()
383            .expect("split_at(32) always produces 32 bytes")
384    }
385}
386
387impl AsRef<[u8]> for Secp256k1Signature {
388    fn as_ref(&self) -> &[u8] {
389        &self.bytes
390    }
391}
392
393impl std::fmt::Debug for Secp256k1Signature {
394    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
395        write!(
396            f,
397            "Secp256k1Signature(r={}, s={}, v={})",
398            hex::encode(self.r()),
399            hex::encode(self.s()),
400            self.recovery_id
401        )
402    }
403}
404
405// ============================================================================
406// Secp256k1 Key Pair
407// ============================================================================
408
409/// secp256k1 key pair for Ethereum, Bitcoin, Tron, and Ripple.
410///
411/// This key pair uses the secp256k1 elliptic curve, which is the standard
412/// for most major blockchain networks.
413///
414/// # Security
415///
416/// - The signing key is stored securely and is `ZeroizeOnDrop`
417/// - Signatures are normalized to prevent malleability
418/// - Recovery IDs are computed for Ethereum compatibility
419///
420/// # Example
421///
422/// ```rust
423/// use txgate_crypto::keypair::{KeyPair, Secp256k1KeyPair};
424///
425/// // Generate a new key pair
426/// let keypair = Secp256k1KeyPair::generate();
427///
428/// // Or create from existing bytes
429/// let secret_bytes = [0x42u8; 32]; // Use real secret in production!
430/// let keypair = Secp256k1KeyPair::from_bytes(secret_bytes)
431///     .expect("valid secret key");
432///
433/// // Sign a message hash
434/// let hash = [0u8; 32]; // Use real hash in production!
435/// let signature = keypair.sign(&hash).expect("signing succeeded");
436///
437/// // Get Ethereum address
438/// let address = keypair.public_key().ethereum_address();
439/// println!("Address: 0x{}", hex::encode(address));
440/// ```
441#[allow(clippy::struct_field_names)]
442pub struct Secp256k1KeyPair {
443    /// The signing key (private key)
444    signing_key: SigningKey,
445    /// Cached verifying key (public key) for verification
446    verifying_key: VerifyingKey,
447    /// Cached public key wrapper
448    public_key: Secp256k1PublicKey,
449}
450
451impl Secp256k1KeyPair {
452    /// Create a key pair from a [`SecretKey`].
453    ///
454    /// This consumes the `SecretKey` to ensure the key material exists
455    /// in only one place.
456    ///
457    /// # Errors
458    /// Returns an error if the secret key bytes are not a valid secp256k1
459    /// scalar (e.g., zero or greater than the curve order).
460    ///
461    /// # Example
462    ///
463    /// ```rust
464    /// use txgate_crypto::keys::SecretKey;
465    /// use txgate_crypto::keypair::{KeyPair, Secp256k1KeyPair};
466    ///
467    /// let secret = SecretKey::generate();
468    /// let keypair = Secp256k1KeyPair::from_secret_key(&secret)
469    ///     .expect("valid secret key");
470    /// ```
471    pub fn from_secret_key(secret: &SecretKey) -> Result<Self, SignError> {
472        Self::from_bytes(*secret.as_bytes())
473    }
474
475    /// Verify a signature against a hash using this key pair's public key.
476    ///
477    /// This is primarily useful for testing. In production, verification
478    /// is typically done using the public key alone.
479    ///
480    /// # Arguments
481    /// * `hash` - The 32-byte hash that was signed
482    /// * `signature` - The signature to verify
483    ///
484    /// # Returns
485    /// `true` if the signature is valid, `false` otherwise.
486    #[must_use]
487    pub fn verify(&self, hash: &[u8; 32], signature: &Secp256k1Signature) -> bool {
488        use k256::ecdsa::signature::hazmat::PrehashVerifier;
489
490        let Ok(k256_sig) = K256Signature::from_slice(signature.as_ref()) else {
491            return false;
492        };
493
494        self.verifying_key.verify_prehash(hash, &k256_sig).is_ok()
495    }
496}
497
498impl KeyPair for Secp256k1KeyPair {
499    type Signature = Secp256k1Signature;
500    type PublicKey = Secp256k1PublicKey;
501
502    fn generate() -> Self {
503        let secret = SecretKey::generate();
504        // Generated random keys from OsRng should always be valid secp256k1 scalars
505        // (non-zero and less than the curve order). If this fails, there's a
506        // fundamental issue with the RNG or the k256 library.
507        Self::from_bytes(*secret.as_bytes())
508            .unwrap_or_else(|_| unreachable!("OsRng generated an invalid secp256k1 scalar"))
509    }
510
511    fn from_bytes(bytes: [u8; 32]) -> Result<Self, SignError> {
512        let signing_key =
513            SigningKey::from_bytes((&bytes).into()).map_err(|_| SignError::InvalidKey)?;
514
515        let verifying_key = *signing_key.verifying_key();
516        let public_key = Secp256k1PublicKey::from_verifying_key(&verifying_key);
517
518        Ok(Self {
519            signing_key,
520            verifying_key,
521            public_key,
522        })
523    }
524
525    fn public_key(&self) -> &Self::PublicKey {
526        &self.public_key
527    }
528
529    fn sign(&self, hash: &[u8; 32]) -> Result<Self::Signature, SignError> {
530        // Sign the hash using the prehash signer (for pre-hashed messages)
531        let (signature, recovery_id): (K256Signature, RecoveryId) = self
532            .signing_key
533            .sign_prehash_recoverable(hash)
534            .map_err(|_| SignError::signature_failed("secp256k1 signing failed"))?;
535
536        // Normalize the signature to prevent malleability
537        // k256 already normalizes signatures when using sign_prehash_recoverable
538        let normalized = signature.normalize_s();
539
540        // Get the signature bytes
541        let sig_bytes = normalized.unwrap_or(signature).to_bytes();
542        let mut bytes = [0u8; 64];
543        bytes.copy_from_slice(&sig_bytes);
544
545        // Adjust recovery ID if S was normalized
546        let final_recovery_id = if normalized.is_some() {
547            // If S was normalized, flip the recovery ID
548            recovery_id.to_byte() ^ 1
549        } else {
550            recovery_id.to_byte()
551        };
552
553        Ok(Secp256k1Signature {
554            bytes,
555            recovery_id: final_recovery_id,
556        })
557    }
558}
559
560// Implement Debug for Secp256k1KeyPair without exposing the private key
561impl std::fmt::Debug for Secp256k1KeyPair {
562    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
563        f.debug_struct("Secp256k1KeyPair")
564            .field("public_key", &self.public_key)
565            .finish_non_exhaustive()
566    }
567}
568
569// ============================================================================
570// Ed25519 Public Key
571// ============================================================================
572
573/// Wrapper for ed25519 public keys.
574///
575/// Stores the 32-byte public key for ed25519 operations.
576/// Used for Solana and other ed25519-based chains.
577///
578/// # Example
579///
580/// ```rust
581/// use txgate_crypto::keypair::{KeyPair, Ed25519KeyPair};
582///
583/// let keypair = Ed25519KeyPair::generate();
584/// let pubkey = keypair.public_key();
585///
586/// // Get raw bytes (32 bytes)
587/// assert_eq!(pubkey.as_bytes().len(), 32);
588///
589/// // Get Solana address (base58 encoded)
590/// let address = pubkey.solana_address();
591/// assert!(!address.is_empty());
592/// ```
593#[derive(Clone, PartialEq, Eq)]
594pub struct Ed25519PublicKey {
595    /// The 32-byte public key
596    bytes: [u8; 32],
597}
598
599impl Ed25519PublicKey {
600    /// Create a new public key from raw bytes.
601    #[must_use]
602    pub const fn from_bytes(bytes: [u8; 32]) -> Self {
603        Self { bytes }
604    }
605
606    /// Get the raw public key bytes (32 bytes).
607    #[must_use]
608    pub const fn as_bytes(&self) -> &[u8; 32] {
609        &self.bytes
610    }
611
612    /// Derive the Solana address from this public key.
613    ///
614    /// The Solana address is simply the base58-encoded 32-byte public key.
615    ///
616    /// # Example
617    ///
618    /// ```rust
619    /// use txgate_crypto::keypair::{KeyPair, Ed25519KeyPair};
620    ///
621    /// let keypair = Ed25519KeyPair::generate();
622    /// let address = keypair.public_key().solana_address();
623    /// // Solana addresses are base58 encoded, typically 32-44 characters
624    /// assert!(address.len() >= 32 && address.len() <= 44);
625    /// ```
626    #[must_use]
627    pub fn solana_address(&self) -> String {
628        bs58::encode(&self.bytes).into_string()
629    }
630}
631
632impl AsRef<[u8]> for Ed25519PublicKey {
633    fn as_ref(&self) -> &[u8] {
634        &self.bytes
635    }
636}
637
638impl std::fmt::Debug for Ed25519PublicKey {
639    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
640        write!(f, "Ed25519PublicKey({})", hex::encode(self.bytes))
641    }
642}
643
644// ============================================================================
645// Ed25519 Signature
646// ============================================================================
647
648/// Wrapper for ed25519 signatures.
649///
650/// Contains the 64-byte signature.
651///
652/// # Example
653///
654/// ```rust
655/// use txgate_crypto::keypair::{KeyPair, Ed25519KeyPair};
656///
657/// let keypair = Ed25519KeyPair::generate();
658/// let hash = [0u8; 32];
659/// let signature = keypair.sign(&hash).expect("signing failed");
660///
661/// // Get signature bytes (64 bytes)
662/// assert_eq!(signature.as_ref().len(), 64);
663/// ```
664#[derive(Clone, PartialEq, Eq)]
665pub struct Ed25519Signature {
666    /// The 64-byte signature
667    bytes: [u8; 64],
668}
669
670impl Ed25519Signature {
671    /// Create a signature from raw bytes.
672    #[must_use]
673    pub const fn from_bytes(bytes: [u8; 64]) -> Self {
674        Self { bytes }
675    }
676}
677
678impl AsRef<[u8]> for Ed25519Signature {
679    fn as_ref(&self) -> &[u8] {
680        &self.bytes
681    }
682}
683
684impl std::fmt::Debug for Ed25519Signature {
685    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
686        write!(f, "Ed25519Signature({})", hex::encode(self.bytes))
687    }
688}
689
690// ============================================================================
691// Ed25519 Key Pair
692// ============================================================================
693
694/// Ed25519 key pair for Solana and other ed25519-based chains.
695///
696/// This key pair uses the ed25519 elliptic curve, which is the standard
697/// for Solana and some other blockchain networks.
698///
699/// # Security
700///
701/// - The signing key is stored securely using [`ed25519_dalek::SigningKey`]
702/// - The signing key implements `Zeroize` and `ZeroizeOnDrop`, ensuring secret
703///   material is automatically zeroed when dropped (the "zeroize" feature is
704///   explicitly enabled in the workspace Cargo.toml)
705/// - `Debug` output does not expose the private key
706/// - Uses ed25519-dalek for cryptographic operations
707///
708/// # Example
709///
710/// ```rust
711/// use txgate_crypto::keypair::{KeyPair, Ed25519KeyPair};
712///
713/// // Generate a new key pair
714/// let keypair = Ed25519KeyPair::generate();
715///
716/// // Or create from existing bytes
717/// let secret_bytes = [0x42u8; 32]; // Use real secret in production!
718/// let keypair = Ed25519KeyPair::from_bytes(secret_bytes)
719///     .expect("valid secret key");
720///
721/// // Sign a message hash
722/// let hash = [0u8; 32]; // Use real hash in production!
723/// let signature = keypair.sign(&hash).expect("signing succeeded");
724///
725/// // Get Solana address
726/// let address = keypair.public_key().solana_address();
727/// println!("Solana address: {address}");
728/// ```
729pub struct Ed25519KeyPair {
730    /// The signing key (private key)
731    signing_key: ed25519_dalek::SigningKey,
732    /// Cached public key wrapper
733    public_key: Ed25519PublicKey,
734}
735
736impl Ed25519KeyPair {
737    /// Create a key pair from a [`SecretKey`].
738    ///
739    /// # Errors
740    /// Returns an error if the secret key bytes are not valid.
741    ///
742    /// # Example
743    ///
744    /// ```rust
745    /// use txgate_crypto::keys::SecretKey;
746    /// use txgate_crypto::keypair::{KeyPair, Ed25519KeyPair};
747    ///
748    /// let secret = SecretKey::generate();
749    /// let keypair = Ed25519KeyPair::from_secret_key(&secret)
750    ///     .expect("valid secret key");
751    /// ```
752    pub fn from_secret_key(secret: &SecretKey) -> Result<Self, SignError> {
753        Self::from_bytes(*secret.as_bytes())
754    }
755
756    /// Verify a signature against a hash using this key pair's public key.
757    ///
758    /// # Arguments
759    /// * `hash` - The 32-byte hash that was signed
760    /// * `signature` - The signature to verify
761    ///
762    /// # Returns
763    /// `true` if the signature is valid, `false` otherwise.
764    #[must_use]
765    pub fn verify(&self, hash: &[u8; 32], signature: &Ed25519Signature) -> bool {
766        use ed25519_dalek::Verifier;
767
768        let Ok(sig) = ed25519_dalek::Signature::from_slice(signature.as_ref()) else {
769            return false;
770        };
771
772        self.signing_key.verifying_key().verify(hash, &sig).is_ok()
773    }
774}
775
776impl KeyPair for Ed25519KeyPair {
777    type Signature = Ed25519Signature;
778    type PublicKey = Ed25519PublicKey;
779
780    fn generate() -> Self {
781        let secret = SecretKey::generate();
782        // Generated random keys from OsRng should always be valid ed25519 keys
783        Self::from_bytes(*secret.as_bytes())
784            .unwrap_or_else(|_| unreachable!("OsRng generated an invalid ed25519 key"))
785    }
786
787    fn from_bytes(bytes: [u8; 32]) -> Result<Self, SignError> {
788        let signing_key = ed25519_dalek::SigningKey::from_bytes(&bytes);
789        let verifying_key = signing_key.verifying_key();
790        let public_key = Ed25519PublicKey::from_bytes(verifying_key.to_bytes());
791
792        Ok(Self {
793            signing_key,
794            public_key,
795        })
796    }
797
798    fn public_key(&self) -> &Self::PublicKey {
799        &self.public_key
800    }
801
802    fn sign(&self, hash: &[u8; 32]) -> Result<Self::Signature, SignError> {
803        use ed25519_dalek::Signer;
804
805        let signature = self.signing_key.sign(hash);
806        let bytes = signature.to_bytes();
807
808        Ok(Ed25519Signature { bytes })
809    }
810}
811
812// Implement Debug for Ed25519KeyPair without exposing the private key
813impl std::fmt::Debug for Ed25519KeyPair {
814    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
815        f.debug_struct("Ed25519KeyPair")
816            .field("public_key", &self.public_key)
817            .finish_non_exhaustive()
818    }
819}
820
821// ============================================================================
822// Tests
823// ============================================================================
824
825#[cfg(test)]
826mod tests {
827    #![allow(clippy::expect_used)]
828    #![allow(clippy::uninlined_format_args)]
829    #![allow(clippy::useless_vec)]
830    #![allow(clippy::panic)]
831
832    use super::*;
833
834    // ------------------------------------------------------------------------
835    // Secp256k1KeyPair Tests
836    // ------------------------------------------------------------------------
837
838    #[test]
839    fn test_generate_produces_valid_keypair() {
840        let keypair = Secp256k1KeyPair::generate();
841
842        // Public key should have correct lengths
843        assert_eq!(keypair.public_key().compressed().len(), 33);
844        assert_eq!(keypair.public_key().uncompressed().len(), 65);
845
846        // Compressed key should start with 0x02 or 0x03
847        let prefix = keypair.public_key().compressed()[0];
848        assert!(prefix == 0x02 || prefix == 0x03);
849
850        // Uncompressed key should start with 0x04
851        assert_eq!(keypair.public_key().uncompressed()[0], 0x04);
852    }
853
854    #[test]
855    fn test_generate_produces_unique_keys() {
856        let keypair1 = Secp256k1KeyPair::generate();
857        let keypair2 = Secp256k1KeyPair::generate();
858
859        // Should generate different public keys
860        assert_ne!(
861            keypair1.public_key().compressed(),
862            keypair2.public_key().compressed()
863        );
864    }
865
866    #[test]
867    fn test_from_bytes_success() {
868        // A known valid secp256k1 private key
869        let bytes = [
870            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
871            0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
872            0x1d, 0x1e, 0x1f, 0x20,
873        ];
874
875        let result = Secp256k1KeyPair::from_bytes(bytes);
876        assert!(result.is_ok());
877    }
878
879    #[test]
880    fn test_from_bytes_invalid_zero() {
881        // Zero is not a valid secp256k1 scalar
882        let bytes = [0u8; 32];
883        let result = Secp256k1KeyPair::from_bytes(bytes);
884        assert!(matches!(result, Err(SignError::InvalidKey)));
885    }
886
887    #[test]
888    fn test_from_secret_key() {
889        let secret = SecretKey::generate();
890        let result = Secp256k1KeyPair::from_secret_key(&secret);
891        assert!(result.is_ok());
892    }
893
894    #[test]
895    fn test_deterministic_public_key() {
896        // Same private key should always produce the same public key
897        let bytes = [0x42u8; 32];
898
899        let keypair1 = Secp256k1KeyPair::from_bytes(bytes).expect("valid key");
900        let keypair2 = Secp256k1KeyPair::from_bytes(bytes).expect("valid key");
901
902        assert_eq!(
903            keypair1.public_key().compressed(),
904            keypair2.public_key().compressed()
905        );
906        assert_eq!(
907            keypair1.public_key().uncompressed(),
908            keypair2.public_key().uncompressed()
909        );
910    }
911
912    // ------------------------------------------------------------------------
913    // Signing Tests
914    // ------------------------------------------------------------------------
915
916    #[test]
917    fn test_sign_produces_valid_signature() {
918        let keypair = Secp256k1KeyPair::generate();
919        let hash = [0x42u8; 32];
920
921        let signature = keypair.sign(&hash).expect("signing should succeed");
922
923        // Signature should be 64 bytes
924        assert_eq!(signature.as_ref().len(), 64);
925
926        // Recovery ID should be 0 or 1
927        assert!(signature.recovery_id() == 0 || signature.recovery_id() == 1);
928    }
929
930    #[test]
931    fn test_signature_is_verifiable() {
932        let keypair = Secp256k1KeyPair::generate();
933        let hash = [0x42u8; 32];
934
935        let signature = keypair.sign(&hash).expect("signing should succeed");
936
937        // Verify using keypair's verify method
938        assert!(
939            keypair.verify(&hash, &signature),
940            "signature should be valid"
941        );
942    }
943
944    #[test]
945    fn test_different_messages_produce_different_signatures() {
946        let keypair = Secp256k1KeyPair::generate();
947        let hash1 = [0x42u8; 32];
948        let hash2 = [0x43u8; 32];
949
950        let sig1 = keypair.sign(&hash1).expect("signing should succeed");
951        let sig2 = keypair.sign(&hash2).expect("signing should succeed");
952
953        // Different messages should produce different signatures
954        assert_ne!(sig1.as_ref(), sig2.as_ref());
955    }
956
957    #[test]
958    fn test_recoverable_signature_format() {
959        let keypair = Secp256k1KeyPair::generate();
960        let hash = [0x42u8; 32];
961
962        let signature = keypair.sign(&hash).expect("signing should succeed");
963        let recoverable = signature.to_recoverable_bytes();
964
965        assert_eq!(recoverable.len(), 65);
966        assert_eq!(&recoverable[..64], signature.as_ref());
967        assert_eq!(recoverable[64], signature.recovery_id());
968    }
969
970    #[test]
971    fn test_signature_r_and_s_components() {
972        let keypair = Secp256k1KeyPair::generate();
973        let hash = [0x42u8; 32];
974
975        let signature = keypair.sign(&hash).expect("signing should succeed");
976
977        // R and S should each be 32 bytes
978        assert_eq!(signature.r().len(), 32);
979        assert_eq!(signature.s().len(), 32);
980
981        // Concatenated R and S should equal the full signature
982        let mut combined = [0u8; 64];
983        combined[..32].copy_from_slice(signature.r());
984        combined[32..].copy_from_slice(signature.s());
985        assert_eq!(&combined, signature.as_ref());
986    }
987
988    // ------------------------------------------------------------------------
989    // Ethereum Address Tests
990    // ------------------------------------------------------------------------
991
992    #[test]
993    fn test_ethereum_address_length() {
994        let keypair = Secp256k1KeyPair::generate();
995        let address = keypair.public_key().ethereum_address();
996        assert_eq!(address.len(), 20);
997    }
998
999    #[test]
1000    fn test_ethereum_address_deterministic() {
1001        let bytes = [0x42u8; 32];
1002        let keypair = Secp256k1KeyPair::from_bytes(bytes).expect("valid key");
1003
1004        let address1 = keypair.public_key().ethereum_address();
1005        let address2 = keypair.public_key().ethereum_address();
1006
1007        assert_eq!(address1, address2);
1008    }
1009
1010    /// Test vector from Ethereum yellow paper / well-known test cases
1011    #[test]
1012    fn test_ethereum_address_known_vector() {
1013        // This is a well-known test private key
1014        // Private key: 0xfad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19
1015        let private_key_hex = "fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19";
1016        let expected_address_hex = "96216849c49358b10257cb55b28ea603c874b05e";
1017
1018        let private_key_bytes = hex::decode(private_key_hex).expect("valid hex");
1019        let mut bytes = [0u8; 32];
1020        bytes.copy_from_slice(&private_key_bytes);
1021
1022        let keypair = Secp256k1KeyPair::from_bytes(bytes).expect("valid key");
1023        let address = keypair.public_key().ethereum_address();
1024
1025        assert_eq!(hex::encode(address), expected_address_hex);
1026    }
1027
1028    // ------------------------------------------------------------------------
1029    // Public Key Format Tests
1030    // ------------------------------------------------------------------------
1031
1032    #[test]
1033    fn test_public_key_as_ref_returns_compressed() {
1034        let keypair = Secp256k1KeyPair::generate();
1035        let pubkey = keypair.public_key();
1036
1037        // AsRef should return the compressed format
1038        assert_eq!(pubkey.as_ref(), pubkey.compressed().as_slice());
1039    }
1040
1041    #[test]
1042    fn test_public_key_debug_shows_hex() {
1043        let keypair = Secp256k1KeyPair::generate();
1044        let debug_output = format!("{:?}", keypair.public_key());
1045
1046        // Should contain the prefix "Secp256k1PublicKey("
1047        assert!(debug_output.starts_with("Secp256k1PublicKey("));
1048        // Should be hex encoded (66 characters for 33 bytes)
1049        assert!(debug_output.len() > 66);
1050    }
1051
1052    // ------------------------------------------------------------------------
1053    // Thread Safety Tests
1054    // ------------------------------------------------------------------------
1055
1056    #[test]
1057    fn test_keypair_is_send_sync() {
1058        fn assert_send_sync<T: Send + Sync>() {}
1059        assert_send_sync::<Secp256k1KeyPair>();
1060    }
1061
1062    #[test]
1063    fn test_public_key_is_send_sync() {
1064        fn assert_send_sync<T: Send + Sync>() {}
1065        assert_send_sync::<Secp256k1PublicKey>();
1066    }
1067
1068    #[test]
1069    fn test_signature_is_send_sync() {
1070        fn assert_send_sync<T: Send + Sync>() {}
1071        assert_send_sync::<Secp256k1Signature>();
1072    }
1073
1074    // ------------------------------------------------------------------------
1075    // Debug Output Tests
1076    // ------------------------------------------------------------------------
1077
1078    #[test]
1079    fn test_keypair_debug_does_not_expose_private_key() {
1080        let keypair = Secp256k1KeyPair::generate();
1081        let debug_output = format!("{:?}", keypair);
1082
1083        // Should show public key
1084        assert!(debug_output.contains("public_key"));
1085        // Should not contain the private key (indicated by finish_non_exhaustive)
1086        assert!(debug_output.contains(".."));
1087    }
1088
1089    #[test]
1090    fn test_signature_debug_shows_components() {
1091        let keypair = Secp256k1KeyPair::generate();
1092        let hash = [0x42u8; 32];
1093        let signature = keypair.sign(&hash).expect("signing should succeed");
1094
1095        let debug_output = format!("{:?}", signature);
1096
1097        assert!(debug_output.contains("r="));
1098        assert!(debug_output.contains("s="));
1099        assert!(debug_output.contains("v="));
1100    }
1101
1102    // ------------------------------------------------------------------------
1103    // Sign/Verify roundtrip tests
1104    // ------------------------------------------------------------------------
1105
1106    #[test]
1107    fn test_sign_verify_roundtrip_multiple_hashes() {
1108        let keypair = Secp256k1KeyPair::generate();
1109
1110        // Test with various hash values
1111        for i in 0u8..10 {
1112            let hash = [i; 32];
1113            let signature = keypair.sign(&hash).expect("signing should succeed");
1114
1115            // Verify using keypair's verify method
1116            assert!(
1117                keypair.verify(&hash, &signature),
1118                "signature for hash {i} should verify"
1119            );
1120        }
1121    }
1122
1123    #[test]
1124    fn test_wrong_hash_fails_verification() {
1125        let keypair = Secp256k1KeyPair::generate();
1126        let hash1 = [0x42u8; 32];
1127        let hash2 = [0x43u8; 32];
1128
1129        let signature = keypair.sign(&hash1).expect("signing should succeed");
1130
1131        // Verification with wrong hash should fail
1132        assert!(
1133            !keypair.verify(&hash2, &signature),
1134            "verification should fail with wrong hash"
1135        );
1136    }
1137
1138    #[test]
1139    fn test_ethereum_address_never_fails_hash_extraction() {
1140        // This test verifies that the defensive .get() check in ethereum_address()
1141        // always succeeds because Keccak256 always produces 32 bytes
1142        let keypair = Secp256k1KeyPair::generate();
1143        let pubkey = keypair.public_key();
1144
1145        // Call ethereum_address multiple times to ensure consistency
1146        let addr1 = pubkey.ethereum_address();
1147        let addr2 = pubkey.ethereum_address();
1148
1149        assert_eq!(addr1, addr2);
1150        assert_eq!(addr1.len(), 20);
1151    }
1152
1153    #[test]
1154    fn test_signature_normalization_both_paths() {
1155        // Test that signature normalization works correctly
1156        // We can't force a specific normalization path, but we can verify
1157        // that all signatures are in normalized form
1158        let keypair = Secp256k1KeyPair::generate();
1159
1160        for i in 0..10 {
1161            let hash = [i; 32];
1162            let signature = keypair.sign(&hash).expect("signing should succeed");
1163
1164            // Verify the signature is valid
1165            assert!(keypair.verify(&hash, &signature));
1166
1167            // Recovery ID should always be 0 or 1
1168            assert!(signature.recovery_id() <= 1);
1169        }
1170    }
1171
1172    // ========================================================================
1173    // Phase 2: Signature Normalization Coverage
1174    // ========================================================================
1175
1176    #[test]
1177    fn should_produce_normalized_signatures_with_varied_hashes() {
1178        // Arrange: Generate keypair
1179        let keypair = Secp256k1KeyPair::generate();
1180
1181        // Act & Assert: Test with many different hashes to exercise both
1182        // normalization branches (when S is already low, and when it needs normalization)
1183        for i in 0..100 {
1184            // Create varied hash patterns
1185            let mut hash = [0u8; 32];
1186            hash[0] = i;
1187            hash[31] = 255 - i;
1188
1189            let signature = keypair.sign(&hash).expect("signing should succeed");
1190
1191            // All signatures should be normalized (low-S)
1192            assert!(
1193                keypair.verify(&hash, &signature),
1194                "Normalized signature should verify"
1195            );
1196
1197            // Recovery ID should be valid (0 or 1)
1198            let recovery_id = signature.recovery_id();
1199            assert!(
1200                recovery_id <= 1,
1201                "Recovery ID should be 0 or 1, got {recovery_id}"
1202            );
1203        }
1204    }
1205
1206    #[test]
1207    fn should_handle_normalization_with_all_zero_hash() {
1208        // Arrange: All zeros hash (edge case)
1209        let keypair = Secp256k1KeyPair::generate();
1210        let hash = [0u8; 32];
1211
1212        // Act: Sign the zero hash
1213        let signature = keypair.sign(&hash).expect("signing should succeed");
1214
1215        // Assert: Signature is valid and normalized
1216        assert!(keypair.verify(&hash, &signature));
1217        assert!(signature.recovery_id() <= 1);
1218    }
1219
1220    #[test]
1221    fn should_handle_normalization_with_all_max_hash() {
1222        // Arrange: All 0xFF hash (edge case)
1223        let keypair = Secp256k1KeyPair::generate();
1224        let hash = [0xFFu8; 32];
1225
1226        // Act: Sign the max hash
1227        let signature = keypair.sign(&hash).expect("signing should succeed");
1228
1229        // Assert: Signature is valid and normalized
1230        assert!(keypair.verify(&hash, &signature));
1231        assert!(signature.recovery_id() <= 1);
1232    }
1233
1234    #[test]
1235    fn should_handle_recovery_id_flip_when_normalized() {
1236        // Arrange: Generate multiple signatures to exercise the recovery ID flip logic
1237        let keypair = Secp256k1KeyPair::generate();
1238
1239        // Act & Assert: Generate many signatures
1240        // When normalize_s() returns Some(_), recovery ID is flipped (XOR 1)
1241        // When normalize_s() returns None, recovery ID is unchanged
1242        for i in 0..50 {
1243            let hash = [i; 32];
1244            let signature = keypair.sign(&hash).expect("signing should succeed");
1245
1246            // Recovery ID should always be valid (0 or 1)
1247            assert!(signature.recovery_id() <= 1);
1248
1249            // Verify the signature with the recovery ID
1250            assert!(keypair.verify(&hash, &signature));
1251        }
1252    }
1253
1254    #[test]
1255    fn should_produce_consistent_signatures_with_same_hash() {
1256        // Arrange: Same keypair, same hash
1257        let keypair = Secp256k1KeyPair::generate();
1258        let hash = [0x42u8; 32];
1259
1260        // Act: Sign the same hash multiple times (with randomized k)
1261        let sig1 = keypair.sign(&hash).expect("signing should succeed");
1262        let sig2 = keypair.sign(&hash).expect("signing should succeed");
1263
1264        // Assert: Both should verify (but may not be identical due to random k)
1265        assert!(keypair.verify(&hash, &sig1));
1266        assert!(keypair.verify(&hash, &sig2));
1267
1268        // Both should be normalized
1269        assert!(sig1.recovery_id() <= 1);
1270        assert!(sig2.recovery_id() <= 1);
1271    }
1272
1273    #[test]
1274    fn should_handle_normalization_edge_case_patterns() {
1275        // Arrange: Test various bit patterns that might affect S normalization
1276        let keypair = Secp256k1KeyPair::generate();
1277
1278        let test_patterns = vec![
1279            [0x00u8; 32], // All zeros
1280            [0xFFu8; 32], // All ones
1281            [0xAAu8; 32], // Alternating 10101010
1282            [0x55u8; 32], // Alternating 01010101
1283            {
1284                let mut h = [0u8; 32];
1285                h[0] = 0xFF;
1286                h
1287            }, // First byte max
1288            {
1289                let mut h = [0u8; 32];
1290                h[31] = 0xFF;
1291                h
1292            }, // Last byte max
1293        ];
1294
1295        // Act & Assert: All patterns should produce valid normalized signatures
1296        for (idx, hash) in test_patterns.iter().enumerate() {
1297            let signature = keypair
1298                .sign(hash)
1299                .unwrap_or_else(|_| panic!("Pattern {idx} signing should succeed"));
1300
1301            assert!(
1302                keypair.verify(hash, &signature),
1303                "Pattern {idx} should verify"
1304            );
1305            assert!(
1306                signature.recovery_id() <= 1,
1307                "Pattern {idx} recovery ID should be valid"
1308            );
1309        }
1310    }
1311
1312    // ========================================================================
1313    // Ed25519 KeyPair Tests
1314    // ========================================================================
1315
1316    #[test]
1317    fn test_ed25519_generate_produces_valid_keypair() {
1318        let keypair = Ed25519KeyPair::generate();
1319
1320        // Public key should be 32 bytes
1321        assert_eq!(keypair.public_key().as_bytes().len(), 32);
1322    }
1323
1324    #[test]
1325    fn test_ed25519_generate_produces_unique_keys() {
1326        let keypair1 = Ed25519KeyPair::generate();
1327        let keypair2 = Ed25519KeyPair::generate();
1328
1329        // Should generate different public keys
1330        assert_ne!(
1331            keypair1.public_key().as_bytes(),
1332            keypair2.public_key().as_bytes()
1333        );
1334    }
1335
1336    #[test]
1337    fn test_ed25519_from_bytes_success() {
1338        let bytes = [0x42u8; 32];
1339        let result = Ed25519KeyPair::from_bytes(bytes);
1340        assert!(result.is_ok());
1341    }
1342
1343    #[test]
1344    fn test_ed25519_from_bytes_zero_is_valid() {
1345        // Unlike secp256k1, zero bytes are valid for ed25519
1346        let bytes = [0u8; 32];
1347        let result = Ed25519KeyPair::from_bytes(bytes);
1348        assert!(result.is_ok());
1349    }
1350
1351    #[test]
1352    fn test_ed25519_from_secret_key() {
1353        let secret = SecretKey::generate();
1354        let result = Ed25519KeyPair::from_secret_key(&secret);
1355        assert!(result.is_ok());
1356    }
1357
1358    #[test]
1359    fn test_ed25519_deterministic_public_key() {
1360        // Same private key should always produce the same public key
1361        let bytes = [0x42u8; 32];
1362
1363        let keypair1 = Ed25519KeyPair::from_bytes(bytes).expect("valid key");
1364        let keypair2 = Ed25519KeyPair::from_bytes(bytes).expect("valid key");
1365
1366        assert_eq!(
1367            keypair1.public_key().as_bytes(),
1368            keypair2.public_key().as_bytes()
1369        );
1370    }
1371
1372    #[test]
1373    fn test_ed25519_sign_produces_valid_signature() {
1374        let keypair = Ed25519KeyPair::generate();
1375        let hash = [0x42u8; 32];
1376
1377        let signature = keypair.sign(&hash).expect("signing should succeed");
1378
1379        // Ed25519 signature should be 64 bytes
1380        assert_eq!(signature.as_ref().len(), 64);
1381    }
1382
1383    #[test]
1384    fn test_ed25519_signature_is_verifiable() {
1385        let keypair = Ed25519KeyPair::generate();
1386        let hash = [0x42u8; 32];
1387
1388        let signature = keypair.sign(&hash).expect("signing should succeed");
1389
1390        // Verify using keypair's verify method
1391        assert!(
1392            keypair.verify(&hash, &signature),
1393            "signature should be valid"
1394        );
1395    }
1396
1397    #[test]
1398    fn test_ed25519_different_messages_produce_different_signatures() {
1399        let keypair = Ed25519KeyPair::generate();
1400        let hash1 = [0x42u8; 32];
1401        let hash2 = [0x43u8; 32];
1402
1403        let sig1 = keypair.sign(&hash1).expect("signing should succeed");
1404        let sig2 = keypair.sign(&hash2).expect("signing should succeed");
1405
1406        // Different messages should produce different signatures
1407        assert_ne!(sig1.as_ref(), sig2.as_ref());
1408    }
1409
1410    #[test]
1411    fn test_ed25519_wrong_hash_fails_verification() {
1412        let keypair = Ed25519KeyPair::generate();
1413        let hash1 = [0x42u8; 32];
1414        let hash2 = [0x43u8; 32];
1415
1416        let signature = keypair.sign(&hash1).expect("signing should succeed");
1417
1418        // Verification with wrong hash should fail
1419        assert!(
1420            !keypair.verify(&hash2, &signature),
1421            "verification should fail with wrong hash"
1422        );
1423    }
1424
1425    #[test]
1426    fn test_ed25519_solana_address_format() {
1427        let keypair = Ed25519KeyPair::generate();
1428        let address = keypair.public_key().solana_address();
1429
1430        // Solana addresses are base58 encoded 32-byte public keys
1431        // Typically 32-44 characters
1432        assert!(
1433            address.len() >= 32 && address.len() <= 44,
1434            "Solana address should be 32-44 characters: {address}"
1435        );
1436    }
1437
1438    #[test]
1439    fn test_ed25519_solana_address_deterministic() {
1440        let bytes = [0x42u8; 32];
1441        let keypair = Ed25519KeyPair::from_bytes(bytes).expect("valid key");
1442
1443        let address1 = keypair.public_key().solana_address();
1444        let address2 = keypair.public_key().solana_address();
1445
1446        assert_eq!(address1, address2);
1447    }
1448
1449    #[test]
1450    fn test_ed25519_sign_verify_roundtrip_multiple_hashes() {
1451        let keypair = Ed25519KeyPair::generate();
1452
1453        for i in 0u8..10 {
1454            let hash = [i; 32];
1455            let signature = keypair.sign(&hash).expect("signing should succeed");
1456
1457            assert!(
1458                keypair.verify(&hash, &signature),
1459                "signature for hash {i} should verify"
1460            );
1461        }
1462    }
1463
1464    #[test]
1465    fn test_ed25519_keypair_is_send_sync() {
1466        fn assert_send_sync<T: Send + Sync>() {}
1467        assert_send_sync::<Ed25519KeyPair>();
1468    }
1469
1470    #[test]
1471    fn test_ed25519_public_key_is_send_sync() {
1472        fn assert_send_sync<T: Send + Sync>() {}
1473        assert_send_sync::<Ed25519PublicKey>();
1474    }
1475
1476    #[test]
1477    fn test_ed25519_signature_is_send_sync() {
1478        fn assert_send_sync<T: Send + Sync>() {}
1479        assert_send_sync::<Ed25519Signature>();
1480    }
1481
1482    #[test]
1483    fn test_ed25519_keypair_debug_does_not_expose_private_key() {
1484        let keypair = Ed25519KeyPair::generate();
1485        let debug_output = format!("{:?}", keypair);
1486
1487        // Should show public key
1488        assert!(debug_output.contains("public_key"));
1489        // Should not contain the private key (indicated by finish_non_exhaustive)
1490        assert!(debug_output.contains(".."));
1491    }
1492
1493    #[test]
1494    fn test_ed25519_signature_debug_shows_hex() {
1495        let keypair = Ed25519KeyPair::generate();
1496        let hash = [0x42u8; 32];
1497        let signature = keypair.sign(&hash).expect("signing should succeed");
1498
1499        let debug_output = format!("{:?}", signature);
1500        assert!(debug_output.starts_with("Ed25519Signature("));
1501    }
1502
1503    #[test]
1504    fn test_ed25519_public_key_debug_shows_hex() {
1505        let keypair = Ed25519KeyPair::generate();
1506        let debug_output = format!("{:?}", keypair.public_key());
1507
1508        assert!(debug_output.starts_with("Ed25519PublicKey("));
1509    }
1510
1511    #[test]
1512    fn test_ed25519_public_key_as_ref_returns_bytes() {
1513        let keypair = Ed25519KeyPair::generate();
1514        let pubkey = keypair.public_key();
1515
1516        // AsRef should return the 32-byte public key
1517        assert_eq!(pubkey.as_ref().len(), 32);
1518        assert_eq!(pubkey.as_ref(), pubkey.as_bytes());
1519    }
1520
1521    #[test]
1522    fn test_ed25519_signature_from_bytes() {
1523        let bytes = [0x42u8; 64];
1524        let sig = Ed25519Signature::from_bytes(bytes);
1525        assert_eq!(sig.as_ref(), &bytes);
1526    }
1527
1528    #[test]
1529    fn test_ed25519_public_key_from_bytes() {
1530        let bytes = [0x42u8; 32];
1531        let pubkey = Ed25519PublicKey::from_bytes(bytes);
1532        assert_eq!(pubkey.as_bytes(), &bytes);
1533    }
1534
1535    #[test]
1536    fn test_ed25519_invalid_signature_fails_verification() {
1537        let keypair = Ed25519KeyPair::generate();
1538        let hash = [0x42u8; 32];
1539
1540        // Create an invalid signature (all zeros)
1541        let invalid_sig = Ed25519Signature::from_bytes([0u8; 64]);
1542
1543        // Verification should fail
1544        assert!(!keypair.verify(&hash, &invalid_sig));
1545    }
1546
1547    #[test]
1548    fn test_ed25519_known_test_vector() {
1549        // Test vector from https://ed25519.cr.yp.to/software.html
1550        // Secret key (seed): 32 bytes of 0x9d repeated
1551        let secret_bytes = [0x9du8; 32];
1552        let keypair = Ed25519KeyPair::from_bytes(secret_bytes).expect("valid key");
1553
1554        // Sign a message
1555        let message = [0u8; 32];
1556        let signature = keypair.sign(&message).expect("signing should succeed");
1557
1558        // Verify the signature
1559        assert!(keypair.verify(&message, &signature));
1560    }
1561}
1562
1563#[cfg(test)]
1564mod proptest_tests {
1565    #![allow(clippy::expect_used)]
1566
1567    use super::*;
1568    use proptest::prelude::*;
1569
1570    proptest! {
1571        #[test]
1572        fn test_sign_verify_roundtrip(hash in any::<[u8; 32]>()) {
1573            let keypair = Secp256k1KeyPair::generate();
1574            let signature = keypair.sign(&hash).expect("signing should succeed");
1575
1576            // Verify signature using keypair's verify method
1577            prop_assert!(keypair.verify(&hash, &signature));
1578        }
1579
1580        #[test]
1581        fn test_recovery_id_is_valid(hash in any::<[u8; 32]>()) {
1582            let keypair = Secp256k1KeyPair::generate();
1583            let signature = keypair.sign(&hash).expect("signing should succeed");
1584
1585            // Recovery ID should always be 0 or 1
1586            prop_assert!(signature.recovery_id() <= 1);
1587        }
1588
1589        #[test]
1590        fn test_public_key_formats_consistent(seed in any::<[u8; 32]>()) {
1591            // Skip invalid seeds (zero)
1592            if seed == [0u8; 32] {
1593                return Ok(());
1594            }
1595
1596            if let Ok(keypair) = Secp256k1KeyPair::from_bytes(seed) {
1597                let pubkey = keypair.public_key();
1598
1599                // Compressed should start with 02 or 03
1600                let prefix = pubkey.compressed()[0];
1601                prop_assert!(prefix == 0x02 || prefix == 0x03);
1602
1603                // Uncompressed should start with 04
1604                prop_assert_eq!(pubkey.uncompressed()[0], 0x04);
1605
1606                // X coordinate should be the same in both formats
1607                prop_assert_eq!(&pubkey.compressed()[1..33], &pubkey.uncompressed()[1..33]);
1608            }
1609        }
1610    }
1611}