mirror of
https://github.com/luxfi/precompile.git
synced 2026-09-24 00:26:21 +00:00
Forty-five of the fifty-six suite modules keyed off <name>Config and eleven did not, in three other spellings: Verify, Threshold, Encapsulate. The LP-4200 unified PQCrypto block carried four of those conventions across its eight addresses — two KEMs named differently (mlkemConfig beside hqcEncapsulate) and two threshold schemes named differently (pulsarVerify beside coronaThreshold) — so the block that exists to be one thing did not read as one thing. The deviations also named an OPERATION where a precompile belongs. HQC decapsulates as well as encapsulates and the Verify precompiles do more than verify, so hqcEncapsulate was the name of one entry point standing in for the whole module. A config key selects a precompile; it is not a method selector. All fifty-six are <name>Config now. There is no compatibility shim: an upgrade.json naming an old key is naming something that does not exist, which luxd already refuses loudly, and every chain config in the estate moves with this. Co-authored-by: Hanzo Dev <[email protected]>
ML-DSA Precompile (FIPS 204)
Post-quantum digital signature verification precompile for Lux EVM.
Overview
The ML-DSA (Module-Lattice-Based Digital Signature Algorithm) precompile provides quantum-resistant signature verification based on the ML-DSA algorithm and standardized in FIPS 204.
Precompile Address: 0x0200000000000000000000000000000000000006
Features
- Quantum Resistant: Secure against attacks from both classical and quantum computers
- NIST Standardized: Implements FIPS 204 (ML-DSA-65)
- High Performance: ~108μs verification time on Apple M1
- Security Level 3: Equivalent to AES-192 security
Specifications
ML-DSA-65 Parameters
| Parameter | Value |
|---|---|
| Public Key Size | 1952 bytes |
| Signature Size | 3309 bytes |
| Security Level | NIST Level 3 |
| Quantum Security | ~192 bits |
Gas Costs
| Operation | Cost |
|---|---|
| Base Cost | 100,000 gas |
| Per Message Byte | 10 gas |
Examples:
- Empty message: 100,000 gas
- 100-byte message: 101,000 gas
- 1KB message: 110,240 gas
- 10KB message: 202,400 gas
Input Format
The precompile expects a specific binary input format:
[Offset] [Size] [Description]
0 1952 ML-DSA-65 public key
1952 32 Message length (uint256, big-endian)
1984 3309 ML-DSA-65 signature
5293 variable Message bytes
Output Format
Returns a 32-byte word:
0x0000...0001- Signature is valid0x0000...0000- Signature is invalid
Usage
Solidity
// Interface
IMLDSA mldsa = IMLDSA(0x0200000000000000000000000000000000000006);
bool isValid = mldsa.verify(publicKey, message, signature);
require(isValid, "Invalid signature");
// Using library
using MLDSALib for bytes;
function verifyData(
bytes calldata publicKey,
bytes calldata data,
bytes calldata signature
) external view {
MLDSALib.verifyOrRevert(publicKey, data, signature);
// Signature is valid
}
// Gas estimation
uint256 estimatedGas = MLDSALib.estimateGas(message.length);
TypeScript/ethers.js
import { ethers } from 'ethers';
const MLDSA_ADDRESS = '0x0200000000000000000000000000000000000006';
// ABI for the verify function
const abi = [
'function verify(bytes calldata publicKey, bytes calldata message, bytes calldata signature) external view returns (bool)'
];
const mldsa = new ethers.Contract(MLDSA_ADDRESS, abi, provider);
// Verify signature
const isValid = await mldsa.verify(publicKey, message, signature);
console.log('Signature valid:', isValid);
Go
import (
"github.com/luxfi/node/crypto/mldsa"
"github.com/luxfi/evm/precompile/contracts/mldsa"
)
// Generate key pair
seed := make([]byte, 32)
sk, _ := mldsa.NewSigningKey(mldsa.ModeML_DSA_65, seed)
pk := sk.PublicKey()
// Sign message
message := []byte("Hello, quantum world!")
signature := sk.Sign(message, nil)
// Verify via precompile
input := prepareMLDSAInput(pk, message, signature)
result, gas, err := mldsa.MLDSAVerifyPrecompile.Run(
nil, addr, addr, input, 200000, false,
)
valid := result[31] == 1 // Check last byte
Performance Benchmarks
Measured on Apple M1 Max:
| Message Size | Verification Time | Gas Used |
|---|---|---|
| 18 bytes (small) | ~170μs | 100,180 |
| 10KB (large) | ~218μs | 202,400 |
Memory Usage:
- 33,314 bytes per operation
- 6 allocations per operation
Security Considerations
- Quantum Resistance: ML-DSA-65 provides Level 3 security (~192-bit quantum security)
- Side-Channel Protection: Implementation uses constant-time operations
- FIPS 204 Compliance: Follows NIST's standardized algorithm
- No Context Support: Current implementation doesn't support context strings (passes empty context)
Comparison with Other Algorithms
| Algorithm | Type | Signature Size | Verify Time | Quantum Secure |
|---|---|---|---|---|
| ECDSA (secp256k1) | Classical | 65 bytes | ~88μs | ❌ No |
| ML-DSA-65 | Post-Quantum | 3,309 bytes | ~108μs | ✅ Yes |
| SLH-DSA-128s | Post-Quantum | 7,856 bytes | ~4.2ms | ✅ Yes |
Migration Guide
From ECDSA to ML-DSA
// Old ECDSA verification
bytes32 hash = keccak256(message);
address signer = ecrecover(hash, v, r, s);
require(signer == expectedSigner, "Invalid signature");
// New ML-DSA verification
IMLDSA mldsa = IMLDSA(0x0200000000000000000000000000000000000006);
bool valid = mldsa.verify(mldsaPublicKey, message, mldsaSignature);
require(valid, "Invalid ML-DSA signature");
Testing
# Run unit tests
cd /Users/z/work/lux/evm/precompile/contracts/mldsa
go test -v
# Run benchmarks
go test -bench=. -benchmem
# Test with different message sizes
go test -v -run TestMLDSAVerify_LargeMessage
References
- FIPS 204: Module-Lattice-Based Digital Signature Standard
- ML-DSA Specification
- Cloudflare CIRCL Library
Related Precompiles
- SLH-DSA (
0x0200000000000000000000000000000000000007): Hash-based signatures - ML-KEM (
0x0200000000000000000000000000000000000008): Post-quantum key encapsulation - PQCrypto (
0x0200000000000000000000000000000000000005): General post-quantum operations
License
Copyright (C) 2025, Lux Industries, Inc. All rights reserved.