Skip to main content

txgate_crypto/
signer.rs

1//! High-level signing traits and implementations.
2//!
3//! This module provides the [`Signer`] trait for abstracting over different
4//! signing implementations, and concrete implementations for specific curves.
5//!
6//! # Supported Signers
7//!
8//! - [`Secp256k1Signer`] - For Ethereum, Bitcoin, Tron, and Ripple
9//! - [`Ed25519Signer`] - For Solana and other ed25519-based chains
10//!
11//! # Example
12//!
13//! ```rust
14//! use txgate_crypto::signer::{Signer, Secp256k1Signer, Chain};
15//!
16//! // Generate a new signer
17//! let signer = Secp256k1Signer::generate();
18//!
19//! // Get the Ethereum address
20//! let address = signer.address(Chain::Ethereum).expect("valid address");
21//! println!("Ethereum address: {address}");
22//!
23//! // Sign a message hash
24//! let hash = [0u8; 32]; // In practice, this would be a real hash
25//! let signature = signer.sign(&hash).expect("signing failed");
26//! assert_eq!(signature.len(), 65); // r || s || v
27//! ```
28
29use bitcoin::secp256k1::PublicKey as BitcoinPublicKey;
30use bitcoin::{Address, CompressedPublicKey, Network};
31use sha3::{Digest, Keccak256};
32
33use crate::keypair::{KeyPair, Secp256k1KeyPair};
34use txgate_core::error::SignError;
35
36// ============================================================================
37// Chain Enum
38// ============================================================================
39
40/// Supported blockchain networks for address derivation.
41///
42/// This enum represents the different blockchain networks that `TxGate` supports
43/// for address derivation. Each chain may have different address formats and
44/// derivation rules.
45///
46/// # Example
47///
48/// ```rust
49/// use txgate_crypto::signer::{Signer, Secp256k1Signer, Chain};
50///
51/// let signer = Secp256k1Signer::generate();
52///
53/// // Derive Ethereum address
54/// let eth_addr = signer.address(Chain::Ethereum).expect("valid");
55/// assert!(eth_addr.starts_with("0x"));
56///
57/// // Solana requires Ed25519, so this will fail
58/// let sol_result = signer.address(Chain::Solana);
59/// assert!(sol_result.is_err());
60/// ```
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
62pub enum Chain {
63    /// Ethereum and EVM-compatible chains (Polygon, BSC, Arbitrum, etc.)
64    Ethereum,
65    /// Bitcoin mainnet
66    Bitcoin,
67    /// Solana (requires Ed25519)
68    Solana,
69    /// Tron network (uses same curve as Ethereum but different address format)
70    Tron,
71    /// Ripple/XRP Ledger
72    Ripple,
73}
74
75impl std::fmt::Display for Chain {
76    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77        match self {
78            Self::Ethereum => write!(f, "ethereum"),
79            Self::Bitcoin => write!(f, "bitcoin"),
80            Self::Solana => write!(f, "solana"),
81            Self::Tron => write!(f, "tron"),
82            Self::Ripple => write!(f, "ripple"),
83        }
84    }
85}
86
87// ============================================================================
88// CurveType Enum
89// ============================================================================
90
91/// Elliptic curve types supported by `TxGate`.
92///
93/// This enum identifies the cryptographic curve used by a signer,
94/// which is important for ensuring compatibility with different blockchains.
95///
96/// # Curve Selection
97///
98/// - [`CurveType::Secp256k1`] - Used by Ethereum, Bitcoin, Tron, Ripple
99/// - [`CurveType::Ed25519`] - Used by Solana, NEAR, Cosmos (some chains)
100#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
101pub enum CurveType {
102    /// The secp256k1 curve used by Bitcoin, Ethereum, and related chains.
103    Secp256k1,
104    /// The Ed25519 curve used by Solana, NEAR, and some other chains.
105    Ed25519,
106}
107
108impl std::fmt::Display for CurveType {
109    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110        match self {
111            Self::Secp256k1 => write!(f, "secp256k1"),
112            Self::Ed25519 => write!(f, "ed25519"),
113        }
114    }
115}
116
117// ============================================================================
118// Signer Trait
119// ============================================================================
120
121/// Trait for transaction signing operations.
122///
123/// This trait provides a high-level interface for signing transaction hashes
124/// and deriving blockchain addresses from the underlying key pair.
125///
126/// # Thread Safety
127///
128/// All implementations must be `Send + Sync` to support multi-threaded
129/// signing operations.
130///
131/// # Signature Format
132///
133/// For secp256k1 signers, the signature is returned as 65 bytes:
134/// - `r` (32 bytes) - The R component of the ECDSA signature
135/// - `s` (32 bytes) - The S component of the ECDSA signature
136/// - `v` (1 byte) - The recovery ID (0 or 1)
137///
138/// For Ethereum transactions, you may need to adjust `v`:
139/// - Legacy transactions: `v = recovery_id + 27`
140/// - EIP-155: `v = recovery_id + 35 + chain_id * 2`
141/// - EIP-2930/EIP-1559: `v = recovery_id`
142///
143/// # Example
144///
145/// ```rust
146/// use txgate_crypto::signer::{Signer, Secp256k1Signer, Chain, CurveType};
147///
148/// fn sign_transaction<S: Signer>(signer: &S, hash: &[u8; 32]) -> Vec<u8> {
149///     signer.sign(hash).expect("signing failed")
150/// }
151///
152/// let signer = Secp256k1Signer::generate();
153/// assert_eq!(signer.curve(), CurveType::Secp256k1);
154///
155/// let hash = [0u8; 32];
156/// let sig = sign_transaction(&signer, &hash);
157/// assert_eq!(sig.len(), 65);
158/// ```
159pub trait Signer: Send + Sync {
160    /// Sign a 32-byte message hash.
161    ///
162    /// # Arguments
163    /// * `hash` - The pre-computed hash of the message/transaction to sign.
164    ///   This should be a cryptographic hash (e.g., Keccak-256 for Ethereum),
165    ///   NOT the raw message.
166    ///
167    /// # Returns
168    /// A signature that can be verified against this signer's public key.
169    /// For secp256k1, this is 65 bytes: `r || s || v` where `v` is the recovery ID.
170    ///
171    /// # Errors
172    /// Returns an error if signing fails (e.g., due to key corruption).
173    ///
174    /// # Example
175    ///
176    /// ```rust
177    /// use txgate_crypto::signer::{Signer, Secp256k1Signer};
178    /// use sha3::{Digest, Keccak256};
179    ///
180    /// let signer = Secp256k1Signer::generate();
181    ///
182    /// // Hash the message first
183    /// let message = b"Hello, Ethereum!";
184    /// let hash: [u8; 32] = Keccak256::digest(message).into();
185    ///
186    /// // Sign the hash
187    /// let signature = signer.sign(&hash).expect("signing failed");
188    /// assert_eq!(signature.len(), 65);
189    /// ```
190    fn sign(&self, hash: &[u8; 32]) -> Result<Vec<u8>, SignError>;
191
192    /// Get the public key bytes.
193    ///
194    /// Returns the compressed public key:
195    /// - secp256k1: 33 bytes (prefix + X coordinate)
196    /// - ed25519: 32 bytes
197    ///
198    /// # Example
199    ///
200    /// ```rust
201    /// use txgate_crypto::signer::{Signer, Secp256k1Signer};
202    ///
203    /// let signer = Secp256k1Signer::generate();
204    /// let pubkey = signer.public_key();
205    /// assert_eq!(pubkey.len(), 33); // Compressed secp256k1
206    /// ```
207    fn public_key(&self) -> &[u8];
208
209    /// Derive the blockchain address for a specific chain.
210    ///
211    /// # Arguments
212    /// * `chain` - The blockchain network to derive the address for.
213    ///
214    /// # Returns
215    /// The address as a string with the appropriate format for the chain:
216    /// - Ethereum: EIP-55 checksummed hex with `0x` prefix
217    /// - Bitcoin: `Base58Check` encoded P2PKH address
218    /// - Tron: `Base58Check` encoded with `0x41` prefix (starts with `T`)
219    /// - Solana: Base58 encoded (requires Ed25519)
220    /// - Ripple: `Base58Check` with Ripple alphabet
221    ///
222    /// # Errors
223    /// Returns an error if:
224    /// - The chain requires a different curve (e.g., Solana needs Ed25519)
225    /// - Address derivation is not yet implemented for the chain
226    ///
227    /// # Example
228    ///
229    /// ```rust
230    /// use txgate_crypto::signer::{Signer, Secp256k1Signer, Chain};
231    ///
232    /// let signer = Secp256k1Signer::generate();
233    ///
234    /// // Get Ethereum address (EIP-55 checksummed)
235    /// let address = signer.address(Chain::Ethereum).expect("valid");
236    /// assert!(address.starts_with("0x"));
237    /// assert_eq!(address.len(), 42); // 0x + 40 hex chars
238    /// ```
239    fn address(&self, chain: Chain) -> Result<String, SignError>;
240
241    /// Get the curve type used by this signer.
242    ///
243    /// This is useful for determining compatibility with different chains.
244    ///
245    /// # Example
246    ///
247    /// ```rust
248    /// use txgate_crypto::signer::{Signer, Secp256k1Signer, CurveType};
249    ///
250    /// let signer = Secp256k1Signer::generate();
251    /// assert_eq!(signer.curve(), CurveType::Secp256k1);
252    /// ```
253    fn curve(&self) -> CurveType;
254}
255
256// ============================================================================
257// Secp256k1Signer Implementation
258// ============================================================================
259
260/// Secp256k1 signer for Ethereum, Bitcoin, Tron, and Ripple.
261///
262/// This signer wraps a [`Secp256k1KeyPair`] and provides a high-level
263/// signing interface that returns recoverable signatures suitable for
264/// blockchain transactions.
265///
266/// # Signature Format
267///
268/// Signatures are returned as 65 bytes: `r (32) || s (32) || v (1)`
269/// where `v` is the raw recovery ID (0 or 1).
270///
271/// # Security
272///
273/// - The underlying key pair uses secure key material handling
274/// - Signatures are normalized to prevent malleability
275/// - Recovery IDs are computed correctly for `ecrecover`
276///
277/// # Example
278///
279/// ```rust
280/// use txgate_crypto::signer::{Signer, Secp256k1Signer, Chain};
281///
282/// // Generate a new signer
283/// let signer = Secp256k1Signer::generate();
284///
285/// // Or create from raw bytes
286/// let secret = [0x42u8; 32]; // Use real secret in production!
287/// let signer = Secp256k1Signer::from_bytes(secret).expect("valid key");
288///
289/// // Get the Ethereum address
290/// let address = signer.address(Chain::Ethereum).expect("valid");
291/// println!("Address: {address}");
292///
293/// // Sign a hash
294/// let hash = [0u8; 32];
295/// let signature = signer.sign(&hash).expect("signing failed");
296/// assert_eq!(signature.len(), 65);
297/// ```
298#[derive(Debug)]
299pub struct Secp256k1Signer {
300    /// The underlying key pair
301    key_pair: Secp256k1KeyPair,
302    /// Cached compressed public key bytes
303    public_key_bytes: [u8; 33],
304}
305
306impl Secp256k1Signer {
307    /// Create a new signer from a key pair.
308    ///
309    /// # Arguments
310    /// * `key_pair` - The secp256k1 key pair to use for signing.
311    ///
312    /// # Example
313    ///
314    /// ```rust
315    /// use txgate_crypto::keypair::{KeyPair, Secp256k1KeyPair};
316    /// use txgate_crypto::signer::Secp256k1Signer;
317    ///
318    /// let key_pair = Secp256k1KeyPair::generate();
319    /// let signer = Secp256k1Signer::new(key_pair);
320    /// ```
321    #[must_use]
322    pub fn new(key_pair: Secp256k1KeyPair) -> Self {
323        let public_key_bytes = *key_pair.public_key().compressed();
324        Self {
325            key_pair,
326            public_key_bytes,
327        }
328    }
329
330    /// Create a new signer with a randomly generated key.
331    ///
332    /// Uses a cryptographically secure random number generator.
333    ///
334    /// # Example
335    ///
336    /// ```rust
337    /// use txgate_crypto::signer::Secp256k1Signer;
338    ///
339    /// let signer = Secp256k1Signer::generate();
340    /// ```
341    #[must_use]
342    pub fn generate() -> Self {
343        Self::new(Secp256k1KeyPair::generate())
344    }
345
346    /// Create a signer from raw secret key bytes.
347    ///
348    /// # Arguments
349    /// * `bytes` - The 32-byte secret key material.
350    ///
351    /// # Errors
352    /// Returns an error if the bytes don't represent a valid secp256k1
353    /// secret key (e.g., zero or greater than the curve order).
354    ///
355    /// # Example
356    ///
357    /// ```rust
358    /// use txgate_crypto::signer::Secp256k1Signer;
359    ///
360    /// let secret = [0x42u8; 32];
361    /// let signer = Secp256k1Signer::from_bytes(secret).expect("valid key");
362    /// ```
363    pub fn from_bytes(bytes: [u8; 32]) -> Result<Self, SignError> {
364        let key_pair = Secp256k1KeyPair::from_bytes(bytes)?;
365        Ok(Self::new(key_pair))
366    }
367
368    /// Get a reference to the underlying key pair.
369    ///
370    /// This is useful when you need access to the full key pair functionality.
371    #[must_use]
372    pub const fn key_pair(&self) -> &Secp256k1KeyPair {
373        &self.key_pair
374    }
375
376    /// Derive the Ethereum address and return as EIP-55 checksummed string.
377    fn ethereum_address(&self) -> String {
378        let address = self.key_pair.public_key().ethereum_address();
379        to_eip55_checksum(&address)
380    }
381
382    /// Derive the Bitcoin P2WPKH (bech32) address for the specified network.
383    ///
384    /// P2WPKH addresses start with `bc1q` on mainnet and `tb1q` on testnet.
385    ///
386    /// # Arguments
387    /// * `network` - The Bitcoin network (mainnet, testnet, signet, regtest).
388    ///
389    /// # Returns
390    /// The bech32-encoded P2WPKH address.
391    fn bitcoin_p2wpkh_address(&self, network: Network) -> Result<String, SignError> {
392        // Get the compressed public key bytes (33 bytes)
393        let compressed = self.public_key_bytes;
394
395        // Create a bitcoin PublicKey from the compressed bytes
396        let bitcoin_pubkey = BitcoinPublicKey::from_slice(&compressed)
397            .map_err(|e| SignError::signature_failed(format!("Invalid public key: {e}")))?;
398
399        // Create CompressedPublicKey wrapper
400        let compressed_pubkey = CompressedPublicKey(bitcoin_pubkey);
401
402        // Create P2WPKH address
403        let address = Address::p2wpkh(&compressed_pubkey, network);
404
405        Ok(address.to_string())
406    }
407}
408
409impl Signer for Secp256k1Signer {
410    fn sign(&self, hash: &[u8; 32]) -> Result<Vec<u8>, SignError> {
411        let signature = self.key_pair.sign(hash)?;
412        // Return 65-byte recoverable signature: r || s || v
413        Ok(signature.to_recoverable_bytes().to_vec())
414    }
415
416    fn public_key(&self) -> &[u8] {
417        &self.public_key_bytes
418    }
419
420    fn address(&self, chain: Chain) -> Result<String, SignError> {
421        match chain {
422            Chain::Ethereum => Ok(self.ethereum_address()),
423            Chain::Bitcoin => {
424                // P2WPKH bech32 address (starts with bc1q)
425                self.bitcoin_p2wpkh_address(Network::Bitcoin)
426            }
427            Chain::Tron => {
428                // TRON uses same format as Ethereum but with T prefix and Base58Check
429                // TODO: Implement Tron address derivation
430                Err(SignError::signature_failed(
431                    "Tron address derivation not yet implemented",
432                ))
433            }
434            Chain::Ripple => {
435                // Ripple uses secp256k1 but with Base58Check using Ripple alphabet
436                // TODO: Implement Ripple address derivation
437                Err(SignError::signature_failed(
438                    "Ripple address derivation not yet implemented",
439                ))
440            }
441            Chain::Solana => {
442                // Solana requires Ed25519, not secp256k1
443                Err(SignError::wrong_curve("ed25519", "secp256k1"))
444            }
445        }
446    }
447
448    fn curve(&self) -> CurveType {
449        CurveType::Secp256k1
450    }
451}
452
453// ============================================================================
454// Ed25519Signer Implementation
455// ============================================================================
456
457/// Ed25519 signer for Solana and other ed25519-based chains.
458///
459/// This signer wraps an [`Ed25519KeyPair`](crate::keypair::Ed25519KeyPair) and provides
460/// a high-level signing interface suitable for blockchain transactions.
461///
462/// # Signature Format
463///
464/// Signatures are returned as 64 bytes: the standard ed25519 signature format.
465///
466/// # Security
467///
468/// - The underlying key pair uses secure key material handling
469/// - Uses ed25519-dalek for cryptographic operations
470///
471/// # Example
472///
473/// ```rust
474/// use txgate_crypto::signer::{Signer, Ed25519Signer, Chain};
475///
476/// // Generate a new signer
477/// let signer = Ed25519Signer::generate();
478///
479/// // Get the Solana address
480/// let address = signer.address(Chain::Solana).expect("valid");
481/// println!("Solana address: {address}");
482///
483/// // Sign a hash
484/// let hash = [0u8; 32];
485/// let signature = signer.sign(&hash).expect("signing failed");
486/// assert_eq!(signature.len(), 64);
487/// ```
488#[derive(Debug)]
489pub struct Ed25519Signer {
490    /// The underlying key pair
491    key_pair: crate::keypair::Ed25519KeyPair,
492    /// Cached public key bytes
493    public_key_bytes: [u8; 32],
494}
495
496impl Ed25519Signer {
497    /// Create a new signer from a key pair.
498    ///
499    /// # Arguments
500    /// * `key_pair` - The ed25519 key pair to use for signing.
501    ///
502    /// # Example
503    ///
504    /// ```rust
505    /// use txgate_crypto::keypair::{KeyPair, Ed25519KeyPair};
506    /// use txgate_crypto::signer::Ed25519Signer;
507    ///
508    /// let key_pair = Ed25519KeyPair::generate();
509    /// let signer = Ed25519Signer::new(key_pair);
510    /// ```
511    #[must_use]
512    pub fn new(key_pair: crate::keypair::Ed25519KeyPair) -> Self {
513        let public_key_bytes = *key_pair.public_key().as_bytes();
514        Self {
515            key_pair,
516            public_key_bytes,
517        }
518    }
519
520    /// Create a new signer with a randomly generated key.
521    ///
522    /// Uses a cryptographically secure random number generator.
523    ///
524    /// # Example
525    ///
526    /// ```rust
527    /// use txgate_crypto::signer::Ed25519Signer;
528    ///
529    /// let signer = Ed25519Signer::generate();
530    /// ```
531    #[must_use]
532    pub fn generate() -> Self {
533        Self::new(crate::keypair::Ed25519KeyPair::generate())
534    }
535
536    /// Create a signer from raw secret key bytes.
537    ///
538    /// # Arguments
539    /// * `bytes` - The 32-byte secret key material.
540    ///
541    /// # Errors
542    /// Returns an error if the bytes don't represent a valid ed25519 secret key.
543    ///
544    /// # Example
545    ///
546    /// ```rust
547    /// use txgate_crypto::signer::Ed25519Signer;
548    ///
549    /// let secret = [0x42u8; 32];
550    /// let signer = Ed25519Signer::from_bytes(secret).expect("valid key");
551    /// ```
552    pub fn from_bytes(bytes: [u8; 32]) -> Result<Self, SignError> {
553        let key_pair = crate::keypair::Ed25519KeyPair::from_bytes(bytes)?;
554        Ok(Self::new(key_pair))
555    }
556
557    /// Get a reference to the underlying key pair.
558    ///
559    /// This is useful when you need access to the full key pair functionality.
560    #[must_use]
561    pub const fn key_pair(&self) -> &crate::keypair::Ed25519KeyPair {
562        &self.key_pair
563    }
564
565    /// Derive the Solana address (base58-encoded public key).
566    fn solana_address(&self) -> String {
567        self.key_pair.public_key().solana_address()
568    }
569}
570
571impl Signer for Ed25519Signer {
572    fn sign(&self, hash: &[u8; 32]) -> Result<Vec<u8>, SignError> {
573        let signature = self.key_pair.sign(hash)?;
574        // Return 64-byte ed25519 signature
575        Ok(signature.as_ref().to_vec())
576    }
577
578    fn public_key(&self) -> &[u8] {
579        &self.public_key_bytes
580    }
581
582    fn address(&self, chain: Chain) -> Result<String, SignError> {
583        match chain {
584            Chain::Solana => Ok(self.solana_address()),
585            Chain::Ethereum | Chain::Bitcoin | Chain::Tron | Chain::Ripple => {
586                // These chains require secp256k1, not ed25519
587                Err(SignError::wrong_curve("secp256k1", "ed25519"))
588            }
589        }
590    }
591
592    fn curve(&self) -> CurveType {
593        CurveType::Ed25519
594    }
595}
596
597// ============================================================================
598// EIP-55 Address Checksum
599// ============================================================================
600
601/// Convert an Ethereum address to EIP-55 checksummed format.
602///
603/// EIP-55 uses a mixed-case hexadecimal encoding where the case of each
604/// letter encodes a checksum. This helps prevent typos when copying addresses.
605///
606/// # Algorithm
607///
608/// 1. Convert the address to lowercase hex (without `0x` prefix)
609/// 2. Hash the hex string with Keccak-256
610/// 3. For each character in the hex address:
611///    - If it's a digit (0-9), keep it as-is
612///    - If it's a letter (a-f), uppercase it if the corresponding nibble
613///      in the hash is >= 8
614///
615/// # Arguments
616/// * `address` - The 20-byte Ethereum address.
617///
618/// # Returns
619/// The EIP-55 checksummed address string with `0x` prefix.
620///
621/// # Example
622///
623/// ```ignore
624/// let address = hex::decode("5aaeb6053f3e94c9b9a09f33669435e7ef1beaed").unwrap();
625/// let checksummed = to_eip55_checksum(&address.try_into().unwrap());
626/// assert_eq!(checksummed, "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed");
627/// ```
628fn to_eip55_checksum(address: &[u8; 20]) -> String {
629    // Step 1: Convert to lowercase hex (without 0x prefix)
630    let hex_addr = hex::encode(address);
631
632    // Step 2: Hash the lowercase hex address
633    // Keccak256 always produces exactly 32 bytes
634    let hash: [u8; 32] = Keccak256::digest(hex_addr.as_bytes()).into();
635
636    // Step 3: Build the checksummed address
637    let mut checksummed = String::with_capacity(42);
638    checksummed.push_str("0x");
639
640    for (i, c) in hex_addr.chars().enumerate() {
641        if c.is_ascii_digit() {
642            // Digits don't have case, keep as-is
643            checksummed.push(c);
644        } else {
645            // For letters, check the corresponding nibble in the hash
646            // Each byte in the hash has two nibbles (4 bits each)
647            // Since hex_addr is exactly 40 chars (20 bytes * 2), i/2 ranges from 0 to 19
648            // The hash is 32 bytes, so this indexing is always safe
649            // We use get() for safety even though the bounds are guaranteed
650            let hash_byte = hash.get(i / 2).copied().unwrap_or(0);
651            let nibble = if i % 2 == 0 {
652                (hash_byte >> 4) & 0x0f // High nibble
653            } else {
654                hash_byte & 0x0f // Low nibble
655            };
656
657            // If nibble >= 8, uppercase the letter
658            if nibble >= 8 {
659                checksummed.push(c.to_ascii_uppercase());
660            } else {
661                checksummed.push(c);
662            }
663        }
664    }
665
666    checksummed
667}
668
669// ============================================================================
670// Tests
671// ============================================================================
672
673#[cfg(test)]
674mod tests {
675    #![allow(clippy::expect_used)]
676    #![allow(clippy::indexing_slicing)]
677    #![allow(clippy::panic)]
678    #![allow(clippy::uninlined_format_args)]
679
680    use super::*;
681
682    // ------------------------------------------------------------------------
683    // Chain Enum Tests
684    // ------------------------------------------------------------------------
685
686    #[test]
687    fn test_chain_display() {
688        assert_eq!(Chain::Ethereum.to_string(), "ethereum");
689        assert_eq!(Chain::Bitcoin.to_string(), "bitcoin");
690        assert_eq!(Chain::Solana.to_string(), "solana");
691        assert_eq!(Chain::Tron.to_string(), "tron");
692        assert_eq!(Chain::Ripple.to_string(), "ripple");
693    }
694
695    #[test]
696    fn test_chain_equality() {
697        assert_eq!(Chain::Ethereum, Chain::Ethereum);
698        assert_ne!(Chain::Ethereum, Chain::Bitcoin);
699    }
700
701    #[test]
702    fn test_chain_debug() {
703        let debug_output = format!("{:?}", Chain::Ethereum);
704        assert_eq!(debug_output, "Ethereum");
705    }
706
707    // ------------------------------------------------------------------------
708    // CurveType Enum Tests
709    // ------------------------------------------------------------------------
710
711    #[test]
712    fn test_curve_type_display() {
713        assert_eq!(CurveType::Secp256k1.to_string(), "secp256k1");
714        assert_eq!(CurveType::Ed25519.to_string(), "ed25519");
715    }
716
717    #[test]
718    fn test_curve_type_equality() {
719        assert_eq!(CurveType::Secp256k1, CurveType::Secp256k1);
720        assert_ne!(CurveType::Secp256k1, CurveType::Ed25519);
721    }
722
723    // ------------------------------------------------------------------------
724    // Secp256k1Signer Creation Tests
725    // ------------------------------------------------------------------------
726
727    #[test]
728    fn test_generate_creates_valid_signer() {
729        let signer = Secp256k1Signer::generate();
730
731        // Public key should be 33 bytes (compressed)
732        assert_eq!(signer.public_key().len(), 33);
733
734        // Curve type should be secp256k1
735        assert_eq!(signer.curve(), CurveType::Secp256k1);
736    }
737
738    #[test]
739    fn test_generate_produces_unique_signers() {
740        let signer1 = Secp256k1Signer::generate();
741        let signer2 = Secp256k1Signer::generate();
742
743        // Should generate different public keys
744        assert_ne!(signer1.public_key(), signer2.public_key());
745    }
746
747    #[test]
748    fn test_from_bytes_success() {
749        let bytes = [0x42u8; 32];
750        let result = Secp256k1Signer::from_bytes(bytes);
751        assert!(result.is_ok());
752    }
753
754    #[test]
755    fn test_from_bytes_invalid_zero() {
756        let bytes = [0u8; 32];
757        let result = Secp256k1Signer::from_bytes(bytes);
758        assert!(matches!(result, Err(SignError::InvalidKey)));
759    }
760
761    #[test]
762    fn test_from_bytes_deterministic() {
763        let bytes = [0x42u8; 32];
764
765        let signer1 = Secp256k1Signer::from_bytes(bytes).expect("valid key");
766        let signer2 = Secp256k1Signer::from_bytes(bytes).expect("valid key");
767
768        assert_eq!(signer1.public_key(), signer2.public_key());
769    }
770
771    #[test]
772    fn test_new_from_keypair() {
773        let key_pair = Secp256k1KeyPair::generate();
774        let expected_pubkey = *key_pair.public_key().compressed();
775
776        let signer = Secp256k1Signer::new(key_pair);
777
778        assert_eq!(signer.public_key(), &expected_pubkey);
779    }
780
781    // ------------------------------------------------------------------------
782    // Signing Tests
783    // ------------------------------------------------------------------------
784
785    #[test]
786    fn test_sign_produces_65_bytes() {
787        let signer = Secp256k1Signer::generate();
788        let hash = [0x42u8; 32];
789
790        let signature = signer.sign(&hash).expect("signing should succeed");
791
792        // Signature should be 65 bytes: r (32) || s (32) || v (1)
793        assert_eq!(signature.len(), 65);
794    }
795
796    #[test]
797    fn test_sign_recovery_id_valid() {
798        let signer = Secp256k1Signer::generate();
799        let hash = [0x42u8; 32];
800
801        let signature = signer.sign(&hash).expect("signing should succeed");
802
803        // Recovery ID (last byte) should be 0 or 1
804        let recovery_id = signature[64];
805        assert!(recovery_id == 0 || recovery_id == 1);
806    }
807
808    #[test]
809    fn test_different_hashes_produce_different_signatures() {
810        let signer = Secp256k1Signer::generate();
811        let hash1 = [0x42u8; 32];
812        let hash2 = [0x43u8; 32];
813
814        let sig1 = signer.sign(&hash1).expect("signing should succeed");
815        let sig2 = signer.sign(&hash2).expect("signing should succeed");
816
817        assert_ne!(sig1, sig2);
818    }
819
820    #[test]
821    fn test_sign_is_deterministic_with_same_key() {
822        // Note: ECDSA is NOT deterministic by default (uses random k).
823        // However, the signature components should be consistent with the keypair.
824        let bytes = [0x42u8; 32];
825        let signer = Secp256k1Signer::from_bytes(bytes).expect("valid key");
826        let hash = [0x42u8; 32];
827
828        // Sign twice with same key and hash
829        let sig1 = signer.sign(&hash).expect("signing should succeed");
830        let sig2 = signer.sign(&hash).expect("signing should succeed");
831
832        // Note: These may or may not be equal depending on k256's implementation
833        // k256 uses RFC 6979 deterministic nonces, so they should be equal
834        assert_eq!(sig1, sig2, "k256 uses RFC 6979 deterministic nonces");
835    }
836
837    // ------------------------------------------------------------------------
838    // Ethereum Address Tests
839    // ------------------------------------------------------------------------
840
841    #[test]
842    fn test_ethereum_address_format() {
843        let signer = Secp256k1Signer::generate();
844
845        let address = signer.address(Chain::Ethereum).expect("valid address");
846
847        // Should start with 0x
848        assert!(address.starts_with("0x"));
849
850        // Should be 42 characters (0x + 40 hex chars)
851        assert_eq!(address.len(), 42);
852    }
853
854    #[test]
855    fn test_ethereum_address_deterministic() {
856        let bytes = [0x42u8; 32];
857        let signer = Secp256k1Signer::from_bytes(bytes).expect("valid key");
858
859        let address1 = signer.address(Chain::Ethereum).expect("valid address");
860        let address2 = signer.address(Chain::Ethereum).expect("valid address");
861
862        assert_eq!(address1, address2);
863    }
864
865    /// Test EIP-55 checksum with a known test vector.
866    #[test]
867    fn test_eip55_checksum_known_vector() {
868        // Test vector from EIP-55 specification
869        // Address: 0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed
870        let address_hex = "5aaeb6053f3e94c9b9a09f33669435e7ef1beaed";
871        let mut address = [0u8; 20];
872        hex::decode_to_slice(address_hex, &mut address).expect("valid hex");
873
874        let checksummed = to_eip55_checksum(&address);
875
876        assert_eq!(checksummed, "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed");
877    }
878
879    /// Test EIP-55 checksum with all-lowercase address.
880    #[test]
881    fn test_eip55_checksum_all_lowercase() {
882        // fb6916095ca1df60bb79ce92ce3ea74c37c5d359
883        let address_hex = "fb6916095ca1df60bb79ce92ce3ea74c37c5d359";
884        let mut address = [0u8; 20];
885        hex::decode_to_slice(address_hex, &mut address).expect("valid hex");
886
887        let checksummed = to_eip55_checksum(&address);
888
889        assert_eq!(checksummed, "0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359");
890    }
891
892    /// Test EIP-55 checksum with another known vector.
893    #[test]
894    fn test_eip55_checksum_vector_2() {
895        // dbf03b407c01e7cd3cbea99509d93f8dddc8c6fb
896        let address_hex = "dbf03b407c01e7cd3cbea99509d93f8dddc8c6fb";
897        let mut address = [0u8; 20];
898        hex::decode_to_slice(address_hex, &mut address).expect("valid hex");
899
900        let checksummed = to_eip55_checksum(&address);
901
902        assert_eq!(checksummed, "0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB");
903    }
904
905    /// Test with a real Ethereum address from a known private key.
906    #[test]
907    fn test_ethereum_address_known_private_key() {
908        // This is a well-known test private key
909        // Private key: 0xfad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19
910        let private_key_hex = "fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19";
911        let mut private_key = [0u8; 32];
912        hex::decode_to_slice(private_key_hex, &mut private_key).expect("valid hex");
913
914        let signer = Secp256k1Signer::from_bytes(private_key).expect("valid key");
915        let address = signer.address(Chain::Ethereum).expect("valid address");
916
917        // The expected address (lowercase) is: 0x96216849c49358b10257cb55b28ea603c874b05e
918        // We need to verify the EIP-55 checksummed version
919        let expected_raw = "96216849c49358b10257cb55b28ea603c874b05e";
920        let mut expected_bytes = [0u8; 20];
921        hex::decode_to_slice(expected_raw, &mut expected_bytes).expect("valid hex");
922        let expected_checksummed = to_eip55_checksum(&expected_bytes);
923
924        assert_eq!(address, expected_checksummed);
925    }
926
927    // ------------------------------------------------------------------------
928    // Unsupported Chain Tests
929    // ------------------------------------------------------------------------
930
931    #[test]
932    fn test_bitcoin_address_format() {
933        let signer = Secp256k1Signer::generate();
934
935        let address = signer.address(Chain::Bitcoin).expect("valid address");
936
937        // P2WPKH mainnet addresses start with bc1q
938        assert!(
939            address.starts_with("bc1q"),
940            "Address should start with bc1q: {address}"
941        );
942
943        // P2WPKH addresses are 42 or 62 characters depending on format
944        // bc1q (4) + 38 characters = 42 for standard P2WPKH
945        assert_eq!(
946            address.len(),
947            42,
948            "P2WPKH address should be 42 characters: {address}"
949        );
950    }
951
952    #[test]
953    fn test_bitcoin_address_deterministic() {
954        let bytes = [0x42u8; 32];
955        let signer = Secp256k1Signer::from_bytes(bytes).expect("valid key");
956
957        let address1 = signer.address(Chain::Bitcoin).expect("valid address");
958        let address2 = signer.address(Chain::Bitcoin).expect("valid address");
959
960        assert_eq!(address1, address2);
961    }
962
963    #[test]
964    fn test_bitcoin_address_known_private_key() {
965        // Use a known test private key and verify the address
966        let private_key_hex = "fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19";
967        let mut private_key = [0u8; 32];
968        hex::decode_to_slice(private_key_hex, &mut private_key).expect("valid hex");
969
970        let signer = Secp256k1Signer::from_bytes(private_key).expect("valid key");
971        let address = signer.address(Chain::Bitcoin).expect("valid address");
972
973        // Verify it's a valid bech32 address
974        assert!(address.starts_with("bc1q"));
975        // The address should be deterministic - same key always produces same address
976        let address2 = signer.address(Chain::Bitcoin).expect("valid address");
977        assert_eq!(address, address2);
978    }
979
980    #[test]
981    fn test_tron_address_not_implemented() {
982        let signer = Secp256k1Signer::generate();
983
984        let result = signer.address(Chain::Tron);
985
986        assert!(result.is_err());
987        match result {
988            Err(SignError::SignatureFailed { context }) => {
989                assert!(context.contains("Tron"));
990            }
991            _ => panic!("Expected SignatureFailed error"),
992        }
993    }
994
995    #[test]
996    fn test_ripple_address_not_implemented() {
997        let signer = Secp256k1Signer::generate();
998
999        let result = signer.address(Chain::Ripple);
1000
1001        assert!(result.is_err());
1002        match result {
1003            Err(SignError::SignatureFailed { context }) => {
1004                assert!(context.contains("Ripple"));
1005            }
1006            _ => panic!("Expected SignatureFailed error"),
1007        }
1008    }
1009
1010    #[test]
1011    fn test_solana_address_wrong_curve() {
1012        let signer = Secp256k1Signer::generate();
1013
1014        let result = signer.address(Chain::Solana);
1015
1016        assert!(result.is_err());
1017        match result {
1018            Err(SignError::WrongCurve { expected, actual }) => {
1019                assert_eq!(expected, "ed25519");
1020                assert_eq!(actual, "secp256k1");
1021            }
1022            _ => panic!("Expected WrongCurve error"),
1023        }
1024    }
1025
1026    // ------------------------------------------------------------------------
1027    // Thread Safety Tests
1028    // ------------------------------------------------------------------------
1029
1030    #[test]
1031    fn test_signer_is_send_sync() {
1032        fn assert_send_sync<T: Send + Sync>() {}
1033        assert_send_sync::<Secp256k1Signer>();
1034    }
1035
1036    #[test]
1037    fn test_signer_trait_object_is_send_sync() {
1038        fn assert_send_sync<T: Send + Sync + ?Sized>() {}
1039        assert_send_sync::<dyn Signer>();
1040    }
1041
1042    // ------------------------------------------------------------------------
1043    // Debug Output Tests
1044    // ------------------------------------------------------------------------
1045
1046    #[test]
1047    fn test_signer_debug_output() {
1048        let signer = Secp256k1Signer::generate();
1049        let debug_output = format!("{:?}", signer);
1050
1051        // Should contain the struct name
1052        assert!(debug_output.contains("Secp256k1Signer"));
1053        // Should not expose the private key (inherits from KeyPair's Debug)
1054    }
1055
1056    // ------------------------------------------------------------------------
1057    // Key Pair Access Tests
1058    // ------------------------------------------------------------------------
1059
1060    #[test]
1061    fn test_key_pair_accessor() {
1062        let bytes = [0x42u8; 32];
1063        let signer = Secp256k1Signer::from_bytes(bytes).expect("valid key");
1064
1065        let key_pair = signer.key_pair();
1066
1067        // Should be able to access the underlying key pair
1068        assert_eq!(key_pair.public_key().compressed(), signer.public_key());
1069    }
1070
1071    // ------------------------------------------------------------------------
1072    // EIP-55 Checksum Edge Cases
1073    // ------------------------------------------------------------------------
1074
1075    #[test]
1076    fn test_eip55_checksum_all_digits_address() {
1077        // Address with only digits (no letters to case-transform)
1078        let address_hex = "1234567890123456789012345678901234567890";
1079        let mut address = [0u8; 20];
1080        hex::decode_to_slice(address_hex, &mut address).expect("valid hex");
1081
1082        let checksummed = to_eip55_checksum(&address);
1083
1084        // Should still be all lowercase digits with 0x prefix
1085        assert!(checksummed.starts_with("0x"));
1086        assert_eq!(checksummed.len(), 42);
1087        assert!(checksummed[2..].chars().all(|c| c.is_ascii_digit()));
1088    }
1089
1090    #[test]
1091    fn test_eip55_checksum_boundary_nibbles() {
1092        // Test addresses that exercise boundary conditions in nibble extraction
1093        // High nibbles (even indices) and low nibbles (odd indices)
1094        let test_vectors = vec![
1095            // Address designed to test nibble extraction at different positions
1096            ("0000000000000000000000000000000000000000", 42),
1097            ("ffffffffffffffffffffffffffffffffffffffff", 42),
1098            ("abcdefabcdefabcdefabcdefabcdefabcdefabcd", 42),
1099        ];
1100
1101        for (hex_addr, expected_len) in test_vectors {
1102            let mut address = [0u8; 20];
1103            hex::decode_to_slice(hex_addr, &mut address).expect("valid hex");
1104
1105            let checksummed = to_eip55_checksum(&address);
1106            assert_eq!(checksummed.len(), expected_len);
1107            assert!(checksummed.starts_with("0x"));
1108        }
1109    }
1110
1111    #[test]
1112    fn test_eip55_checksum_hash_byte_boundary() {
1113        // Test that we correctly handle hash byte extraction at i/2
1114        // This exercises the hash.get(i/2) logic
1115        for i in 0..20 {
1116            let mut address = [0u8; 20];
1117            address[i] = 0xAB; // Mix of letters to test case conversion
1118
1119            let checksummed = to_eip55_checksum(&address);
1120            assert_eq!(checksummed.len(), 42);
1121            assert!(checksummed.starts_with("0x"));
1122        }
1123    }
1124
1125    #[test]
1126    fn test_eip55_checksum_even_odd_indices() {
1127        // Specifically test even and odd character indices for nibble extraction
1128        let address_hex = "aabbccddeeff00112233445566778899aabbccdd";
1129        let mut address = [0u8; 20];
1130        hex::decode_to_slice(address_hex, &mut address).expect("valid hex");
1131
1132        let checksummed = to_eip55_checksum(&address);
1133
1134        // Verify format is correct
1135        assert_eq!(checksummed.len(), 42);
1136        assert!(checksummed.starts_with("0x"));
1137
1138        // Verify all characters after 0x are valid hex
1139        assert!(checksummed[2..].chars().all(|c| c.is_ascii_hexdigit()));
1140    }
1141
1142    // ------------------------------------------------------------------------
1143    // Signature Verification Roundtrip Tests
1144    // ------------------------------------------------------------------------
1145
1146    #[test]
1147    fn test_sign_and_verify_with_keypair() {
1148        let bytes = [0x42u8; 32];
1149        let signer = Secp256k1Signer::from_bytes(bytes).expect("valid key");
1150        let hash = [0x42u8; 32];
1151
1152        let signature = signer.sign(&hash).expect("signing should succeed");
1153
1154        // Extract the signature without recovery ID for verification
1155        let sig_bytes: [u8; 64] = signature[..64].try_into().expect("64 bytes");
1156        let sig = crate::keypair::Secp256k1Signature::from_bytes_and_recovery_id(
1157            sig_bytes,
1158            signature[64],
1159        );
1160
1161        // Verify using the key pair
1162        assert!(signer.key_pair().verify(&hash, &sig));
1163    }
1164
1165    // ------------------------------------------------------------------------
1166    // Ed25519Signer Creation Tests
1167    // ------------------------------------------------------------------------
1168
1169    #[test]
1170    fn test_ed25519_generate_creates_valid_signer() {
1171        let signer = Ed25519Signer::generate();
1172
1173        // Public key should be 32 bytes
1174        assert_eq!(signer.public_key().len(), 32);
1175
1176        // Curve type should be ed25519
1177        assert_eq!(signer.curve(), CurveType::Ed25519);
1178    }
1179
1180    #[test]
1181    fn test_ed25519_generate_produces_unique_signers() {
1182        let signer1 = Ed25519Signer::generate();
1183        let signer2 = Ed25519Signer::generate();
1184
1185        // Should generate different public keys
1186        assert_ne!(signer1.public_key(), signer2.public_key());
1187    }
1188
1189    #[test]
1190    fn test_ed25519_from_bytes_success() {
1191        let bytes = [0x42u8; 32];
1192        let result = Ed25519Signer::from_bytes(bytes);
1193        assert!(result.is_ok());
1194    }
1195
1196    #[test]
1197    fn test_ed25519_from_bytes_deterministic() {
1198        let bytes = [0x42u8; 32];
1199
1200        let signer1 = Ed25519Signer::from_bytes(bytes).expect("valid key");
1201        let signer2 = Ed25519Signer::from_bytes(bytes).expect("valid key");
1202
1203        assert_eq!(signer1.public_key(), signer2.public_key());
1204    }
1205
1206    #[test]
1207    fn test_ed25519_new_from_keypair() {
1208        let key_pair = crate::keypair::Ed25519KeyPair::generate();
1209        let expected_pubkey = *key_pair.public_key().as_bytes();
1210
1211        let signer = Ed25519Signer::new(key_pair);
1212
1213        assert_eq!(signer.public_key(), &expected_pubkey);
1214    }
1215
1216    // ------------------------------------------------------------------------
1217    // Ed25519 Signing Tests
1218    // ------------------------------------------------------------------------
1219
1220    #[test]
1221    fn test_ed25519_sign_produces_64_bytes() {
1222        let signer = Ed25519Signer::generate();
1223        let hash = [0x42u8; 32];
1224
1225        let signature = signer.sign(&hash).expect("signing should succeed");
1226
1227        // Ed25519 signature should be 64 bytes
1228        assert_eq!(signature.len(), 64);
1229    }
1230
1231    #[test]
1232    fn test_ed25519_different_hashes_produce_different_signatures() {
1233        let signer = Ed25519Signer::generate();
1234        let hash1 = [0x42u8; 32];
1235        let hash2 = [0x43u8; 32];
1236
1237        let sig1 = signer.sign(&hash1).expect("signing should succeed");
1238        let sig2 = signer.sign(&hash2).expect("signing should succeed");
1239
1240        assert_ne!(sig1, sig2);
1241    }
1242
1243    #[test]
1244    fn test_ed25519_sign_is_deterministic() {
1245        // Ed25519 signatures are deterministic (no random k)
1246        let bytes = [0x42u8; 32];
1247        let signer = Ed25519Signer::from_bytes(bytes).expect("valid key");
1248        let hash = [0x42u8; 32];
1249
1250        let sig1 = signer.sign(&hash).expect("signing should succeed");
1251        let sig2 = signer.sign(&hash).expect("signing should succeed");
1252
1253        assert_eq!(sig1, sig2, "ed25519 signatures should be deterministic");
1254    }
1255
1256    // ------------------------------------------------------------------------
1257    // Ed25519 Solana Address Tests
1258    // ------------------------------------------------------------------------
1259
1260    #[test]
1261    fn test_ed25519_solana_address_format() {
1262        let signer = Ed25519Signer::generate();
1263
1264        let address = signer.address(Chain::Solana).expect("valid address");
1265
1266        // Solana addresses are base58 encoded, typically 32-44 characters
1267        assert!(
1268            address.len() >= 32 && address.len() <= 44,
1269            "Solana address should be 32-44 characters: {address}"
1270        );
1271    }
1272
1273    #[test]
1274    fn test_ed25519_solana_address_deterministic() {
1275        let bytes = [0x42u8; 32];
1276        let signer = Ed25519Signer::from_bytes(bytes).expect("valid key");
1277
1278        let address1 = signer.address(Chain::Solana).expect("valid address");
1279        let address2 = signer.address(Chain::Solana).expect("valid address");
1280
1281        assert_eq!(address1, address2);
1282    }
1283
1284    // ------------------------------------------------------------------------
1285    // Ed25519 Wrong Curve Tests
1286    // ------------------------------------------------------------------------
1287
1288    #[test]
1289    fn test_ed25519_ethereum_address_wrong_curve() {
1290        let signer = Ed25519Signer::generate();
1291
1292        let result = signer.address(Chain::Ethereum);
1293
1294        assert!(result.is_err());
1295        match result {
1296            Err(SignError::WrongCurve { expected, actual }) => {
1297                assert_eq!(expected, "secp256k1");
1298                assert_eq!(actual, "ed25519");
1299            }
1300            _ => panic!("Expected WrongCurve error"),
1301        }
1302    }
1303
1304    #[test]
1305    fn test_ed25519_bitcoin_address_wrong_curve() {
1306        let signer = Ed25519Signer::generate();
1307
1308        let result = signer.address(Chain::Bitcoin);
1309
1310        assert!(result.is_err());
1311        match result {
1312            Err(SignError::WrongCurve { expected, actual }) => {
1313                assert_eq!(expected, "secp256k1");
1314                assert_eq!(actual, "ed25519");
1315            }
1316            _ => panic!("Expected WrongCurve error"),
1317        }
1318    }
1319
1320    #[test]
1321    fn test_ed25519_tron_address_wrong_curve() {
1322        let signer = Ed25519Signer::generate();
1323
1324        let result = signer.address(Chain::Tron);
1325
1326        assert!(result.is_err());
1327        match result {
1328            Err(SignError::WrongCurve { expected, actual }) => {
1329                assert_eq!(expected, "secp256k1");
1330                assert_eq!(actual, "ed25519");
1331            }
1332            _ => panic!("Expected WrongCurve error"),
1333        }
1334    }
1335
1336    #[test]
1337    fn test_ed25519_ripple_address_wrong_curve() {
1338        let signer = Ed25519Signer::generate();
1339
1340        let result = signer.address(Chain::Ripple);
1341
1342        assert!(result.is_err());
1343        match result {
1344            Err(SignError::WrongCurve { expected, actual }) => {
1345                assert_eq!(expected, "secp256k1");
1346                assert_eq!(actual, "ed25519");
1347            }
1348            _ => panic!("Expected WrongCurve error"),
1349        }
1350    }
1351
1352    // ------------------------------------------------------------------------
1353    // Ed25519 Thread Safety Tests
1354    // ------------------------------------------------------------------------
1355
1356    #[test]
1357    fn test_ed25519_signer_is_send_sync() {
1358        fn assert_send_sync<T: Send + Sync>() {}
1359        assert_send_sync::<Ed25519Signer>();
1360    }
1361
1362    // ------------------------------------------------------------------------
1363    // Ed25519 Debug and Key Pair Access Tests
1364    // ------------------------------------------------------------------------
1365
1366    #[test]
1367    fn test_ed25519_signer_debug_output() {
1368        let signer = Ed25519Signer::generate();
1369        let debug_output = format!("{:?}", signer);
1370
1371        // Should contain the struct name
1372        assert!(debug_output.contains("Ed25519Signer"));
1373    }
1374
1375    #[test]
1376    fn test_ed25519_key_pair_accessor() {
1377        let bytes = [0x42u8; 32];
1378        let signer = Ed25519Signer::from_bytes(bytes).expect("valid key");
1379
1380        let key_pair = signer.key_pair();
1381
1382        // Should be able to access the underlying key pair
1383        assert_eq!(key_pair.public_key().as_bytes(), signer.public_key());
1384    }
1385
1386    // ------------------------------------------------------------------------
1387    // Ed25519 Sign and Verify Roundtrip
1388    // ------------------------------------------------------------------------
1389
1390    #[test]
1391    fn test_ed25519_sign_and_verify_with_keypair() {
1392        let bytes = [0x42u8; 32];
1393        let signer = Ed25519Signer::from_bytes(bytes).expect("valid key");
1394        let hash = [0x42u8; 32];
1395
1396        let signature = signer.sign(&hash).expect("signing should succeed");
1397
1398        // Convert signature bytes to Ed25519Signature
1399        let sig_bytes: [u8; 64] = signature.try_into().expect("64 bytes");
1400        let sig = crate::keypair::Ed25519Signature::from_bytes(sig_bytes);
1401
1402        // Verify using the key pair
1403        assert!(signer.key_pair().verify(&hash, &sig));
1404    }
1405}
1406
1407#[cfg(test)]
1408mod proptest_tests {
1409    #![allow(clippy::expect_used)]
1410    #![allow(clippy::indexing_slicing)]
1411
1412    use super::*;
1413    use proptest::prelude::*;
1414
1415    proptest! {
1416        #[test]
1417        fn test_sign_always_produces_65_bytes(hash in any::<[u8; 32]>()) {
1418            let signer = Secp256k1Signer::generate();
1419            let signature = signer.sign(&hash).expect("signing should succeed");
1420            prop_assert_eq!(signature.len(), 65);
1421        }
1422
1423        #[test]
1424        fn test_recovery_id_always_valid(hash in any::<[u8; 32]>()) {
1425            let signer = Secp256k1Signer::generate();
1426            let signature = signer.sign(&hash).expect("signing should succeed");
1427            let recovery_id = signature[64];
1428            prop_assert!(recovery_id <= 1);
1429        }
1430
1431        #[test]
1432        fn test_ethereum_address_always_valid_format(seed in any::<[u8; 32]>()) {
1433            // Skip invalid seeds
1434            if seed == [0u8; 32] {
1435                return Ok(());
1436            }
1437
1438            if let Ok(signer) = Secp256k1Signer::from_bytes(seed) {
1439                let address = signer.address(Chain::Ethereum).expect("valid address");
1440
1441                // Should start with 0x
1442                prop_assert!(address.starts_with("0x"));
1443
1444                // Should be 42 characters
1445                prop_assert_eq!(address.len(), 42);
1446
1447                // Remaining characters should be valid hex
1448                prop_assert!(address[2..].chars().all(|c| c.is_ascii_hexdigit()));
1449            }
1450        }
1451    }
1452}