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;