1mod aws_kms_signer;
34mod azure_key_vault_signer;
35mod cdp_signer;
36mod google_cloud_kms_signer;
37mod local_signer;
38mod turnkey_signer;
39pub(crate) mod utils;
40mod vault_signer;
41use aws_kms_signer::*;
42use azure_key_vault_signer::*;
43use cdp_signer::*;
44use google_cloud_kms_signer::*;
45use local_signer::*;
46use oz_keystore::HashicorpCloudClient;
47use turnkey_signer::*;
48use vault_signer::*;
49
50use async_trait::async_trait;
51use std::sync::Arc;
52
53use crate::{
54 domain::{
55 SignDataRequest, SignDataResponse, SignDataResponseEvm, SignTransactionResponse,
56 SignTypedDataRequest,
57 },
58 models::{
59 Address, NetworkTransactionData, Signer as SignerDomainModel, SignerConfig,
60 SignerRepoModel, SignerType, TransactionRepoModel, VaultSignerConfig,
61 },
62 services::{
63 signer::Signer,
64 signer::SignerError,
65 signer::SignerFactoryError,
66 turnkey::TurnkeyService,
67 vault::{VaultConfig, VaultService, VaultServiceTrait},
68 AwsKmsService, AzureKeyVaultService, CdpService, GoogleCloudKmsService,
69 TurnkeyServiceTrait,
70 },
71};
72use eyre::Result;
73
74const EIP712_PREFIX: [u8; 2] = [0x19, 0x01];
76const EIP712_MESSAGE_SIZE: usize = 66; const SECP256K1_SIGNATURE_LENGTH: usize = 65;
80
81const HASH_LENGTH: usize = 32;
83
84fn validate_and_decode_hex(value: &str, field_name: &str) -> Result<Vec<u8>, SignerError> {
94 let hex_str = value.strip_prefix("0x").unwrap_or(value);
95
96 if let Some((pos, ch)) = hex_str
98 .chars()
99 .enumerate()
100 .find(|(_, c)| !c.is_ascii_hexdigit())
101 {
102 return Err(SignerError::SigningError(format!(
103 "Invalid {} hex: non-hexadecimal character '{}' at position {} (input: {}...)",
104 field_name,
105 ch,
106 pos,
107 &hex_str[..hex_str.len().min(16)] )));
109 }
110
111 hex::decode(hex_str).map_err(|e| {
113 SignerError::SigningError(format!(
114 "Invalid {} hex: failed to decode - {} (input: {}...)",
115 field_name,
116 e,
117 &hex_str[..hex_str.len().min(16)]
118 ))
119 })
120}
121
122pub fn construct_eip712_message_hash(
188 request: &SignTypedDataRequest,
189) -> Result<[u8; 32], SignerError> {
190 let domain_separator = validate_and_decode_hex(&request.domain_separator, "domain separator")?;
192
193 let hash_struct = validate_and_decode_hex(&request.hash_struct_message, "hash struct message")?;
195
196 if domain_separator.len() != HASH_LENGTH {
198 return Err(SignerError::SigningError(format!(
199 "Invalid domain separator length: expected {} bytes, got {}",
200 HASH_LENGTH,
201 domain_separator.len()
202 )));
203 }
204 if hash_struct.len() != HASH_LENGTH {
205 return Err(SignerError::SigningError(format!(
206 "Invalid hash struct length: expected {} bytes, got {}",
207 HASH_LENGTH,
208 hash_struct.len()
209 )));
210 }
211
212 let mut eip712_message = [0u8; EIP712_MESSAGE_SIZE];
215 eip712_message[0..2].copy_from_slice(&EIP712_PREFIX);
216 eip712_message[2..34].copy_from_slice(&domain_separator);
217 eip712_message[34..66].copy_from_slice(&hash_struct);
218
219 use alloy::primitives::keccak256;
221 let message_hash = keccak256(eip712_message);
222
223 Ok(message_hash.into())
224}
225
226pub(crate) fn validate_and_format_signature(
246 signature_bytes: &[u8],
247 signer_name: &str,
248) -> Result<SignDataResponse, SignerError> {
249 if signature_bytes.len() != SECP256K1_SIGNATURE_LENGTH {
250 return Err(SignerError::SigningError(format!(
251 "Invalid signature length from {}: expected {} bytes, got {}",
252 signer_name,
253 SECP256K1_SIGNATURE_LENGTH,
254 signature_bytes.len()
255 )));
256 }
257
258 let r = hex::encode(&signature_bytes[0..32]);
259 let s = hex::encode(&signature_bytes[32..64]);
260 let v = signature_bytes[64];
261
262 Ok(SignDataResponse::Evm(SignDataResponseEvm {
263 r,
264 s,
265 v,
266 sig: hex::encode(signature_bytes),
267 }))
268}
269
270#[async_trait]
271pub trait DataSignerTrait: Send + Sync {
272 async fn sign_data(&self, request: SignDataRequest) -> Result<SignDataResponse, SignerError>;
274
275 async fn sign_typed_data(
277 &self,
278 request: SignTypedDataRequest,
279 ) -> Result<SignDataResponse, SignerError>;
280}
281
282pub enum EvmSigner {
283 Local(LocalSigner),
284 Vault(VaultSigner<VaultService>),
285 Turnkey(TurnkeySigner),
286 Cdp(CdpSigner),
287 AwsKms(AwsKmsSigner),
288 AzureKeyVault(AzureKeyVaultSigner),
289 GoogleCloudKms(Box<GoogleCloudKmsSigner>),
290}
291
292#[async_trait]
293impl Signer for EvmSigner {
294 async fn address(&self) -> Result<Address, SignerError> {
295 match self {
296 Self::Local(signer) => signer.address().await,
297 Self::Vault(signer) => signer.address().await,
298 Self::Turnkey(signer) => signer.address().await,
299 Self::Cdp(signer) => signer.address().await,
300 Self::AwsKms(signer) => signer.address().await,
301 Self::AzureKeyVault(signer) => signer.address().await,
302 Self::GoogleCloudKms(signer) => signer.address().await,
303 }
304 }
305
306 async fn sign_transaction(
307 &self,
308 transaction: NetworkTransactionData,
309 ) -> Result<SignTransactionResponse, SignerError> {
310 match self {
311 Self::Local(signer) => signer.sign_transaction(transaction).await,
312 Self::Vault(signer) => signer.sign_transaction(transaction).await,
313 Self::Turnkey(signer) => signer.sign_transaction(transaction).await,
314 Self::Cdp(signer) => signer.sign_transaction(transaction).await,
315 Self::AwsKms(signer) => signer.sign_transaction(transaction).await,
316 Self::AzureKeyVault(signer) => signer.sign_transaction(transaction).await,
317 Self::GoogleCloudKms(signer) => signer.sign_transaction(transaction).await,
318 }
319 }
320}
321
322#[async_trait]
323impl DataSignerTrait for EvmSigner {
324 async fn sign_data(&self, request: SignDataRequest) -> Result<SignDataResponse, SignerError> {
325 match self {
326 Self::Local(signer) => signer.sign_data(request).await,
327 Self::Vault(signer) => signer.sign_data(request).await,
328 Self::Turnkey(signer) => signer.sign_data(request).await,
329 Self::Cdp(signer) => signer.sign_data(request).await,
330 Self::AwsKms(signer) => signer.sign_data(request).await,
331 Self::AzureKeyVault(signer) => signer.sign_data(request).await,
332 Self::GoogleCloudKms(signer) => signer.sign_data(request).await,
333 }
334 }
335
336 async fn sign_typed_data(
337 &self,
338 request: SignTypedDataRequest,
339 ) -> Result<SignDataResponse, SignerError> {
340 match self {
341 Self::Local(signer) => signer.sign_typed_data(request).await,
342 Self::Vault(signer) => signer.sign_typed_data(request).await,
343 Self::Turnkey(signer) => signer.sign_typed_data(request).await,
344 Self::Cdp(signer) => signer.sign_typed_data(request).await,
345 Self::AwsKms(signer) => signer.sign_typed_data(request).await,
346 Self::AzureKeyVault(signer) => signer.sign_typed_data(request).await,
347 Self::GoogleCloudKms(signer) => signer.sign_typed_data(request).await,
348 }
349 }
350}
351
352pub struct EvmSignerFactory;
353
354impl EvmSignerFactory {
355 pub async fn create_evm_signer(
356 signer_model: SignerDomainModel,
357 ) -> Result<EvmSigner, SignerFactoryError> {
358 let signer = match &signer_model.config {
359 SignerConfig::Local(_) => EvmSigner::Local(LocalSigner::new(&signer_model)?),
360 SignerConfig::Vault(config) => {
361 let vault_config = VaultConfig::new(
362 config.address.clone(),
363 config.role_id.clone(),
364 config.secret_id.clone(),
365 config.namespace.clone(),
366 config
367 .mount_point
368 .clone()
369 .unwrap_or_else(|| "secret".to_string()),
370 None,
371 );
372 let vault_service = VaultService::new(vault_config);
373
374 EvmSigner::Vault(VaultSigner::new(
375 signer_model.id.clone(),
376 config.clone(),
377 vault_service,
378 ))
379 }
380 SignerConfig::AwsKms(config) => {
381 let aws_service = AwsKmsService::new(config.clone()).await.map_err(|e| {
382 SignerFactoryError::CreationFailed(format!("AWS KMS service error: {e}"))
383 })?;
384 EvmSigner::AwsKms(AwsKmsSigner::new(aws_service))
385 }
386 SignerConfig::AzureKeyVault(config) => {
387 let azure_service = AzureKeyVaultService::new(config).map_err(|e| {
388 SignerFactoryError::CreationFailed(format!(
389 "Azure Key Vault service error: {e}"
390 ))
391 })?;
392 EvmSigner::AzureKeyVault(AzureKeyVaultSigner::new(azure_service))
393 }
394 SignerConfig::VaultTransit(_) => {
395 return Err(SignerFactoryError::UnsupportedType("Vault Transit".into()));
396 }
397 SignerConfig::Turnkey(config) => {
398 let turnkey_service = TurnkeyService::new(config.clone()).map_err(|e| {
399 SignerFactoryError::CreationFailed(format!("Turnkey service error: {e}"))
400 })?;
401 EvmSigner::Turnkey(TurnkeySigner::new(turnkey_service))
402 }
403 SignerConfig::Cdp(config) => {
404 let cdp_signer = CdpSigner::new(config.clone()).map_err(|e| {
405 SignerFactoryError::CreationFailed(format!("CDP service error: {e}"))
406 })?;
407 EvmSigner::Cdp(cdp_signer)
408 }
409 SignerConfig::GoogleCloudKms(config) => {
410 let gcp_service = GoogleCloudKmsService::new(config).map_err(|e| {
411 SignerFactoryError::CreationFailed(format!(
412 "Google Cloud KMS service error: {e}"
413 ))
414 })?;
415 EvmSigner::GoogleCloudKms(Box::new(GoogleCloudKmsSigner::new(gcp_service)))
416 }
417 };
418
419 Ok(signer)
420 }
421}
422
423#[cfg(test)]
424mod tests {
425 use super::*;
426 use crate::models::{
427 AwsKmsSignerConfig, CdpSignerConfig, EvmTransactionData, GoogleCloudKmsSignerConfig,
428 GoogleCloudKmsSignerKeyConfig, GoogleCloudKmsSignerServiceAccountConfig, LocalSignerConfig,
429 SecretString, SignerConfig, SignerRepoModel, TurnkeySignerConfig, VaultTransitSignerConfig,
430 U256,
431 };
432 use futures;
433 use mockall::predicate::*;
434 use secrets::SecretVec;
435 use std::str::FromStr;
436 use std::sync::Arc;
437
438 fn test_key_bytes() -> SecretVec<u8> {
439 let key_bytes =
440 hex::decode("0000000000000000000000000000000000000000000000000000000000000001")
441 .unwrap();
442 SecretVec::new(key_bytes.len(), |v| v.copy_from_slice(&key_bytes))
443 }
444
445 fn test_key_address() -> Address {
446 Address::Evm([
447 126, 95, 69, 82, 9, 26, 105, 18, 93, 93, 252, 183, 184, 194, 101, 144, 41, 57, 91, 223,
448 ])
449 }
450
451 #[tokio::test]
452 async fn test_create_evm_signer_local() {
453 let signer_model = SignerDomainModel {
454 id: "test".to_string(),
455 config: SignerConfig::Local(LocalSignerConfig {
456 raw_key: test_key_bytes(),
457 }),
458 };
459
460 let signer = EvmSignerFactory::create_evm_signer(signer_model)
461 .await
462 .unwrap();
463
464 assert!(matches!(signer, EvmSigner::Local(_)));
465 }
466
467 #[tokio::test]
468 async fn test_create_evm_signer_test() {
469 let signer_model = SignerDomainModel {
470 id: "test".to_string(),
471 config: SignerConfig::Local(LocalSignerConfig {
472 raw_key: test_key_bytes(),
473 }),
474 };
475
476 let signer = EvmSignerFactory::create_evm_signer(signer_model)
477 .await
478 .unwrap();
479
480 assert!(matches!(signer, EvmSigner::Local(_)));
481 }
482
483 #[tokio::test]
484 async fn test_create_evm_signer_vault() {
485 let signer_model = SignerDomainModel {
486 id: "test".to_string(),
487 config: SignerConfig::Vault(VaultSignerConfig {
488 address: "https://vault.test.com".to_string(),
489 namespace: Some("test-namespace".to_string()),
490 role_id: crate::models::SecretString::new("test-role-id"),
491 secret_id: crate::models::SecretString::new("test-secret-id"),
492 key_name: "test-key".to_string(),
493 mount_point: Some("secret".to_string()),
494 }),
495 };
496
497 let signer = EvmSignerFactory::create_evm_signer(signer_model)
498 .await
499 .unwrap();
500
501 assert!(matches!(signer, EvmSigner::Vault(_)));
502 }
503
504 #[tokio::test]
505 async fn test_create_evm_signer_aws_kms() {
506 let signer_model = SignerDomainModel {
507 id: "test".to_string(),
508 config: SignerConfig::AwsKms(AwsKmsSignerConfig {
509 region: Some("us-east-1".to_string()),
510 key_id: "test-key-id".to_string(),
511 }),
512 };
513
514 let signer = EvmSignerFactory::create_evm_signer(signer_model)
515 .await
516 .unwrap();
517
518 assert!(matches!(signer, EvmSigner::AwsKms(_)));
519 }
520
521 #[tokio::test]
522 async fn test_create_evm_signer_vault_transit() {
523 let signer_model = SignerDomainModel {
524 id: "test".to_string(),
525 config: SignerConfig::VaultTransit(VaultTransitSignerConfig {
526 key_name: "test".to_string(),
527 address: "address".to_string(),
528 namespace: None,
529 role_id: SecretString::new("test-role"),
530 secret_id: SecretString::new("test-secret"),
531 pubkey: "pubkey".to_string(),
532 mount_point: None,
533 }),
534 };
535
536 let result = EvmSignerFactory::create_evm_signer(signer_model).await;
537
538 assert!(matches!(
539 result,
540 Err(SignerFactoryError::UnsupportedType(_))
541 ));
542 }
543
544 #[tokio::test]
545 async fn test_create_evm_signer_turnkey() {
546 let signer_model = SignerDomainModel {
547 id: "test".to_string(),
548 config: SignerConfig::Turnkey(TurnkeySignerConfig {
549 api_private_key: SecretString::new("api_private_key"),
550 api_public_key: "api_public_key".to_string(),
551 organization_id: "organization_id".to_string(),
552 private_key_id: "private_key_id".to_string(),
553 public_key: "047d3bb8e0317927700cf19fed34e0627367be1390ec247dddf8c239e4b4321a49aea80090e49b206b6a3e577a4f11d721ab063482001ee10db40d6f2963233eec".to_string(),
554 }),
555 };
556
557 let signer = EvmSignerFactory::create_evm_signer(signer_model)
558 .await
559 .unwrap();
560 let signer_address = signer.address().await.unwrap();
561
562 assert_eq!(
563 "0xb726167dc2ef2ac582f0a3de4c08ac4abb90626a",
564 signer_address.to_string()
565 );
566 }
567
568 #[tokio::test]
569 async fn test_create_evm_signer_cdp() {
570 let signer_model = SignerDomainModel {
571 id: "test".to_string(),
572 config: SignerConfig::Cdp(CdpSignerConfig {
573 api_key_id: "test-api-key-id".to_string(),
574 api_key_secret: SecretString::new("test-api-key-secret"),
575 wallet_secret: SecretString::new("test-wallet-secret"),
576 account_address: "0xb726167dc2ef2ac582f0a3de4c08ac4abb90626a".to_string(),
577 }),
578 };
579
580 let signer = EvmSignerFactory::create_evm_signer(signer_model)
581 .await
582 .unwrap();
583
584 assert!(matches!(signer, EvmSigner::Cdp(_)));
585 }
586
587 #[tokio::test]
588 async fn test_address_evm_signer_local() {
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_test() {
606 let signer_model = SignerDomainModel {
607 id: "test".to_string(),
608 config: SignerConfig::Local(LocalSignerConfig {
609 raw_key: test_key_bytes(),
610 }),
611 };
612
613 let signer = EvmSignerFactory::create_evm_signer(signer_model)
614 .await
615 .unwrap();
616 let signer_address = signer.address().await.unwrap();
617
618 assert_eq!(test_key_address(), signer_address);
619 }
620
621 #[tokio::test]
622 async fn test_address_evm_signer_turnkey() {
623 let signer_model = SignerDomainModel {
624 id: "test".to_string(),
625 config: SignerConfig::Turnkey(TurnkeySignerConfig {
626 api_private_key: SecretString::new("api_private_key"),
627 api_public_key: "api_public_key".to_string(),
628 organization_id: "organization_id".to_string(),
629 private_key_id: "private_key_id".to_string(),
630 public_key: "047d3bb8e0317927700cf19fed34e0627367be1390ec247dddf8c239e4b4321a49aea80090e49b206b6a3e577a4f11d721ab063482001ee10db40d6f2963233eec".to_string(),
631 }),
632 };
633
634 let signer = EvmSignerFactory::create_evm_signer(signer_model)
635 .await
636 .unwrap();
637 let signer_address = signer.address().await.unwrap();
638
639 assert_eq!(
640 "0xb726167dc2ef2ac582f0a3de4c08ac4abb90626a",
641 signer_address.to_string()
642 );
643 }
644
645 #[tokio::test]
646 async fn test_address_evm_signer_cdp() {
647 let signer_model = SignerDomainModel {
648 id: "test".to_string(),
649 config: SignerConfig::Cdp(CdpSignerConfig {
650 api_key_id: "test-api-key-id".to_string(),
651 api_key_secret: SecretString::new("test-api-key-secret"),
652 wallet_secret: SecretString::new("test-wallet-secret"),
653 account_address: "0xb726167dc2ef2ac582f0a3de4c08ac4abb90626a".to_string(),
654 }),
655 };
656
657 let signer = EvmSignerFactory::create_evm_signer(signer_model)
658 .await
659 .unwrap();
660 let signer_address = signer.address().await.unwrap();
661
662 assert_eq!(
663 "0xb726167dc2ef2ac582f0a3de4c08ac4abb90626a",
664 signer_address.to_string()
665 );
666 }
667
668 #[tokio::test]
669 async fn test_sign_data_evm_signer_local() {
670 let signer_model = SignerDomainModel {
671 id: "test".to_string(),
672 config: SignerConfig::Local(LocalSignerConfig {
673 raw_key: test_key_bytes(),
674 }),
675 };
676
677 let signer = EvmSignerFactory::create_evm_signer(signer_model)
678 .await
679 .unwrap();
680 let request = SignDataRequest {
681 message: "Test message".to_string(),
682 };
683
684 let result = signer.sign_data(request).await;
685
686 assert!(result.is_ok());
687
688 let response = result.unwrap();
689 assert!(matches!(response, SignDataResponse::Evm(_)));
690
691 if let SignDataResponse::Evm(sig) = response {
692 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); }
697 }
698
699 #[tokio::test]
700 async fn test_sign_transaction_evm() {
701 let signer_model = SignerDomainModel {
702 id: "test".to_string(),
703 config: SignerConfig::Local(LocalSignerConfig {
704 raw_key: test_key_bytes(),
705 }),
706 };
707
708 let signer = EvmSignerFactory::create_evm_signer(signer_model)
709 .await
710 .unwrap();
711
712 let transaction_data = NetworkTransactionData::Evm(EvmTransactionData {
713 from: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e".to_string(),
714 to: Some("0x742d35Cc6634C0532925a3b844Bc454e4438f44f".to_string()),
715 gas_price: Some(20000000000),
716 gas_limit: Some(21000),
717 nonce: Some(0),
718 value: U256::from(1000000000000000000u64),
719 data: Some("0x".to_string()),
720 chain_id: 1,
721 hash: None,
722 signature: None,
723 raw: None,
724 max_fee_per_gas: None,
725 max_priority_fee_per_gas: None,
726 speed: None,
727 });
728
729 let result = signer.sign_transaction(transaction_data).await;
730
731 assert!(result.is_ok());
732
733 let signed_tx = result.unwrap();
734
735 assert!(matches!(signed_tx, SignTransactionResponse::Evm(_)));
736
737 if let SignTransactionResponse::Evm(evm_tx) = signed_tx {
738 assert!(!evm_tx.hash.is_empty());
739 assert!(!evm_tx.raw.is_empty());
740 assert!(!evm_tx.signature.sig.is_empty());
741 }
742 }
743
744 #[tokio::test]
745 async fn test_create_evm_signer_google_cloud_kms() {
746 let signer_model = SignerDomainModel {
747 id: "test".to_string(),
748 config: SignerConfig::GoogleCloudKms(Box::new(GoogleCloudKmsSignerConfig {
749 service_account: GoogleCloudKmsSignerServiceAccountConfig {
750 project_id: SecretString::new("project_id"),
751 private_key_id: SecretString::new("private_key_id"),
752 private_key: SecretString::new("-----BEGIN EXAMPLE PRIVATE KEY-----\nFAKEKEYDATA\n-----END EXAMPLE PRIVATE KEY-----\n"),
753 client_email: SecretString::new("client_email@example.com"),
754 client_id: SecretString::new("client_id"),
755 auth_uri: SecretString::new("https://accounts.google.com/o/oauth2/auth"),
756 token_uri: SecretString::new("https://oauth2.googleapis.com/token"),
757 auth_provider_x509_cert_url: SecretString::new("https://www.googleapis.com/oauth2/v1/certs"),
758 client_x509_cert_url: SecretString::new("https://www.googleapis.com/robot/v1/metadata/x509/client_email%40example.com"),
759 universe_domain: SecretString::new("googleapis.com"),
760 },
761 key: GoogleCloudKmsSignerKeyConfig {
762 location: SecretString::new("global"),
763 key_id: SecretString::new("id"),
764 key_ring_id: SecretString::new("key_ring"),
765 key_version: 1,
766 },
767 })),
768 };
769
770 let result = EvmSignerFactory::create_evm_signer(signer_model).await;
771
772 assert!(result.is_ok());
773 assert!(matches!(result.unwrap(), EvmSigner::GoogleCloudKms(_)));
774 }
775
776 #[tokio::test]
777 async fn test_sign_data_with_different_message_types() {
778 let signer_model = SignerDomainModel {
779 id: "test".to_string(),
780 config: SignerConfig::Local(LocalSignerConfig {
781 raw_key: test_key_bytes(),
782 }),
783 };
784
785 let signer = EvmSignerFactory::create_evm_signer(signer_model)
786 .await
787 .unwrap();
788
789 let long_message = "a".repeat(1000);
791 let test_cases = vec![
792 ("Simple message", "Test message"),
793 ("Empty message", ""),
794 ("Unicode message", "🚀 Test message with émojis"),
795 ("Long message", long_message.as_str()),
796 ("JSON message", r#"{"test": "value", "number": 123}"#),
797 ];
798
799 for (name, message) in test_cases {
800 let request = SignDataRequest {
801 message: message.to_string(),
802 };
803
804 let result = signer.sign_data(request).await;
805 assert!(result.is_ok(), "Failed to sign {name}");
806
807 if let Ok(SignDataResponse::Evm(sig)) = result {
808 assert_eq!(sig.r.len(), 64, "Invalid r length for {name}");
809 assert_eq!(sig.s.len(), 64, "Invalid s length for {name}");
810 assert!(sig.v == 27 || sig.v == 28, "Invalid v value for {name}");
811 assert_eq!(sig.sig.len(), 130, "Invalid signature length for {name}");
812 } else {
813 panic!("Expected EVM signature for {name}");
814 }
815 }
816 }
817
818 #[tokio::test]
820 async fn test_eip712_hash_with_0x_prefix() {
821 let request_with_prefix = SignTypedDataRequest {
822 domain_separator: format!("0x{}", "a".repeat(64)),
823 hash_struct_message: format!("0x{}", "b".repeat(64)),
824 };
825
826 let request_without_prefix = SignTypedDataRequest {
827 domain_separator: "a".repeat(64),
828 hash_struct_message: "b".repeat(64),
829 };
830
831 let hash1 = construct_eip712_message_hash(&request_with_prefix).unwrap();
832 let hash2 = construct_eip712_message_hash(&request_without_prefix).unwrap();
833
834 assert_eq!(
835 hash1, hash2,
836 "Hash should be same with or without 0x prefix"
837 );
838 }
839
840 #[tokio::test]
841 async fn test_eip712_deterministic() {
842 let request = SignTypedDataRequest {
843 domain_separator: "a".repeat(64),
844 hash_struct_message: "b".repeat(64),
845 };
846
847 let hash1 = construct_eip712_message_hash(&request).unwrap();
848 let hash2 = construct_eip712_message_hash(&request).unwrap();
849
850 assert_eq!(hash1, hash2, "Hash must be deterministic");
851 }
852
853 #[tokio::test]
854 async fn test_eip712_invalid_domain_length() {
855 let request = SignTypedDataRequest {
856 domain_separator: "a".repeat(30), hash_struct_message: "b".repeat(64),
858 };
859
860 let result = construct_eip712_message_hash(&request);
861 assert!(result.is_err());
862 if let Err(e) = result {
863 assert!(e.to_string().contains("Invalid domain separator length"));
864 }
865 }
866
867 #[tokio::test]
868 async fn test_eip712_invalid_hash_struct_length() {
869 let request = SignTypedDataRequest {
870 domain_separator: "a".repeat(64),
871 hash_struct_message: "b".repeat(30), };
873
874 let result = construct_eip712_message_hash(&request);
875 assert!(result.is_err());
876 if let Err(e) = result {
877 assert!(e.to_string().contains("Invalid hash struct length"));
878 }
879 }
880
881 #[tokio::test]
882 async fn test_eip712_invalid_hex_characters() {
883 let request = SignTypedDataRequest {
884 domain_separator: "zzzz".to_string(), hash_struct_message: "b".repeat(64),
886 };
887
888 let result = construct_eip712_message_hash(&request);
889 assert!(result.is_err());
890 if let Err(e) = result {
891 assert!(e.to_string().contains("non-hexadecimal character"));
892 }
893 }
894
895 #[tokio::test]
896 async fn test_eip712_invalid_hex_at_specific_position() {
897 let request = SignTypedDataRequest {
898 domain_separator: format!("{}z{}", "a".repeat(10), "a".repeat(53)), hash_struct_message: "b".repeat(64),
900 };
901
902 let result = construct_eip712_message_hash(&request);
903 assert!(result.is_err());
904 if let Err(e) = result {
905 let err_msg = e.to_string();
906 assert!(err_msg.contains("non-hexadecimal character"));
907 assert!(err_msg.contains("position 10")); }
909 }
910
911 #[test]
912 fn test_signature_validation_wrong_length() {
913 let sig_64_bytes = vec![0u8; 64];
914 let result = validate_and_format_signature(&sig_64_bytes, "TestSigner");
915 assert!(result.is_err());
916 if let Err(e) = result {
917 assert!(e.to_string().contains("expected 65 bytes"));
918 }
919
920 let sig_66_bytes = vec![0u8; 66];
921 let result = validate_and_format_signature(&sig_66_bytes, "TestSigner");
922 assert!(result.is_err());
923
924 let sig_0_bytes = vec![];
925 let result = validate_and_format_signature(&sig_0_bytes, "TestSigner");
926 assert!(result.is_err());
927 }
928
929 #[test]
930 fn test_signature_validation_correct_length() {
931 let sig_65_bytes = vec![0u8; 65];
932 let result = validate_and_format_signature(&sig_65_bytes, "TestSigner");
933 assert!(result.is_ok());
934
935 if let Ok(SignDataResponse::Evm(sig)) = result {
936 assert_eq!(sig.r.len(), 64); assert_eq!(sig.s.len(), 64); assert_eq!(sig.v, 0); assert_eq!(sig.sig.len(), 130); }
941 }
942
943 #[tokio::test]
944 async fn test_eip712_odd_length_hex_string() {
945 let request = SignTypedDataRequest {
947 domain_separator: "a".repeat(63), hash_struct_message: "b".repeat(64),
949 };
950
951 let result = construct_eip712_message_hash(&request);
952 assert!(result.is_err());
953 }
954
955 #[tokio::test]
956 async fn test_eip712_mixed_case_hex() {
957 let request = SignTypedDataRequest {
959 domain_separator: "AaBbCcDdEeFf11223344556677889900AaBbCcDdEeFf11223344556677889900"
960 .to_string(),
961 hash_struct_message: "b".repeat(64),
962 };
963
964 let result = construct_eip712_message_hash(&request);
965 assert!(result.is_ok());
966 }
967
968 #[tokio::test]
969 async fn test_validate_and_decode_hex_error_includes_input_preview() {
970 let long_invalid_hex = format!("{}z{}", "a".repeat(20), "a".repeat(100));
971 let result = validate_and_decode_hex(&long_invalid_hex, "test_field");
972
973 assert!(result.is_err());
974 if let Err(e) = result {
975 let err_msg = e.to_string();
976 assert!(err_msg.contains("input:"));
978 assert!(err_msg.len() < 200); }
980 }
981}