For the complete documentation index, see llms.txt. This page is also available as Markdown.
aes_gcm_encrypt
On-chain AES-256-GCM encryption
Encrypt data on-chain using Mercury EVM's AES-GCM encryption precompile.
Overview
The AES-GCM encryption precompile at address 0x66 performs AES-256-GCM authenticated encryption. The ciphertext includes a 16-byte authentication tag that ensures integrity and authenticity.
The SeismicSignedProvider filler pipeline uses this precompile internally to encrypt calldata before sending shielded transactions. You can also call it directly for custom encryption workflows.
Precompile Address
0x0000000000000000000000000000000000000066
Input Encoding
Field
Size
Description
key
32 bytes
AES-256 encryption key
nonce
12 bytes
Unique nonce (must never be reused with the same key)
plaintext
Variable
Data to encrypt
The input is the concatenation of key (32 bytes) + nonce (12 bytes) + plaintext (variable length).
use alloy_provider::Provider;
use alloy_primitives::{Address, Bytes};
use alloy_rpc_types_eth::TransactionRequest;
use seismic_prelude::client::*;
use seismic_alloy_provider::precompiles;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let url = "https://testnet-1.seismictest.net/rpc".parse()?;
let provider = SeismicProviderBuilder::new().connect_http(url);
// 32-byte AES key
let key = [0x42u8; 32]; // Use a proper key in production
// 12-byte nonce
let nonce = [0u8; 12];
// Plaintext to encrypt
let plaintext = b"Secret message";
// Using convenience helpers
let ciphertext = precompiles::call::aes_gcm_encrypt(&provider, &key, &nonce, plaintext).await?;
println!("Ciphertext (convenience): 0x{}", hex::encode(&ciphertext));
// Manual approach
let encrypt_address: Address =
"0x0000000000000000000000000000000000000066".parse()?;
let mut input = Vec::new();
input.extend_from_slice(&key);
input.extend_from_slice(&nonce);
input.extend_from_slice(plaintext);
let result = provider
.call(
&TransactionRequest::default()
.to(encrypt_address)
.input(Bytes::from(input).into()),
)
.await?;
println!("Ciphertext ({} bytes): 0x{}", result.len(), hex::encode(&result));
println!(" Plaintext was {} bytes + 16-byte tag", plaintext.len());
Ok(())
}
use alloy_provider::Provider;
use alloy_primitives::{Address, Bytes};
use alloy_rpc_types_eth::TransactionRequest;
use seismic_prelude::client::*;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let url = "https://testnet-1.seismictest.net/rpc".parse()?;
let provider = SeismicProviderBuilder::new().connect_http(url);
let encrypt_address: Address =
"0x0000000000000000000000000000000000000066".parse()?;
let key = [0x42u8; 32];
// Use a counter as nonce (converted to 12 bytes, big-endian)
for i in 0u64..5 {
let mut nonce = [0u8; 12];
nonce[4..12].copy_from_slice(&i.to_be_bytes());
let plaintext = format!("Message {i}");
let mut input = Vec::new();
input.extend_from_slice(&key);
input.extend_from_slice(&nonce);
input.extend_from_slice(plaintext.as_bytes());
let result = provider
.call(
&TransactionRequest::default()
.to(encrypt_address)
.input(Bytes::from(input).into()),
)
.await?;
println!("Ciphertext {i}: 0x{}", hex::encode(&result));
}
Ok(())
}
use alloy_provider::Provider;
use alloy_primitives::{Address, Bytes};
use alloy_rpc_types_eth::TransactionRequest;
use seismic_prelude::client::*;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let url = "https://testnet-1.seismictest.net/rpc".parse()?;
let provider = SeismicProviderBuilder::new().connect_http(url);
let encrypt_address: Address =
"0x0000000000000000000000000000000000000066".parse()?;
let decrypt_address: Address =
"0x0000000000000000000000000000000000000067".parse()?;
let key = [0x42u8; 32];
let nonce = [0u8; 12];
let original = b"Round trip test";
// Encrypt
let mut encrypt_input = Vec::new();
encrypt_input.extend_from_slice(&key);
encrypt_input.extend_from_slice(&nonce);
encrypt_input.extend_from_slice(original);
let ciphertext = provider
.call(
&TransactionRequest::default()
.to(encrypt_address)
.input(Bytes::from(encrypt_input).into()),
)
.await?;
// Decrypt
let mut decrypt_input = Vec::new();
decrypt_input.extend_from_slice(&key);
decrypt_input.extend_from_slice(&nonce);
decrypt_input.extend_from_slice(&ciphertext);
let decrypted = provider
.call(
&TransactionRequest::default()
.to(decrypt_address)
.input(Bytes::from(decrypt_input).into()),
)
.await?;
assert_eq!(&decrypted[..], original);
println!("Round trip successful: {}", String::from_utf8_lossy(&decrypted));
Ok(())
}
use alloy_provider::Provider;
use alloy_primitives::{Address, Bytes};
use alloy_rpc_types_eth::TransactionRequest;
use seismic_prelude::client::*;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let url = "https://testnet-1.seismictest.net/rpc".parse()?;
let provider = SeismicProviderBuilder::new().connect_http(url);
// Step 1: Derive shared key via ECDH
let ecdh_address: Address =
"0x0000000000000000000000000000000000000065".parse()?;
let my_sk: [u8; 32] = [/* ... */; 32];
let peer_pk: [u8; 33] = [/* ... */; 33];
let mut ecdh_input = Vec::with_capacity(65);
ecdh_input.extend_from_slice(&my_sk);
ecdh_input.extend_from_slice(&peer_pk);
let ecdh_result = provider
.call(
&TransactionRequest::default()
.to(ecdh_address)
.input(Bytes::from(ecdh_input).into()),
)
.await?;
let aes_key: [u8; 32] = ecdh_result[..32].try_into()?;
// Step 2: Encrypt with derived key
let encrypt_address: Address =
"0x0000000000000000000000000000000000000066".parse()?;
let nonce = [0u8; 12];
let plaintext = b"Encrypted for peer";
let mut encrypt_input = Vec::new();
encrypt_input.extend_from_slice(&aes_key);
encrypt_input.extend_from_slice(&nonce);
encrypt_input.extend_from_slice(plaintext);
let ciphertext = provider
.call(
&TransactionRequest::default()
.to(encrypt_address)
.input(Bytes::from(encrypt_input).into()),
)
.await?;
println!("Ciphertext: 0x{}", hex::encode(&ciphertext));
Ok(())
}