evm_signer_kms/
lib.rs

1//! EVM account abstraction for signing transactions using [AWS KMS](https://aws.amazon.com/kms)
2//! keys.
3//!
4//! This library provides an abstraction for EVM accounts to sign transactions using AWS KMS keys.
5//! Designed for security, as the private key never leaves the KMS service uncencrypted.
6//! With careful KMS keys policy, the key extraction can be completely disabled making the library
7//! a perfect fit for verifiably secure production environments.
8//!
9//! # Examples
10//!
11//! The following examples demonstrate how to sign EVM transactions using AWS KMS keys.
12//!
13//! ## Free market transaction (i.e. type 2 transaction)
14//!
15//! The following example demonstrates how to sign an
16//! [`EIP-1559`](https://eips.ethereum.org/EIPS/eip-1559) transaction:
17//!
18//! ```rust
19//!  use anyhow::{Result, bail};
20//!  use evm_signer_kms::{
21//!      account::EvmAccount, key::aws_kms::AwsKmsKey,
22//!      transaction::free_market_transaction::FreeMarketTransaction,
23//!  };
24//!  use std::env;
25//!
26//!  // Name of the environment variable with the KMS key ID
27//!  const KMS_KEY_ID_VAR_NAME: &str = "KMS_KEY_ID";
28//!  // Example EIP-1559 transaction JSON
29//!  const FREE_MARKET_TX_JSON: &str = r#"
30//!  {
31//!      "gasLimit": 21000,
32//!      "maxFeePerGas": 100000000000,
33//!      "maxPriorityFeePerGas": 3000000000,
34//!      "chainId": 11155111,
35//!      "nonce": 0,
36//!      "to": "0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb",
37//!      "value": 10000000000000000,
38//!      "data": "",
39//!      "accessList": []
40//!  }
41//!  "#;
42//!
43//!  // Communication with AWS endpoint is asynchronous, so we need to use async main function
44//!  #[tokio::main]
45//!  async fn main() -> Result<()> {
46//!      // Get KMS key ID from environment variable
47//!      let kms_key_id = env::var(KMS_KEY_ID_VAR_NAME).or_else(|_| {
48//!          bail!("Not set: {KMS_KEY_ID_VAR_NAME}");
49//!      })?;
50//!
51//!      // Create a new KMS key
52//!      let kms_key = &AwsKmsKey::new(
53//!          &kms_key_id,
54//!         #[cfg(feature = "sts-assume-role")]
55//!         None).await;
56//!      // Create a new EVM account
57//!      let evm_account = EvmAccount::new(kms_key).await.or_else(|err| {
58//!          bail!("Create EVM account: {err}");
59//!      })?;
60//!
61//!      // Create a new unsigned EIP-1559 transaction
62//!      let unigned_tx =
63//!          serde_json::from_str::<FreeMarketTransaction>(FREE_MARKET_TX_JSON).or_else(|err| {
64//!              bail!("Parse transaction JSON: {err}");
65//!          })?;
66//!
67//!      // Sign the transaction using EVM account
68//!      let signed_tx = evm_account
69//!          .sign_transaction(unigned_tx)
70//!          .await
71//!          .or_else(|err| {
72//!              bail!("Sign transaction: {err}");
73//!          })?;
74//!
75//!      // Use the provided string serialization to get the signed transaction encoding.
76//!      let signed_tx_encoding = serde_plain::to_string(&signed_tx).or_else(|err| {
77//!          bail!("Serialize signed transaction: {err}");
78//!      })?;
79//!
80//!      println!("Encoded signed type-2 transaction: {signed_tx_encoding}");
81//!
82//!      Ok(())
83//!  }
84//! ```
85//!
86//! ## Access list transaction (i.e. type 1 transaction)
87//!
88//! If you want to use an
89//! [`EIP-2930`](https://eips.ethereum.org/EIPS/eip-2930) transaction instead, you declare the
90//! transaction as:
91//!
92//! ```rust
93//! const ACCESS_LIST_TX_JSON: &str = r#"
94//! {
95//!     "gasLimit": 21000,
96//!     "gasPrice": 100000000000,
97//!     "chainId": 11155111,
98//!     "nonce": 0,
99//!     "to": "0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb",
100//!     "value": 10000000000000000,
101//!     "data": "",
102//!     "accessList": [
103//!         [
104//!             "0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae",
105//!             [
106//!                 "0x0000000000000000000000000000000000000000000000000000000000000003",
107//!                 "0x0000000000000000000000000000000000000000000000000000000000000007"
108//!             ]
109//!         ],
110//!         [
111//!             "0xbb9bc244d798123fde783fcc1c72d3bb8c189413",
112//!             []
113//!         ]
114//!     ]
115//! }
116//! "#;
117//! ```
118//!
119//! You will also want to serialize the transaction as an `AccessListTransaction`.
120//!
121//! ## Legacy transaction (i.e. type 0 transaction)
122//!
123//! Legacy transactions are also supported. You can use the `LegacyTransaction` struct during
124//! deserialization, and a sample JSON would look like this:
125//!
126//! ```rust
127//! const LEGACY_TX_JSON: &str = r#"
128//! {
129//!     "gasLimit": 21000,
130//!     "gasPrice": 100000000000,
131//!     "nonce": 0,
132//!     "to": "0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb",
133//!     "value": 10000000000000000,
134//!     "data": ""
135//! }
136//! "#;
137//! ```
138//!
139//! # Key policy requirements
140//!
141//! The principal your code is going to be executing as needs to have the necessary permissions to
142//! use the KMS key. The permissions are set in the KMS key policy.
143//!
144//! At the very least the KMS key policy should have the following permissions that for the IAM role
145//! that your environment assumes:
146//! ```json
147//! {
148//!     "Sid": "AllowKeyUse",
149//!     "Effect": "Allow",
150//!     "Principal": {
151//!         "AWS": "<iam_role_your_environment_assumes>"
152//!     },
153//!     "Action": [
154//!         "kms:DescribeKey",
155//!         "kms:GetPublicKey",
156//!         "kms:Sign",
157//!         "kms:Verify"
158//!     ],
159//!     "Resource": "*"
160//! }
161//! ```
162//!
163//! Furthermore, if you wish to use the library in client code which runs as an AWS resource like
164//! e.g. a AWS Lambda function or a ECS task, you need to allow grants:
165//! ```json
166//! {
167//!     "Sid": "AllowGrantsForAwsResources",
168//!     "Effect": "Allow",
169//!     "Principal": {
170//!         "AWS": "<iam_role_your_environment_assumes>"
171//!     },
172//!     "Action": [
173//!         "kms:CreateGrant",
174//!         "kms:ListGrants",
175//!         "kms:RevokeGrant"
176//!     ],
177//!     "Resource": "*",
178//!     "Condition": {
179//!         "Bool": {
180//!             "kms:GrantIsForAWSResource": "true"
181//!         }
182//!     }
183//! }
184//! ```
185//!
186//! # Explicit role assumption with AWS STS
187//!
188//! If your wish to assume a different IAM role than the one your environment is running as,
189//! for instance to access KMS keys in a different AWS account, or to implement more granular
190//! permissions separation, you can enable the `sts-assume-role` feature of this library.
191//!
192//! ```toml
193//! evm-signer-kms = { version = "0.5.1", features = ["sts-assume-role"] }
194//! ```
195//!
196//! When the feature is enabled, you can provide an optional role ARN when creating
197//! the `AwsKmsKey` instance. The library will then assume the provided role using AWS STS
198//! before accessing the KMS key. If `None` is provided, the library will use the default
199//! credentials provider chain.
200
201/// Abstraction over EVM accounts for signing transactions with AWS KMS keys.
202pub mod account;
203/// Implementation of the KMS key abstraction using AWS KMS SDK.
204pub mod key;
205/// Representations of EVM transactions.
206pub mod transaction;
207/// Type shorthands.
208pub(crate) mod types;