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