Key Encapsulation
ML-KEM (FIPS 203) post-quantum key encapsulation precompile for the Lux EVM.
Overview
ML-KEM (Module-Lattice-Based Key Encapsulation Mechanism), standardized as FIPS 203 in August 2024, provides post-quantum secure key exchange. The Lux EVM includes a precompile for on-chain ML-KEM operations, enabling smart contracts to participate in quantum-resistant key agreement protocols.
ML-KEM Config
| Property | Value |
|---|---|
| Address | 0x0200000000000000000000000000000000000007 |
| Operations | Encapsulate, Decapsulate (view) |
| Supported Levels | ML-KEM-512, ML-KEM-768, ML-KEM-1024 |
| Gas Cost | 8,000 (encapsulate), 10,000 (decapsulate) |
Unlike the signature verification precompiles which simply return valid/invalid, ML-KEM is a key encapsulation mechanism: it produces a shared secret and a ciphertext. The precompile supports both the encapsulation and decapsulation operations.
Parameter Sets
| Parameter Set | Security Level | Public Key | Ciphertext | Shared Secret |
|---|---|---|---|---|
| ML-KEM-512 | NIST Level 1 (128-bit) | 800 bytes | 768 bytes | 32 bytes |
| ML-KEM-768 | NIST Level 3 (192-bit) | 1,184 bytes | 1,088 bytes | 32 bytes |
| ML-KEM-1024 | NIST Level 5 (256-bit) | 1,568 bytes | 1,568 bytes | 32 bytes |
Operations
Encapsulate
Given a public key, produce a shared secret and ciphertext. The ciphertext can be sent to the key holder, who decapsulates it to recover the same shared secret.
Input format: 0x01 || public_key
Output: shared_secret (32 bytes) || ciphertext
import { createPublicClient, http, encodePacked } from 'viem'
import { lux } from 'viem/chains'
const client = createPublicClient({
chain: lux,
transport: http(),
})
const ML_KEM_ADDRESS = '0x0200000000000000000000000000000000000007'
// Encapsulate: produce shared secret + ciphertext
const result = await client.call({
to: ML_KEM_ADDRESS,
data: encodePacked(
['uint8', 'bytes'],
[0x01, recipientPublicKey] // 0x01 = encapsulate operation
),
})
// result.data contains: shared_secret (32 bytes) || ciphertext
const sharedSecret = result.data.slice(0, 66) // 0x + 64 hex chars = 32 bytes
const ciphertext = result.data.slice(66)Decapsulate
Given a secret key and ciphertext, recover the shared secret.
Input format: 0x02 || secret_key || ciphertext
Output: shared_secret (32 bytes)
Decapsulation requires the secret key, which should never be stored on-chain. The decapsulate operation is intended for use in off-chain computation (e.g., via eth_call) or within privacy-preserving enclaves. Submitting a secret key in an on-chain transaction exposes it publicly.
Use Cases
Encrypted On-Chain Messaging
Smart contracts can establish shared secrets between parties for encrypted communication channels, with the key exchange itself being quantum-resistant:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract QuantumSafeChannel {
address constant ML_KEM = 0x0200000000000000000000000000000000000007;
// Recipient registers their ML-KEM public key
mapping(address => bytes) public publicKeys;
event CiphertextPublished(address indexed sender, address indexed recipient, bytes ciphertext);
function registerPublicKey(bytes calldata mlKemPublicKey) external {
publicKeys[msg.sender] = mlKemPublicKey;
}
function initiateChannel(address recipient) external returns (bytes memory ciphertext) {
bytes memory recipientKey = publicKeys[recipient];
require(recipientKey.length > 0, "Recipient not registered");
// Encapsulate to produce shared secret + ciphertext
(bool success, bytes memory result) = ML_KEM.staticcall(
abi.encodePacked(uint8(0x01), recipientKey)
);
require(success, "Encapsulation failed");
// Shared secret (first 32 bytes) stays in contract state or is used ephemerally
// Ciphertext is published for the recipient to decapsulate off-chain
ciphertext = new bytes(result.length - 32);
for (uint i = 0; i < ciphertext.length; i++) {
ciphertext[i] = result[i + 32];
}
emit CiphertextPublished(msg.sender, recipient, ciphertext);
}
}Hybrid Key Exchange
Combine ML-KEM with classical ECDH for defense-in-depth. The final shared secret is derived from both key exchanges, so an attacker must break both to compromise the channel:
import { keccak256, encodePacked } from 'viem'
// Classical ECDH shared secret (from secp256k1)
const classicalSecret = deriveECDHSecret(myPrivateKey, theirPublicKey)
// Post-quantum ML-KEM shared secret (from precompile)
const pqResult = await client.call({
to: ML_KEM_ADDRESS,
data: encodePacked(['uint8', 'bytes'], [0x01, theirMLKEMPublicKey]),
})
const pqSecret = pqResult.data.slice(0, 66)
// Hybrid shared secret: hash both together
const hybridSecret = keccak256(
encodePacked(['bytes', 'bytes'], [classicalSecret, pqSecret])
)Future-Proof Token Bridges
Bridge protocols can use ML-KEM to establish quantum-resistant encrypted channels between validator nodes, ensuring that bridge messages remain confidential even if captured traffic is later decrypted by a quantum computer ("harvest now, decrypt later" attacks).
Is this guide helpful?