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 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 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 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 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 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 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 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 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 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 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 let (pk_ptr, pk_len) = alloc_and_write(public_key)?;
157 let (ud_ptr, ud_len) = alloc_and_write(hrp)?;
158
159 let ret_ptr = func.call(&mut self.store, (pk_ptr, pk_len, ud_ptr, ud_len))?;
161
162 free_func.call(&mut self.store, (pk_ptr, pk_len))?;
164 free_func.call(&mut self.store, (ud_ptr, ud_len))?;
165
166 if ret_ptr == 0 {
168 return Err("address_cosmos returned null pointer".into());
169 }
170
171 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 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 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 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 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 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 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 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_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 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 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 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 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 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_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 }
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 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 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 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 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 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 = Ð_SIGNATURE[..SIGNATURE_RS_LEN]; 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 = Ð_SIGNATURE[..SIGNATURE_RS_LEN];
712 let public_key = ETH_PUBLIC_KEY;
713
714 let chain_id = Some(1); 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 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}