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