Expand description
EVM account abstraction for signing transactions using AWS KMS keys.
This library provides an abstraction for EVM accounts to sign transactions using AWS KMS keys. Designed for security, as the private key never leaves the KMS service uncencrypted. With careful KMS keys policy, the key extraction can be completely disabled making the library a perfect fit for verifiably secure production environments.
§Examples
The following examples demonstrate how to sign EVM transactions using AWS KMS keys.
§Free market transaction (i.e. type 2 transaction)
The following example demonstrates how to sign an
EIP-1559 transaction:
use anyhow::{Result, bail};
use evm_signer_kms::{
account::EvmAccount, key::aws_kms::AwsKmsKey,
transaction::free_market_transaction::FreeMarketTransaction,
};
use std::env;
// Name of the environment variable with the KMS key ID
const KMS_KEY_ID_VAR_NAME: &str = "KMS_KEY_ID";
// Example EIP-1559 transaction JSON
const FREE_MARKET_TX_JSON: &str = r#"
{
"gasLimit": 21000,
"maxFeePerGas": 100000000000,
"maxPriorityFeePerGas": 3000000000,
"chainId": 11155111,
"nonce": 0,
"to": "0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb",
"value": 10000000000000000,
"data": "",
"accessList": []
}
"#;
// Communication with AWS endpoint is asynchronous, so we need to use async main function
#[tokio::main]
async fn main() -> Result<()> {
// Get KMS key ID from environment variable
let kms_key_id = env::var(KMS_KEY_ID_VAR_NAME).or_else(|_| {
bail!("Not set: {KMS_KEY_ID_VAR_NAME}");
})?;
// Create a new KMS key
let kms_key = &AwsKmsKey::new(
&kms_key_id,
#[cfg(feature = "sts-assume-role")]
None).await;
// Create a new EVM account
let evm_account = EvmAccount::new(kms_key).await.or_else(|err| {
bail!("Create EVM account: {err}");
})?;
// Create a new unsigned EIP-1559 transaction
let unigned_tx =
serde_json::from_str::<FreeMarketTransaction>(FREE_MARKET_TX_JSON).or_else(|err| {
bail!("Parse transaction JSON: {err}");
})?;
// Sign the transaction using EVM account
let signed_tx = evm_account
.sign_transaction(unigned_tx)
.await
.or_else(|err| {
bail!("Sign transaction: {err}");
})?;
// Use the provided string serialization to get the signed transaction encoding.
let signed_tx_encoding = serde_plain::to_string(&signed_tx).or_else(|err| {
bail!("Serialize signed transaction: {err}");
})?;
println!("Encoded signed type-2 transaction: {signed_tx_encoding}");
Ok(())
}§Access list transaction (i.e. type 1 transaction)
If you want to use an
EIP-2930 transaction instead, you declare the
transaction as:
const ACCESS_LIST_TX_JSON: &str = r#"
{
"gasLimit": 21000,
"gasPrice": 100000000000,
"chainId": 11155111,
"nonce": 0,
"to": "0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb",
"value": 10000000000000000,
"data": "",
"accessList": [
[
"0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae",
[
"0x0000000000000000000000000000000000000000000000000000000000000003",
"0x0000000000000000000000000000000000000000000000000000000000000007"
]
],
[
"0xbb9bc244d798123fde783fcc1c72d3bb8c189413",
[]
]
]
}
"#;You will also want to serialize the transaction as an AccessListTransaction.
§Legacy transaction (i.e. type 0 transaction)
Legacy transactions are also supported. You can use the LegacyTransaction struct during
deserialization, and a sample JSON would look like this:
const LEGACY_TX_JSON: &str = r#"
{
"gasLimit": 21000,
"gasPrice": 100000000000,
"nonce": 0,
"to": "0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb",
"value": 10000000000000000,
"data": ""
}
"#;§Key policy requirements
The principal your code is going to be executing as needs to have the necessary permissions to use the KMS key. The permissions are set in the KMS key policy.
At the very least the KMS key policy should have the following permissions that for the IAM role that your environment assumes:
{
"Sid": "AllowKeyUse",
"Effect": "Allow",
"Principal": {
"AWS": "<iam_role_your_environment_assumes>"
},
"Action": [
"kms:DescribeKey",
"kms:GetPublicKey",
"kms:Sign",
"kms:Verify"
],
"Resource": "*"
}Furthermore, if you wish to use the library in client code which runs as an AWS resource like e.g. a AWS Lambda function or a ECS task, you need to allow grants:
{
"Sid": "AllowGrantsForAwsResources",
"Effect": "Allow",
"Principal": {
"AWS": "<iam_role_your_environment_assumes>"
},
"Action": [
"kms:CreateGrant",
"kms:ListGrants",
"kms:RevokeGrant"
],
"Resource": "*",
"Condition": {
"Bool": {
"kms:GrantIsForAWSResource": "true"
}
}
}§Explicit role assumption with AWS STS
If your wish to assume a different IAM role than the one your environment is running as,
for instance to access KMS keys in a different AWS account, or to implement more granular
permissions separation, you can enable the sts-assume-role feature of this library.
evm-signer-kms = { version = "0.5.1", features = ["sts-assume-role"] }When the feature is enabled, you can provide an optional role ARN when creating
the AwsKmsKey instance. The library will then assume the provided role using AWS STS
before accessing the KMS key. If None is provided, the library will use the default
credentials provider chain.
Modules§
- account
- Abstraction over EVM accounts for signing transactions with AWS KMS keys.
- key
- Implementation of the KMS key abstraction using AWS KMS SDK.
- transaction
- Representations of EVM transactions.