openzeppelin_relayer/services/signer/evm/
mod.rs

1//! # EVM Signer Implementations
2//!
3//! This module provides various signer implementations for Ethereum Virtual Machine (EVM)
4//! transactions and data signing, including support for EIP-712 typed data.
5//!
6//! ## Architecture
7//!
8//! ```text
9//! EVM Signer (trait implementations)
10//!   ├── LocalSigner             - Encrypted JSON keystore (development/testing)
11//!   ├── AwsKmsSigner           - AWS Key Management Service
12//!   ├── GoogleCloudKmsSigner   - Google Cloud Key Management Service
13//!   ├── VaultSigner            - HashiCorp Vault KV2 backend
14//!   ├── TurnkeySigner          - Turnkey API backend
15//!   └── CdpSigner              - Coinbase Developer Platform
16//! ```
17//!
18//! ## Features
19//!
20//! - **Transaction Signing**: Support for both legacy and EIP-1559 transactions
21//! - **Data Signing**: EIP-191 personal message signing
22//! - **Typed Data**: EIP-712 structured data signing
23//! - **Multi-Backend**: Pluggable signer backends with consistent API
24//!
25//! ## Security
26//!
27//! All implementations follow Ethereum signing standards and include:
28//! - Signature malleability protection (EIP-2 low-s normalization)
29//! - Proper v-value handling for different transaction types
30//! - Input validation and error handling
31
32mod aws_kms_signer;
33mod cdp_signer;
34mod google_cloud_kms_signer;
35mod local_signer;
36mod turnkey_signer;
37pub(crate) mod utils;
38mod vault_signer;
39use aws_kms_signer::*;
40use cdp_signer::*;
41use google_cloud_kms_signer::*;
42use local_signer::*;
43use oz_keystore::HashicorpCloudClient;
44use turnkey_signer::*;
45use vault_signer::*;
46
47use async_trait::async_trait;
48use std::sync::Arc;
49
50use crate::{
51    domain::{
52        SignDataRequest, SignDataResponse, SignDataResponseEvm, SignTransactionResponse,
53        SignTypedDataRequest,
54    },
55    models::{
56        Address, NetworkTransactionData, Signer as SignerDomainModel, SignerConfig,
57        SignerRepoModel, SignerType, TransactionRepoModel, VaultSignerConfig,
58    },
59    services::{
60        signer::Signer,
61        signer::SignerError,
62        signer::SignerFactoryError,
63        turnkey::TurnkeyService,
64        vault::{VaultConfig, VaultService, VaultServiceTrait},
65        AwsKmsService, CdpService, GoogleCloudKmsService, TurnkeyServiceTrait,
66    },
67};
68use eyre::Result;
69
70// EIP-712 and ECDSA Constants
71const EIP712_PREFIX: [u8; 2] = [0x19, 0x01];
72const EIP712_MESSAGE_SIZE: usize = 66; // 2 (prefix) + 32 (domain) + 32 (struct)
73
74/// SECP256K1 signature length: 32 bytes (r) + 32 bytes (s) + 1 byte (v)
75const SECP256K1_SIGNATURE_LENGTH: usize = 65;
76
77/// Keccak256 hash output length
78const HASH_LENGTH: usize = 32;
79
80/// Validates and decodes a hex string
81///
82/// # Arguments
83/// * `value` - The hex string to decode (may have optional "0x" prefix)
84/// * `field_name` - Name of the field for error messages
85///
86/// # Returns
87/// * `Ok(Vec<u8>)` - The decoded bytes
88/// * `Err(SignerError)` - If the hex string is invalid
89fn validate_and_decode_hex(value: &str, field_name: &str) -> Result<Vec<u8>, SignerError> {
90    let hex_str = value.strip_prefix("0x").unwrap_or(value);
91
92    // Check for invalid characters and report position
93    if let Some((pos, ch)) = hex_str
94        .chars()
95        .enumerate()
96        .find(|(_, c)| !c.is_ascii_hexdigit())
97    {
98        return Err(SignerError::SigningError(format!(
99            "Invalid {} hex: non-hexadecimal character '{}' at position {} (input: {}...)",
100            field_name,
101            ch,
102            pos,
103            &hex_str[..hex_str.len().min(16)] // Show first 16 chars
104        )));
105    }
106
107    // Decode the hex string
108    hex::decode(hex_str).map_err(|e| {
109        SignerError::SigningError(format!(
110            "Invalid {} hex: failed to decode - {} (input: {}...)",
111            field_name,
112            e,
113            &hex_str[..hex_str.len().min(16)]
114        ))
115    })
116}
117
118/// Constructs an EIP-712 message hash from domain separator and struct hash
119///
120/// This function implements the EIP-712 typed data signing specification:
121/// <https://eips.ethereum.org/EIPS/eip-712>
122///
123/// # EIP-712 Format
124///
125/// The message hash is constructed as:
126/// ```text
127/// keccak256("\x19\x01" ‖ domainSeparator ‖ hashStruct(message))
128/// ```
129///
130/// # Security Considerations
131///
132/// **CRITICAL SECURITY REQUIREMENTS:**
133///
134/// - **Domain Separator Uniqueness**: The domain separator MUST be unique to your dapp
135///   to prevent cross-contract replay attacks. Include at minimum:
136///   - Contract name
137///   - Contract version
138///   - Chain ID (prevents cross-chain replays)
139///   - Verifying contract address
140///
141/// - **Hash Struct Integrity**: The hashStruct MUST uniquely identify your message type
142///   and data. Collisions allow signature reuse across different messages.
143///
144/// - **Replay Protection**: Always include nonces or timestamps in your message struct
145///   to prevent replay attacks within the same contract.
146///
147/// - **Chain ID**: MUST be included in domain separator to prevent signatures from
148///   being valid on multiple chains (mainnet, testnet, etc.)
149///
150/// # Arguments
151///
152/// * `request` - The typed data signing request containing:
153///   - `domain_separator`: 32-byte hash uniquely identifying the signing domain
154///   - `hash_struct_message`: 32-byte hash of the structured message data
155///
156/// # Returns
157///
158/// * `Ok([u8; 32])` - The 32-byte message hash ready for signing
159/// * `Err(SignerError)` - If validation fails or hex decoding fails
160///
161/// # Errors
162///
163/// Returns `SignerError::SigningError` if:
164/// - Domain separator is not exactly 32 bytes
165/// - Hash struct is not exactly 32 bytes
166/// - Input contains invalid hexadecimal characters
167///
168/// # Examples
169///
170/// ```ignore
171/// use crate::domain::SignTypedDataRequest;
172/// use crate::services::signer::evm::construct_eip712_message_hash;
173///
174/// let request = SignTypedDataRequest {
175///     // 32 bytes as hex (with or without 0x prefix)
176///     domain_separator: "a".repeat(64),
177///     hash_struct_message: "b".repeat(64),
178/// };
179///
180/// let hash = construct_eip712_message_hash(&request)?;
181/// // hash is now ready for signing
182/// ```
183pub fn construct_eip712_message_hash(
184    request: &SignTypedDataRequest,
185) -> Result<[u8; 32], SignerError> {
186    // Decode and validate domain separator
187    let domain_separator = validate_and_decode_hex(&request.domain_separator, "domain separator")?;
188
189    // Decode and validate hash struct message
190    let hash_struct = validate_and_decode_hex(&request.hash_struct_message, "hash struct message")?;
191
192    // Validate lengths (both must be exactly 32 bytes)
193    if domain_separator.len() != HASH_LENGTH {
194        return Err(SignerError::SigningError(format!(
195            "Invalid domain separator length: expected {} bytes, got {}",
196            HASH_LENGTH,
197            domain_separator.len()
198        )));
199    }
200    if hash_struct.len() != HASH_LENGTH {
201        return Err(SignerError::SigningError(format!(
202            "Invalid hash struct length: expected {} bytes, got {}",
203            HASH_LENGTH,
204            hash_struct.len()
205        )));
206    }
207
208    // Construct EIP-712 message: "\x19\x01" ++ domainSeparator ++ hashStruct(message)
209    // Use fixed-size array instead of Vec for better performance (size known at compile time)
210    let mut eip712_message = [0u8; EIP712_MESSAGE_SIZE];
211    eip712_message[0..2].copy_from_slice(&EIP712_PREFIX);
212    eip712_message[2..34].copy_from_slice(&domain_separator);
213    eip712_message[34..66].copy_from_slice(&hash_struct);
214
215    // Hash the EIP-712 message
216    use alloy::primitives::keccak256;
217    let message_hash = keccak256(eip712_message);
218
219    Ok(message_hash.into())
220}
221
222/// Validates signature length and formats it into a SignDataResponse::Evm
223///
224/// This helper eliminates duplication across all EVM signer implementations by providing
225/// a single point for signature validation and formatting.
226///
227/// # Arguments
228/// * `signature_bytes` - The raw signature bytes (expected to be 65 bytes: r + s + v)
229/// * `signer_name` - Name of the signer for error messages (e.g., "AWS KMS", "Turnkey")
230///
231/// # Returns
232/// * `Ok(SignDataResponse)` - A properly formatted EVM signature response
233/// * `Err(SignerError)` - If the signature length is not 65 bytes
234///
235/// # Format
236/// The signature is split into:
237/// - `r`: First 32 bytes (hex encoded)
238/// - `s`: Next 32 bytes (hex encoded)
239/// - `v`: Last byte (recovery ID, typically 27 or 28)
240/// - `sig`: Full 65 bytes (hex encoded)
241pub(crate) fn validate_and_format_signature(
242    signature_bytes: &[u8],
243    signer_name: &str,
244) -> Result<SignDataResponse, SignerError> {
245    if signature_bytes.len() != SECP256K1_SIGNATURE_LENGTH {
246        return Err(SignerError::SigningError(format!(
247            "Invalid signature length from {}: expected {} bytes, got {}",
248            signer_name,
249            SECP256K1_SIGNATURE_LENGTH,
250            signature_bytes.len()
251        )));
252    }
253
254    let r = hex::encode(&signature_bytes[0..32]);
255    let s = hex::encode(&signature_bytes[32..64]);
256    let v = signature_bytes[64];
257
258    Ok(SignDataResponse::Evm(SignDataResponseEvm {
259        r,
260        s,
261        v,
262        sig: hex::encode(signature_bytes),
263    }))
264}
265
266#[async_trait]
267pub trait DataSignerTrait: Send + Sync {
268    /// Signs arbitrary message data
269    async fn sign_data(&self, request: SignDataRequest) -> Result<SignDataResponse, SignerError>;
270
271    /// Signs EIP-712 typed data
272    async fn sign_typed_data(
273        &self,
274        request: SignTypedDataRequest,
275    ) -> Result<SignDataResponse, SignerError>;
276}
277
278pub enum EvmSigner {
279    Local(LocalSigner),
280    Vault(VaultSigner<VaultService>),
281    Turnkey(TurnkeySigner),
282    Cdp(CdpSigner),
283    AwsKms(AwsKmsSigner),
284    GoogleCloudKms(GoogleCloudKmsSigner),
285}
286
287#[async_trait]
288impl Signer for EvmSigner {
289    async fn address(&self) -> Result<Address, SignerError> {
290        match self {
291            Self::Local(signer) => signer.address().await,
292            Self::Vault(signer) => signer.address().await,
293            Self::Turnkey(signer) => signer.address().await,
294            Self::Cdp(signer) => signer.address().await,
295            Self::AwsKms(signer) => signer.address().await,
296            Self::GoogleCloudKms(signer) => signer.address().await,
297        }
298    }
299
300    async fn sign_transaction(
301        &self,
302        transaction: NetworkTransactionData,
303    ) -> Result<SignTransactionResponse, SignerError> {
304        match self {
305            Self::Local(signer) => signer.sign_transaction(transaction).await,
306            Self::Vault(signer) => signer.sign_transaction(transaction).await,
307            Self::Turnkey(signer) => signer.sign_transaction(transaction).await,
308            Self::Cdp(signer) => signer.sign_transaction(transaction).await,
309            Self::AwsKms(signer) => signer.sign_transaction(transaction).await,
310            Self::GoogleCloudKms(signer) => signer.sign_transaction(transaction).await,
311        }
312    }
313}
314
315#[async_trait]
316impl DataSignerTrait for EvmSigner {
317    async fn sign_data(&self, request: SignDataRequest) -> Result<SignDataResponse, SignerError> {
318        match self {
319            Self::Local(signer) => signer.sign_data(request).await,
320            Self::Vault(signer) => signer.sign_data(request).await,
321            Self::Turnkey(signer) => signer.sign_data(request).await,
322            Self::Cdp(signer) => signer.sign_data(request).await,
323            Self::AwsKms(signer) => signer.sign_data(request).await,
324            Self::GoogleCloudKms(signer) => signer.sign_data(request).await,
325        }
326    }
327
328    async fn sign_typed_data(
329        &self,
330        request: SignTypedDataRequest,
331    ) -> Result<SignDataResponse, SignerError> {
332        match self {
333            Self::Local(signer) => signer.sign_typed_data(request).await,
334            Self::Vault(signer) => signer.sign_typed_data(request).await,
335            Self::Turnkey(signer) => signer.sign_typed_data(request).await,
336            Self::Cdp(signer) => signer.sign_typed_data(request).await,
337            Self::AwsKms(signer) => signer.sign_typed_data(request).await,
338            Self::GoogleCloudKms(signer) => signer.sign_typed_data(request).await,
339        }
340    }
341}
342
343pub struct EvmSignerFactory;
344
345impl EvmSignerFactory {
346    pub async fn create_evm_signer(
347        signer_model: SignerDomainModel,
348    ) -> Result<EvmSigner, SignerFactoryError> {
349        let signer = match &signer_model.config {
350            SignerConfig::Local(_) => EvmSigner::Local(LocalSigner::new(&signer_model)?),
351            SignerConfig::Vault(config) => {
352                let vault_config = VaultConfig::new(
353                    config.address.clone(),
354                    config.role_id.clone(),
355                    config.secret_id.clone(),
356                    config.namespace.clone(),
357                    config
358                        .mount_point
359                        .clone()
360                        .unwrap_or_else(|| "secret".to_string()),
361                    None,
362                );
363                let vault_service = VaultService::new(vault_config);
364
365                EvmSigner::Vault(VaultSigner::new(
366                    signer_model.id.clone(),
367                    config.clone(),
368                    vault_service,
369                ))
370            }
371            SignerConfig::AwsKms(config) => {
372                let aws_service = AwsKmsService::new(config.clone()).await.map_err(|e| {
373                    SignerFactoryError::CreationFailed(format!("AWS KMS service error: {e}"))
374                })?;
375                EvmSigner::AwsKms(AwsKmsSigner::new(aws_service))
376            }
377            SignerConfig::VaultTransit(_) => {
378                return Err(SignerFactoryError::UnsupportedType("Vault Transit".into()));
379            }
380            SignerConfig::Turnkey(config) => {
381                let turnkey_service = TurnkeyService::new(config.clone()).map_err(|e| {
382                    SignerFactoryError::CreationFailed(format!("Turnkey service error: {e}"))
383                })?;
384                EvmSigner::Turnkey(TurnkeySigner::new(turnkey_service))
385            }
386            SignerConfig::Cdp(config) => {
387                let cdp_signer = CdpSigner::new(config.clone()).map_err(|e| {
388                    SignerFactoryError::CreationFailed(format!("CDP service error: {e}"))
389                })?;
390                EvmSigner::Cdp(cdp_signer)
391            }
392            SignerConfig::GoogleCloudKms(config) => {
393                let gcp_service = GoogleCloudKmsService::new(config).map_err(|e| {
394                    SignerFactoryError::CreationFailed(format!(
395                        "Google Cloud KMS service error: {e}"
396                    ))
397                })?;
398                EvmSigner::GoogleCloudKms(GoogleCloudKmsSigner::new(gcp_service))
399            }
400        };
401
402        Ok(signer)
403    }
404}
405
406#[cfg(test)]
407mod tests {
408    use super::*;
409    use crate::models::{
410        AwsKmsSignerConfig, CdpSignerConfig, EvmTransactionData, GoogleCloudKmsSignerConfig,
411        GoogleCloudKmsSignerKeyConfig, GoogleCloudKmsSignerServiceAccountConfig, LocalSignerConfig,
412        SecretString, SignerConfig, SignerRepoModel, TurnkeySignerConfig, VaultTransitSignerConfig,
413        U256,
414    };
415    use futures;
416    use mockall::predicate::*;
417    use secrets::SecretVec;
418    use std::str::FromStr;
419    use std::sync::Arc;
420
421    fn test_key_bytes() -> SecretVec<u8> {
422        let key_bytes =
423            hex::decode("0000000000000000000000000000000000000000000000000000000000000001")
424                .unwrap();
425        SecretVec::new(key_bytes.len(), |v| v.copy_from_slice(&key_bytes))
426    }
427
428    fn test_key_address() -> Address {
429        Address::Evm([
430            126, 95, 69, 82, 9, 26, 105, 18, 93, 93, 252, 183, 184, 194, 101, 144, 41, 57, 91, 223,
431        ])
432    }
433
434    #[tokio::test]
435    async fn test_create_evm_signer_local() {
436        let signer_model = SignerDomainModel {
437            id: "test".to_string(),
438            config: SignerConfig::Local(LocalSignerConfig {
439                raw_key: test_key_bytes(),
440            }),
441        };
442
443        let signer = EvmSignerFactory::create_evm_signer(signer_model)
444            .await
445            .unwrap();
446
447        assert!(matches!(signer, EvmSigner::Local(_)));
448    }
449
450    #[tokio::test]
451    async fn test_create_evm_signer_test() {
452        let signer_model = SignerDomainModel {
453            id: "test".to_string(),
454            config: SignerConfig::Local(LocalSignerConfig {
455                raw_key: test_key_bytes(),
456            }),
457        };
458
459        let signer = EvmSignerFactory::create_evm_signer(signer_model)
460            .await
461            .unwrap();
462
463        assert!(matches!(signer, EvmSigner::Local(_)));
464    }
465
466    #[tokio::test]
467    async fn test_create_evm_signer_vault() {
468        let signer_model = SignerDomainModel {
469            id: "test".to_string(),
470            config: SignerConfig::Vault(VaultSignerConfig {
471                address: "https://vault.test.com".to_string(),
472                namespace: Some("test-namespace".to_string()),
473                role_id: crate::models::SecretString::new("test-role-id"),
474                secret_id: crate::models::SecretString::new("test-secret-id"),
475                key_name: "test-key".to_string(),
476                mount_point: Some("secret".to_string()),
477            }),
478        };
479
480        let signer = EvmSignerFactory::create_evm_signer(signer_model)
481            .await
482            .unwrap();
483
484        assert!(matches!(signer, EvmSigner::Vault(_)));
485    }
486
487    #[tokio::test]
488    async fn test_create_evm_signer_aws_kms() {
489        let signer_model = SignerDomainModel {
490            id: "test".to_string(),
491            config: SignerConfig::AwsKms(AwsKmsSignerConfig {
492                region: Some("us-east-1".to_string()),
493                key_id: "test-key-id".to_string(),
494            }),
495        };
496
497        let signer = EvmSignerFactory::create_evm_signer(signer_model)
498            .await
499            .unwrap();
500
501        assert!(matches!(signer, EvmSigner::AwsKms(_)));
502    }
503
504    #[tokio::test]
505    async fn test_create_evm_signer_vault_transit() {
506        let signer_model = SignerDomainModel {
507            id: "test".to_string(),
508            config: SignerConfig::VaultTransit(VaultTransitSignerConfig {
509                key_name: "test".to_string(),
510                address: "address".to_string(),
511                namespace: None,
512                role_id: SecretString::new("test-role"),
513                secret_id: SecretString::new("test-secret"),
514                pubkey: "pubkey".to_string(),
515                mount_point: None,
516            }),
517        };
518
519        let result = EvmSignerFactory::create_evm_signer(signer_model).await;
520
521        assert!(matches!(
522            result,
523            Err(SignerFactoryError::UnsupportedType(_))
524        ));
525    }
526
527    #[tokio::test]
528    async fn test_create_evm_signer_turnkey() {
529        let signer_model = SignerDomainModel {
530            id: "test".to_string(),
531            config: SignerConfig::Turnkey(TurnkeySignerConfig {
532                api_private_key: SecretString::new("api_private_key"),
533                api_public_key: "api_public_key".to_string(),
534                organization_id: "organization_id".to_string(),
535                private_key_id: "private_key_id".to_string(),
536                public_key: "047d3bb8e0317927700cf19fed34e0627367be1390ec247dddf8c239e4b4321a49aea80090e49b206b6a3e577a4f11d721ab063482001ee10db40d6f2963233eec".to_string(),
537            }),
538        };
539
540        let signer = EvmSignerFactory::create_evm_signer(signer_model)
541            .await
542            .unwrap();
543        let signer_address = signer.address().await.unwrap();
544
545        assert_eq!(
546            "0xb726167dc2ef2ac582f0a3de4c08ac4abb90626a",
547            signer_address.to_string()
548        );
549    }
550
551    #[tokio::test]
552    async fn test_create_evm_signer_cdp() {
553        let signer_model = SignerDomainModel {
554            id: "test".to_string(),
555            config: SignerConfig::Cdp(CdpSignerConfig {
556                api_key_id: "test-api-key-id".to_string(),
557                api_key_secret: SecretString::new("test-api-key-secret"),
558                wallet_secret: SecretString::new("test-wallet-secret"),
559                account_address: "0xb726167dc2ef2ac582f0a3de4c08ac4abb90626a".to_string(),
560            }),
561        };
562
563        let signer = EvmSignerFactory::create_evm_signer(signer_model)
564            .await
565            .unwrap();
566
567        assert!(matches!(signer, EvmSigner::Cdp(_)));
568    }
569
570    #[tokio::test]
571    async fn test_address_evm_signer_local() {
572        let signer_model = SignerDomainModel {
573            id: "test".to_string(),
574            config: SignerConfig::Local(LocalSignerConfig {
575                raw_key: test_key_bytes(),
576            }),
577        };
578
579        let signer = EvmSignerFactory::create_evm_signer(signer_model)
580            .await
581            .unwrap();
582        let signer_address = signer.address().await.unwrap();
583
584        assert_eq!(test_key_address(), signer_address);
585    }
586
587    #[tokio::test]
588    async fn test_address_evm_signer_test() {
589        let signer_model = SignerDomainModel {
590            id: "test".to_string(),
591            config: SignerConfig::Local(LocalSignerConfig {
592                raw_key: test_key_bytes(),
593            }),
594        };
595
596        let signer = EvmSignerFactory::create_evm_signer(signer_model)
597            .await
598            .unwrap();
599        let signer_address = signer.address().await.unwrap();
600
601        assert_eq!(test_key_address(), signer_address);
602    }
603
604    #[tokio::test]
605    async fn test_address_evm_signer_turnkey() {
606        let signer_model = SignerDomainModel {
607            id: "test".to_string(),
608            config: SignerConfig::Turnkey(TurnkeySignerConfig {
609                api_private_key: SecretString::new("api_private_key"),
610                api_public_key: "api_public_key".to_string(),
611                organization_id: "organization_id".to_string(),
612                private_key_id: "private_key_id".to_string(),
613                public_key: "047d3bb8e0317927700cf19fed34e0627367be1390ec247dddf8c239e4b4321a49aea80090e49b206b6a3e577a4f11d721ab063482001ee10db40d6f2963233eec".to_string(),
614            }),
615        };
616
617        let signer = EvmSignerFactory::create_evm_signer(signer_model)
618            .await
619            .unwrap();
620        let signer_address = signer.address().await.unwrap();
621
622        assert_eq!(
623            "0xb726167dc2ef2ac582f0a3de4c08ac4abb90626a",
624            signer_address.to_string()
625        );
626    }
627
628    #[tokio::test]
629    async fn test_address_evm_signer_cdp() {
630        let signer_model = SignerDomainModel {
631            id: "test".to_string(),
632            config: SignerConfig::Cdp(CdpSignerConfig {
633                api_key_id: "test-api-key-id".to_string(),
634                api_key_secret: SecretString::new("test-api-key-secret"),
635                wallet_secret: SecretString::new("test-wallet-secret"),
636                account_address: "0xb726167dc2ef2ac582f0a3de4c08ac4abb90626a".to_string(),
637            }),
638        };
639
640        let signer = EvmSignerFactory::create_evm_signer(signer_model)
641            .await
642            .unwrap();
643        let signer_address = signer.address().await.unwrap();
644
645        assert_eq!(
646            "0xb726167dc2ef2ac582f0a3de4c08ac4abb90626a",
647            signer_address.to_string()
648        );
649    }
650
651    #[tokio::test]
652    async fn test_sign_data_evm_signer_local() {
653        let signer_model = SignerDomainModel {
654            id: "test".to_string(),
655            config: SignerConfig::Local(LocalSignerConfig {
656                raw_key: test_key_bytes(),
657            }),
658        };
659
660        let signer = EvmSignerFactory::create_evm_signer(signer_model)
661            .await
662            .unwrap();
663        let request = SignDataRequest {
664            message: "Test message".to_string(),
665        };
666
667        let result = signer.sign_data(request).await;
668
669        assert!(result.is_ok());
670
671        let response = result.unwrap();
672        assert!(matches!(response, SignDataResponse::Evm(_)));
673
674        if let SignDataResponse::Evm(sig) = response {
675            assert_eq!(sig.r.len(), 64); // 32 bytes in hex
676            assert_eq!(sig.s.len(), 64); // 32 bytes in hex
677            assert!(sig.v == 27 || sig.v == 28); // Valid v values
678            assert_eq!(sig.sig.len(), 130); // 65 bytes in hex
679        }
680    }
681
682    #[tokio::test]
683    async fn test_sign_transaction_evm() {
684        let signer_model = SignerDomainModel {
685            id: "test".to_string(),
686            config: SignerConfig::Local(LocalSignerConfig {
687                raw_key: test_key_bytes(),
688            }),
689        };
690
691        let signer = EvmSignerFactory::create_evm_signer(signer_model)
692            .await
693            .unwrap();
694
695        let transaction_data = NetworkTransactionData::Evm(EvmTransactionData {
696            from: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
697            to: Some("0x742d35Cc6634C0532925a3b844Bc454e4438f44f".to_string()),
698            gas_price: Some(20000000000),
699            gas_limit: Some(21000),
700            nonce: Some(0),
701            value: U256::from(1000000000000000000u64),
702            data: Some("0x".to_string()),
703            chain_id: 1,
704            hash: None,
705            signature: None,
706            raw: None,
707            max_fee_per_gas: None,
708            max_priority_fee_per_gas: None,
709            speed: None,
710        });
711
712        let result = signer.sign_transaction(transaction_data).await;
713
714        assert!(result.is_ok());
715
716        let signed_tx = result.unwrap();
717
718        assert!(matches!(signed_tx, SignTransactionResponse::Evm(_)));
719
720        if let SignTransactionResponse::Evm(evm_tx) = signed_tx {
721            assert!(!evm_tx.hash.is_empty());
722            assert!(!evm_tx.raw.is_empty());
723            assert!(!evm_tx.signature.sig.is_empty());
724        }
725    }
726
727    #[tokio::test]
728    async fn test_create_evm_signer_google_cloud_kms() {
729        let signer_model = SignerDomainModel {
730            id: "test".to_string(),
731            config: SignerConfig::GoogleCloudKms(GoogleCloudKmsSignerConfig {
732                service_account: GoogleCloudKmsSignerServiceAccountConfig {
733                    project_id: "project_id".to_string(),
734                    private_key_id: SecretString::new("private_key_id"),
735                    private_key: SecretString::new("-----BEGIN EXAMPLE PRIVATE KEY-----\nFAKEKEYDATA\n-----END EXAMPLE PRIVATE KEY-----\n"),
736                    client_email: SecretString::new("client_email@example.com"),
737                    client_id: "client_id".to_string(),
738                    auth_uri: "https://accounts.google.com/o/oauth2/auth".to_string(),
739                    token_uri: "https://oauth2.googleapis.com/token".to_string(),
740                    auth_provider_x509_cert_url: "https://www.googleapis.com/oauth2/v1/certs".to_string(),
741                    client_x509_cert_url: "https://www.googleapis.com/robot/v1/metadata/x509/client_email%40example.com".to_string(),
742                    universe_domain: "googleapis.com".to_string(),
743                },
744                key: GoogleCloudKmsSignerKeyConfig {
745                    location: "global".to_string(),
746                    key_id: "id".to_string(),
747                    key_ring_id: "key_ring".to_string(),
748                    key_version: 1,
749                },
750            }),
751        };
752
753        let result = EvmSignerFactory::create_evm_signer(signer_model).await;
754
755        assert!(result.is_ok());
756        assert!(matches!(result.unwrap(), EvmSigner::GoogleCloudKms(_)));
757    }
758
759    #[tokio::test]
760    async fn test_sign_data_with_different_message_types() {
761        let signer_model = SignerDomainModel {
762            id: "test".to_string(),
763            config: SignerConfig::Local(LocalSignerConfig {
764                raw_key: test_key_bytes(),
765            }),
766        };
767
768        let signer = EvmSignerFactory::create_evm_signer(signer_model)
769            .await
770            .unwrap();
771
772        // Test with various message types
773        let long_message = "a".repeat(1000);
774        let test_cases = vec![
775            ("Simple message", "Test message"),
776            ("Empty message", ""),
777            ("Unicode message", "🚀 Test message with émojis"),
778            ("Long message", long_message.as_str()),
779            ("JSON message", r#"{"test": "value", "number": 123}"#),
780        ];
781
782        for (name, message) in test_cases {
783            let request = SignDataRequest {
784                message: message.to_string(),
785            };
786
787            let result = signer.sign_data(request).await;
788            assert!(result.is_ok(), "Failed to sign {}", name);
789
790            if let Ok(SignDataResponse::Evm(sig)) = result {
791                assert_eq!(sig.r.len(), 64, "Invalid r length for {}", name);
792                assert_eq!(sig.s.len(), 64, "Invalid s length for {}", name);
793                assert!(sig.v == 27 || sig.v == 28, "Invalid v value for {}", name);
794                assert_eq!(sig.sig.len(), 130, "Invalid signature length for {}", name);
795            } else {
796                panic!("Expected EVM signature for {}", name);
797            }
798        }
799    }
800
801    // Edge case tests for EIP-712 and signature validation
802    #[tokio::test]
803    async fn test_eip712_hash_with_0x_prefix() {
804        let request_with_prefix = SignTypedDataRequest {
805            domain_separator: format!("0x{}", "a".repeat(64)),
806            hash_struct_message: format!("0x{}", "b".repeat(64)),
807        };
808
809        let request_without_prefix = SignTypedDataRequest {
810            domain_separator: "a".repeat(64),
811            hash_struct_message: "b".repeat(64),
812        };
813
814        let hash1 = construct_eip712_message_hash(&request_with_prefix).unwrap();
815        let hash2 = construct_eip712_message_hash(&request_without_prefix).unwrap();
816
817        assert_eq!(
818            hash1, hash2,
819            "Hash should be same with or without 0x prefix"
820        );
821    }
822
823    #[tokio::test]
824    async fn test_eip712_deterministic() {
825        let request = SignTypedDataRequest {
826            domain_separator: "a".repeat(64),
827            hash_struct_message: "b".repeat(64),
828        };
829
830        let hash1 = construct_eip712_message_hash(&request).unwrap();
831        let hash2 = construct_eip712_message_hash(&request).unwrap();
832
833        assert_eq!(hash1, hash2, "Hash must be deterministic");
834    }
835
836    #[tokio::test]
837    async fn test_eip712_invalid_domain_length() {
838        let request = SignTypedDataRequest {
839            domain_separator: "a".repeat(30), // Too short
840            hash_struct_message: "b".repeat(64),
841        };
842
843        let result = construct_eip712_message_hash(&request);
844        assert!(result.is_err());
845        if let Err(e) = result {
846            assert!(e.to_string().contains("Invalid domain separator length"));
847        }
848    }
849
850    #[tokio::test]
851    async fn test_eip712_invalid_hash_struct_length() {
852        let request = SignTypedDataRequest {
853            domain_separator: "a".repeat(64),
854            hash_struct_message: "b".repeat(30), // Too short
855        };
856
857        let result = construct_eip712_message_hash(&request);
858        assert!(result.is_err());
859        if let Err(e) = result {
860            assert!(e.to_string().contains("Invalid hash struct length"));
861        }
862    }
863
864    #[tokio::test]
865    async fn test_eip712_invalid_hex_characters() {
866        let request = SignTypedDataRequest {
867            domain_separator: "zzzz".to_string(), // Invalid hex
868            hash_struct_message: "b".repeat(64),
869        };
870
871        let result = construct_eip712_message_hash(&request);
872        assert!(result.is_err());
873        if let Err(e) = result {
874            assert!(e.to_string().contains("non-hexadecimal character"));
875        }
876    }
877
878    #[tokio::test]
879    async fn test_eip712_invalid_hex_at_specific_position() {
880        let request = SignTypedDataRequest {
881            domain_separator: format!("{}z{}", "a".repeat(10), "a".repeat(53)), // 'z' at position 10
882            hash_struct_message: "b".repeat(64),
883        };
884
885        let result = construct_eip712_message_hash(&request);
886        assert!(result.is_err());
887        if let Err(e) = result {
888            let err_msg = e.to_string();
889            assert!(err_msg.contains("non-hexadecimal character"));
890            assert!(err_msg.contains("position 10")); // Error should report exact position
891        }
892    }
893
894    #[test]
895    fn test_signature_validation_wrong_length() {
896        let sig_64_bytes = vec![0u8; 64];
897        let result = validate_and_format_signature(&sig_64_bytes, "TestSigner");
898        assert!(result.is_err());
899        if let Err(e) = result {
900            assert!(e.to_string().contains("expected 65 bytes"));
901        }
902
903        let sig_66_bytes = vec![0u8; 66];
904        let result = validate_and_format_signature(&sig_66_bytes, "TestSigner");
905        assert!(result.is_err());
906
907        let sig_0_bytes = vec![];
908        let result = validate_and_format_signature(&sig_0_bytes, "TestSigner");
909        assert!(result.is_err());
910    }
911
912    #[test]
913    fn test_signature_validation_correct_length() {
914        let sig_65_bytes = vec![0u8; 65];
915        let result = validate_and_format_signature(&sig_65_bytes, "TestSigner");
916        assert!(result.is_ok());
917
918        if let Ok(SignDataResponse::Evm(sig)) = result {
919            assert_eq!(sig.r.len(), 64); // 32 bytes as hex
920            assert_eq!(sig.s.len(), 64); // 32 bytes as hex
921            assert_eq!(sig.v, 0); // Last byte
922            assert_eq!(sig.sig.len(), 130); // 65 bytes as hex
923        }
924    }
925
926    #[tokio::test]
927    async fn test_eip712_odd_length_hex_string() {
928        // Odd-length hex strings should fail during decoding
929        let request = SignTypedDataRequest {
930            domain_separator: "a".repeat(63), // Odd length
931            hash_struct_message: "b".repeat(64),
932        };
933
934        let result = construct_eip712_message_hash(&request);
935        assert!(result.is_err());
936    }
937
938    #[tokio::test]
939    async fn test_eip712_mixed_case_hex() {
940        // Mixed case hex should work
941        let request = SignTypedDataRequest {
942            domain_separator: "AaBbCcDdEeFf11223344556677889900AaBbCcDdEeFf11223344556677889900"
943                .to_string(),
944            hash_struct_message: "b".repeat(64),
945        };
946
947        let result = construct_eip712_message_hash(&request);
948        assert!(result.is_ok());
949    }
950
951    #[tokio::test]
952    async fn test_validate_and_decode_hex_error_includes_input_preview() {
953        let long_invalid_hex = format!("{}z{}", "a".repeat(20), "a".repeat(100));
954        let result = validate_and_decode_hex(&long_invalid_hex, "test_field");
955
956        assert!(result.is_err());
957        if let Err(e) = result {
958            let err_msg = e.to_string();
959            // Should include first 16 chars of input for debugging
960            assert!(err_msg.contains("input:"));
961            assert!(err_msg.len() < 200); // Error message should be reasonably sized
962        }
963    }
964}