For the complete documentation index, see llms.txt. This page is also available as Markdown.
aes_gcm_decrypt
On-chain AES-256-GCM decryption
Decrypt data on-chain using Mercury EVM's AES-GCM decryption precompile.
Overview
The AES-GCM decryption precompile at address 0x67 performs AES-256-GCM authenticated decryption. The ciphertext must include the 16-byte authentication tag appended during encryption. If the tag does not verify, the precompile reverts.
Precompile Address
0x0000000000000000000000000000000000000067
Input Encoding
Field
Size
Description
key
32 bytes
AES-256 decryption key (must match encryption key)
nonce
12 bytes
Nonce (must match the nonce used during encryption)
ciphertext
Variable
Encrypted data including the 16-byte authentication tag
The input is the concatenation of key (32 bytes) + nonce (12 bytes) + ciphertext (variable length, includes 16-byte tag).
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);
let key = [0x42u8; 32];
let nonce = [0u8; 12];
let plaintext = b"Secret message";
// Encrypt first using convenience helper
let ciphertext = precompiles::call::aes_gcm_encrypt(&provider, &key, &nonce, plaintext).await?;
// Decrypt using convenience helper
let decrypted = precompiles::call::aes_gcm_decrypt(&provider, &key, &nonce, &ciphertext).await?;
assert_eq!(&decrypted[..], plaintext);
println!("Decrypted (convenience): {}", String::from_utf8_lossy(&decrypted));
// Manual approach
let encrypt_address: Address =
"0x0000000000000000000000000000000000000066".parse()?;
let decrypt_address: Address =
"0x0000000000000000000000000000000000000067".parse()?;
// Encrypt first
let mut encrypt_input = Vec::new();
encrypt_input.extend_from_slice(&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?;
// 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[..], plaintext);
println!("Decrypted: {}", 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);
let encrypt_address: Address =
"0x0000000000000000000000000000000000000066".parse()?;
let decrypt_address: Address =
"0x0000000000000000000000000000000000000067".parse()?;
let key = [0x42u8; 32];
let messages = ["Message 1", "Message 2", "Message 3"];
let mut ciphertexts = Vec::new();
// Encrypt each message with a unique nonce
for (i, msg) in messages.iter().enumerate() {
let mut nonce = [0u8; 12];
nonce[11] = i as u8;
let mut input = Vec::new();
input.extend_from_slice(&key);
input.extend_from_slice(&nonce);
input.extend_from_slice(msg.as_bytes());
let ct = provider
.call(
&TransactionRequest::default()
.to(encrypt_address)
.input(Bytes::from(input).into()),
)
.await?;
ciphertexts.push((nonce, ct));
}
// Decrypt all messages
for (i, (nonce, ct)) in ciphertexts.iter().enumerate() {
let mut input = Vec::new();
input.extend_from_slice(&key);
input.extend_from_slice(nonce);
input.extend_from_slice(ct);
let decrypted = provider
.call(
&TransactionRequest::default()
.to(decrypt_address)
.input(Bytes::from(input).into()),
)
.await?;
println!("Message {i}: {}", 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);
let decrypt_address: Address =
"0x0000000000000000000000000000000000000067".parse()?;
let key = [0x42u8; 32];
let nonce = [0u8; 12];
let bad_ciphertext = b"this is not valid ciphertext";
let mut input = Vec::new();
input.extend_from_slice(&key);
input.extend_from_slice(&nonce);
input.extend_from_slice(bad_ciphertext);
// Decryption will fail if the authentication tag doesn't verify
match provider
.call(
&TransactionRequest::default()
.to(decrypt_address)
.input(Bytes::from(input).into()),
)
.await
{
Ok(plaintext) => println!("Decrypted: 0x{}", hex::encode(&plaintext)),
Err(e) => println!("Decryption failed (expected): {e}"),
}
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);
// Bob derives the same shared secret that Alice used to encrypt
let ecdh_address: Address =
"0x0000000000000000000000000000000000000065".parse()?;
let bob_sk: [u8; 32] = [/* Bob's private key */; 32];
let alice_pk: [u8; 33] = [/* Alice's compressed public key */; 33];
let mut ecdh_input = Vec::with_capacity(65);
ecdh_input.extend_from_slice(&bob_sk);
ecdh_input.extend_from_slice(&alice_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()?;
// Decrypt the ciphertext Alice sent
let decrypt_address: Address =
"0x0000000000000000000000000000000000000067".parse()?;
let nonce = [0u8; 12]; // Must match Alice's encryption nonce
let ciphertext: &[u8] = &[/* ciphertext from Alice */];
let mut decrypt_input = Vec::new();
decrypt_input.extend_from_slice(&aes_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?;
println!("Bob received: {}", String::from_utf8_lossy(&decrypted));
Ok(())
}