openzeppelin_relayer/services/signer/evm/
mod.rs

1//! EVM signer implementation for managing Ethereum-compatible private keys and signing operations.
2//! This module provides various EVM signer implementations, including local keystore, HashiCorp Vault, Google Cloud KMS, AWS KMS, and Turnkey.
3//!
4//! # Architecture
5//!
6//! ```text
7//! EvmSigner
8//!   ├── LocalSigner (encrypted JSON keystore)
9//!   ├── AwsKmsSigner (AWS KMS backend)
10//!   ├── Vault (HashiCorp Vault backend)
11//!   ├── Google Cloud KMS signer
12//!   ├── AWS KMS Signer
13//!   └── Turnkey (Turnkey backend)
14//! ```
15mod aws_kms_signer;
16mod cdp_signer;
17mod google_cloud_kms_signer;
18mod local_signer;
19mod turnkey_signer;
20mod vault_signer;
21use aws_kms_signer::*;
22use cdp_signer::*;
23use google_cloud_kms_signer::*;
24use local_signer::*;
25use oz_keystore::HashicorpCloudClient;
26use turnkey_signer::*;
27use vault_signer::*;
28
29use async_trait::async_trait;
30use color_eyre::config;
31use std::sync::Arc;
32
33use crate::{
34    domain::{
35        SignDataRequest, SignDataResponse, SignDataResponseEvm, SignTransactionResponse,
36        SignTypedDataRequest,
37    },
38    models::{
39        Address, NetworkTransactionData, Signer as SignerDomainModel, SignerConfig,
40        SignerRepoModel, SignerType, TransactionRepoModel, VaultSignerConfig,
41    },
42    services::{
43        signer::Signer,
44        signer::SignerError,
45        signer::SignerFactoryError,
46        turnkey::TurnkeyService,
47        vault::{VaultConfig, VaultService, VaultServiceTrait},
48        AwsKmsService, CdpService, GoogleCloudKmsService, TurnkeyServiceTrait,
49    },
50};
51use eyre::Result;
52
53#[async_trait]
54pub trait DataSignerTrait: Send + Sync {
55    /// Signs arbitrary message data
56    async fn sign_data(&self, request: SignDataRequest) -> Result<SignDataResponse, SignerError>;
57
58    /// Signs EIP-712 typed data
59    async fn sign_typed_data(
60        &self,
61        request: SignTypedDataRequest,
62    ) -> Result<SignDataResponse, SignerError>;
63}
64
65pub enum EvmSigner {
66    Local(LocalSigner),
67    Vault(VaultSigner<VaultService>),
68    Turnkey(TurnkeySigner),
69    Cdp(CdpSigner),
70    AwsKms(AwsKmsSigner),
71    GoogleCloudKms(GoogleCloudKmsSigner),
72}
73
74#[async_trait]
75impl Signer for EvmSigner {
76    async fn address(&self) -> Result<Address, SignerError> {
77        match self {
78            Self::Local(signer) => signer.address().await,
79            Self::Vault(signer) => signer.address().await,
80            Self::Turnkey(signer) => signer.address().await,
81            Self::Cdp(signer) => signer.address().await,
82            Self::AwsKms(signer) => signer.address().await,
83            Self::GoogleCloudKms(signer) => signer.address().await,
84        }
85    }
86
87    async fn sign_transaction(
88        &self,
89        transaction: NetworkTransactionData,
90    ) -> Result<SignTransactionResponse, SignerError> {
91        match self {
92            Self::Local(signer) => signer.sign_transaction(transaction).await,
93            Self::Vault(signer) => signer.sign_transaction(transaction).await,
94            Self::Turnkey(signer) => signer.sign_transaction(transaction).await,
95            Self::Cdp(signer) => signer.sign_transaction(transaction).await,
96            Self::AwsKms(signer) => signer.sign_transaction(transaction).await,
97            Self::GoogleCloudKms(signer) => signer.sign_transaction(transaction).await,
98        }
99    }
100}
101
102#[async_trait]
103impl DataSignerTrait for EvmSigner {
104    async fn sign_data(&self, request: SignDataRequest) -> Result<SignDataResponse, SignerError> {
105        match self {
106            Self::Local(signer) => signer.sign_data(request).await,
107            Self::Vault(signer) => signer.sign_data(request).await,
108            Self::Turnkey(signer) => signer.sign_data(request).await,
109            Self::Cdp(signer) => signer.sign_data(request).await,
110            Self::AwsKms(signer) => signer.sign_data(request).await,
111            Self::GoogleCloudKms(signer) => signer.sign_data(request).await,
112        }
113    }
114
115    async fn sign_typed_data(
116        &self,
117        request: SignTypedDataRequest,
118    ) -> Result<SignDataResponse, SignerError> {
119        match self {
120            Self::Local(signer) => signer.sign_typed_data(request).await,
121            Self::Vault(signer) => signer.sign_typed_data(request).await,
122            Self::Turnkey(signer) => signer.sign_typed_data(request).await,
123            Self::Cdp(signer) => signer.sign_typed_data(request).await,
124            Self::AwsKms(signer) => signer.sign_typed_data(request).await,
125            Self::GoogleCloudKms(signer) => signer.sign_typed_data(request).await,
126        }
127    }
128}
129
130pub struct EvmSignerFactory;
131
132impl EvmSignerFactory {
133    pub async fn create_evm_signer(
134        signer_model: SignerDomainModel,
135    ) -> Result<EvmSigner, SignerFactoryError> {
136        let signer = match &signer_model.config {
137            SignerConfig::Local(_) => EvmSigner::Local(LocalSigner::new(&signer_model)?),
138            SignerConfig::Vault(config) => {
139                let vault_config = VaultConfig::new(
140                    config.address.clone(),
141                    config.role_id.clone(),
142                    config.secret_id.clone(),
143                    config.namespace.clone(),
144                    config
145                        .mount_point
146                        .clone()
147                        .unwrap_or_else(|| "secret".to_string()),
148                    None,
149                );
150                let vault_service = VaultService::new(vault_config);
151
152                EvmSigner::Vault(VaultSigner::new(
153                    signer_model.id.clone(),
154                    config.clone(),
155                    vault_service,
156                ))
157            }
158            SignerConfig::AwsKms(config) => {
159                let aws_service = AwsKmsService::new(config.clone()).await.map_err(|e| {
160                    SignerFactoryError::CreationFailed(format!("AWS KMS service error: {}", e))
161                })?;
162                EvmSigner::AwsKms(AwsKmsSigner::new(aws_service))
163            }
164            SignerConfig::VaultTransit(_) => {
165                return Err(SignerFactoryError::UnsupportedType("Vault Transit".into()));
166            }
167            SignerConfig::Turnkey(config) => {
168                let turnkey_service = TurnkeyService::new(config.clone()).map_err(|e| {
169                    SignerFactoryError::CreationFailed(format!("Turnkey service error: {}", e))
170                })?;
171                EvmSigner::Turnkey(TurnkeySigner::new(turnkey_service))
172            }
173            SignerConfig::Cdp(config) => {
174                let cdp_signer = CdpSigner::new(config.clone()).map_err(|e| {
175                    SignerFactoryError::CreationFailed(format!("CDP service error: {}", e))
176                })?;
177                EvmSigner::Cdp(cdp_signer)
178            }
179            SignerConfig::GoogleCloudKms(config) => {
180                let gcp_service = GoogleCloudKmsService::new(config).map_err(|e| {
181                    SignerFactoryError::CreationFailed(format!(
182                        "Google Cloud KMS service error: {}",
183                        e
184                    ))
185                })?;
186                EvmSigner::GoogleCloudKms(GoogleCloudKmsSigner::new(gcp_service))
187            }
188        };
189
190        Ok(signer)
191    }
192}
193
194#[cfg(test)]
195mod tests {
196    use super::*;
197    use crate::models::{
198        AwsKmsSignerConfig, CdpSignerConfig, EvmTransactionData, GoogleCloudKmsSignerConfig,
199        GoogleCloudKmsSignerKeyConfig, GoogleCloudKmsSignerServiceAccountConfig, LocalSignerConfig,
200        SecretString, SignerConfig, SignerRepoModel, TurnkeySignerConfig, VaultTransitSignerConfig,
201        U256,
202    };
203    use futures;
204    use mockall::predicate::*;
205    use secrets::SecretVec;
206    use std::str::FromStr;
207    use std::sync::Arc;
208
209    fn test_key_bytes() -> SecretVec<u8> {
210        let key_bytes =
211            hex::decode("0000000000000000000000000000000000000000000000000000000000000001")
212                .unwrap();
213        SecretVec::new(key_bytes.len(), |v| v.copy_from_slice(&key_bytes))
214    }
215
216    fn test_key_address() -> Address {
217        Address::Evm([
218            126, 95, 69, 82, 9, 26, 105, 18, 93, 93, 252, 183, 184, 194, 101, 144, 41, 57, 91, 223,
219        ])
220    }
221
222    #[tokio::test]
223    async fn test_create_evm_signer_local() {
224        let signer_model = SignerDomainModel {
225            id: "test".to_string(),
226            config: SignerConfig::Local(LocalSignerConfig {
227                raw_key: test_key_bytes(),
228            }),
229        };
230
231        let signer = EvmSignerFactory::create_evm_signer(signer_model)
232            .await
233            .unwrap();
234
235        assert!(matches!(signer, EvmSigner::Local(_)));
236    }
237
238    #[tokio::test]
239    async fn test_create_evm_signer_test() {
240        let signer_model = SignerDomainModel {
241            id: "test".to_string(),
242            config: SignerConfig::Local(LocalSignerConfig {
243                raw_key: test_key_bytes(),
244            }),
245        };
246
247        let signer = EvmSignerFactory::create_evm_signer(signer_model)
248            .await
249            .unwrap();
250
251        assert!(matches!(signer, EvmSigner::Local(_)));
252    }
253
254    #[tokio::test]
255    async fn test_create_evm_signer_vault() {
256        let signer_model = SignerDomainModel {
257            id: "test".to_string(),
258            config: SignerConfig::Vault(VaultSignerConfig {
259                address: "https://vault.test.com".to_string(),
260                namespace: Some("test-namespace".to_string()),
261                role_id: crate::models::SecretString::new("test-role-id"),
262                secret_id: crate::models::SecretString::new("test-secret-id"),
263                key_name: "test-key".to_string(),
264                mount_point: Some("secret".to_string()),
265            }),
266        };
267
268        let signer = EvmSignerFactory::create_evm_signer(signer_model)
269            .await
270            .unwrap();
271
272        assert!(matches!(signer, EvmSigner::Vault(_)));
273    }
274
275    #[tokio::test]
276    async fn test_create_evm_signer_aws_kms() {
277        let signer_model = SignerDomainModel {
278            id: "test".to_string(),
279            config: SignerConfig::AwsKms(AwsKmsSignerConfig {
280                region: Some("us-east-1".to_string()),
281                key_id: "test-key-id".to_string(),
282            }),
283        };
284
285        let signer = EvmSignerFactory::create_evm_signer(signer_model)
286            .await
287            .unwrap();
288
289        assert!(matches!(signer, EvmSigner::AwsKms(_)));
290    }
291
292    #[tokio::test]
293    async fn test_create_evm_signer_vault_transit() {
294        let signer_model = SignerDomainModel {
295            id: "test".to_string(),
296            config: SignerConfig::VaultTransit(VaultTransitSignerConfig {
297                key_name: "test".to_string(),
298                address: "address".to_string(),
299                namespace: None,
300                role_id: SecretString::new("test-role"),
301                secret_id: SecretString::new("test-secret"),
302                pubkey: "pubkey".to_string(),
303                mount_point: None,
304            }),
305        };
306
307        let result = EvmSignerFactory::create_evm_signer(signer_model).await;
308
309        assert!(matches!(
310            result,
311            Err(SignerFactoryError::UnsupportedType(_))
312        ));
313    }
314
315    #[tokio::test]
316    async fn test_create_evm_signer_turnkey() {
317        let signer_model = SignerDomainModel {
318            id: "test".to_string(),
319            config: SignerConfig::Turnkey(TurnkeySignerConfig {
320                api_private_key: SecretString::new("api_private_key"),
321                api_public_key: "api_public_key".to_string(),
322                organization_id: "organization_id".to_string(),
323                private_key_id: "private_key_id".to_string(),
324                public_key: "047d3bb8e0317927700cf19fed34e0627367be1390ec247dddf8c239e4b4321a49aea80090e49b206b6a3e577a4f11d721ab063482001ee10db40d6f2963233eec".to_string(),
325            }),
326        };
327
328        let signer = EvmSignerFactory::create_evm_signer(signer_model)
329            .await
330            .unwrap();
331        let signer_address = signer.address().await.unwrap();
332
333        assert_eq!(
334            "0xb726167dc2ef2ac582f0a3de4c08ac4abb90626a",
335            signer_address.to_string()
336        );
337    }
338
339    #[tokio::test]
340    async fn test_create_evm_signer_cdp() {
341        let signer_model = SignerDomainModel {
342            id: "test".to_string(),
343            config: SignerConfig::Cdp(CdpSignerConfig {
344                api_key_id: "test-api-key-id".to_string(),
345                api_key_secret: SecretString::new("test-api-key-secret"),
346                wallet_secret: SecretString::new("test-wallet-secret"),
347                account_address: "0xb726167dc2ef2ac582f0a3de4c08ac4abb90626a".to_string(),
348            }),
349        };
350
351        let signer = EvmSignerFactory::create_evm_signer(signer_model)
352            .await
353            .unwrap();
354
355        assert!(matches!(signer, EvmSigner::Cdp(_)));
356    }
357
358    #[tokio::test]
359    async fn test_address_evm_signer_local() {
360        let signer_model = SignerDomainModel {
361            id: "test".to_string(),
362            config: SignerConfig::Local(LocalSignerConfig {
363                raw_key: test_key_bytes(),
364            }),
365        };
366
367        let signer = EvmSignerFactory::create_evm_signer(signer_model)
368            .await
369            .unwrap();
370        let signer_address = signer.address().await.unwrap();
371
372        assert_eq!(test_key_address(), signer_address);
373    }
374
375    #[tokio::test]
376    async fn test_address_evm_signer_test() {
377        let signer_model = SignerDomainModel {
378            id: "test".to_string(),
379            config: SignerConfig::Local(LocalSignerConfig {
380                raw_key: test_key_bytes(),
381            }),
382        };
383
384        let signer = EvmSignerFactory::create_evm_signer(signer_model)
385            .await
386            .unwrap();
387        let signer_address = signer.address().await.unwrap();
388
389        assert_eq!(test_key_address(), signer_address);
390    }
391
392    #[tokio::test]
393    async fn test_address_evm_signer_turnkey() {
394        let signer_model = SignerDomainModel {
395            id: "test".to_string(),
396            config: SignerConfig::Turnkey(TurnkeySignerConfig {
397                api_private_key: SecretString::new("api_private_key"),
398                api_public_key: "api_public_key".to_string(),
399                organization_id: "organization_id".to_string(),
400                private_key_id: "private_key_id".to_string(),
401                public_key: "047d3bb8e0317927700cf19fed34e0627367be1390ec247dddf8c239e4b4321a49aea80090e49b206b6a3e577a4f11d721ab063482001ee10db40d6f2963233eec".to_string(),
402            }),
403        };
404
405        let signer = EvmSignerFactory::create_evm_signer(signer_model)
406            .await
407            .unwrap();
408        let signer_address = signer.address().await.unwrap();
409
410        assert_eq!(
411            "0xb726167dc2ef2ac582f0a3de4c08ac4abb90626a",
412            signer_address.to_string()
413        );
414    }
415
416    #[tokio::test]
417    async fn test_address_evm_signer_cdp() {
418        let signer_model = SignerDomainModel {
419            id: "test".to_string(),
420            config: SignerConfig::Cdp(CdpSignerConfig {
421                api_key_id: "test-api-key-id".to_string(),
422                api_key_secret: SecretString::new("test-api-key-secret"),
423                wallet_secret: SecretString::new("test-wallet-secret"),
424                account_address: "0xb726167dc2ef2ac582f0a3de4c08ac4abb90626a".to_string(),
425            }),
426        };
427
428        let signer = EvmSignerFactory::create_evm_signer(signer_model)
429            .await
430            .unwrap();
431        let signer_address = signer.address().await.unwrap();
432
433        assert_eq!(
434            "0xb726167dc2ef2ac582f0a3de4c08ac4abb90626a",
435            signer_address.to_string()
436        );
437    }
438
439    #[tokio::test]
440    async fn test_sign_data_evm_signer_local() {
441        let signer_model = SignerDomainModel {
442            id: "test".to_string(),
443            config: SignerConfig::Local(LocalSignerConfig {
444                raw_key: test_key_bytes(),
445            }),
446        };
447
448        let signer = EvmSignerFactory::create_evm_signer(signer_model)
449            .await
450            .unwrap();
451        let request = SignDataRequest {
452            message: "Test message".to_string(),
453        };
454
455        let result = signer.sign_data(request).await;
456
457        assert!(result.is_ok());
458
459        let response = result.unwrap();
460        assert!(matches!(response, SignDataResponse::Evm(_)));
461
462        if let SignDataResponse::Evm(sig) = response {
463            assert_eq!(sig.r.len(), 64); // 32 bytes in hex
464            assert_eq!(sig.s.len(), 64); // 32 bytes in hex
465            assert!(sig.v == 27 || sig.v == 28); // Valid v values
466            assert_eq!(sig.sig.len(), 130); // 65 bytes in hex
467        }
468    }
469
470    #[tokio::test]
471    async fn test_sign_transaction_evm() {
472        let signer_model = SignerDomainModel {
473            id: "test".to_string(),
474            config: SignerConfig::Local(LocalSignerConfig {
475                raw_key: test_key_bytes(),
476            }),
477        };
478
479        let signer = EvmSignerFactory::create_evm_signer(signer_model)
480            .await
481            .unwrap();
482
483        let transaction_data = NetworkTransactionData::Evm(EvmTransactionData {
484            from: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
485            to: Some("0x742d35Cc6634C0532925a3b844Bc454e4438f44f".to_string()),
486            gas_price: Some(20000000000),
487            gas_limit: Some(21000),
488            nonce: Some(0),
489            value: U256::from(1000000000000000000u64),
490            data: Some("0x".to_string()),
491            chain_id: 1,
492            hash: None,
493            signature: None,
494            raw: None,
495            max_fee_per_gas: None,
496            max_priority_fee_per_gas: None,
497            speed: None,
498        });
499
500        let result = signer.sign_transaction(transaction_data).await;
501
502        assert!(result.is_ok());
503
504        let signed_tx = result.unwrap();
505
506        assert!(matches!(signed_tx, SignTransactionResponse::Evm(_)));
507
508        if let SignTransactionResponse::Evm(evm_tx) = signed_tx {
509            assert!(!evm_tx.hash.is_empty());
510            assert!(!evm_tx.raw.is_empty());
511            assert!(!evm_tx.signature.sig.is_empty());
512        }
513    }
514
515    #[tokio::test]
516    async fn test_create_evm_signer_google_cloud_kms() {
517        let signer_model = SignerDomainModel {
518            id: "test".to_string(),
519            config: SignerConfig::GoogleCloudKms(GoogleCloudKmsSignerConfig {
520                service_account: GoogleCloudKmsSignerServiceAccountConfig {
521                    project_id: "project_id".to_string(),
522                    private_key_id: SecretString::new("private_key_id"),
523                    private_key: SecretString::new("-----BEGIN EXAMPLE PRIVATE KEY-----\nFAKEKEYDATA\n-----END EXAMPLE PRIVATE KEY-----\n"),
524                    client_email: SecretString::new("client_email@example.com"),
525                    client_id: "client_id".to_string(),
526                    auth_uri: "https://accounts.google.com/o/oauth2/auth".to_string(),
527                    token_uri: "https://oauth2.googleapis.com/token".to_string(),
528                    auth_provider_x509_cert_url: "https://www.googleapis.com/oauth2/v1/certs".to_string(),
529                    client_x509_cert_url: "https://www.googleapis.com/robot/v1/metadata/x509/client_email%40example.com".to_string(),
530                    universe_domain: "googleapis.com".to_string(),
531                },
532                key: GoogleCloudKmsSignerKeyConfig {
533                    location: "global".to_string(),
534                    key_id: "id".to_string(),
535                    key_ring_id: "key_ring".to_string(),
536                    key_version: 1,
537                },
538            }),
539        };
540
541        let result = EvmSignerFactory::create_evm_signer(signer_model).await;
542
543        assert!(result.is_ok());
544        assert!(matches!(result.unwrap(), EvmSigner::GoogleCloudKms(_)));
545    }
546
547    #[tokio::test]
548    async fn test_sign_data_with_different_message_types() {
549        let signer_model = SignerDomainModel {
550            id: "test".to_string(),
551            config: SignerConfig::Local(LocalSignerConfig {
552                raw_key: test_key_bytes(),
553            }),
554        };
555
556        let signer = EvmSignerFactory::create_evm_signer(signer_model)
557            .await
558            .unwrap();
559
560        // Test with various message types
561        let long_message = "a".repeat(1000);
562        let test_cases = vec![
563            ("Simple message", "Test message"),
564            ("Empty message", ""),
565            ("Unicode message", "🚀 Test message with émojis"),
566            ("Long message", long_message.as_str()),
567            ("JSON message", r#"{"test": "value", "number": 123}"#),
568        ];
569
570        for (name, message) in test_cases {
571            let request = SignDataRequest {
572                message: message.to_string(),
573            };
574
575            let result = signer.sign_data(request).await;
576            assert!(result.is_ok(), "Failed to sign {}", name);
577
578            if let Ok(SignDataResponse::Evm(sig)) = result {
579                assert_eq!(sig.r.len(), 64, "Invalid r length for {}", name);
580                assert_eq!(sig.s.len(), 64, "Invalid s length for {}", name);
581                assert!(sig.v == 27 || sig.v == 28, "Invalid v value for {}", name);
582                assert_eq!(sig.sig.len(), 130, "Invalid signature length for {}", name);
583            } else {
584                panic!("Expected EVM signature for {}", name);
585            }
586        }
587    }
588}