miden_validator/signers/kms.rs
1use aws_sdk_kms::error::SdkError;
2use aws_sdk_kms::operation::sign::SignError;
3use aws_sdk_kms::types::SigningAlgorithmSpec;
4use miden_protocol::Word;
5use miden_protocol::crypto::dsa::ecdsa_k256_keccak::{PublicKey, Signature};
6use miden_protocol::crypto::hash::keccak::Keccak256;
7use miden_protocol::utils::serde::{DeserializationError, Serializable};
8
9// KMS SIGNER ERROR
10// ================================================================================================
11
12#[derive(Debug, thiserror::Error)]
13pub enum KmsSignerError {
14 /// The KMS backend errored out.
15 #[error("KMS service failure")]
16 KmsServiceError(#[source] Box<SdkError<SignError>>),
17 /// The KMS backend did not error but returned an empty signature.
18 #[error("KMS request returned an empty result")]
19 EmptyBlob,
20 /// The KMS backend returned a signature with an invalid format.
21 #[error("invalid signature format")]
22 SignatureFormatError(#[source] DeserializationError),
23 /// The KMS backend returned a signature that was not able to be verified.
24 #[error("invalid signature")]
25 InvalidSignature,
26}
27
28// KMS SIGNER
29// ================================================================================================
30
31/// Block signer that uses AWS KMS to create signatures.
32pub struct KmsSigner {
33 key_id: String,
34 pub_key: PublicKey,
35 client: aws_sdk_kms::Client,
36}
37
38impl KmsSigner {
39 /// Constructs a new KMS signer and retrieves the corresponding public key from the AWS backend.
40 ///
41 /// The supplied `key_id` must be a valid AWS KMS key ID in the AWS region corresponding to the
42 /// typical `AWS_REGION` env var.
43 ///
44 /// A policy statement such as the following is required to allow a process on an EC2 instance
45 /// to use this signer:
46 /// ```json
47 /// {
48 /// "Sid": "AllowEc2RoleUseOfKey",
49 /// "Effect": "Allow",
50 /// "Principal": {
51 /// "AWS": "arn:aws:iam::<account_id>:role/<role_name>"
52 /// },
53 /// "Action": [
54 /// "kms:Sign",
55 /// "kms:Verify",
56 /// "kms:DescribeKey"
57 /// "kms:GetPublicKey"
58 /// ],
59 /// "Resource": "*"
60 /// },
61 /// ```
62 pub async fn new(key_id: impl Into<String>) -> anyhow::Result<Self> {
63 let version = aws_config::BehaviorVersion::v2026_01_12();
64 let config = aws_config::load_defaults(version).await;
65 let client = aws_sdk_kms::Client::new(&config);
66 let key_id = key_id.into();
67
68 // Retrieve DER-encoded SPKI.
69 let pub_key_output = client.get_public_key().key_id(key_id.clone()).send().await?;
70 let spki_der = pub_key_output.public_key().ok_or(KmsSignerError::EmptyBlob)?.as_ref();
71
72 // Decode the compressed SPKI as a Miden public key.
73 let pub_key = PublicKey::from_der(spki_der)?;
74 Ok(Self { key_id, pub_key, client })
75 }
76
77 pub async fn sign(&self, commitment: Word) -> Result<Signature, KmsSignerError> {
78 // The Validator produces Ethereum-style ECDSA (secp256k1) signatures over Keccak-256
79 // digests. AWS KMS does not support SHA-3 hashing for ECDSA keys (ECC_SECG_P256K1 being the
80 // corresponding AWS key-spec), so we pre-hash the message and pass MessageType::Digest. KMS
81 // signs the provided 32-byte digest verbatim.
82 let msg = commitment.to_bytes();
83 let digest = Keccak256::hash(&msg);
84
85 // Request signature from KMS backend.
86 let sign_output = self
87 .client
88 .sign()
89 .key_id(&self.key_id)
90 .signing_algorithm(SigningAlgorithmSpec::EcdsaSha256)
91 .message_type(aws_sdk_kms::types::MessageType::Digest)
92 .message(digest.to_bytes().into())
93 .send()
94 .await
95 .map_err(Box::from)
96 .map_err(KmsSignerError::KmsServiceError)?;
97
98 // Decode DER-encoded signature.
99 let sig_der = sign_output.signature().ok_or(KmsSignerError::EmptyBlob)?;
100 // Recovery id is not used by verify(pk), so 0 is fine.
101 let recovery_id = 0;
102 let sig = Signature::from_der(sig_der.as_ref(), recovery_id)
103 .map_err(KmsSignerError::SignatureFormatError)?;
104
105 // Check the returned signature.
106 if sig.verify(commitment, &self.pub_key) {
107 Ok(sig)
108 } else {
109 Err(KmsSignerError::InvalidSignature)
110 }
111 }
112
113 pub fn public_key(&self) -> PublicKey {
114 self.pub_key.clone()
115 }
116}