1use ethers_core::{
4 k256::ecdsa::{Error as K256Error, Signature as KSig, VerifyingKey},
5 types::{
6 transaction::{eip2718::TypedTransaction, eip712::Eip712},
7 Address, Signature as EthSig, H256,
8 },
9 utils::hash_message,
10};
11use rusoto_core::RusotoError;
12use rusoto_kms::{
13 GetPublicKeyError, GetPublicKeyRequest, Kms, KmsClient, SignError, SignRequest, SignResponse,
14};
15use tracing::{debug, instrument, trace};
16
17mod utils;
18use utils::{apply_eip155, verifying_key_to_address};
19
20#[derive(Clone)]
47pub struct AwsSigner {
48 kms: KmsClient,
49 chain_id: u64,
50 key_id: String,
51 pubkey: VerifyingKey,
52 address: Address,
53}
54
55impl std::fmt::Debug for AwsSigner {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 f.debug_struct("AwsSigner")
58 .field("key_id", &self.key_id)
59 .field("chain_id", &self.chain_id)
60 .field("pubkey", &hex::encode(self.pubkey.to_sec1_bytes()))
61 .field("address", &self.address)
62 .finish()
63 }
64}
65
66impl std::fmt::Display for AwsSigner {
67 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68 write!(
69 f,
70 "AwsSigner {{ address: {}, chain_id: {}, key_id: {} }}",
71 self.address, self.chain_id, self.key_id
72 )
73 }
74}
75
76#[derive(thiserror::Error, Debug)]
78pub enum AwsSignerError {
79 #[error("{0}")]
80 SignError(#[from] RusotoError<SignError>),
81 #[error("{0}")]
82 GetPublicKeyError(#[from] RusotoError<GetPublicKeyError>),
83 #[error("{0}")]
84 K256(#[from] K256Error),
85 #[error("{0}")]
86 Spki(spki::Error),
87 #[error("{0}")]
88 Other(String),
89 #[error(transparent)]
90 HexError(#[from] hex::FromHexError),
92 #[error("error encoding eip712 struct: {0:?}")]
94 Eip712Error(String),
95}
96
97impl From<String> for AwsSignerError {
98 fn from(s: String) -> Self {
99 Self::Other(s)
100 }
101}
102
103impl From<spki::Error> for AwsSignerError {
104 fn from(e: spki::Error) -> Self {
105 Self::Spki(e)
106 }
107}
108
109#[instrument(err, skip(kms, key_id), fields(key_id = %key_id.as_ref()))]
110async fn request_get_pubkey<T>(
111 kms: &KmsClient,
112 key_id: T,
113) -> Result<rusoto_kms::GetPublicKeyResponse, RusotoError<GetPublicKeyError>>
114where
115 T: AsRef<str>,
116{
117 debug!("Dispatching get_public_key");
118
119 let req = GetPublicKeyRequest { grant_tokens: None, key_id: key_id.as_ref().to_owned() };
120 trace!("{:?}", &req);
121 let resp = kms.get_public_key(req).await;
122 trace!("{:?}", &resp);
123 resp
124}
125
126#[instrument(err, skip(kms, digest, key_id), fields(digest = %hex::encode(digest), key_id = %key_id.as_ref()))]
127async fn request_sign_digest<T>(
128 kms: &KmsClient,
129 key_id: T,
130 digest: [u8; 32],
131) -> Result<SignResponse, RusotoError<SignError>>
132where
133 T: AsRef<str>,
134{
135 debug!("Dispatching sign");
136 let req = SignRequest {
137 grant_tokens: None,
138 key_id: key_id.as_ref().to_owned(),
139 message: digest.to_vec().into(),
140 message_type: Some("DIGEST".to_owned()),
141 signing_algorithm: "ECDSA_SHA_256".to_owned(),
142 };
143 trace!("{:?}", &req);
144 let resp = kms.sign(req).await;
145 trace!("{:?}", &resp);
146 resp
147}
148
149impl AwsSigner {
150 #[instrument(err, skip(kms, key_id, chain_id), fields(key_id = %key_id.as_ref()))]
155 pub async fn new<T>(
156 kms: KmsClient,
157 key_id: T,
158 chain_id: u64,
159 ) -> Result<AwsSigner, AwsSignerError>
160 where
161 T: AsRef<str>,
162 {
163 let pubkey = request_get_pubkey(&kms, &key_id).await.map(utils::decode_pubkey)??;
164 let address = verifying_key_to_address(&pubkey);
165
166 debug!(
167 "Instantiated AWS signer with pubkey 0x{} and address 0x{}",
168 hex::encode(pubkey.to_sec1_bytes()),
169 hex::encode(address)
170 );
171
172 Ok(Self { kms, chain_id, key_id: key_id.as_ref().to_owned(), pubkey, address })
173 }
174
175 pub async fn get_pubkey_for_key<T>(&self, key_id: T) -> Result<VerifyingKey, AwsSignerError>
177 where
178 T: AsRef<str>,
179 {
180 request_get_pubkey(&self.kms, key_id).await.map(utils::decode_pubkey)?
181 }
182
183 pub async fn get_pubkey(&self) -> Result<VerifyingKey, AwsSignerError> {
185 self.get_pubkey_for_key(&self.key_id).await
186 }
187
188 pub async fn sign_digest_with_key<T>(
190 &self,
191 key_id: T,
192 digest: [u8; 32],
193 ) -> Result<KSig, AwsSignerError>
194 where
195 T: AsRef<str>,
196 {
197 request_sign_digest(&self.kms, key_id, digest).await.map(utils::decode_signature)?
198 }
199
200 pub async fn sign_digest(&self, digest: [u8; 32]) -> Result<KSig, AwsSignerError> {
202 self.sign_digest_with_key(self.key_id.clone(), digest).await
203 }
204
205 #[instrument(err, skip(digest), fields(digest = %hex::encode(digest)))]
208 async fn sign_digest_with_eip155(
209 &self,
210 digest: H256,
211 chain_id: u64,
212 ) -> Result<EthSig, AwsSignerError> {
213 let sig = self.sign_digest(digest.into()).await?;
214 let mut sig =
215 utils::sig_from_digest_bytes_trial_recovery(&sig, digest.into(), &self.pubkey);
216 apply_eip155(&mut sig, chain_id);
217 Ok(sig)
218 }
219}
220
221#[async_trait::async_trait]
222impl super::Signer for AwsSigner {
223 type Error = AwsSignerError;
224
225 #[instrument(err, skip(message))]
226 #[allow(clippy::blocks_in_conditions)]
227 async fn sign_message<S: Send + Sync + AsRef<[u8]>>(
228 &self,
229 message: S,
230 ) -> Result<EthSig, Self::Error> {
231 let message = message.as_ref();
232 let message_hash = hash_message(message);
233 trace!("{:?}", message_hash);
234 trace!("{:?}", message);
235
236 self.sign_digest_with_eip155(message_hash, self.chain_id).await
237 }
238
239 #[instrument(err)]
240 #[allow(clippy::blocks_in_conditions)]
241 async fn sign_transaction(&self, tx: &TypedTransaction) -> Result<EthSig, Self::Error> {
242 let mut tx_with_chain = tx.clone();
243 let chain_id = tx_with_chain.chain_id().map(|id| id.as_u64()).unwrap_or(self.chain_id);
244 tx_with_chain.set_chain_id(chain_id);
245
246 let sighash = tx_with_chain.sighash();
247 self.sign_digest_with_eip155(sighash, chain_id).await
248 }
249
250 async fn sign_typed_data<T: Eip712 + Send + Sync>(
251 &self,
252 payload: &T,
253 ) -> Result<EthSig, Self::Error> {
254 let digest =
255 payload.encode_eip712().map_err(|e| Self::Error::Eip712Error(e.to_string()))?;
256
257 let sig = self.sign_digest(digest).await?;
258 let sig = utils::sig_from_digest_bytes_trial_recovery(&sig, digest, &self.pubkey);
259
260 Ok(sig)
261 }
262
263 fn address(&self) -> Address {
264 self.address
265 }
266
267 fn chain_id(&self) -> u64 {
269 self.chain_id
270 }
271
272 fn with_chain_id<T: Into<u64>>(mut self, chain_id: T) -> Self {
274 self.chain_id = chain_id.into();
275 self
276 }
277}
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282 use crate::Signer;
283 use rusoto_core::{
284 credential::{EnvironmentProvider, StaticProvider},
285 Client, HttpClient, Region,
286 };
287
288 #[allow(dead_code)]
289 fn static_client() -> KmsClient {
290 let access_key = "".to_owned();
291 let secret_access_key = "".to_owned();
292
293 let client = Client::new_with(
294 StaticProvider::new(access_key, secret_access_key, None, None),
295 HttpClient::new().unwrap(),
296 );
297 KmsClient::new_with_client(client, Region::UsWest1)
298 }
299
300 #[allow(dead_code)]
301 fn env_client() -> KmsClient {
302 let client = Client::new_with(EnvironmentProvider::default(), HttpClient::new().unwrap());
303 KmsClient::new_with_client(client, Region::UsWest1)
304 }
305
306 #[tokio::test]
307 async fn it_signs_messages() {
308 let chain_id = 1;
309 let key_id = match std::env::var("AWS_KEY_ID") {
310 Ok(id) => id,
311 _ => return,
312 };
313 let client = env_client();
314 let signer = AwsSigner::new(client, key_id, chain_id).await.unwrap();
315
316 let message = vec![0, 1, 2, 3];
317
318 let sig = signer.sign_message(&message).await.unwrap();
319 sig.verify(message, signer.address).expect("valid sig");
320 }
321}