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