1mod 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 async fn sign_data(&self, request: SignDataRequest) -> Result<SignDataResponse, SignerError>;
57
58 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); assert_eq!(sig.s.len(), 64); assert!(sig.v == 27 || sig.v == 28); assert_eq!(sig.sig.len(), 130); }
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 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}