kms_secp256k1_api/services/
casper_keys_service.rs1use crate::config::Config;
2use crate::constants::{CASPER_SECP_LEN, CASPER_SECP_PREFIX};
3use crate::services::crypto_service::CryptoService;
4use crate::services::keys_service::{KeyEntry, KeysService, KeysServiceTrait, SigEntry};
5use casper_rust_wasm_sdk::types::hash::transaction_hash::TransactionHash;
6use casper_rust_wasm_sdk::types::public_key::PublicKey;
7use casper_rust_wasm_sdk::types::transaction::Transaction;
8use tracing::error;
9
10pub struct CasperKeysService {
11 keys_service: KeysService,
12}
13
14impl CasperKeysService {
15 pub async fn new(config: Config, crypto_service: CryptoService) -> Result<Self, String> {
21 let keys_service = KeysService::new(config, crypto_service).await?;
22 Ok(Self { keys_service })
23 }
24}
25
26#[async_trait::async_trait]
27impl KeysServiceTrait for CasperKeysService {
28 async fn create_key(&mut self, _config: &Config) -> Result<KeyEntry, String> {
39 let (key_id, public_key_base64) = self
40 .keys_service
41 .kms_client_service
42 .create_key()
43 .await
44 .map_err(|e| {
45 let msg = format!("Failed to create_key in KmsClientService: {e}");
46 error!("{}", &msg);
47 msg
48 })?;
49
50 let public_key = self
51 .keys_service
52 .crypto_service
53 .public_key(&public_key_base64)
54 .map_err(|e| {
55 let msg = format!("public_key conversion failed: {e:?}");
56 error!("{}", &msg);
57 msg
58 })?; let address = Self::resolve_key(&public_key); if public_key.is_empty() {
63 let msg = "No public key generated".to_string();
64 error!("{}", &msg);
65 return Err(msg);
66 }
67
68 self.keys_service
70 .kms_client_service
71 .create_alias(&key_id, &address)
72 .await
73 .map_err(|e| {
74 let msg = format!("Error creating alias: {e:?}");
75 error!("{}", &msg);
76 msg
77 })?;
78
79 Ok(KeyEntry {
82 public_key: Some(public_key).into(),
83 address: address.into(),
84 public_key_base64: public_key_base64.into(),
85 key_id: key_id.into(),
86 })
87 }
88
89 async fn sign_transaction_hash(
104 &mut self,
105 config: &Config,
106 transaction_hash: &str,
107 key: &str,
108 ) -> Result<SigEntry, String> {
109 if !config.is_casper_mode() {
110 return Err("Only Casper mode is supported".to_string());
111 }
112
113 if let Err(e) = TransactionHash::new(transaction_hash) {
114 error!(
115 "Error reading parameters: transaction_hash : {}",
116 transaction_hash
117 );
118 error!("Validation error: {:?}", e);
119 return Err(format!("Error reading transaction parameters: {e}"));
120 }
121
122 let public_key = Self::resolve_key(key);
123
124 if let Err(e) = PublicKey::new(&public_key) {
125 error!(
126 "Error reading parameters \npublic_key : {}\ntransaction_hash : {}",
127 public_key, transaction_hash
128 );
129 error!("Validation error: {:?}", e);
130 return Err(format!("Error reading transaction parameters: {e}"));
131 }
132
133 let signature = self
134 .keys_service
135 .sign(transaction_hash, &public_key, Some(CASPER_SECP_PREFIX))
136 .await?;
137
138 let verified = self
140 .verify(transaction_hash, &signature, &public_key)
141 .await?;
142 if !verified {
143 return Err("Signature verification failed after signing".to_string());
144 }
145
146 Ok(SigEntry {
147 address: public_key.clone().into(),
148 public_key: public_key.replacen(CASPER_SECP_PREFIX, "", 1).into(),
149 signature: signature.into(),
150 })
151 }
152
153 async fn sign_transaction(
174 &mut self,
175 config: &Config,
176 transaction_str: &str,
177 key: &str,
178 ) -> Result<String, String> {
179 if !config.is_casper_mode() {
180 return Err("Only Casper mode is supported".to_string());
181 }
182
183 let transaction: Transaction = Transaction::from_json_string(transaction_str)
184 .map_err(|e| format!("Failed to parse transaction: {e}"))?;
185
186 let transaction_hash_str = transaction.hash().to_string();
187
188 TransactionHash::new(&transaction_hash_str).map_err(|e| {
189 error!("Invalid transaction hash: {:?}", e);
190 "Invalid transaction hash".to_string()
191 })?;
192
193 let public_key = Self::resolve_key(key);
194
195 PublicKey::new(&public_key).map_err(|e| {
196 error!("Invalid public key: {:?}", e);
197 "Invalid public key".to_string()
198 })?;
199
200 let signature = self
201 .keys_service
202 .sign(&transaction_hash_str, &public_key, Some(CASPER_SECP_PREFIX))
203 .await
204 .map_err(|e| format!("Signing failed: {e}"))?;
205
206 let verified = self
208 .verify(&transaction_hash_str, &signature, &public_key)
209 .await?;
210 if !verified {
211 return Err("Signature verification failed after signing".to_string());
212 }
213
214 let signed_transaction = transaction.add_signature(&public_key, &signature);
215
216 signed_transaction
217 .to_json_string()
218 .map_err(|e| format!("Failed to serialize signed transaction: {e}"))
219 }
220
221 async fn verify(
222 &mut self,
223 transaction_hash_hex: &str,
224 signature_hex: &str,
225 key: &str,
226 ) -> Result<bool, String> {
227 let key = Self::resolve_key(key);
228 self.keys_service
229 .verify(transaction_hash_hex, signature_hex, &key)
230 }
231
232 async fn verify_via_kms(
233 &mut self,
234 transaction_hash_hex: &str,
235 signature_hex: &str,
236 key: &str,
237 ) -> Result<bool, String> {
238 let key = Self::resolve_key(key);
239 self.keys_service
240 .verify_via_kms(transaction_hash_hex, signature_hex, &key)
241 .await
242 }
243
244 async fn delete_key(&mut self, key: &str) -> Result<bool, String> {
245 let key = Self::resolve_key(key);
246 self.keys_service.delete_key(&key).await
247 }
248
249 async fn list_keys(&mut self) -> Result<Vec<KeyEntry>, String> {
250 self.keys_service.list_keys().await
251 }
252}
253
254impl CasperKeysService {
255 fn resolve_key(key: &str) -> String {
273 if key.len() == CASPER_SECP_LEN {
274 key.to_string()
275 } else {
276 format!("{CASPER_SECP_PREFIX}{key}")
277 }
278 }
279}
280
281#[cfg(test)]
282mod tests {
283 use base64::Engine;
284 use base64::engine::general_purpose::STANDARD;
285 use casper_rust_wasm_sdk::{
286 SDK, types::transaction_params::transaction_str_params::TransactionStrParams,
287 };
288 use regex::Regex;
289 use serde_json::json;
290
291 use super::*;
292 use crate::{
293 config::ConfigBuilder,
294 constants::{
295 CASPER_PUBLIC_KEY_PREFIXED, SIGNATURE, SIGNATURE_PREFIXED, SIGNATURE_RSV_LEN,
296 TRANSACTION_HASH, WASM_PATH,
297 },
298 services::{crypto_service::CryptoService, keys_service::KeyEntry},
299 wasm_loader::WasmLoader,
300 };
301
302 #[tokio::test]
303 async fn test_create_key() {
304 let config = ConfigBuilder::new()
305 .with_casper_mode()
306 .with_aws_mode(false)
307 .build();
308
309 let wasm_loader = WasmLoader::new(WASM_PATH)
310 .await
311 .expect("Failed to load WASM module");
312
313 let crypto_service =
314 CryptoService::new(&wasm_loader).expect("Failed to initialize CryptoService");
315
316 let mut service = CasperKeysService::new(config.clone(), crypto_service)
318 .await
319 .expect("Failed to create CasperKeysService");
320
321 let result = service.create_key(&config).await;
323
324 assert!(result.is_ok(), "create_key failed: {result:?}");
326 let key = result.unwrap();
327 assert!(key.address.to_string().starts_with(CASPER_SECP_PREFIX));
328 assert!(!key.address.to_string().is_empty());
329 }
330
331 #[tokio::test]
332 async fn test_verify_signature() {
333 let config = ConfigBuilder::new()
334 .with_casper_mode()
335 .with_aws_mode(false)
336 .build();
337
338 let wasm_loader = WasmLoader::new(WASM_PATH)
339 .await
340 .expect("Failed to load WASM module");
341
342 let crypto_service =
343 CryptoService::new(&wasm_loader).expect("Failed to initialize CryptoService");
344
345 let mut service = CasperKeysService::new(config.clone(), crypto_service)
347 .await
348 .expect("Failed to create CasperKeysService");
349
350 let result = service
352 .verify(
353 TRANSACTION_HASH,
354 SIGNATURE_PREFIXED,
355 CASPER_PUBLIC_KEY_PREFIXED,
356 )
357 .await
358 .unwrap();
359 assert!(result, "Expected signature to verify correctly");
360
361 let result = service
363 .verify(TRANSACTION_HASH, SIGNATURE, CASPER_PUBLIC_KEY_PREFIXED)
364 .await
365 .unwrap();
366 assert!(result, "Expected signature to verify correctly");
367
368 let result = service
370 .verify("bad_hash", "bad_signature", "bad_key")
371 .await
372 .unwrap();
373 assert!(!result, "Expected signature verification to fail");
374 }
375
376 #[tokio::test]
377 async fn test_verify_via_kms_signature() {
378 let config = ConfigBuilder::new()
379 .with_casper_mode()
380 .with_aws_mode(false)
381 .build();
382
383 let wasm_loader = WasmLoader::new(WASM_PATH)
384 .await
385 .expect("Failed to load WASM module");
386
387 let crypto_service =
388 CryptoService::new(&wasm_loader).expect("Failed to initialize CryptoService");
389
390 let mut service = CasperKeysService::new(config.clone(), crypto_service)
392 .await
393 .expect("Failed to create CasperKeysService");
394
395 let result = service
397 .verify_via_kms(
398 TRANSACTION_HASH,
399 SIGNATURE_PREFIXED,
400 CASPER_PUBLIC_KEY_PREFIXED,
401 )
402 .await
403 .unwrap();
404 assert!(result, "Expected signature to verify correctly");
405
406 let result = service
408 .verify_via_kms(TRANSACTION_HASH, SIGNATURE, CASPER_PUBLIC_KEY_PREFIXED)
409 .await
410 .unwrap();
411 assert!(result, "Expected signature to verify correctly");
412
413 let result = service
415 .verify_via_kms("bad_hash", "bad_signature", "bad_key")
416 .await
417 .unwrap();
418 assert!(!result, "Expected signature verification to fail");
419 }
420
421 #[tokio::test]
422 async fn test_delete_key() {
423 let config = ConfigBuilder::new()
424 .with_casper_mode()
425 .with_aws_mode(false)
426 .build();
427
428 let wasm_loader = WasmLoader::new(WASM_PATH)
429 .await
430 .expect("Failed to load WASM module");
431
432 let crypto_service =
433 CryptoService::new(&wasm_loader).expect("Failed to initialize CryptoService");
434
435 let mut service = CasperKeysService::new(config.clone(), crypto_service)
436 .await
437 .expect("Failed to create CasperKeysService");
438
439 let result = service
440 .delete_key("known_public_key")
441 .await
442 .expect("Failed to delete known public key");
443 assert!(result, "Expected key to be deleted successfully");
444
445 let result = service
446 .delete_key("unknown_key")
447 .await
448 .expect("Failed to delete unknown public key");
449 assert!(!result, "Expected key deletion to fail for unknown key");
450 }
451
452 #[tokio::test]
453 async fn test_list_keys() {
454 let config = ConfigBuilder::new()
455 .with_casper_mode()
456 .with_aws_mode(false)
457 .build();
458
459 let wasm_loader = WasmLoader::new(WASM_PATH)
460 .await
461 .expect("Failed to load WASM module");
462
463 let crypto_service =
464 CryptoService::new(&wasm_loader).expect("Failed to initialize CryptoService");
465
466 let mut service = CasperKeysService::new(config.clone(), crypto_service)
467 .await
468 .expect("Failed to create CasperKeysService");
469
470 let result = service.list_keys().await;
471
472 assert!(result.is_ok(), "Expected list_keys to succeed");
473
474 let keys = result.unwrap();
475 assert_eq!(
476 keys[0],
477 KeyEntry {
478 address: "address_1".to_string().into(),
479 public_key_base64: STANDARD.encode("public_key_1_base64").into(),
480 public_key: Some("public_key_1".to_string()).into(),
481 key_id: "key_id_1".to_string().into(),
482 }
483 );
484 assert_eq!(
485 keys[1],
486 KeyEntry {
487 address: "address_2".to_string().into(),
488 public_key_base64: STANDARD.encode("public_key_2_base64").into(),
489 public_key: Some("public_key_2".to_string()).into(),
490 key_id: "key_id_2".to_string().into(),
491 }
492 );
493 }
494
495 #[tokio::test]
496 async fn test_sign_transaction_hash() {
497 let config = ConfigBuilder::new()
498 .with_casper_mode()
499 .with_aws_mode(false)
500 .build();
501
502 let wasm_loader = WasmLoader::new(WASM_PATH)
503 .await
504 .expect("Failed to load WASM module");
505
506 let crypto_service =
507 CryptoService::new(&wasm_loader).expect("Failed to initialize CryptoService");
508
509 let mut service = CasperKeysService::new(config.clone(), crypto_service)
510 .await
511 .expect("Failed to create CasperKeysService");
512
513 let transaction_hash = TRANSACTION_HASH;
514
515 let public_key = CASPER_PUBLIC_KEY_PREFIXED;
516
517 let result = service
518 .sign_transaction_hash(&config, transaction_hash, public_key)
519 .await;
520
521 assert!(result.is_ok(), "Expected signing to succeed");
522
523 let signed = result.unwrap();
524
525 let expected_sig_hex = SIGNATURE;
526
527 let expected_prefixed = format!("{CASPER_SECP_PREFIX}{expected_sig_hex}");
528
529 assert_eq!(
530 signed.signature.to_string(),
531 expected_prefixed,
532 "Expected signature to match expected format"
533 );
534 }
535
536 #[tokio::test]
537 async fn test_sign_transaction() {
538 let config = ConfigBuilder::new()
539 .with_casper_mode()
540 .with_aws_mode(false)
541 .build();
542
543 let wasm_loader = WasmLoader::new(WASM_PATH)
544 .await
545 .expect("Failed to load WASM module");
546
547 let crypto_service =
548 CryptoService::new(&wasm_loader).expect("Failed to initialize CryptoService");
549
550 let mut service = CasperKeysService::new(config.clone(), crypto_service)
552 .await
553 .expect("Failed to create CasperKeysService");
554
555 let public_key = CASPER_PUBLIC_KEY_PREFIXED;
556
557 let transaction_params = TransactionStrParams::default();
559 transaction_params.set_chain_name("casper-net-1");
560 transaction_params.set_initiator_addr(public_key);
561 transaction_params.set_payment_amount("100000000");
562
563 let sdk = SDK::new(None, None, None);
565 let transaction = sdk
566 .make_transfer_transaction(None, public_key, "2500000000", transaction_params, None)
567 .expect("Failed to create transfer transaction");
568
569 let mut transaction_str = transaction.to_json_string().unwrap_or_default();
570
571 let re = Regex::new(r#""hash"\s*:\s*"[^"]+""#).unwrap();
572
573 transaction_str = re
575 .replace(&transaction_str, format!(r#""hash": "{TRANSACTION_HASH}""#))
576 .to_string();
577
578 let signed_transaction_json = service
580 .sign_transaction(&config, &transaction_str, public_key)
581 .await
582 .expect("Failed to sign transaction");
583
584 let signed_transaction = Transaction::from_json_string(&signed_transaction_json)
586 .expect("Failed to parse signed transaction");
587
588 let approvals = signed_transaction.approvals();
590
591 for approval in approvals {
592 let signer = approval.signer().to_hex_string();
593 let signature = approval.signature().to_hex_string();
594
595 assert!(public_key.contains(&signer), "Unexpected signer: {signer}");
596
597 assert_eq!(
598 signature.len(),
599 SIGNATURE_RSV_LEN,
600 "Signature length incorrect for signer {}: {}",
601 signer,
602 signature.len()
603 );
604 }
605 }
606
607 #[tokio::test]
608 async fn test_sign_transaction_malformed_json() {
609 let config = ConfigBuilder::new()
610 .with_casper_mode()
611 .with_aws_mode(false)
612 .build();
613
614 let wasm_loader = WasmLoader::new(WASM_PATH)
615 .await
616 .expect("Failed to load WASM");
617
618 let crypto_service =
619 CryptoService::new(&wasm_loader).expect("Failed to create CryptoService");
620
621 let mut service = CasperKeysService::new(config.clone(), crypto_service)
622 .await
623 .expect("Failed to create service");
624
625 let bad_json = "{ this is not valid JSON }";
626
627 let result = service
628 .sign_transaction(&config, bad_json, CASPER_PUBLIC_KEY_PREFIXED)
629 .await;
630
631 assert!(result.is_err());
632 let err = result.unwrap_err();
633 assert!(
634 err.contains("Failed to parse json-args")
635 || err.contains("Failed to parse transaction"),
636 "Expected parsing error, got: {err}"
637 );
638 }
639
640 #[tokio::test]
641 async fn test_sign_transaction_missing_transaction_field() {
642 let config = ConfigBuilder::new()
643 .with_casper_mode()
644 .with_aws_mode(false)
645 .build();
646
647 let wasm_loader = WasmLoader::new(WASM_PATH)
648 .await
649 .expect("Failed to load WASM");
650
651 let crypto_service =
652 CryptoService::new(&wasm_loader).expect("Failed to create CryptoService");
653
654 let mut service = CasperKeysService::new(config.clone(), crypto_service)
655 .await
656 .expect("Failed to create service");
657
658 let minimal_transaction = json!({
659 "some": "value"
660 })
661 .to_string();
662
663 let result = service
664 .sign_transaction(&config, &minimal_transaction, CASPER_PUBLIC_KEY_PREFIXED)
665 .await;
666
667 assert!(
668 result.is_err(),
669 "Expected failure due to missing transaction fields"
670 );
671 }
672
673 #[tokio::test]
674 async fn test_sign_transaction_hash_with_invalid_input() {
675 let config = ConfigBuilder::new()
676 .with_casper_mode()
677 .with_aws_mode(false)
678 .build();
679
680 let wasm_loader = WasmLoader::new(WASM_PATH)
681 .await
682 .expect("Failed to load WASM module");
683
684 let crypto_service =
685 CryptoService::new(&wasm_loader).expect("Failed to initialize CryptoService");
686
687 let mut service = CasperKeysService::new(config.clone(), crypto_service)
688 .await
689 .expect("Failed to create CasperKeysService");
690
691 let result = service
693 .sign_transaction_hash(&config, "invalid_hash", "invalid_key")
694 .await;
695
696 assert!(
697 result.is_err(),
698 "Expected signing to fail due to invalid input"
699 );
700 let error = result.unwrap_err();
701 assert!(
702 error.contains("Error reading transaction parameters"),
703 "Unexpected error: {error}"
704 );
705 }
706
707 #[tokio::test]
708 async fn test_sign_transaction_malformed_approvals() {
709 let config = ConfigBuilder::new()
710 .with_casper_mode()
711 .with_aws_mode(false)
712 .build();
713
714 let wasm_loader = WasmLoader::new(WASM_PATH)
715 .await
716 .expect("Failed to load WASM");
717
718 let crypto_service =
719 CryptoService::new(&wasm_loader).expect("Failed to create CryptoService");
720
721 let mut service = CasperKeysService::new(config.clone(), crypto_service)
722 .await
723 .expect("Failed to create service");
724
725 let transaction = json!({
727 "hash": TRANSACTION_HASH,
728 "approvals": "not an array"
729 })
730 .to_string();
731
732 let result = service
733 .sign_transaction(&config, &transaction, CASPER_PUBLIC_KEY_PREFIXED)
734 .await;
735
736 assert!(
737 result.is_err(),
738 "Expected failure due to malformed approvals field"
739 );
740 }
741}