1use crate::config::Config;
2use crate::constants::{ETH_SECP_LEN, SIGNATURE_RS_LEN};
3use crate::services::crypto_service::CryptoService;
4use crate::services::keys_service::{KeyEntry, KeysService, KeysServiceTrait, SigEntry};
5use ethers::types::{H256, Signature, TransactionRequest};
6use k256::PublicKey;
7use serde_json::json;
8use std::str::FromStr;
9use tracing::{error, info};
10
11pub struct EthereumKeysService {
12 keys_service: KeysService,
13}
14
15impl EthereumKeysService {
16 pub async fn new(config: Config, crypto_service: CryptoService) -> Result<Self, String> {
30 let keys_service = KeysService::new(config, crypto_service).await?;
31 Ok(Self { keys_service })
32 }
33}
34
35#[async_trait::async_trait]
36impl KeysServiceTrait for EthereumKeysService {
37 async fn create_key(&mut self, _config: &Config) -> Result<KeyEntry, String> {
48 let (key_id, public_key_base64) = self
49 .keys_service
50 .kms_client_service
51 .create_key()
52 .await
53 .map_err(|e| {
54 let msg = format!("Failed to create_key in KmsClientService: {e}");
55 error!("{}", &msg);
56 msg
57 })?;
58
59 let public_key = self
60 .keys_service
61 .crypto_service
62 .public_key(&public_key_base64)
63 .map_err(|e| {
64 let msg = format!("public_key conversion failed: {e:?}");
65 error!("{}", &msg);
66 msg
67 })?;
68
69 let key = self.resolve_key(&public_key)?;
70
71 if public_key.is_empty() {
72 let msg = "No public key generated".to_string();
73 error!("{}", &msg);
74 return Err(msg);
75 }
76
77 self.keys_service
79 .kms_client_service
80 .create_alias(&key_id, &key)
81 .await
82 .map_err(|e| {
83 let msg = format!("Error creating alias: {e:?}");
84 error!("{}", &msg);
85 msg
86 })?;
87
88 info!("{}", &public_key_base64);
89
90 Ok(KeyEntry {
91 public_key: Some(public_key.clone()).into(),
92 address: key.into(),
93 public_key_base64: public_key_base64.into(),
94 key_id: key_id.into(),
95 })
96 }
97
98 async fn sign_transaction_hash(
112 &mut self,
113 config: &Config,
114 transaction_hash: &str,
115 key: &str,
116 ) -> Result<SigEntry, String> {
117 Self::ensure_ethereum_mode(config)?;
118
119 Self::validate_transaction_hash(transaction_hash)?;
120
121 let key = self.resolve_key(key)?;
122 let public_key = self.resolve_public_key(&key).await?;
123
124 Self::validate_public_key(&public_key, "sign_transaction_hash")?;
125
126 let signature = self
128 .sign_with_recovery_id(transaction_hash, &key, &public_key, config)
129 .await?;
130
131 Ok(SigEntry {
132 address: key.into(),
133 public_key: public_key.into(),
134 signature: signature.into(),
135 })
136 }
137
138 async fn sign_transaction(
159 &mut self,
160 config: &Config,
161 transaction_str: &str,
162 key: &str,
163 ) -> Result<String, String> {
164 Self::ensure_ethereum_mode(config)?;
165
166 let tx_json: serde_json::Value = serde_json::from_str(transaction_str)
167 .map_err(|e| format!("Failed to parse input JSON: {e}"))?;
168
169 let (transaction_value, mut signatures) = match tx_json {
171 serde_json::Value::Object(mut map) => {
172 if let Some(tx) = map.remove("transaction") {
173 let sigs = map
174 .remove("signatures")
175 .and_then(|v| v.as_array().cloned())
176 .unwrap_or_default();
177 (tx, sigs)
178 } else {
179 (serde_json::Value::Object(map), vec![])
180 }
181 }
182 _ => return Err("Unsupported transaction format".to_string()),
183 };
184
185 let transaction: TransactionRequest = serde_json::from_value(transaction_value.clone())
187 .map_err(|e| format!("Failed to parse transaction: {e}"))?;
188
189 let mut transaction_hash = format!("{:x}", transaction.sighash());
190 transaction_hash = transaction_hash.trim_start_matches("0x").to_string();
191
192 let key = self.resolve_key(key)?;
193 let public_key = self.resolve_public_key(&key).await?;
194
195 Self::validate_public_key(&public_key, "sign_transaction")?;
197
198 let signature_hex = self
200 .sign_with_recovery_id(&transaction_hash, &key, &public_key, config)
201 .await?;
202
203 let signature = Signature::from_str(&signature_hex)
205 .map_err(|e| format!("Failed to serialize signature: {e}"))?;
206
207 signatures.push(Self::signature_to_json(
209 &key,
210 &signature,
211 &public_key,
212 &transaction_hash,
213 ));
214
215 let result = json!({
217 "transaction": transaction_value,
218 "signatures": signatures,
219 });
220
221 serde_json::to_string(&result)
222 .map_err(|e| format!("Failed to serialize signed transaction JSON: {e}"))
223 }
224
225 async fn verify(
226 &mut self,
227 transaction_hash: &str,
228 signature_hex: &str,
229 key: &str,
230 ) -> Result<bool, String> {
231 let public_key = self.resolve_public_key(key).await?;
232 self.keys_service
233 .verify_eip155(transaction_hash, signature_hex, &public_key)
234 }
235
236 async fn verify_via_kms(
237 &mut self,
238 transaction_hash: &str,
239 signature_hex: &str,
240 key: &str,
241 ) -> Result<bool, String> {
242 let public_key = self.resolve_public_key(key).await?;
243 self.keys_service
244 .verify_via_kms_eip155(transaction_hash, signature_hex, &public_key)
245 .await
246 }
247
248 async fn delete_key(&mut self, key: &str) -> Result<bool, String> {
249 let key = self.resolve_key(key)?;
250 self.keys_service.delete_key(&key).await
251 }
252
253 async fn list_keys(&mut self) -> Result<Vec<KeyEntry>, String> {
254 self.keys_service.list_keys().await
255 }
256}
257
258impl EthereumKeysService {
259 async fn sign_with_recovery_id(
271 &mut self,
272 transaction_hash: &str,
273 key: &str,
274 public_key: &str,
275 config: &Config,
276 ) -> Result<String, String> {
277 let mut signature_hex = self
278 .keys_service
279 .sign(transaction_hash, key, None)
280 .await
281 .map_err(|e| format!("Signing failed: {e}"))?;
282
283 if signature_hex.len() == SIGNATURE_RS_LEN {
284 let v_hex = match self.keys_service.crypto_service.recover_v(
285 transaction_hash,
286 &signature_hex,
287 public_key,
288 Some(config.get_eth_chain_id()),
289 ) {
290 Ok(v) => v,
291 Err(e) => {
292 error!("Failed to recover v: {}", e);
293 String::new()
294 }
295 };
296
297 if !v_hex.is_empty() {
298 signature_hex.push_str(&v_hex);
299 }
300 }
301
302 Self::validate_signature_length(&signature_hex)?;
303
304 let is_valid = self
306 .verify(transaction_hash, &signature_hex, public_key)
307 .await?;
308 if !is_valid {
309 return Err("Signature verification failed".to_string());
310 }
311
312 Ok(signature_hex)
313 }
314
315 fn resolve_key(&mut self, key: &str) -> Result<String, String> {
333 if key.len() == ETH_SECP_LEN {
334 self.keys_service
335 .crypto_service
336 .address_eth(key)
337 .map_err(|e| {
338 let msg = format!("Failed to convert public key to address: {e:?}");
339 error!("{}", &msg);
340 msg
341 })
342 } else {
343 Ok(key.to_string())
344 }
345 }
346
347 pub async fn resolve_public_key(&mut self, key: &str) -> Result<String, String> {
365 if key.len() == ETH_SECP_LEN {
366 Ok(key.to_string())
367 } else {
368 let public_key = self
369 .keys_service
370 .kms_client_service
371 .get_public_key(key)
372 .await
373 .map_err(|e| {
374 let msg = format!("Failed to get public key from alias with KMS: {e}");
375 error!("{}", msg);
376 msg
377 })?;
378 self.keys_service
379 .crypto_service
380 .public_key(&public_key)
381 .map_err(|e| {
382 let msg = format!("public_key conversion failed: {e:?}");
383 error!("{}", &msg);
384 msg
385 })
386 }
387 }
388
389 fn validate_public_key(public_key: &str, context: &str) -> Result<(), String> {
390 let public_key_bytes = hex::decode(public_key).map_err(|e| {
391 let msg = format!("Error decoding public key in {context}: {e:?}");
392 error!("{}", msg);
393 msg
394 })?;
395
396 PublicKey::from_sec1_bytes(&public_key_bytes).map_err(|e| {
397 let msg = format!("Invalid public key bytes in {context}: {e:?}");
398 error!("{}", msg);
399 msg
400 })?;
401
402 Ok(())
403 }
404
405 fn validate_signature_length(hex: &str) -> Result<(), String> {
406 let bytes = hex::decode(hex).map_err(|_| "Invalid hex in signature".to_string())?;
407 if bytes.len() == 65 {
408 Ok(())
409 } else {
410 Err("Invalid signature length (expected 65 bytes)".to_string())
411 }
412 }
413
414 fn ensure_ethereum_mode(config: &Config) -> Result<(), String> {
415 if config.is_ethereum_mode() {
416 Ok(())
417 } else {
418 Err("Only Ethereum mode is supported".to_string())
419 }
420 }
421
422 fn validate_transaction_hash(transaction_hash: &str) -> Result<H256, String> {
423 transaction_hash.parse::<H256>().map_err(|e| {
424 let msg = format!("Invalid transaction hash: {e:?}");
425 error!("{}", msg);
426 msg
427 })
428 }
429
430 fn signature_to_json(
431 address: &str,
432 sig: &Signature,
433 public_key: &str,
434 hash: &str,
435 ) -> serde_json::Value {
436 json!({
437 "address": address,
438 "signer": public_key,
439 "v": format!("{:x}", sig.v),
440 "r": format!("{:x}", sig.r),
441 "s": format!("{:x}", sig.s),
442 "hash": hash,
443 "signature": sig.to_string(),
444 })
445 }
446}
447
448#[cfg(test)]
449mod tests {
450 use super::*;
451 use crate::{
452 config::ConfigBuilder,
453 constants::{
454 ETH_ADDRESS, ETH_PUBLIC_KEY, ETH_SIGNATURE, ETH_TRANSACTION, ETH_TRANSACTION_HASH,
455 WASM_PATH,
456 },
457 services::crypto_service::CryptoService,
458 wasm_loader::WasmLoader,
459 };
460 use base64::Engine;
461 use base64::engine::general_purpose::STANDARD;
462 use serde_json::Value;
463
464 #[tokio::test]
465 async fn test_create_key() {
466 let config = ConfigBuilder::new()
467 .with_ethereum_mode()
468 .with_aws_mode(false)
469 .build();
470
471 let wasm_loader = WasmLoader::new(WASM_PATH)
472 .await
473 .expect("Failed to load WASM module");
474
475 let crypto_service =
476 CryptoService::new(&wasm_loader).expect("Failed to initialize CryptoService");
477
478 let mut service = EthereumKeysService::new(config.clone(), crypto_service)
480 .await
481 .expect("Failed to create EthereumKeysService");
482
483 let result = service.create_key(&config).await;
485
486 assert!(result.is_ok(), "create_key failed: {result:?}");
488 let key = result.unwrap();
489 let pubkey = key.public_key.as_deref().expect("Missing public key");
490
491 assert!(
492 pubkey.starts_with("02") || pubkey.starts_with("03"),
493 "Public key must start with 02 or 03"
494 );
495 assert!(!pubkey.is_empty(), "Public key must not be empty");
496 }
497
498 #[tokio::test]
499 async fn test_verify_signature() {
500 let config = ConfigBuilder::new()
501 .with_ethereum_mode()
502 .with_aws_mode(false)
503 .build();
504
505 let wasm_loader = WasmLoader::new(WASM_PATH)
506 .await
507 .expect("Failed to load WASM module");
508
509 let crypto_service =
510 CryptoService::new(&wasm_loader).expect("Failed to initialize CryptoService");
511
512 let mut service = EthereumKeysService::new(config.clone(), crypto_service)
514 .await
515 .expect("Failed to create EthereumKeysService");
516
517 let result = service
519 .verify(ETH_TRANSACTION_HASH, ETH_SIGNATURE, ETH_PUBLIC_KEY)
520 .await
521 .unwrap();
522 assert!(result, "Expected signature to verify correctly");
523
524 let result = service
526 .verify("bad_hash", "bad_signature", "bad_key")
527 .await
528 .unwrap();
529 assert!(!result, "Expected signature verification to fail");
530 }
531
532 #[tokio::test]
533 async fn test_verify_via_kms_signature() {
534 let config = ConfigBuilder::new()
535 .with_ethereum_mode()
536 .with_aws_mode(false)
537 .build();
538
539 let wasm_loader = WasmLoader::new(WASM_PATH)
540 .await
541 .expect("Failed to load WASM module");
542
543 let crypto_service =
544 CryptoService::new(&wasm_loader).expect("Failed to initialize CryptoService");
545
546 let mut service = EthereumKeysService::new(config.clone(), crypto_service)
548 .await
549 .expect("Failed to create EthereumKeysService");
550
551 let result = service
553 .verify_via_kms(ETH_TRANSACTION_HASH, ETH_SIGNATURE, ETH_PUBLIC_KEY)
554 .await
555 .unwrap();
556
557 assert!(result, "Expected signature to verify correctly");
558
559 let result = service
561 .verify_via_kms("bad_hash", "bad_signature", "bad_key")
562 .await
563 .unwrap();
564 assert!(!result, "Expected signature verification to fail");
565 }
566
567 #[tokio::test]
568 async fn test_delete_key() {
569 let config = ConfigBuilder::new()
570 .with_ethereum_mode()
571 .with_aws_mode(false)
572 .build();
573
574 let wasm_loader = WasmLoader::new(WASM_PATH)
575 .await
576 .expect("Failed to load WASM module");
577
578 let crypto_service =
579 CryptoService::new(&wasm_loader).expect("Failed to initialize CryptoService");
580
581 let mut service = EthereumKeysService::new(config.clone(), crypto_service)
583 .await
584 .expect("Failed to create EthereumKeysService");
585
586 let result = service
587 .delete_key("known_public_key")
588 .await
589 .expect("Failed to delete known public key");
590 assert!(result, "Expected key to be deleted successfully");
591
592 let result = service
593 .delete_key("unknown_key")
594 .await
595 .expect("Failed to delete unknown public key");
596 assert!(!result, "Expected key deletion to fail for unknown key");
597 }
598
599 #[tokio::test]
600 async fn test_list_keys() {
601 let config = ConfigBuilder::new()
602 .with_ethereum_mode()
603 .with_aws_mode(false)
604 .build();
605
606 let wasm_loader = WasmLoader::new(WASM_PATH)
607 .await
608 .expect("Failed to load WASM module");
609
610 let crypto_service =
611 CryptoService::new(&wasm_loader).expect("Failed to initialize CryptoService");
612
613 let mut service = EthereumKeysService::new(config.clone(), crypto_service)
615 .await
616 .expect("Failed to create EthereumKeysService");
617
618 let result = service.list_keys().await;
619
620 assert!(result.is_ok(), "Expected list_keys to succeed");
621
622 let keys = result.unwrap();
623 assert_eq!(keys.len(), 2);
624 assert_eq!(
625 keys[0],
626 KeyEntry {
627 address: "address_1".to_string().into(),
628 public_key_base64: STANDARD.encode("public_key_1_base64").into(),
629 public_key: Some("public_key_1".to_string()).into(),
630 key_id: "key_id_1".to_string().into(),
631 }
632 );
633 assert_eq!(
634 keys[1],
635 KeyEntry {
636 address: "address_2".to_string().into(),
637 public_key_base64: STANDARD.encode("public_key_2_base64").into(),
638 public_key: Some("public_key_2".to_string()).into(),
639 key_id: "key_id_2".to_string().into(),
640 }
641 );
642 }
643
644 #[tokio::test]
645 async fn test_sign_transaction_hash() {
646 let config = ConfigBuilder::new()
647 .with_ethereum_mode()
648 .with_aws_mode(false)
649 .build();
650
651 let wasm_loader = WasmLoader::new(WASM_PATH)
652 .await
653 .expect("Failed to load WASM module");
654
655 let crypto_service =
656 CryptoService::new(&wasm_loader).expect("Failed to initialize CryptoService");
657
658 let mut service = EthereumKeysService::new(config.clone(), crypto_service)
660 .await
661 .expect("Failed to create EthereumKeysService");
662
663 let result = service
664 .sign_transaction_hash(&config, ETH_TRANSACTION_HASH, ETH_PUBLIC_KEY)
665 .await;
666
667 assert!(result.is_ok(), "Expected signing to succeed");
668
669 let signed = result.unwrap();
670
671 assert_eq!(
672 signed.signature.to_string(),
673 ETH_SIGNATURE,
674 "Expected signature to match expected format"
675 );
676 }
677
678 #[tokio::test]
679 async fn test_sign_transaction_hash_from_address() {
680 let config = ConfigBuilder::new()
681 .with_ethereum_mode()
682 .with_aws_mode(false)
683 .build();
684
685 let wasm_loader = WasmLoader::new(WASM_PATH)
686 .await
687 .expect("Failed to load WASM module");
688
689 let crypto_service =
690 CryptoService::new(&wasm_loader).expect("Failed to initialize CryptoService");
691
692 let mut service = EthereumKeysService::new(config.clone(), crypto_service)
694 .await
695 .expect("Failed to create EthereumKeysService");
696
697 let result = service
698 .sign_transaction_hash(&config, ETH_TRANSACTION_HASH, ETH_ADDRESS)
699 .await;
700
701 assert!(result.is_ok(), "Expected signing to succeed");
702
703 let signed = result.unwrap();
704
705 assert_eq!(
706 signed.signature.to_string(),
707 ETH_SIGNATURE,
708 "Expected signature to match expected format"
709 );
710 }
711
712 #[tokio::test]
713 async fn test_sign_transaction() {
714 let config = ConfigBuilder::new()
715 .with_ethereum_mode()
716 .with_aws_mode(false)
717 .build();
718
719 let wasm_loader = WasmLoader::new(WASM_PATH)
720 .await
721 .expect("Failed to load WASM module");
722
723 let crypto_service =
724 CryptoService::new(&wasm_loader).expect("Failed to initialize CryptoService");
725
726 let mut service = EthereumKeysService::new(config.clone(), crypto_service)
728 .await
729 .expect("Failed to create EthereumKeysService");
730
731 let result = service
732 .sign_transaction(&config, ETH_TRANSACTION, ETH_PUBLIC_KEY)
733 .await;
734
735 assert!(result.is_ok(), "Expected signing to succeed");
736
737 let signed = result.unwrap();
738 let json: Value = serde_json::from_str(&signed).expect("Invalid JSON returned");
739
740 let signatures = json["signatures"]
741 .as_array()
742 .expect("Missing 'signatures' array");
743 let first = &signatures[0];
744
745 let signer = first["signer"].as_str().expect("Missing 'signer'");
746 let signature = first["signature"].as_str().expect("Missing 'signature'");
747
748 assert_eq!(
749 signer, ETH_PUBLIC_KEY,
750 "Expected signer to match ETH_PUBLIC_KEY"
751 );
752
753 assert_eq!(
754 signature, ETH_SIGNATURE,
755 "Expected signature to match expected format"
756 );
757 }
758
759 #[tokio::test]
760 async fn test_sign_transaction_from_address() {
761 let config = ConfigBuilder::new()
762 .with_ethereum_mode()
763 .with_aws_mode(false)
764 .build();
765
766 let wasm_loader = WasmLoader::new(WASM_PATH)
767 .await
768 .expect("Failed to load WASM module");
769
770 let crypto_service =
771 CryptoService::new(&wasm_loader).expect("Failed to initialize CryptoService");
772
773 let mut service = EthereumKeysService::new(config.clone(), crypto_service)
775 .await
776 .expect("Failed to create EthereumKeysService");
777
778 let result = service
779 .sign_transaction(&config, ETH_TRANSACTION, ETH_ADDRESS)
780 .await;
781
782 assert!(result.is_ok(), "Expected signing to succeed");
783
784 let signed = result.unwrap();
785 let json: Value = serde_json::from_str(&signed).expect("Invalid JSON returned");
786
787 let signatures = json["signatures"]
788 .as_array()
789 .expect("Missing 'signatures' array");
790 let first = &signatures[0];
791
792 let signer = first["signer"].as_str().expect("Missing 'signer'");
793 let signature = first["signature"].as_str().expect("Missing 'signature'");
794
795 assert_eq!(
796 signer, ETH_PUBLIC_KEY,
797 "Expected signer to match ETH_PUBLIC_KEY"
798 );
799
800 assert_eq!(
801 signature, ETH_SIGNATURE,
802 "Expected signature to match expected format"
803 );
804 }
805
806 #[tokio::test]
807 async fn test_sign_transaction_malformed_json() {
808 let config = ConfigBuilder::new()
809 .with_ethereum_mode()
810 .with_aws_mode(false)
811 .build();
812
813 let wasm_loader = WasmLoader::new(WASM_PATH)
814 .await
815 .expect("Failed to load WASM");
816
817 let crypto_service =
818 CryptoService::new(&wasm_loader).expect("Failed to create crypto service");
819
820 let mut service = EthereumKeysService::new(config.clone(), crypto_service)
821 .await
822 .expect("Failed to create service");
823
824 let bad_json = "{ this is not valid JSON }";
825
826 let result = service
827 .sign_transaction(&config, bad_json, ETH_PUBLIC_KEY)
828 .await;
829 assert!(result.is_err());
830 assert!(
831 result
832 .unwrap_err()
833 .starts_with("Failed to parse input JSON"),
834 "Expected JSON parsing failure"
835 );
836 }
837
838 #[tokio::test]
839 async fn test_sign_transaction_missing_transaction_field() {
840 let config = ConfigBuilder::new()
841 .with_ethereum_mode()
842 .with_aws_mode(false)
843 .build();
844
845 let wasm_loader = WasmLoader::new(WASM_PATH)
846 .await
847 .expect("Failed to load WASM");
848
849 let crypto_service =
850 CryptoService::new(&wasm_loader).expect("Failed to create crypto service");
851
852 let mut service = EthereumKeysService::new(config.clone(), crypto_service)
853 .await
854 .expect("Failed to create service");
855
856 let transaction = json!({
858 "to": "0xdeadbeef",
859 "value": "0x1"
860 })
861 .to_string();
862
863 let result = service
864 .sign_transaction(&config, &transaction, ETH_PUBLIC_KEY)
865 .await;
866
867 assert!(result.is_err() || result.is_ok()); }
870
871 #[tokio::test]
872 async fn test_sign_transaction_malformed_signatures() {
873 let config = ConfigBuilder::new()
874 .with_ethereum_mode()
875 .with_aws_mode(false)
876 .build();
877
878 let wasm_loader = WasmLoader::new(WASM_PATH)
879 .await
880 .expect("Failed to load WASM");
881
882 let crypto_service =
883 CryptoService::new(&wasm_loader).expect("Failed to create crypto service");
884
885 let mut service = EthereumKeysService::new(config.clone(), crypto_service)
886 .await
887 .expect("Failed to create service");
888
889 let transaction = json!({
890 "signatures": "not an array"
891 })
892 .to_string();
893
894 let result = service
895 .sign_transaction(&config, &transaction, ETH_PUBLIC_KEY)
896 .await;
897
898 assert!(result.is_err());
899 }
900}