kms_secp256k1_api/services/
ethereum_keys_service.rs

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    /// Creates a new instance of the service.
17    ///
18    /// # Parameters
19    /// - `config`: The configuration settings for the service.
20    /// - `crypto_service`: The cryptographic service used for key management.
21    ///
22    /// # Returns
23    /// Returns `Ok(Self)` if the service is successfully created, or
24    /// an `Err(String)` containing an error message if initialization fails.
25    ///
26    /// # Errors
27    /// This function returns an error if the underlying `KeysService::new`
28    /// call fails, propagating its error as a string.
29    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    /// Creates a new KMS key, derives its public key with an optional prefix based on config,
38    /// registers an alias for it, and returns the formatted public key.
39    ///
40    /// # Arguments
41    ///
42    /// * `config` - Reference to the config struct used to decide prefixing.
43    ///
44    /// # Errors
45    ///
46    /// Returns an error if key creation, public key conversion, or alias creation fails.
47    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        // Create alias for the key
78        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    /// Signs a transaction hash using the provided public key and configuration mode.
99    ///
100    /// Delegates signing to the keys service `sign` method.
101    ///
102    /// # Arguments
103    ///
104    /// * `config` - Reference to the application configuration to determine signing mode.
105    /// * `transaction_hash` - The hash of the transaction to be signed.
106    /// * `public_key` - The public key corresponding to the private key for signing.
107    ///
108    /// # Errors
109    ///
110    /// Returns an error if the signing operation fails.
111    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        // Perform signing
127        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    /// Signs a serialized Ethereum transaction using the provided public key.
139    ///
140    /// Parses the input transaction string, verifies the transaction hash and public key,
141    /// signs the hash, attaches the signature to the transaction, and returns the signed
142    /// transaction as a JSON string.
143    ///
144    /// # Arguments
145    ///
146    /// * `config` - Reference to the application configuration. Only Ethereum mode is supported.
147    /// * `transaction_str` - A JSON string representing the transaction to be signed.
148    /// * `public_key` - The public key corresponding to the signing key.
149    ///
150    /// # Errors
151    ///
152    /// Returns an error if:
153    /// - The application is not in Ethereum mode.
154    /// - The transaction JSON is invalid.
155    /// - The transaction hash or public key is invalid.
156    /// - The signing process fails.
157    /// - The final transaction serialization fails.
158    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        // Extract transaction and existing signatures if wrapped
170        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        // Deserialize transaction for sighash
186        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        // Decode and validate public key bytes
196        Self::validate_public_key(&public_key, "sign_transaction")?;
197
198        // Perform signing
199        let signature_hex = self
200            .sign_with_recovery_id(&transaction_hash, &key, &public_key, config)
201            .await?;
202
203        // Parse signature to get v, r, s
204        let signature = Signature::from_str(&signature_hex)
205            .map_err(|e| format!("Failed to serialize signature: {e}"))?;
206
207        // Append new signature to signatures array
208        signatures.push(Self::signature_to_json(
209            &key,
210            &signature,
211            &public_key,
212            &transaction_hash,
213        ));
214
215        // Return wrapped transaction and signatures
216        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    /// Signs a transaction hash using the provided key, and appends the recovery byte (`v`)
260    /// if the resulting signature is 128 hex characters long (i.e., 64 bytes).
261    ///
262    /// # Parameters
263    /// - `transaction_hash`: The hex-encoded transaction hash to sign.
264    /// - `public_key`: The public key associated with the signer.
265    /// - `config`: The application config (used to determine chain ID).
266    ///
267    /// # Returns
268    /// - `Ok(String)`: The final hex-encoded signature (possibly with `v` appended).
269    /// - `Err(String)`: An error message if signing fails or recovery fails.
270    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        // Verify signature
305        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    /// Resolves a given key string to an Ethereum address.
316    ///
317    /// This function checks whether the input `key` is a compressed secp256k1 public key
318    /// (by comparing its length to the expected `ETH_SECP_LEN`). If so, it attempts to convert
319    /// the public key to its corresponding Ethereum address using the crypto service. Otherwise,
320    /// it assumes the key is already an address and returns it as-is.
321    ///
322    /// # Parameters
323    /// - `key`: A string that is either an Ethereum address or a compressed public key (hex-encoded, starting with "02"/"03").
324    ///
325    /// # Returns
326    /// - `Ok(String)`: The resolved Ethereum address as a string.
327    /// - `Err(String)`: An error message if public key conversion fails.
328    ///
329    /// # Errors
330    /// - Returns an error if the input is treated as a public key and the conversion fails.
331    ///
332    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    /// Resolves a key string to its corresponding public key.
348    ///
349    /// If the input `key` is already a compressed secp256k1 public key (determined by its length),
350    /// it is returned directly. Otherwise, it is treated as an alias and resolved via the KMS service,
351    /// followed by conversion to a usable public key format.
352    ///
353    /// # Parameters
354    /// - `key`: A compressed public key or a key alias.
355    ///
356    /// # Returns
357    /// - `Ok(String)`: The resolved public key as a hex string.
358    /// - `Err(String)`: An error message if resolution or conversion fails.
359    ///
360    /// # Errors
361    /// Returns an error if:
362    /// - The public key cannot be retrieved from the KMS for the given alias.
363    /// - Conversion from raw public key bytes to the expected hex format fails.
364    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        // Create the service via the real constructor, it will use MockKmsClientService internally
479        let mut service = EthereumKeysService::new(config.clone(), crypto_service)
480            .await
481            .expect("Failed to create EthereumKeysService");
482
483        // Call create_key on the service under test
484        let result = service.create_key(&config).await;
485
486        // Assert the key is returned with the casper prefix
487        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        // Create EthereumKeysService (uses mocked KMS + real CryptoService)
513        let mut service = EthereumKeysService::new(config.clone(), crypto_service)
514            .await
515            .expect("Failed to create EthereumKeysService");
516
517        // Valid signature
518        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        //Invalid signature
525        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        // Create EthereumKeysService (uses mocked KMS + real CryptoService)
547        let mut service = EthereumKeysService::new(config.clone(), crypto_service)
548            .await
549            .expect("Failed to create EthereumKeysService");
550
551        // Valid signature
552        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        // Invalid signature
560        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        // Create EthereumKeysService (uses mocked KMS + real CryptoService)
582        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        // Create EthereumKeysService (uses mocked KMS + real CryptoService)
614        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        // Create EthereumKeysService (uses mocked KMS + real CryptoService)
659        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        // Create EthereumKeysService (uses mocked KMS + real CryptoService)
693        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        // Create EthereumKeysService (uses mocked KMS + real CryptoService)
727        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        // Create EthereumKeysService (uses mocked KMS + real CryptoService)
774        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        // JSON object with no "transaction" field — should fallback to whole object
857        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        // Either parse or signing might fail depending on internals
868        assert!(result.is_err() || result.is_ok()); // up to your logic
869    }
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}