1use alloy::primitives::keccak256;
34use async_trait::async_trait;
35use aws_config::{meta::region::RegionProviderChain, BehaviorVersion, Region};
36use aws_sdk_kms::{
37 primitives::Blob,
38 types::{MessageType, SigningAlgorithmSpec},
39 Client,
40};
41use once_cell::sync::Lazy;
42use serde::Serialize;
43use std::collections::HashMap;
44use tokio::sync::RwLock;
45
46use crate::{
47 models::{Address, AwsKmsSignerConfig},
48 services::signer::evm::utils::recover_evm_signature_from_der,
49 utils::{
50 self, derive_ethereum_address_from_der, derive_solana_address_from_der,
51 derive_stellar_address_from_der,
52 },
53};
54use tracing::debug;
55
56#[cfg(test)]
57use mockall::{automock, mock};
58
59#[derive(Clone, Debug, thiserror::Error, Serialize)]
60pub enum AwsKmsError {
61 #[error("AWS KMS response parse error: {0}")]
62 ParseError(String),
63 #[error("AWS KMS config error: {0}")]
64 ConfigError(String),
65 #[error("AWS KMS get error: {0}")]
66 GetError(String),
67 #[error("AWS KMS signing error: {0}")]
68 SignError(String),
69 #[error("AWS KMS permissions error: {0}")]
70 PermissionError(String),
71 #[error("AWS KMS public key error: {0}")]
72 RecoveryError(#[from] utils::Secp256k1Error),
73 #[error("AWS KMS conversion error: {0}")]
74 ConvertError(String),
75 #[error("AWS KMS Other error: {0}")]
76 Other(String),
77}
78
79pub type AwsKmsResult<T> = Result<T, AwsKmsError>;
80
81#[async_trait]
82#[cfg_attr(test, automock)]
83pub trait AwsKmsEvmService: Send + Sync {
84 async fn get_evm_address(&self) -> AwsKmsResult<Address>;
86 async fn sign_payload_evm(&self, payload: &[u8]) -> AwsKmsResult<Vec<u8>>;
96
97 async fn sign_hash_evm(&self, hash: &[u8; 32]) -> AwsKmsResult<Vec<u8>>;
107}
108
109#[async_trait]
110#[cfg_attr(test, automock)]
111pub trait AwsKmsK256: Send + Sync {
112 async fn get_der_public_key<'a, 'b>(&'a self, key_id: &'b str) -> AwsKmsResult<Vec<u8>>;
114 async fn sign_digest<'a, 'b>(
116 &'a self,
117 key_id: &'b str,
118 digest: [u8; 32],
119 ) -> AwsKmsResult<Vec<u8>>;
120}
121
122#[async_trait]
125#[cfg_attr(test, automock)]
126pub trait AwsKmsEd25519: Send + Sync {
127 async fn get_ed25519_public_key<'a, 'b>(&'a self, key_id: &'b str) -> AwsKmsResult<Vec<u8>>;
129 async fn sign_ed25519<'a, 'b>(
132 &'a self,
133 key_id: &'b str,
134 message: &'b [u8],
135 ) -> AwsKmsResult<Vec<u8>>;
136}
137
138#[async_trait]
140#[cfg_attr(test, automock)]
141pub trait AwsKmsSolanaService: Send + Sync {
142 async fn get_solana_address(&self) -> AwsKmsResult<Address>;
144 async fn sign_solana(&self, message: &[u8]) -> AwsKmsResult<Vec<u8>>;
146}
147
148#[async_trait]
150#[cfg_attr(test, automock)]
151pub trait AwsKmsStellarService: Send + Sync {
152 async fn get_stellar_address(&self) -> AwsKmsResult<Address>;
154 async fn sign_stellar(&self, message: &[u8]) -> AwsKmsResult<Vec<u8>>;
156}
157
158#[cfg(test)]
159mock! {
160 pub AwsKmsClient { }
161 impl Clone for AwsKmsClient {
162 fn clone(&self) -> Self;
163 }
164
165 #[async_trait]
166 impl AwsKmsK256 for AwsKmsClient {
167 async fn get_der_public_key<'a, 'b>(&'a self, key_id: &'b str) -> AwsKmsResult<Vec<u8>>;
168 async fn sign_digest<'a, 'b>(
169 &'a self,
170 key_id: &'b str,
171 digest: [u8; 32],
172 ) -> AwsKmsResult<Vec<u8>>;
173 }
174
175 #[async_trait]
176 impl AwsKmsEd25519 for AwsKmsClient {
177 async fn get_ed25519_public_key<'a, 'b>(&'a self, key_id: &'b str) -> AwsKmsResult<Vec<u8>>;
178 async fn sign_ed25519<'a, 'b>(
179 &'a self,
180 key_id: &'b str,
181 message: &'b [u8],
182 ) -> AwsKmsResult<Vec<u8>>;
183 }
184}
185
186static KMS_DER_PK_CACHE: Lazy<RwLock<HashMap<String, Vec<u8>>>> =
188 Lazy::new(|| RwLock::new(HashMap::new()));
189
190static KMS_ED25519_PK_CACHE: Lazy<RwLock<HashMap<String, Vec<u8>>>> =
192 Lazy::new(|| RwLock::new(HashMap::new()));
193
194#[derive(Debug, Clone)]
195pub struct AwsKmsClient {
196 inner: Client,
197}
198
199#[async_trait]
200impl AwsKmsK256 for AwsKmsClient {
201 async fn get_der_public_key<'a, 'b>(&'a self, key_id: &'b str) -> AwsKmsResult<Vec<u8>> {
202 let cached = {
204 let cache_read = KMS_DER_PK_CACHE.read().await;
205 cache_read.get(key_id).cloned()
206 };
207 if let Some(cached) = cached {
208 return Ok(cached);
209 }
210
211 let get_output = self
213 .inner
214 .get_public_key()
215 .key_id(key_id)
216 .send()
217 .await
218 .map_err(|e| {
219 AwsKmsError::GetError(format!(
220 "Failed to get secp256k1 public key for key '{key_id}': {e:?}"
221 ))
222 })?;
223
224 let der_pk_blob = get_output
225 .public_key
226 .ok_or(AwsKmsError::GetError(
227 "No public key blob found".to_string(),
228 ))?
229 .into_inner();
230
231 let mut cache_write = KMS_DER_PK_CACHE.write().await;
233 cache_write.insert(key_id.to_string(), der_pk_blob.clone());
234 drop(cache_write);
235
236 Ok(der_pk_blob)
237 }
238
239 async fn sign_digest<'a, 'b>(
240 &'a self,
241 key_id: &'b str,
242 digest: [u8; 32],
243 ) -> AwsKmsResult<Vec<u8>> {
244 let sign_result = self
246 .inner
247 .sign()
248 .key_id(key_id)
249 .signing_algorithm(SigningAlgorithmSpec::EcdsaSha256)
250 .message_type(MessageType::Digest)
251 .message(Blob::new(digest))
252 .send()
253 .await;
254
255 let der_signature = sign_result
257 .map_err(|e| AwsKmsError::PermissionError(e.to_string()))?
258 .signature
259 .ok_or(AwsKmsError::SignError(
260 "Signature not found in response".to_string(),
261 ))?
262 .into_inner();
263
264 Ok(der_signature)
265 }
266}
267
268#[async_trait]
269impl AwsKmsEd25519 for AwsKmsClient {
270 async fn get_ed25519_public_key<'a, 'b>(&'a self, key_id: &'b str) -> AwsKmsResult<Vec<u8>> {
271 let cached = {
273 let cache_read = KMS_ED25519_PK_CACHE.read().await;
274 cache_read.get(key_id).cloned()
275 };
276 if let Some(cached) = cached {
277 return Ok(cached);
278 }
279
280 let get_output = self
282 .inner
283 .get_public_key()
284 .key_id(key_id)
285 .send()
286 .await
287 .map_err(|e| {
288 AwsKmsError::GetError(format!(
289 "Failed to get Ed25519 public key for key '{key_id}': {e:?}"
290 ))
291 })?;
292
293 let der_pk_blob = get_output
294 .public_key
295 .ok_or(AwsKmsError::GetError(
296 "No public key blob found".to_string(),
297 ))?
298 .into_inner();
299
300 let mut cache_write = KMS_ED25519_PK_CACHE.write().await;
302 cache_write.insert(key_id.to_string(), der_pk_blob.clone());
303 drop(cache_write);
304
305 Ok(der_pk_blob)
306 }
307
308 async fn sign_ed25519<'a, 'b>(
309 &'a self,
310 key_id: &'b str,
311 message: &'b [u8],
312 ) -> AwsKmsResult<Vec<u8>> {
313 debug!("Signing Ed25519 message with AWS KMS, key_id: {}", key_id);
314
315 let sign_result = self
318 .inner
319 .sign()
320 .key_id(key_id)
321 .signing_algorithm(SigningAlgorithmSpec::Ed25519Sha512)
322 .message_type(MessageType::Raw)
323 .message(Blob::new(message))
324 .send()
325 .await;
326
327 let signature = sign_result
329 .map_err(|e| AwsKmsError::SignError(e.to_string()))?
330 .signature
331 .ok_or(AwsKmsError::SignError(
332 "Signature not found in response".to_string(),
333 ))?
334 .into_inner();
335
336 if signature.len() != 64 {
338 return Err(AwsKmsError::SignError(format!(
339 "Invalid Ed25519 signature length: expected 64 bytes, got {}",
340 signature.len()
341 )));
342 }
343
344 Ok(signature)
345 }
346}
347
348#[derive(Debug, Clone)]
349pub struct AwsKmsService<T: AwsKmsK256 + AwsKmsEd25519 + Clone = AwsKmsClient> {
350 pub kms_key_id: String,
351 client: T,
352}
353
354impl AwsKmsService<AwsKmsClient> {
355 pub async fn new(config: AwsKmsSignerConfig) -> AwsKmsResult<Self> {
356 let region_provider =
357 RegionProviderChain::first_try(config.region.map(Region::new)).or_default_provider();
358
359 let auth_config = aws_config::defaults(BehaviorVersion::latest())
360 .region(region_provider)
361 .load()
362 .await;
363 let client = AwsKmsClient {
364 inner: Client::new(&auth_config),
365 };
366
367 Ok(Self {
368 kms_key_id: config.key_id,
369 client,
370 })
371 }
372}
373
374#[cfg(test)]
375impl<T: AwsKmsK256 + AwsKmsEd25519 + Clone> AwsKmsService<T> {
376 pub fn new_for_testing(client: T, config: AwsKmsSignerConfig) -> Self {
377 Self {
378 client,
379 kms_key_id: config.key_id,
380 }
381 }
382}
383
384impl<T: AwsKmsK256 + AwsKmsEd25519 + Clone> AwsKmsService<T> {
385 async fn sign_and_recover_evm(
394 &self,
395 digest: [u8; 32],
396 original_bytes: &[u8],
397 use_prehash_recovery: bool,
398 ) -> AwsKmsResult<Vec<u8>> {
399 let der_signature = self.client.sign_digest(&self.kms_key_id, digest).await?;
401
402 let der_pk = self.client.get_der_public_key(&self.kms_key_id).await?;
404
405 recover_evm_signature_from_der(
407 &der_signature,
408 &der_pk,
409 digest,
410 original_bytes,
411 use_prehash_recovery,
412 )
413 .map_err(|e| AwsKmsError::ParseError(e.to_string()))
414 }
415
416 pub async fn sign_payload_evm(&self, bytes: &[u8]) -> AwsKmsResult<Vec<u8>> {
426 let digest = keccak256(bytes).0;
427 self.sign_and_recover_evm(digest, bytes, false).await
428 }
429
430 pub async fn sign_hash_evm(&self, hash: &[u8; 32]) -> AwsKmsResult<Vec<u8>> {
440 self.sign_and_recover_evm(*hash, hash, true).await
441 }
442}
443
444#[async_trait]
445impl<T: AwsKmsK256 + AwsKmsEd25519 + Clone> AwsKmsEvmService for AwsKmsService<T> {
446 async fn get_evm_address(&self) -> AwsKmsResult<Address> {
447 let der = self.client.get_der_public_key(&self.kms_key_id).await?;
448 let eth_address = derive_ethereum_address_from_der(&der)
449 .map_err(|e| AwsKmsError::ParseError(e.to_string()))?;
450 Ok(Address::Evm(eth_address))
451 }
452
453 async fn sign_payload_evm(&self, message: &[u8]) -> AwsKmsResult<Vec<u8>> {
454 let digest = keccak256(message).0;
455 self.sign_and_recover_evm(digest, message, false).await
456 }
457
458 async fn sign_hash_evm(&self, hash: &[u8; 32]) -> AwsKmsResult<Vec<u8>> {
459 self.sign_and_recover_evm(*hash, hash, true).await
461 }
462}
463
464#[async_trait]
465impl<T: AwsKmsK256 + AwsKmsEd25519 + Clone> AwsKmsSolanaService for AwsKmsService<T> {
466 async fn get_solana_address(&self) -> AwsKmsResult<Address> {
467 let der = self.client.get_ed25519_public_key(&self.kms_key_id).await?;
468 let solana_address = derive_solana_address_from_der(&der)
469 .map_err(|e| AwsKmsError::ParseError(e.to_string()))?;
470 Ok(Address::Solana(solana_address))
471 }
472
473 async fn sign_solana(&self, message: &[u8]) -> AwsKmsResult<Vec<u8>> {
474 self.client.sign_ed25519(&self.kms_key_id, message).await
475 }
476}
477
478#[async_trait]
479impl<T: AwsKmsK256 + AwsKmsEd25519 + Clone> AwsKmsStellarService for AwsKmsService<T> {
480 async fn get_stellar_address(&self) -> AwsKmsResult<Address> {
481 let der = self.client.get_ed25519_public_key(&self.kms_key_id).await?;
482 let stellar_address = derive_stellar_address_from_der(&der)
483 .map_err(|e| AwsKmsError::ParseError(e.to_string()))?;
484 Ok(Address::Stellar(stellar_address))
485 }
486
487 async fn sign_stellar(&self, message: &[u8]) -> AwsKmsResult<Vec<u8>> {
488 self.client.sign_ed25519(&self.kms_key_id, message).await
489 }
490}
491
492#[cfg(test)]
493pub mod tests {
494 use super::*;
495
496 use alloy::primitives::utils::eip191_message;
497 use k256::{
498 ecdsa::SigningKey,
499 elliptic_curve::rand_core::OsRng,
500 pkcs8::{der::Encode, EncodePublicKey},
501 };
502 use mockall::predicate::{eq, ne};
503
504 pub struct TestEd25519Keys {
506 pub public_key_der: Vec<u8>,
507 pub public_key_raw: [u8; 32],
508 }
509
510 impl TestEd25519Keys {
511 pub fn new() -> Self {
512 let public_key_raw: [u8; 32] = [
514 0x9d, 0x45, 0x7e, 0x45, 0xe4, 0x16, 0xc4, 0xc6, 0x77, 0x67, 0x6a, 0x42, 0xff, 0x96,
515 0x8e, 0x3c, 0xf8, 0xdc, 0x73, 0xc8, 0xf3, 0x3a, 0x8d, 0x19, 0x81, 0x29, 0x7b, 0xfa,
516 0x3e, 0x00, 0x30, 0xba,
517 ];
518
519 let mut public_key_der = vec![
521 0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03, 0x21, 0x00, ];
527 public_key_der.extend_from_slice(&public_key_raw);
528
529 Self {
530 public_key_der,
531 public_key_raw,
532 }
533 }
534 }
535
536 pub fn setup_mock_kms_client() -> (MockAwsKmsClient, SigningKey) {
537 let mut client = MockAwsKmsClient::new();
538 let signing_key = SigningKey::random(&mut OsRng);
539 let s = signing_key
540 .verifying_key()
541 .to_public_key_der()
542 .unwrap()
543 .to_der()
544 .unwrap();
545
546 client
547 .expect_get_der_public_key()
548 .with(eq("test-key-id"))
549 .return_const(Ok(s));
550 client
551 .expect_get_der_public_key()
552 .with(ne("test-key-id"))
553 .return_const(Err(AwsKmsError::GetError("Key does not exist".to_string())));
554
555 client
556 .expect_sign_digest()
557 .withf(|key_id, _| key_id.ne("test-key-id"))
558 .return_const(Err(AwsKmsError::SignError(
559 "Key does not exist".to_string(),
560 )));
561
562 let key = signing_key.clone();
563 client
564 .expect_sign_digest()
565 .withf(|key_id, _| key_id.eq("test-key-id"))
566 .returning(move |_, digest| {
567 let (signature, _) = signing_key
568 .sign_prehash_recoverable(&digest)
569 .map_err(|e| AwsKmsError::SignError(e.to_string()))?;
570 let der_signature = signature.to_der().as_bytes().to_vec();
571 Ok(der_signature)
572 });
573
574 let test_ed25519_keys = TestEd25519Keys::new();
576 client
577 .expect_get_ed25519_public_key()
578 .with(eq("test-key-id"))
579 .return_const(Ok(test_ed25519_keys.public_key_der.clone()));
580 client
581 .expect_get_ed25519_public_key()
582 .with(ne("test-key-id"))
583 .return_const(Err(AwsKmsError::GetError("Key does not exist".to_string())));
584
585 client
587 .expect_sign_ed25519()
588 .withf(|key_id, _| key_id.eq("test-key-id"))
589 .returning(|_, _| Ok(vec![0u8; 64]));
590 client
591 .expect_sign_ed25519()
592 .withf(|key_id, _| key_id.ne("test-key-id"))
593 .return_const(Err(AwsKmsError::SignError(
594 "Key does not exist".to_string(),
595 )));
596
597 client.expect_clone().return_once(MockAwsKmsClient::new);
598
599 (client, key)
600 }
601
602 #[tokio::test]
603 async fn test_get_public_key() {
604 let (mock_client, key) = setup_mock_kms_client();
605 let kms = AwsKmsService::new_for_testing(
606 mock_client,
607 AwsKmsSignerConfig {
608 region: Some("us-east-1".to_string()),
609 key_id: "test-key-id".to_string(),
610 },
611 );
612
613 let result = kms.get_evm_address().await;
614 assert!(result.is_ok());
615 if let Ok(Address::Evm(evm_address)) = result {
616 let expected_address = derive_ethereum_address_from_der(
617 key.verifying_key().to_public_key_der().unwrap().as_bytes(),
618 )
619 .unwrap();
620 assert_eq!(expected_address, evm_address);
621 }
622 }
623
624 #[tokio::test]
625 async fn test_get_public_key_fail() {
626 let (mock_client, _) = setup_mock_kms_client();
627 let kms = AwsKmsService::new_for_testing(
628 mock_client,
629 AwsKmsSignerConfig {
630 region: Some("us-east-1".to_string()),
631 key_id: "invalid-key-id".to_string(),
632 },
633 );
634
635 let result = kms.get_evm_address().await;
636 assert!(result.is_err());
637 if let Err(err) = result {
638 assert!(matches!(err, AwsKmsError::GetError(_)))
639 }
640 }
641
642 #[tokio::test]
643 async fn test_sign_digest() {
644 let (mock_client, _) = setup_mock_kms_client();
645 let kms = AwsKmsService::new_for_testing(
646 mock_client,
647 AwsKmsSignerConfig {
648 region: Some("us-east-1".to_string()),
649 key_id: "test-key-id".to_string(),
650 },
651 );
652
653 let message_eip = eip191_message(b"Hello World!");
654 let result = kms.sign_payload_evm(&message_eip).await;
655
656 assert!(result.is_ok());
658 }
659
660 #[tokio::test]
661 async fn test_sign_digest_fail() {
662 let (mock_client, _) = setup_mock_kms_client();
663 let kms = AwsKmsService::new_for_testing(
664 mock_client,
665 AwsKmsSignerConfig {
666 region: Some("us-east-1".to_string()),
667 key_id: "invalid-key-id".to_string(),
668 },
669 );
670
671 let message_eip = eip191_message(b"Hello World!");
672 let result = kms.sign_payload_evm(&message_eip).await;
673 assert!(result.is_err());
674 if let Err(err) = result {
675 assert!(matches!(err, AwsKmsError::SignError(_)))
676 }
677 }
678
679 #[tokio::test]
680 async fn test_get_solana_address() {
681 let (mock_client, _) = setup_mock_kms_client();
682 let kms = AwsKmsService::new_for_testing(
683 mock_client,
684 AwsKmsSignerConfig {
685 region: Some("us-east-1".to_string()),
686 key_id: "test-key-id".to_string(),
687 },
688 );
689
690 let result = kms.get_solana_address().await;
691 assert!(result.is_ok());
692 if let Ok(Address::Solana(solana_address)) = result {
693 assert!(!solana_address.is_empty());
695 assert!(solana_address.len() >= 32 && solana_address.len() <= 44);
696 let test_keys = TestEd25519Keys::new();
698 let expected_address = bs58::encode(test_keys.public_key_raw).into_string();
699 assert_eq!(solana_address, expected_address);
700 } else {
701 panic!("Expected Solana address");
702 }
703 }
704
705 #[tokio::test]
706 async fn test_get_solana_address_fail() {
707 let (mock_client, _) = setup_mock_kms_client();
708 let kms = AwsKmsService::new_for_testing(
709 mock_client,
710 AwsKmsSignerConfig {
711 region: Some("us-east-1".to_string()),
712 key_id: "invalid-key-id".to_string(),
713 },
714 );
715
716 let result = kms.get_solana_address().await;
717 assert!(result.is_err());
718 if let Err(err) = result {
719 assert!(matches!(err, AwsKmsError::GetError(_)))
720 }
721 }
722
723 #[tokio::test]
724 async fn test_sign_solana() {
725 let (mock_client, _) = setup_mock_kms_client();
726 let kms = AwsKmsService::new_for_testing(
727 mock_client,
728 AwsKmsSignerConfig {
729 region: Some("us-east-1".to_string()),
730 key_id: "test-key-id".to_string(),
731 },
732 );
733
734 let message = b"Test Solana message";
735 let result = kms.sign_solana(message).await;
736 assert!(result.is_ok());
737 let signature = result.unwrap();
738 assert_eq!(signature.len(), 64); }
740
741 #[tokio::test]
742 async fn test_sign_solana_fail() {
743 let (mock_client, _) = setup_mock_kms_client();
744 let kms = AwsKmsService::new_for_testing(
745 mock_client,
746 AwsKmsSignerConfig {
747 region: Some("us-east-1".to_string()),
748 key_id: "invalid-key-id".to_string(),
749 },
750 );
751
752 let message = b"Test Solana message";
753 let result = kms.sign_solana(message).await;
754 assert!(result.is_err());
755 if let Err(err) = result {
756 assert!(matches!(err, AwsKmsError::SignError(_)))
757 }
758 }
759
760 #[tokio::test]
761 async fn test_get_stellar_address() {
762 let (mock_client, _) = setup_mock_kms_client();
763 let kms = AwsKmsService::new_for_testing(
764 mock_client,
765 AwsKmsSignerConfig {
766 region: Some("us-east-1".to_string()),
767 key_id: "test-key-id".to_string(),
768 },
769 );
770
771 let result = kms.get_stellar_address().await;
772 assert!(result.is_ok());
773 if let Ok(Address::Stellar(stellar_address)) = result {
774 assert!(stellar_address.starts_with('G'));
776 assert_eq!(stellar_address.len(), 56);
778 } else {
779 panic!("Expected Stellar address");
780 }
781 }
782
783 #[tokio::test]
784 async fn test_get_stellar_address_fail() {
785 let (mock_client, _) = setup_mock_kms_client();
786 let kms = AwsKmsService::new_for_testing(
787 mock_client,
788 AwsKmsSignerConfig {
789 region: Some("us-east-1".to_string()),
790 key_id: "invalid-key-id".to_string(),
791 },
792 );
793
794 let result = kms.get_stellar_address().await;
795 assert!(result.is_err());
796 if let Err(err) = result {
797 assert!(matches!(err, AwsKmsError::GetError(_)))
798 }
799 }
800
801 #[tokio::test]
802 async fn test_sign_stellar() {
803 let (mock_client, _) = setup_mock_kms_client();
804 let kms = AwsKmsService::new_for_testing(
805 mock_client,
806 AwsKmsSignerConfig {
807 region: Some("us-east-1".to_string()),
808 key_id: "test-key-id".to_string(),
809 },
810 );
811
812 let message = b"Test Stellar message";
813 let result = kms.sign_stellar(message).await;
814 assert!(result.is_ok());
815 let signature = result.unwrap();
816 assert_eq!(signature.len(), 64); }
818
819 #[tokio::test]
820 async fn test_sign_stellar_fail() {
821 let (mock_client, _) = setup_mock_kms_client();
822 let kms = AwsKmsService::new_for_testing(
823 mock_client,
824 AwsKmsSignerConfig {
825 region: Some("us-east-1".to_string()),
826 key_id: "invalid-key-id".to_string(),
827 },
828 );
829
830 let message = b"Test Stellar message";
831 let result = kms.sign_stellar(message).await;
832 assert!(result.is_err());
833 if let Err(err) = result {
834 assert!(matches!(err, AwsKmsError::SignError(_)))
835 }
836 }
837
838 }