feat(precompiles): add ML-KEM key encapsulation precompile

Implements FIPS 203 ML-KEM precompile at address 0x0200...0007:
- Encapsulate: generate shared secret + ciphertext from public key
- Decapsulate: recover shared secret from ciphertext using private key

Security levels:
- ML-KEM-512: 128-bit (NIST Level 1)
- ML-KEM-768: 192-bit (NIST Level 3) - recommended
- ML-KEM-1024: 256-bit (NIST Level 5)

Completes the PQ crypto precompile suite alongside ML-DSA and SLH-DSA.

See LP-4318 for specification.
This commit is contained in:
Zach Kelling
2025-12-25 20:35:30 +00:00
parent 41b7f88698
commit 1ca3355770
5 changed files with 779 additions and 0 deletions
+141
View File
@@ -0,0 +1,141 @@
// SPDX-License-Identifier: MIT
// Copyright (C) 2025, Lux Industries, Inc. All rights reserved.
pragma solidity ^0.8.20;
/**
* @title IMLKEM
* @notice Interface for the ML-KEM (FIPS 203) key encapsulation precompile
* @dev Precompile address: 0x0200000000000000000000000000000000000007
*
* ML-KEM provides post-quantum secure key encapsulation for establishing
* shared secrets between parties. This precompile supports three security levels:
* - ML-KEM-512: 128-bit security (NIST Level 1)
* - ML-KEM-768: 192-bit security (NIST Level 3)
* - ML-KEM-1024: 256-bit security (NIST Level 5)
*
* See LP-4318 for full specification.
*/
interface IMLKEM {
/// @notice ML-KEM mode for 128-bit security (NIST Level 1)
uint8 constant MODE_MLKEM_512 = 0x00;
/// @notice ML-KEM mode for 192-bit security (NIST Level 3)
uint8 constant MODE_MLKEM_768 = 0x01;
/// @notice ML-KEM mode for 256-bit security (NIST Level 5)
uint8 constant MODE_MLKEM_1024 = 0x02;
/// @notice Key sizes for ML-KEM-512
uint256 constant MLKEM_512_PUBLIC_KEY_SIZE = 800;
uint256 constant MLKEM_512_PRIVATE_KEY_SIZE = 1632;
uint256 constant MLKEM_512_CIPHERTEXT_SIZE = 768;
/// @notice Key sizes for ML-KEM-768
uint256 constant MLKEM_768_PUBLIC_KEY_SIZE = 1184;
uint256 constant MLKEM_768_PRIVATE_KEY_SIZE = 2400;
uint256 constant MLKEM_768_CIPHERTEXT_SIZE = 1088;
/// @notice Key sizes for ML-KEM-1024
uint256 constant MLKEM_1024_PUBLIC_KEY_SIZE = 1568;
uint256 constant MLKEM_1024_PRIVATE_KEY_SIZE = 3168;
uint256 constant MLKEM_1024_CIPHERTEXT_SIZE = 1568;
/// @notice Shared secret size (same for all modes)
uint256 constant SHARED_SECRET_SIZE = 32;
/**
* @notice Encapsulate a shared secret using a public key
* @param mode The ML-KEM mode (0=512, 1=768, 2=1024)
* @param publicKey The recipient's public key
* @return ciphertext The encapsulated ciphertext to send to recipient
* @return sharedSecret The 32-byte shared secret
*/
function encapsulate(
uint8 mode,
bytes calldata publicKey
) external view returns (
bytes memory ciphertext,
bytes32 sharedSecret
);
/**
* @notice Decapsulate a ciphertext to recover the shared secret
* @param mode The ML-KEM mode (0=512, 1=768, 2=1024)
* @param privateKey The recipient's private key
* @param ciphertext The encapsulated ciphertext from sender
* @return sharedSecret The 32-byte shared secret
*/
function decapsulate(
uint8 mode,
bytes calldata privateKey,
bytes calldata ciphertext
) external view returns (bytes32 sharedSecret);
}
/**
* @title MLKEMCaller
* @notice Helper library for calling the ML-KEM precompile
*/
library MLKEMCaller {
address constant MLKEM_PRECOMPILE = 0x0200000000000000000000000000000000000007;
uint8 constant OP_ENCAPSULATE = 0x01;
uint8 constant OP_DECAPSULATE = 0x02;
error MLKEMCallFailed();
error InvalidResultLength();
/**
* @notice Encapsulate using ML-KEM-768 (recommended)
*/
function encapsulate768(bytes memory publicKey) internal view returns (bytes memory ciphertext, bytes32 sharedSecret) {
return encapsulate(IMLKEM.MODE_MLKEM_768, publicKey);
}
/**
* @notice Decapsulate using ML-KEM-768 (recommended)
*/
function decapsulate768(bytes memory privateKey, bytes memory ciphertext) internal view returns (bytes32 sharedSecret) {
return decapsulate(IMLKEM.MODE_MLKEM_768, privateKey, ciphertext);
}
/**
* @notice Encapsulate with specified mode
*/
function encapsulate(uint8 mode, bytes memory publicKey) internal view returns (bytes memory ciphertext, bytes32 sharedSecret) {
bytes memory input = abi.encodePacked(OP_ENCAPSULATE, mode, publicKey);
(bool success, bytes memory result) = MLKEM_PRECOMPILE.staticcall(input);
if (!success) revert MLKEMCallFailed();
// Result is ciphertext || sharedSecret(32 bytes)
if (result.length < 32) revert InvalidResultLength();
uint256 ctLen = result.length - 32;
ciphertext = new bytes(ctLen);
for (uint256 i = 0; i < ctLen; i++) {
ciphertext[i] = result[i];
}
assembly {
sharedSecret := mload(add(add(result, 32), ctLen))
}
}
/**
* @notice Decapsulate with specified mode
*/
function decapsulate(uint8 mode, bytes memory privateKey, bytes memory ciphertext) internal view returns (bytes32 sharedSecret) {
bytes memory input = abi.encodePacked(OP_DECAPSULATE, mode, privateKey, ciphertext);
(bool success, bytes memory result) = MLKEM_PRECOMPILE.staticcall(input);
if (!success) revert MLKEMCallFailed();
if (result.length != 32) revert InvalidResultLength();
assembly {
sharedSecret := mload(add(result, 32))
}
}
}
+97
View File
@@ -0,0 +1,97 @@
# ML-KEM Precompile
Post-quantum key encapsulation mechanism precompile implementing FIPS 203 (ML-KEM).
## Address
`0x0200000000000000000000000000000000000007`
## Specification
See [LP-4318: ML-KEM Post-Quantum Key Encapsulation](https://lps.lux.network/docs/lp-4318-ml-kem-post-quantum-key-encapsulation/)
## Operations
### Encapsulate (0x01)
Generate a shared secret and ciphertext from a public key.
**Input:**
| Offset | Size | Description |
|--------|------|-------------|
| 0 | 1 | Operation (0x01) |
| 1 | 1 | Mode (0x00=512, 0x01=768, 0x02=1024) |
| 2 | varies | Public key |
**Output:**
| Offset | Size | Description |
|--------|------|-------------|
| 0 | varies | Ciphertext |
| varies | 32 | Shared secret |
### Decapsulate (0x02)
Recover the shared secret from a ciphertext using a private key.
**Input:**
| Offset | Size | Description |
|--------|------|-------------|
| 0 | 1 | Operation (0x02) |
| 1 | 1 | Mode (0x00=512, 0x01=768, 0x02=1024) |
| 2 | varies | Private key |
| varies | varies | Ciphertext |
**Output:**
| Offset | Size | Description |
|--------|------|-------------|
| 0 | 32 | Shared secret |
## Key Sizes
| Mode | Public Key | Private Key | Ciphertext | Shared Secret |
|------|------------|-------------|------------|---------------|
| ML-KEM-512 | 800 | 1632 | 768 | 32 |
| ML-KEM-768 | 1184 | 2400 | 1088 | 32 |
| ML-KEM-1024 | 1568 | 3168 | 1568 | 32 |
## Gas Costs
| Operation | ML-KEM-512 | ML-KEM-768 | ML-KEM-1024 |
|-----------|------------|------------|-------------|
| Encapsulate | 50,000 | 75,000 | 100,000 |
| Decapsulate | 60,000 | 90,000 | 120,000 |
## Security Levels
- **ML-KEM-512**: 128-bit security (NIST Level 1)
- **ML-KEM-768**: 192-bit security (NIST Level 3) - **Recommended**
- **ML-KEM-1024**: 256-bit security (NIST Level 5)
## Usage Example (Solidity)
```solidity
import {MLKEMCaller} from "./IMLKEM.sol";
contract QuantumSecureExchange {
using MLKEMCaller for *;
function establishSecret(bytes calldata recipientPubKey)
external view
returns (bytes memory ciphertext, bytes32 sharedSecret)
{
return MLKEMCaller.encapsulate768(recipientPubKey);
}
function recoverSecret(
bytes calldata privateKey,
bytes calldata ciphertext
) external view returns (bytes32 sharedSecret) {
return MLKEMCaller.decapsulate768(privateKey, ciphertext);
}
}
```
## References
- [FIPS 203: Module-Lattice-Based Key-Encapsulation Mechanism Standard](https://csrc.nist.gov/pubs/fips/203/final)
- [NIST Post-Quantum Cryptography](https://csrc.nist.gov/projects/post-quantum-cryptography)
+257
View File
@@ -0,0 +1,257 @@
// Copyright (C) 2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// Package mlkem implements the ML-KEM (FIPS 203) key encapsulation precompile.
// Address: 0x0200000000000000000000000000000000000007
//
// See LP-4318 for full specification.
package mlkem
import (
"errors"
"fmt"
"github.com/luxfi/crypto/mlkem"
"github.com/luxfi/geth/common"
"github.com/luxfi/precompiles/contract"
)
var (
// ContractAddress is the address of the ML-KEM precompile
ContractAddress = common.HexToAddress("0x0200000000000000000000000000000000000007")
// Singleton instance
MLKEMPrecompile = &mlkemPrecompile{}
_ contract.StatefulPrecompiledContract = &mlkemPrecompile{}
ErrInvalidInputLength = errors.New("invalid input length")
ErrInvalidMode = errors.New("invalid ML-KEM mode")
ErrUnsupportedMode = errors.New("unsupported ML-KEM mode")
ErrUnsupportedOperation = errors.New("unsupported operation")
ErrEncapsulationFailed = errors.New("encapsulation failed")
ErrDecapsulationFailed = errors.New("decapsulation failed")
)
// Operation selectors
const (
OpEncapsulate = 0x01 // Generate shared secret + ciphertext from public key
OpDecapsulate = 0x02 // Recover shared secret from ciphertext using private key
)
// ML-KEM modes (FIPS 203)
const (
ModeMLKEM512 uint8 = 0x00 // ML-KEM-512 (128-bit security, NIST Level 1)
ModeMLKEM768 uint8 = 0x01 // ML-KEM-768 (192-bit security, NIST Level 3)
ModeMLKEM1024 uint8 = 0x02 // ML-KEM-1024 (256-bit security, NIST Level 5)
)
// Size constants for ML-KEM-512 (NIST Level 1)
const (
MLKEM512PublicKeySize = 800
MLKEM512PrivateKeySize = 1632
MLKEM512CiphertextSize = 768
MLKEM512SharedKeySize = 32
)
// Size constants for ML-KEM-768 (NIST Level 3)
const (
MLKEM768PublicKeySize = 1184
MLKEM768PrivateKeySize = 2400
MLKEM768CiphertextSize = 1088
MLKEM768SharedKeySize = 32
)
// Size constants for ML-KEM-1024 (NIST Level 5)
const (
MLKEM1024PublicKeySize = 1568
MLKEM1024PrivateKeySize = 3168
MLKEM1024CiphertextSize = 1568
MLKEM1024SharedKeySize = 32
)
// Gas costs - based on computational complexity
const (
// Encapsulation gas costs per mode
MLKEM512EncapsulateGas uint64 = 50_000 // Smaller, faster
MLKEM768EncapsulateGas uint64 = 75_000 // Medium
MLKEM1024EncapsulateGas uint64 = 100_000 // Larger, slower
// Decapsulation gas costs per mode
MLKEM512DecapsulateGas uint64 = 60_000 // Slightly more than encaps
MLKEM768DecapsulateGas uint64 = 90_000
MLKEM1024DecapsulateGas uint64 = 120_000
)
type mlkemPrecompile struct{}
// Address returns the address of the ML-KEM precompile
func (p *mlkemPrecompile) Address() common.Address {
return ContractAddress
}
// getModeParams returns the parameters for a given ML-KEM mode
func getModeParams(mode uint8) (pubKeySize, privKeySize, ctSize, sharedSize int, encapsGas, decapsGas uint64, mlkemMode mlkem.Mode, err error) {
switch mode {
case ModeMLKEM512:
return MLKEM512PublicKeySize, MLKEM512PrivateKeySize, MLKEM512CiphertextSize, MLKEM512SharedKeySize,
MLKEM512EncapsulateGas, MLKEM512DecapsulateGas, mlkem.MLKEM512, nil
case ModeMLKEM768:
return MLKEM768PublicKeySize, MLKEM768PrivateKeySize, MLKEM768CiphertextSize, MLKEM768SharedKeySize,
MLKEM768EncapsulateGas, MLKEM768DecapsulateGas, mlkem.MLKEM768, nil
case ModeMLKEM1024:
return MLKEM1024PublicKeySize, MLKEM1024PrivateKeySize, MLKEM1024CiphertextSize, MLKEM1024SharedKeySize,
MLKEM1024EncapsulateGas, MLKEM1024DecapsulateGas, mlkem.MLKEM1024, nil
default:
return 0, 0, 0, 0, 0, 0, 0, ErrUnsupportedMode
}
}
// RequiredGas calculates the gas required for ML-KEM operations
func (p *mlkemPrecompile) RequiredGas(input []byte) uint64 {
if len(input) < 2 {
return MLKEM768EncapsulateGas // Default for invalid input
}
op := input[0]
mode := input[1]
_, _, _, _, encapsGas, decapsGas, _, err := getModeParams(mode)
if err != nil {
return MLKEM768EncapsulateGas // Default for invalid mode
}
switch op {
case OpEncapsulate:
return encapsGas
case OpDecapsulate:
return decapsGas
default:
return MLKEM768EncapsulateGas
}
}
// Run implements the ML-KEM precompile
// Input format:
// [0] = operation byte (0x01 = encapsulate, 0x02 = decapsulate)
// [1] = mode byte (0x00 = 512, 0x01 = 768, 0x02 = 1024)
// [2:...] = operation-specific data
//
// Encapsulate input:
// [2:2+pubKeySize] = public key
//
// Encapsulate output:
// [0:ctSize] = ciphertext
// [ctSize:ctSize+32] = shared secret (32 bytes)
//
// Decapsulate input:
// [2:2+privKeySize] = private key
// [2+privKeySize:2+privKeySize+ctSize] = ciphertext
//
// Decapsulate output:
// [0:32] = shared secret (32 bytes)
func (p *mlkemPrecompile) Run(
accessibleState contract.AccessibleState,
caller common.Address,
addr common.Address,
input []byte,
suppliedGas uint64,
readOnly bool,
) ([]byte, uint64, error) {
// Calculate required gas
gasCost := p.RequiredGas(input)
if suppliedGas < gasCost {
return nil, 0, errors.New("out of gas")
}
// Minimum: op byte + mode byte
if len(input) < 2 {
return nil, suppliedGas - gasCost, ErrInvalidInputLength
}
op := input[0]
mode := input[1]
var result []byte
var err error
switch op {
case OpEncapsulate:
result, err = p.encapsulate(mode, input[2:])
case OpDecapsulate:
result, err = p.decapsulate(mode, input[2:])
default:
err = fmt.Errorf("%w: 0x%02x", ErrUnsupportedOperation, op)
}
if err != nil {
return nil, suppliedGas - gasCost, err
}
return result, suppliedGas - gasCost, nil
}
// encapsulate generates a shared secret and ciphertext from a public key
func (p *mlkemPrecompile) encapsulate(mode uint8, input []byte) ([]byte, error) {
pubKeySize, _, ctSize, sharedSize, _, _, mlkemMode, err := getModeParams(mode)
if err != nil {
return nil, err
}
// Validate input length
if len(input) != pubKeySize {
return nil, fmt.Errorf("%w: expected %d bytes for public key, got %d",
ErrInvalidInputLength, pubKeySize, len(input))
}
// Parse public key
pk, err := mlkem.PublicKeyFromBytes(input, mlkemMode)
if err != nil {
return nil, fmt.Errorf("invalid public key: %w", err)
}
// Encapsulate - generates ciphertext and shared secret
ciphertext, sharedSecret, err := pk.Encapsulate()
if err != nil {
return nil, fmt.Errorf("%w: %v", ErrEncapsulationFailed, err)
}
// Return ciphertext || sharedSecret
result := make([]byte, ctSize+sharedSize)
copy(result[:ctSize], ciphertext)
copy(result[ctSize:], sharedSecret)
return result, nil
}
// decapsulate recovers the shared secret from a ciphertext using a private key
func (p *mlkemPrecompile) decapsulate(mode uint8, input []byte) ([]byte, error) {
_, privKeySize, ctSize, _, _, _, mlkemMode, err := getModeParams(mode)
if err != nil {
return nil, err
}
// Validate input length
expectedLen := privKeySize + ctSize
if len(input) != expectedLen {
return nil, fmt.Errorf("%w: expected %d bytes (privKey=%d + ct=%d), got %d",
ErrInvalidInputLength, expectedLen, privKeySize, ctSize, len(input))
}
// Parse private key
privKeyBytes := input[:privKeySize]
ciphertext := input[privKeySize:]
sk, err := mlkem.PrivateKeyFromBytes(privKeyBytes, mlkemMode)
if err != nil {
return nil, fmt.Errorf("invalid private key: %w", err)
}
// Decapsulate - recovers shared secret
sharedSecret, err := sk.Decapsulate(ciphertext)
if err != nil {
return nil, fmt.Errorf("%w: %v", ErrDecapsulationFailed, err)
}
return sharedSecret, nil
}
+244
View File
@@ -0,0 +1,244 @@
// Copyright (C) 2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package mlkem
import (
"bytes"
"testing"
"github.com/luxfi/crypto/mlkem"
"github.com/luxfi/geth/common"
)
// mockAccessibleState implements contract.AccessibleState for testing
type mockAccessibleState struct{}
func (m *mockAccessibleState) GetStateDB() interface{} { return nil }
func (m *mockAccessibleState) GetBlockContext() interface{} { return nil }
func TestMLKEMPrecompileAddress(t *testing.T) {
expected := common.HexToAddress("0x0200000000000000000000000000000000000007")
if MLKEMPrecompile.Address() != expected {
t.Errorf("expected address %s, got %s", expected.Hex(), MLKEMPrecompile.Address().Hex())
}
}
func TestRequiredGas(t *testing.T) {
tests := []struct {
name string
input []byte
expected uint64
}{
{"empty input", []byte{}, MLKEM768EncapsulateGas},
{"only op byte", []byte{OpEncapsulate}, MLKEM768EncapsulateGas},
{"encapsulate 512", []byte{OpEncapsulate, ModeMLKEM512}, MLKEM512EncapsulateGas},
{"encapsulate 768", []byte{OpEncapsulate, ModeMLKEM768}, MLKEM768EncapsulateGas},
{"encapsulate 1024", []byte{OpEncapsulate, ModeMLKEM1024}, MLKEM1024EncapsulateGas},
{"decapsulate 512", []byte{OpDecapsulate, ModeMLKEM512}, MLKEM512DecapsulateGas},
{"decapsulate 768", []byte{OpDecapsulate, ModeMLKEM768}, MLKEM768DecapsulateGas},
{"decapsulate 1024", []byte{OpDecapsulate, ModeMLKEM1024}, MLKEM1024DecapsulateGas},
{"invalid mode", []byte{OpEncapsulate, 0xFF}, MLKEM768EncapsulateGas},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gas := MLKEMPrecompile.RequiredGas(tt.input)
if gas != tt.expected {
t.Errorf("expected gas %d, got %d", tt.expected, gas)
}
})
}
}
func TestEncapsulateDecapsulate(t *testing.T) {
modes := []struct {
name string
mode uint8
mlkemMode mlkem.Mode
}{
{"ML-KEM-512", ModeMLKEM512, mlkem.MLKEM512},
{"ML-KEM-768", ModeMLKEM768, mlkem.MLKEM768},
{"ML-KEM-1024", ModeMLKEM1024, mlkem.MLKEM1024},
}
for _, m := range modes {
t.Run(m.name, func(t *testing.T) {
// Generate key pair
pk, sk, err := mlkem.GenerateKey(m.mlkemMode)
if err != nil {
t.Fatalf("failed to generate key pair: %v", err)
}
// Build encapsulate input
encInput := make([]byte, 2+len(pk.Bytes()))
encInput[0] = OpEncapsulate
encInput[1] = m.mode
copy(encInput[2:], pk.Bytes())
// Run encapsulate
result, remainingGas, err := MLKEMPrecompile.Run(
nil, // accessibleState not used
common.Address{},
ContractAddress,
encInput,
1_000_000, // suppliedGas
false, // readOnly
)
if err != nil {
t.Fatalf("encapsulate failed: %v", err)
}
if remainingGas == 0 {
t.Error("expected remaining gas > 0")
}
// Parse result: ciphertext || sharedSecret
ctSize := mlkem.GetCiphertextSize(m.mlkemMode)
if len(result) != ctSize+32 {
t.Fatalf("expected result length %d, got %d", ctSize+32, len(result))
}
ciphertext := result[:ctSize]
sharedSecret1 := result[ctSize:]
// Build decapsulate input
decInput := make([]byte, 2+len(sk.Bytes())+len(ciphertext))
decInput[0] = OpDecapsulate
decInput[1] = m.mode
copy(decInput[2:], sk.Bytes())
copy(decInput[2+len(sk.Bytes()):], ciphertext)
// Run decapsulate
sharedSecret2, remainingGas, err := MLKEMPrecompile.Run(
nil,
common.Address{},
ContractAddress,
decInput,
1_000_000,
false,
)
if err != nil {
t.Fatalf("decapsulate failed: %v", err)
}
if remainingGas == 0 {
t.Error("expected remaining gas > 0")
}
// Verify shared secrets match
if !bytes.Equal(sharedSecret1, sharedSecret2) {
t.Error("shared secrets do not match")
}
})
}
}
func TestInvalidInputs(t *testing.T) {
tests := []struct {
name string
input []byte
}{
{"empty", []byte{}},
{"only op", []byte{OpEncapsulate}},
{"invalid op", []byte{0xFF, ModeMLKEM768}},
{"encapsulate no key", []byte{OpEncapsulate, ModeMLKEM768}},
{"encapsulate wrong size", []byte{OpEncapsulate, ModeMLKEM768, 0x01, 0x02, 0x03}},
{"decapsulate no data", []byte{OpDecapsulate, ModeMLKEM768}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, _, err := MLKEMPrecompile.Run(
nil,
common.Address{},
ContractAddress,
tt.input,
1_000_000,
false,
)
if err == nil {
t.Error("expected error for invalid input")
}
})
}
}
func TestOutOfGas(t *testing.T) {
// Generate a valid key for testing
pk, _, err := mlkem.GenerateKey(mlkem.MLKEM768)
if err != nil {
t.Fatalf("failed to generate key: %v", err)
}
input := make([]byte, 2+len(pk.Bytes()))
input[0] = OpEncapsulate
input[1] = ModeMLKEM768
copy(input[2:], pk.Bytes())
// Run with insufficient gas
_, _, err = MLKEMPrecompile.Run(
nil,
common.Address{},
ContractAddress,
input,
100, // Very low gas
false,
)
if err == nil || err.Error() != "out of gas" {
t.Errorf("expected 'out of gas' error, got: %v", err)
}
}
func BenchmarkEncapsulate(b *testing.B) {
modes := []struct {
name string
mode uint8
mlkemMode mlkem.Mode
}{
{"ML-KEM-512", ModeMLKEM512, mlkem.MLKEM512},
{"ML-KEM-768", ModeMLKEM768, mlkem.MLKEM768},
{"ML-KEM-1024", ModeMLKEM1024, mlkem.MLKEM1024},
}
for _, m := range modes {
pk, _, _ := mlkem.GenerateKey(m.mlkemMode)
input := make([]byte, 2+len(pk.Bytes()))
input[0] = OpEncapsulate
input[1] = m.mode
copy(input[2:], pk.Bytes())
b.Run(m.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
MLKEMPrecompile.Run(nil, common.Address{}, ContractAddress, input, 1_000_000, false)
}
})
}
}
func BenchmarkDecapsulate(b *testing.B) {
modes := []struct {
name string
mode uint8
mlkemMode mlkem.Mode
}{
{"ML-KEM-512", ModeMLKEM512, mlkem.MLKEM512},
{"ML-KEM-768", ModeMLKEM768, mlkem.MLKEM768},
{"ML-KEM-1024", ModeMLKEM1024, mlkem.MLKEM1024},
}
for _, m := range modes {
pk, sk, _ := mlkem.GenerateKey(m.mlkemMode)
ct, _, _ := pk.Encapsulate()
input := make([]byte, 2+len(sk.Bytes())+len(ct))
input[0] = OpDecapsulate
input[1] = m.mode
copy(input[2:], sk.Bytes())
copy(input[2+len(sk.Bytes()):], ct)
b.Run(m.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
MLKEMPrecompile.Run(nil, common.Address{}, ContractAddress, input, 1_000_000, false)
}
})
}
}
+40
View File
@@ -0,0 +1,40 @@
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package mlkem
import (
"github.com/luxfi/geth/common"
"github.com/luxfi/precompiles/contract"
)
var (
// Module is the precompile module singleton
Module = &module{
address: ContractAddress,
contract: MLKEMPrecompile,
}
)
type module struct {
address common.Address
contract contract.StatefulPrecompiledContract
}
// Address returns the address where the stateful precompile is accessible.
func (m *module) Address() common.Address {
return m.address
}
// Contract returns a thread-safe singleton that can be used as the StatefulPrecompiledContract
func (m *module) Contract() contract.StatefulPrecompiledContract {
return m.contract
}
// Configure is a no-op for ML-KEM as it has no configuration
func (m *module) Configure(
_ contract.StateDB,
_ common.Address,
) error {
return nil
}