kms_secp256k1_api/services/
crypto_service.rs

1use crate::{
2    constants::DEFAULT_ETH_CHAIN_ID,
3    wasm_loader::{WasmInstance, WasmLoader},
4};
5use std::{error::Error, sync::Arc};
6use wasmtime::TypedFunc;
7
8pub type CryptoService = WasmInstance;
9
10impl CryptoService {
11    /// Creates a new `CryptoService` by instantiating a WASM module from the given loader.
12    ///
13    /// # Errors
14    ///
15    /// Returns an error if the WASM module instantiation fails.
16    pub fn new(loader: &Arc<WasmLoader>) -> Result<Self, Box<dyn Error>> {
17        let instance = loader.instantiate()?;
18        Ok(instance)
19    }
20
21    fn invoke_wasm_str_func(
22        &mut self,
23        func: &TypedFunc<(i32, i32), i32>,
24        input: &str,
25    ) -> Result<String, Box<dyn std::error::Error>> {
26        let input_bytes = input.as_bytes();
27        let input_len = i32::try_from(input_bytes.len()).unwrap_or_default();
28
29        // 1. Allocation
30        let alloc_func = self.alloc.typed::<i32, i32>(&self.store)?;
31        let input_ptr_i32 = alloc_func.call(&mut self.store, input_len)?;
32        if input_ptr_i32 < 0 {
33            return Err("Allocation failed: negative pointer returned".into());
34        }
35        let input_ptr = usize::try_from(input_ptr_i32).map_err(|_| "Pointer conversion failed")?;
36
37        // 2. Write input into memory
38        let mem_data = self.memory.data_mut(&mut self.store);
39        let input_len_usize = usize::try_from(input_len).map_err(|_| "Invalid input length")?;
40        mem_data[input_ptr..input_ptr + input_len_usize].copy_from_slice(input_bytes);
41
42        // 3. Call wasm function
43        let output_ptr = func.call(
44            &mut self.store,
45            (i32::try_from(input_ptr).unwrap_or_default(), input_len),
46        )?;
47        if output_ptr == 0 {
48            return Err("WASM function returned null pointer".into());
49        }
50
51        // 4. Read null-terminated result string
52        let result_str = {
53            let mem_data = self.memory.data(&self.store);
54            let output_ptr_usize =
55                usize::try_from(output_ptr).map_err(|_| "Invalid output pointer")?;
56
57            let mut end = output_ptr_usize;
58            while end < mem_data.len() && mem_data[end] != 0 {
59                end += 1;
60            }
61            let output_bytes = &mem_data[output_ptr_usize..end];
62            std::str::from_utf8(output_bytes)?.to_string()
63        };
64
65        // 5. Free memory
66        let free_func = self.free.typed::<(i32, i32), ()>(&self.store)?;
67        free_func.call(
68            &mut self.store,
69            (
70                output_ptr,
71                i32::try_from(result_str.len() + 1).unwrap_or_default(),
72            ),
73        )?;
74        free_func.call(
75            &mut self.store,
76            (i32::try_from(input_ptr).unwrap_or_default(), input_len),
77        )?;
78
79        Ok(result_str)
80    }
81
82    ///
83    /// # Arguments
84    ///
85    /// * `public_key` - The public key as a string.
86    ///
87    /// # Returns
88    ///
89    /// A `Result` containing the hexadecimal string if successful, or an error if the WASM invocation fails.
90    ///
91    /// # Errors
92    ///
93    /// Returns an error if the WASM function cannot be typed or invoked correctly.
94    pub fn public_key(&mut self, public_key: &str) -> Result<String, Box<dyn std::error::Error>> {
95        let func = self.public_key.typed::<(i32, i32), i32>(&self.store)?;
96        self.invoke_wasm_str_func(&func, public_key)
97    }
98
99    ///
100    /// # Arguments
101    ///
102    /// * `public_key` - The public key as a string.
103    ///
104    /// # Returns
105    ///
106    /// A `Result` containing the Ethereum address as a hexadecimal string (with `0x` prefix) if successful,
107    /// or an error if the WASM invocation fails.
108    ///
109    /// # Errors
110    ///
111    /// Returns an error if the WASM function cannot be typed or invoked correctly,
112    /// or if the WASM function returns an error string.
113    pub fn address_eth(&mut self, public_key: &str) -> Result<String, Box<dyn std::error::Error>> {
114        let func = self.address_eth.typed::<(i32, i32), i32>(&self.store)?;
115        self.invoke_wasm_str_func(&func, public_key)
116    }
117
118    ///
119    /// # Arguments
120    ///
121    /// * `public_key` - The compressed secp256k1 public key as a hex string (33 bytes, starts with 0x02 or 0x03).
122    /// * `hrp` - The desired Bech32 HRP prefix (e.g., "cosmos", "osmo"). If empty or invalid, defaults to `"cosmos"`.
123    ///
124    /// # Returns
125    ///
126    /// A `Result` containing the Cosmos address as a Bech32m string if successful,
127    /// or an error if the WASM invocation fails.
128    ///
129    /// # Errors
130    ///
131    /// Returns an error if the WASM function cannot be typed or invoked correctly,
132    /// or if the WASM function returns an error string.
133    pub fn address_cosmos(
134        &mut self,
135        public_key: &str,
136        hrp: &str,
137    ) -> Result<String, Box<dyn std::error::Error>> {
138        let alloc_func = self.alloc.typed::<i32, i32>(&self.store)?;
139        let free_func = self.free.typed::<(i32, i32), ()>(&self.store)?;
140        let func = self
141            .address_cosmos
142            .typed::<(i32, i32, i32, i32), i32>(&self.store)?;
143
144        // Helper closure to allocate and write string input into WASM memory
145        let mut alloc_and_write = |input: &str| -> Result<(i32, i32), Box<dyn std::error::Error>> {
146            let bytes = input.as_bytes();
147            let len = i32::try_from(bytes.len())?;
148            let ptr_i32 = alloc_func.call(&mut self.store, len)?;
149            let ptr = usize::try_from(ptr_i32)?;
150            let mem = self.memory.data_mut(&mut self.store);
151            mem[ptr..ptr + bytes.len()].copy_from_slice(bytes);
152            Ok((ptr_i32, len))
153        };
154
155        // Write the public key and hrp into WASM memory
156        let (pk_ptr, pk_len) = alloc_and_write(public_key)?;
157        let (ud_ptr, ud_len) = alloc_and_write(hrp)?;
158
159        // Call the WASM function
160        let ret_ptr = func.call(&mut self.store, (pk_ptr, pk_len, ud_ptr, ud_len))?;
161
162        // Free input memory
163        free_func.call(&mut self.store, (pk_ptr, pk_len))?;
164        free_func.call(&mut self.store, (ud_ptr, ud_len))?;
165
166        // Check for null return
167        if ret_ptr == 0 {
168            return Err("address_cosmos returned null pointer".into());
169        }
170
171        // Read null-terminated string from WASM memory
172        let mem = self.memory.data(&self.store);
173        let mut end = usize::try_from(ret_ptr)
174            .map_err(|_| "address_cosmos pointer cannot be negative".to_string())?;
175        while end < mem.len() && mem[end] != 0 {
176            end += 1;
177        }
178        let start =
179            usize::try_from(ret_ptr).map_err(|_| "ret_ptr cannot be negative".to_string())?;
180        let result_bytes = &mem[start..end];
181        let result_str = std::str::from_utf8(result_bytes)?.to_string();
182
183        // Free result string in WASM memory
184        let len_i32 = i32::try_from(result_str.len())
185            .map_err(|_| "result_str length exceeds i32::MAX".to_string())?;
186        free_func.call(&mut self.store, (ret_ptr, len_i32))?;
187
188        Ok(result_str)
189    }
190
191    ///
192    /// # Arguments
193    ///
194    /// * `signature` - The signature to convert as a string.
195    ///
196    /// # Returns
197    ///
198    /// A `Result` containing the converted string if successful, or an error if the WASM invocation fails.
199    ///
200    /// # Errors
201    ///
202    /// Returns an error if the WASM function cannot be typed or invoked correctly.
203    pub fn convert(&mut self, signature: &str) -> Result<String, Box<dyn std::error::Error>> {
204        let func = self.convert.typed::<(i32, i32), i32>(&self.store)?;
205        self.invoke_wasm_str_func(&func, signature)
206    }
207
208    ///
209    /// # Arguments
210    ///
211    /// * `signature` - The converted signature as a string.
212    ///
213    /// # Returns
214    ///
215    /// A `Result` containing the original signature if successful, or an error if the WASM invocation fails.
216    ///
217    /// # Errors
218    ///
219    /// Returns an error if the WASM function cannot be typed or invoked correctly.
220    pub fn unconvert(&mut self, signature: &str) -> Result<String, Box<dyn std::error::Error>> {
221        let func = self.unconvert.typed::<(i32, i32), i32>(&self.store)?;
222        self.invoke_wasm_str_func(&func, signature)
223    }
224
225    /// Verifies a message signature using a public key via the WASM module.
226    ///
227    /// # Arguments
228    ///
229    /// * `message` - The original message.
230    /// * `signature` - The signature to verify.
231    /// * `public_key` - The public key used for verification.
232    ///
233    /// # Returns
234    ///
235    /// `Ok(true)` if the signature is valid, `Ok(false)` if invalid, or an error on failure.
236    ///
237    /// # Errors
238    ///
239    /// Returns an error if memory allocation or WASM function invocation fails.
240    pub fn verify(
241        &mut self,
242        message: &str,
243        signature: &str,
244        public_key: &str,
245    ) -> Result<bool, Box<dyn std::error::Error>> {
246        let alloc_func = self.alloc.typed::<i32, i32>(&self.store)?;
247        let free_func = self.free.typed::<(i32, i32), ()>(&self.store)?;
248        let func = self
249            .verify
250            .typed::<(i32, i32, i32, i32, i32, i32), i32>(&self.store)?;
251
252        // Helper closure to allocate memory and write input string bytes
253        let mut alloc_and_write = |input: &str| -> Result<(i32, i32), Box<dyn std::error::Error>> {
254            let bytes = input.as_bytes();
255            let len = i32::try_from(bytes.len()).unwrap_or_default();
256            let ptr_i32 = alloc_func.call(&mut self.store, len)?;
257            let ptr =
258                usize::try_from(ptr_i32).map_err(|_| "Invalid pointer returned from alloc")?;
259            let mem_data = self.memory.data_mut(&mut self.store);
260            mem_data[ptr..ptr + bytes.len()].copy_from_slice(bytes);
261            Ok((ptr_i32, len))
262        };
263
264        // Allocate and write each input string
265        let (msg_ptr, msg_len) = alloc_and_write(message)?;
266        let (sig_ptr, sig_len) = alloc_and_write(signature)?;
267        let (pk_ptr, pk_len) = alloc_and_write(public_key)?;
268
269        // Call the WASM verify function with pointers and lengths
270        let result = func.call(
271            &mut self.store,
272            (msg_ptr, msg_len, sig_ptr, sig_len, pk_ptr, pk_len),
273        )?;
274
275        // Free allocated memory after call
276        free_func.call(&mut self.store, (msg_ptr, msg_len))?;
277        free_func.call(&mut self.store, (sig_ptr, sig_len))?;
278        free_func.call(&mut self.store, (pk_ptr, pk_len))?;
279
280        Ok(result == 1)
281    }
282
283    /// Recovers the Ethereum-compatible recovery ID `v` (as a hex string) from a signature and public key via the WASM module.
284    ///
285    /// # Arguments
286    ///
287    /// * `message` - The original message hash as a hex string (64 characters).
288    /// * `signature` - The signature without the recovery ID (r + s) as a hex string (128 characters).
289    /// * `public_key` - The compressed public key as a hex string (66 characters).
290    ///
291    /// # Returns
292    ///
293    /// Returns `Ok(String)` containing the recovered `v` value in hex (e.g., `"1b"`) if successful,
294    /// or an error string from the WASM module if recovery fails.
295    ///
296    /// # Errors
297    ///
298    /// Returns an error if memory allocation fails, the WASM function invocation fails,
299    /// or if the returned pointer is null or the returned string is invalid UTF-8.
300    pub fn recover_v(
301        &mut self,
302        message: &str,
303        signature: &str,
304        public_key: &str,
305        eth_chain_id: Option<u8>,
306    ) -> Result<String, Box<dyn std::error::Error>> {
307        let alloc_func = self.alloc.typed::<i32, i32>(&self.store)?;
308        let free_func = self.free.typed::<(i32, i32), ()>(&self.store)?;
309        let func = self
310            .recover_v
311            .typed::<(i32, i32, i32, i32, i32, i32, i32), i32>(&self.store)?;
312
313        let mut alloc_and_write = |input: &str| -> Result<(i32, i32), Box<dyn std::error::Error>> {
314            let bytes = input.as_bytes();
315            let len = i32::try_from(bytes.len())?;
316            let ptr_i32 = alloc_func.call(&mut self.store, len)?;
317            let ptr = usize::try_from(ptr_i32)?;
318            let mem_data = self.memory.data_mut(&mut self.store);
319            mem_data[ptr..ptr + bytes.len()].copy_from_slice(bytes);
320            Ok((ptr_i32, len))
321        };
322
323        let (msg_ptr, msg_len) = alloc_and_write(message)?;
324        let (sig_ptr, sig_len) = alloc_and_write(signature)?;
325        let (pk_ptr, pk_len) = alloc_and_write(public_key)?;
326
327        let chain_id = i32::from(eth_chain_id.unwrap_or(DEFAULT_ETH_CHAIN_ID));
328
329        let ret_ptr = func.call(
330            &mut self.store,
331            (msg_ptr, msg_len, sig_ptr, sig_len, pk_ptr, pk_len, chain_id),
332        )?;
333
334        free_func.call(&mut self.store, (msg_ptr, msg_len))?;
335        free_func.call(&mut self.store, (sig_ptr, sig_len))?;
336        free_func.call(&mut self.store, (pk_ptr, pk_len))?;
337
338        if ret_ptr == 0 {
339            return Err("recover_v returned null pointer".into());
340        }
341
342        let mem_data = self.memory.data(&self.store);
343
344        let mut end =
345            usize::try_from(ret_ptr).map_err(|_| "ret_ptr cannot be negative".to_string())?;
346        while end < mem_data.len() && mem_data[end] != 0 {
347            end += 1;
348        }
349        let start =
350            usize::try_from(ret_ptr).map_err(|_| "ret_ptr cannot be negative".to_string())?;
351        let cstr_bytes = &mem_data[start..end];
352        let result_str = std::str::from_utf8(cstr_bytes)?.to_string();
353
354        let len_i32 = i32::try_from(result_str.len())
355            .map_err(|_| "result_str length exceeds i32::MAX".to_string())?;
356        free_func.call(&mut self.store, (ret_ptr, len_i32))?;
357
358        Ok(result_str)
359    }
360    /// Verifies an Ethereum EIP-155 message signature using a public key via the WASM module.
361    ///
362    /// # Arguments
363    ///
364    /// * `message_hash` - The 32-byte hex-encoded hash of the original message.
365    /// * `signature` - The 65-byte signature (r\[32\] + s\[32\] + v\[1\]), hex-encoded.
366    /// * `public_key` - The compressed 33-byte public key, hex-encoded.
367    ///
368    /// # Returns
369    ///
370    /// `Ok(true)` if the signature is valid, `Ok(false)` if invalid, or an error on failure.
371    ///
372    /// # Errors
373    ///
374    /// Returns an error if memory allocation or WASM function invocation fails.
375    pub fn verify_eip155(
376        &mut self,
377        message_hash: &str,
378        signature: &str,
379        public_key: &str,
380    ) -> Result<bool, Box<dyn std::error::Error>> {
381        let alloc_func = self.alloc.typed::<i32, i32>(&self.store)?;
382        let free_func = self.free.typed::<(i32, i32), ()>(&self.store)?;
383        let func = self
384            .verify_eip155
385            .typed::<(i32, i32, i32, i32, i32, i32), i32>(&self.store)?;
386
387        // Helper to allocate and write string input
388        let mut alloc_and_write = |input: &str| -> Result<(i32, i32), Box<dyn std::error::Error>> {
389            let bytes = input.as_bytes();
390            let len = i32::try_from(bytes.len()).unwrap_or_default();
391            let ptr_i32 = alloc_func.call(&mut self.store, len)?;
392            let ptr =
393                usize::try_from(ptr_i32).map_err(|_| "Invalid pointer returned from alloc")?;
394            let mem_data = self.memory.data_mut(&mut self.store);
395            mem_data[ptr..ptr + bytes.len()].copy_from_slice(bytes);
396            Ok((ptr_i32, len))
397        };
398
399        // Allocate and write message, signature, and public key
400        let (msg_ptr, msg_len) = alloc_and_write(message_hash)?;
401        let (sig_ptr, sig_len) = alloc_and_write(signature)?;
402        let (pk_ptr, pk_len) = alloc_and_write(public_key)?;
403
404        // Invoke WASM function
405        let result = func.call(
406            &mut self.store,
407            (msg_ptr, msg_len, sig_ptr, sig_len, pk_ptr, pk_len),
408        )?;
409
410        // Free allocated memory
411        free_func.call(&mut self.store, (msg_ptr, msg_len))?;
412        free_func.call(&mut self.store, (sig_ptr, sig_len))?;
413        free_func.call(&mut self.store, (pk_ptr, pk_len))?;
414
415        Ok(result == 1)
416    }
417}
418
419#[cfg(test)]
420mod tests {
421    use crate::{
422        constants::{
423            CASPER_PUBLIC_KEY_PREFIXED, CASPER_SECP_PREFIX, COSMOS_PUBLIC_KEY, DEFAULT_COSMOS_HRP,
424            ETH_PUBLIC_KEY, ETH_SIGNATURE, ETH_SIGNATURE_V, ETH_TRANSACTION_HASH, SIGNATURE,
425            SIGNATURE_PREFIXED, SIGNATURE_RS_LEN, TRANSACTION_HASH, WASM_PATH,
426        },
427        services::crypto_service::CryptoService,
428        wasm_loader::WasmLoader,
429    };
430
431    #[tokio::test]
432    async fn test_crypto_service_public_key() {
433        let wasm_loader = WasmLoader::new(WASM_PATH)
434            .await
435            .expect("Failed to load WASM module");
436
437        let mut crypto_service =
438            CryptoService::new(&wasm_loader).expect("Failed to initialize CryptoService");
439
440        let public_key_input = "MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAE95tY6siYB+8oxfUTxjuBxNd+LEqeXrLT4TZ7jSSvoGlk+5z1txG+n4Zmeii1ncqyMqaghf//enmalrKJdF8mZQ==";
441
442        let result = crypto_service.public_key(public_key_input);
443
444        match result {
445            Ok(hex) => {
446                assert!(!hex.is_empty(), "Expected non-empty hex output");
447                // println!("Converted public key hex: {hex}");
448            }
449            Err(e) => panic!("Failed to convert public key: {e}"),
450        }
451    }
452
453    #[tokio::test]
454    async fn test_crypto_service_verify() {
455        let wasm_loader = WasmLoader::new(WASM_PATH)
456            .await
457            .expect("Failed to load WASM module");
458
459        let mut crypto_service =
460            CryptoService::new(&wasm_loader).expect("Failed to initialize CryptoService");
461
462        // Full key with prefix
463        let message = TRANSACTION_HASH;
464        let signature = SIGNATURE_PREFIXED;
465        let public_key = CASPER_PUBLIC_KEY_PREFIXED;
466
467        let is_valid = crypto_service
468            .verify(message, signature, public_key)
469            .expect("Verification failed");
470
471        assert!(
472            is_valid,
473            "Expected valid signature for public key with prefix"
474        );
475
476        // Key without prefix
477        let signature = SIGNATURE;
478        let public_key = CASPER_PUBLIC_KEY_PREFIXED.trim_start_matches(CASPER_SECP_PREFIX);
479
480        let is_valid = crypto_service
481            .verify(message, signature, public_key)
482            .expect("Verification failed");
483
484        assert!(
485            is_valid,
486            "Expected valid signature for compressed public key format"
487        );
488    }
489
490    #[tokio::test]
491    async fn test_crypto_service_verify_eip155() {
492        let wasm_loader = WasmLoader::new(WASM_PATH)
493            .await
494            .expect("Failed to load WASM module");
495
496        let mut crypto_service =
497            CryptoService::new(&wasm_loader).expect("Failed to initialize CryptoService");
498
499        // Full key with prefix
500        let message = ETH_TRANSACTION_HASH;
501        let signature = ETH_SIGNATURE;
502        let public_key = ETH_PUBLIC_KEY;
503
504        let is_valid = crypto_service
505            .verify_eip155(message, signature, public_key)
506            .expect("Verification failed");
507
508        assert!(is_valid, "Expected valid signature for public key");
509    }
510
511    #[tokio::test]
512    async fn test_crypto_service_unconvert() {
513        let wasm_loader = WasmLoader::new(WASM_PATH)
514            .await
515            .expect("Failed to load WASM module");
516
517        let mut crypto_service =
518            CryptoService::new(&wasm_loader).expect("Failed to initialize CryptoService");
519
520        // Signature with prefix
521        let signature_with_prefix = SIGNATURE_PREFIXED;
522        let result_with_prefix = crypto_service
523            .unconvert(signature_with_prefix)
524            .expect("Failed to unconvert signature with prefix");
525
526        assert!(
527            !result_with_prefix.is_empty(),
528            "Expected non-empty result for signature with prefix"
529        );
530
531        // Signature without prefix
532        let signature_without_prefix = SIGNATURE;
533        let result_without_prefix = crypto_service
534            .unconvert(signature_without_prefix)
535            .expect("Failed to unconvert plain signature");
536
537        assert!(
538            !result_without_prefix.is_empty(),
539            "Expected non-empty result for plain signature"
540        );
541
542        assert_eq!(result_with_prefix, result_without_prefix);
543    }
544
545    #[tokio::test]
546    async fn test_crypto_service_unconvert_eip155() {
547        let wasm_loader = WasmLoader::new(WASM_PATH)
548            .await
549            .expect("Failed to load WASM module");
550
551        let mut crypto_service =
552            CryptoService::new(&wasm_loader).expect("Failed to initialize CryptoService");
553
554        let signature = ETH_SIGNATURE.to_string();
555        let result = crypto_service
556            .unconvert(&signature)
557            .expect("Failed to unconvert signature");
558
559        assert!(
560            !result.is_empty(),
561            "Expected non-empty result for signature with prefix"
562        );
563
564        assert_eq!(result, result);
565    }
566
567    #[tokio::test]
568    async fn test_crypto_service_convert_roundtrip() {
569        let wasm_loader = WasmLoader::new(WASM_PATH)
570            .await
571            .expect("Failed to load WASM module");
572
573        let mut crypto_service =
574            CryptoService::new(&wasm_loader).expect("Failed to initialize CryptoService");
575
576        let signature_without_prefix = SIGNATURE;
577
578        let signature = crypto_service
579            .unconvert(signature_without_prefix)
580            .expect("Failed to unconvert signature with prefix");
581
582        let signature = crypto_service
583            .convert(&signature)
584            .expect("Failed to convert back to signature");
585
586        assert_eq!(
587            signature.to_lowercase(),
588            signature_without_prefix.to_lowercase()
589        );
590    }
591
592    #[tokio::test]
593    async fn test_crypto_service_case_insensitive_signature_roundtrip() {
594        let wasm_loader = WasmLoader::new(WASM_PATH)
595            .await
596            .expect("Failed to load WASM module");
597
598        let mut crypto_service =
599            CryptoService::new(&wasm_loader).expect("Failed to initialize CryptoService");
600
601        let signature = SIGNATURE.to_uppercase();
602
603        let unconverted = crypto_service.unconvert(&signature).unwrap();
604        let result = crypto_service.convert(&unconverted).unwrap();
605
606        assert_eq!(result.to_lowercase(), SIGNATURE.to_lowercase());
607    }
608
609    #[tokio::test]
610    async fn test_crypto_service_convert_roundtrip_eip155() {
611        let wasm_loader = WasmLoader::new(WASM_PATH)
612            .await
613            .expect("Failed to load WASM module");
614
615        let mut crypto_service =
616            CryptoService::new(&wasm_loader).expect("Failed to initialize CryptoService");
617
618        let signature_without_prefix = ETH_SIGNATURE;
619
620        let signature = crypto_service
621            .unconvert(signature_without_prefix)
622            .expect("Failed to unconvert signature");
623
624        let signature = crypto_service
625            .convert(&signature)
626            .expect("Failed to convert back to signature");
627
628        assert_eq!(
629            signature.to_lowercase(),
630            signature_without_prefix.to_lowercase()
631        );
632    }
633
634    #[tokio::test]
635    async fn test_crypto_service_convert_with_prefix_roundtrip() {
636        let wasm_loader = WasmLoader::new(WASM_PATH)
637            .await
638            .expect("Failed to load WASM module");
639
640        let mut crypto_service =
641            CryptoService::new(&wasm_loader).expect("Failed to initialize CryptoService");
642
643        let signature_with_prefix = SIGNATURE_PREFIXED;
644
645        let signature = crypto_service
646            .unconvert(signature_with_prefix)
647            .expect("Failed to unconvert signature with prefix");
648
649        let signature = crypto_service
650            .convert(&signature)
651            .expect("Failed to convert back to signature");
652
653        assert_eq!(
654            format!("{CASPER_SECP_PREFIX}{}", signature.to_lowercase()),
655            signature_with_prefix.to_lowercase()
656        );
657    }
658
659    #[tokio::test]
660    async fn test_crypto_service_recover_v() {
661        let wasm_loader = WasmLoader::new(WASM_PATH)
662            .await
663            .expect("Failed to load WASM module");
664
665        let mut crypto_service =
666            CryptoService::new(&wasm_loader).expect("Failed to initialize CryptoService");
667
668        let message = TRANSACTION_HASH;
669        let signature = &SIGNATURE;
670        let public_key = CASPER_PUBLIC_KEY_PREFIXED;
671
672        let result = crypto_service
673            .recover_v(message, signature, public_key, None)
674            .expect("Failed to recover v");
675
676        assert!(!result.is_empty(), "Expected non-empty v from recover_v");
677
678        assert_eq!(result, "0", "Expected non-empty 0 v");
679    }
680
681    #[tokio::test]
682    async fn test_crypto_service_recover_v_eip155() {
683        let wasm_loader = WasmLoader::new(WASM_PATH)
684            .await
685            .expect("Failed to load WASM module");
686
687        let mut crypto_service =
688            CryptoService::new(&wasm_loader).expect("Failed to initialize CryptoService");
689
690        let message = ETH_TRANSACTION_HASH;
691        let signature = &ETH_SIGNATURE[..SIGNATURE_RS_LEN]; // remove recovery byte (r + s)
692        let public_key = ETH_PUBLIC_KEY;
693
694        let result = crypto_service
695            .recover_v(message, signature, public_key, None)
696            .expect("Failed to recover v");
697
698        assert_eq!(result, ETH_SIGNATURE_V, "Expected non-empty v");
699    }
700
701    #[tokio::test]
702    async fn test_crypto_service_recover_v_with_chain_id() {
703        let wasm_loader = WasmLoader::new(WASM_PATH)
704            .await
705            .expect("Failed to load WASM module");
706
707        let mut crypto_service =
708            CryptoService::new(&wasm_loader).expect("Failed to initialize CryptoService");
709
710        let message = ETH_TRANSACTION_HASH;
711        let signature = &ETH_SIGNATURE[..SIGNATURE_RS_LEN];
712        let public_key = ETH_PUBLIC_KEY;
713
714        let chain_id = Some(1); // Ethereum mainnet
715
716        let result = crypto_service
717            .recover_v(message, signature, public_key, chain_id)
718            .expect("Failed to recover v");
719
720        assert_eq!(result, ETH_SIGNATURE_V, "Expected non-empty v");
721
722        let result = crypto_service
723            .recover_v(message, signature, public_key, None)
724            .expect("Failed to recover v");
725
726        assert_eq!(result, ETH_SIGNATURE_V, "Expected non-empty v");
727    }
728
729    #[tokio::test]
730    async fn test_crypto_service_invalid_public_key() {
731        let wasm_loader = WasmLoader::new(WASM_PATH)
732            .await
733            .expect("Failed to load WASM module");
734
735        let mut crypto_service =
736            CryptoService::new(&wasm_loader).expect("Failed to initialize CryptoService");
737
738        let invalid_key = "invalid_key";
739
740        let result = crypto_service.public_key(invalid_key);
741
742        assert!(
743            result.is_err(),
744            "Expected an error when passing invalid public key input"
745        );
746    }
747
748    #[tokio::test]
749    async fn test_crypto_service_empty_signature() {
750        let wasm_loader = WasmLoader::new(WASM_PATH)
751            .await
752            .expect("Failed to load WASM module");
753
754        let mut crypto_service =
755            CryptoService::new(&wasm_loader).expect("Failed to initialize CryptoService");
756
757        let message = ETH_TRANSACTION_HASH;
758        let signature = "";
759        let public_key = ETH_PUBLIC_KEY;
760
761        let result = crypto_service.verify_eip155(message, signature, public_key);
762
763        assert!(
764            result.is_err() || !result.unwrap(),
765            "Expected false or error for empty signature"
766        );
767    }
768
769    #[tokio::test]
770    async fn test_crypto_service_address_eth() {
771        let wasm_loader = WasmLoader::new(WASM_PATH)
772            .await
773            .expect("Failed to load WASM module");
774
775        let mut crypto_service =
776            CryptoService::new(&wasm_loader).expect("Failed to initialize CryptoService");
777
778        let public_key = ETH_PUBLIC_KEY;
779
780        let address = crypto_service
781            .address_eth(public_key)
782            .expect("Failed to derive Ethereum address");
783
784        assert!(
785            address.starts_with("0x") && address.len() == 42,
786            "Expected valid Ethereum address, got: {address}"
787        );
788    }
789
790    #[tokio::test]
791    async fn test_crypto_service_address_cosmos() {
792        let wasm_loader = WasmLoader::new(WASM_PATH)
793            .await
794            .expect("Failed to load WASM module");
795
796        let mut crypto_service =
797            CryptoService::new(&wasm_loader).expect("Failed to initialize CryptoService");
798
799        let public_key = COSMOS_PUBLIC_KEY;
800
801        let address = crypto_service
802            .address_cosmos(public_key, DEFAULT_COSMOS_HRP)
803            .expect("Failed to derive Cosmos address");
804
805        // Use the constant for prefix dynamically
806        let expected_prefix = format!("{DEFAULT_COSMOS_HRP}1");
807
808        assert!(
809            address.starts_with(&expected_prefix),
810            "Address should start with '{expected_prefix}', got: {address}"
811        );
812
813        assert!(
814            address.len() >= expected_prefix.len() + 38,
815            "Address length too short: {}",
816            address.len()
817        );
818    }
819}