Files
Hanzo AI 318e7cefca feat(addresses): canonicalize PQ precompiles to LP-4200 unified block
Per LP-4200 unified PQCrypto block:
- ML-KEM       0x012201 (was 0x0200...0007)
- ML-DSA       0x012202 (was 0x0200...0006)
- SLH-DSA      0x012203 (was 0x0600...0001)
- Corona     0x012204 (was 0x0200...000B)

Adds LP-12xxx range (0x012000-0x012FFF) to modules/registerer
whitelist so the new block validates as in-range.

LLM.md updated to mark legacy 0x0600 / 0x0800 ranges as superseded
by the unified block.

Solidity callers updated in luxfi/standard (#59).
Spec docs reconciled in lp-3500/lp-3520 (#61).
2026-05-01 01:26:30 -07:00

194 lines
4.7 KiB
Go

// Copyright (C) 2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package mlkem
import (
"testing"
"github.com/luxfi/crypto/mlkem"
"github.com/luxfi/geth/common"
)
func TestMLKEMPrecompileAddress(t *testing.T) {
expected := common.HexToAddress("0x0000000000000000000000000000000000012201")
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},
{"invalid mode", []byte{OpEncapsulate, 0xFF}, MLKEM768EncapsulateGas},
{"decapsulate rejected", []byte{0x02, ModeMLKEM768}, 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 TestEncapsulate(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) {
pk, _, err := mlkem.GenerateKey(m.mlkemMode)
if err != nil {
t.Fatalf("failed to generate key pair: %v", err)
}
encInput := make([]byte, 2+SeedSize+len(pk.Bytes()))
encInput[0] = OpEncapsulate
encInput[1] = m.mode
copy(encInput[2+SeedSize:], pk.Bytes())
result, remainingGas, err := MLKEMPrecompile.Run(
nil,
common.Address{},
ContractAddress,
encInput,
1_000_000,
false,
)
if err != nil {
t.Fatalf("encapsulate failed: %v", err)
}
if remainingGas == 0 {
t.Error("expected remaining gas > 0")
}
ctSize := mlkem.GetCiphertextSize(m.mlkemMode)
if len(result) != ctSize+32 {
t.Fatalf("expected result length %d, got %d", ctSize+32, len(result))
}
})
}
}
func TestDecapsulateRejected(t *testing.T) {
// Verify that the old OpDecapsulate (0x02) is rejected
pk, _, err := mlkem.GenerateKey(mlkem.MLKEM768)
if err != nil {
t.Fatalf("failed to generate key: %v", err)
}
input := make([]byte, 2+SeedSize+len(pk.Bytes()))
input[0] = 0x02 // Old OpDecapsulate
input[1] = ModeMLKEM768
copy(input[2+SeedSize:], pk.Bytes())
_, _, err = MLKEMPrecompile.Run(
nil,
common.Address{},
ContractAddress,
input,
1_000_000,
false,
)
if err == nil {
t.Error("expected error for decapsulate operation")
}
}
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}},
}
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) {
pk, _, err := mlkem.GenerateKey(mlkem.MLKEM768)
if err != nil {
t.Fatalf("failed to generate key: %v", err)
}
input := make([]byte, 2+SeedSize+len(pk.Bytes()))
input[0] = OpEncapsulate
input[1] = ModeMLKEM768
copy(input[2+SeedSize:], pk.Bytes())
_, _, err = MLKEMPrecompile.Run(
nil,
common.Address{},
ContractAddress,
input,
100,
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+SeedSize+len(pk.Bytes()))
input[0] = OpEncapsulate
input[1] = m.mode
copy(input[2+SeedSize:], 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)
}
})
}
}