mirror of
https://github.com/luxfi/crypto.git
synced 2026-07-27 01:54:50 +00:00
fix: replace ML-KEM random stub with crypto/mlkem, complete precompile registration
The pure-Go ML-KEM fallback was filling keys/ciphertexts/secrets with rand.Read, silently producing incorrect results without CGO+liboqs. Replaced with Go 1.24+ crypto/mlkem (FIPS 203) for both 768 and 1024 parameter sets. Completed ML-DSA (0x0110-0x0118), ML-KEM (0x0120-0x0128), and SLH-DSA (0x0130-0x0138) precompile registration that was left as a comment.
This commit is contained in:
+1
-1
@@ -20,7 +20,7 @@ func NewHybridKEM() KEM {
|
||||
// In production, this should handle errors properly
|
||||
return &HybridKEMImpl{
|
||||
x25519: &X25519Impl{},
|
||||
mlkem: &MLKEM768Impl{k: 3},
|
||||
mlkem: &MLKEM768Impl{},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+89
-129
@@ -1,128 +1,94 @@
|
||||
package kem
|
||||
|
||||
// NOTE: This file contains pure Go implementations that are used when CGO is not available.
|
||||
// When CGO is enabled and liboqs is installed, the optimized versions in mlkem_cgo.go will be used instead.
|
||||
// Pure Go implementation of ML-KEM using crypto/mlkem from the Go standard library (FIPS 203).
|
||||
// When CGO is enabled and liboqs is installed, the optimized versions in mlkem_c.go are used instead.
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/mlkem"
|
||||
"crypto/subtle"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// MLKEM768 implements ML-KEM-768 (Kyber768)
|
||||
type MLKEM768Impl struct {
|
||||
k int // k parameter (3 for ML-KEM-768)
|
||||
}
|
||||
// MLKEM768Impl implements ML-KEM-768 using Go's crypto/mlkem (FIPS 203)
|
||||
type MLKEM768Impl struct{}
|
||||
|
||||
// MLKEM768PublicKey represents an ML-KEM-768 public key
|
||||
// MLKEM768PublicKey represents an ML-KEM-768 encapsulation key
|
||||
type MLKEM768PublicKey struct {
|
||||
data []byte
|
||||
}
|
||||
|
||||
// MLKEM768PrivateKey represents an ML-KEM-768 private key
|
||||
// MLKEM768PrivateKey represents an ML-KEM-768 decapsulation key (seed form)
|
||||
type MLKEM768PrivateKey struct {
|
||||
data []byte
|
||||
pk *MLKEM768PublicKey
|
||||
}
|
||||
|
||||
// GenerateKeyPair generates a new ML-KEM-768 key pair.
|
||||
// This is the pure-Go fallback; when CGO and liboqs are available,
|
||||
// mlkem_c.go provides the optimized implementation.
|
||||
// GenerateKeyPair generates a new ML-KEM-768 key pair using crypto/mlkem.
|
||||
func (m *MLKEM768Impl) GenerateKeyPair() (PublicKey, PrivateKey, error) {
|
||||
pk := &MLKEM768PublicKey{
|
||||
data: make([]byte, mlkem768PublicKeySize),
|
||||
}
|
||||
sk := &MLKEM768PrivateKey{
|
||||
data: make([]byte, mlkem768PrivateKeySize),
|
||||
pk: pk,
|
||||
}
|
||||
|
||||
// Generate random key material for the pure-Go fallback.
|
||||
if _, err := rand.Read(pk.data); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if _, err := rand.Read(sk.data); err != nil {
|
||||
dk, err := mlkem.GenerateKey768()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
ekBytes := dk.EncapsulationKey().Bytes()
|
||||
dkBytes := dk.Bytes()
|
||||
|
||||
pk := &MLKEM768PublicKey{data: ekBytes}
|
||||
sk := &MLKEM768PrivateKey{data: dkBytes, pk: pk}
|
||||
return pk, sk, nil
|
||||
}
|
||||
|
||||
// Encapsulate generates a shared secret and ciphertext
|
||||
// Encapsulate generates a shared secret and ciphertext from an encapsulation key.
|
||||
func (m *MLKEM768Impl) Encapsulate(pk PublicKey) ([]byte, []byte, error) {
|
||||
mlkemPK, ok := pk.(*MLKEM768PublicKey)
|
||||
if !ok {
|
||||
return nil, nil, errors.New("invalid public key type")
|
||||
}
|
||||
|
||||
ciphertext := make([]byte, mlkem768CiphertextSize)
|
||||
sharedSecret := make([]byte, mlkem768SharedSecretSize)
|
||||
|
||||
// Pure-Go fallback: randomized output. CGO+liboqs provides real KEM.
|
||||
if _, err := rand.Read(ciphertext); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if _, err := rand.Read(sharedSecret); err != nil {
|
||||
ek, err := mlkem.NewEncapsulationKey768(mlkemPK.data)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// In production, this would perform actual ML-KEM encapsulation
|
||||
_ = mlkemPK.data
|
||||
|
||||
sharedSecret, ciphertext := ek.Encapsulate()
|
||||
return ciphertext, sharedSecret, nil
|
||||
}
|
||||
|
||||
// Decapsulate recovers the shared secret from ciphertext
|
||||
// Decapsulate recovers the shared secret from ciphertext using the decapsulation key.
|
||||
func (m *MLKEM768Impl) Decapsulate(sk PrivateKey, ciphertext []byte) ([]byte, error) {
|
||||
mlkemSK, ok := sk.(*MLKEM768PrivateKey)
|
||||
if !ok {
|
||||
return nil, errors.New("invalid private key type")
|
||||
}
|
||||
|
||||
if len(ciphertext) != mlkem768CiphertextSize {
|
||||
if len(ciphertext) != mlkem.CiphertextSize768 {
|
||||
return nil, errors.New("invalid ciphertext size")
|
||||
}
|
||||
|
||||
sharedSecret := make([]byte, mlkem768SharedSecretSize)
|
||||
|
||||
// Pure-Go fallback: randomized output. CGO+liboqs provides real KEM.
|
||||
if _, err := rand.Read(sharedSecret); err != nil {
|
||||
dk, err := mlkem.NewDecapsulationKey768(mlkemSK.data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// In production, this would perform actual ML-KEM decapsulation
|
||||
_ = mlkemSK.data
|
||||
_ = ciphertext
|
||||
|
||||
return sharedSecret, nil
|
||||
return dk.Decapsulate(ciphertext)
|
||||
}
|
||||
|
||||
// PublicKeySize returns the size of public keys
|
||||
func (m *MLKEM768Impl) PublicKeySize() int {
|
||||
return mlkem768PublicKeySize
|
||||
}
|
||||
// PublicKeySize returns the size of ML-KEM-768 encapsulation keys.
|
||||
func (m *MLKEM768Impl) PublicKeySize() int { return mlkem.EncapsulationKeySize768 }
|
||||
|
||||
// PrivateKeySize returns the size of private keys
|
||||
func (m *MLKEM768Impl) PrivateKeySize() int {
|
||||
return mlkem768PrivateKeySize
|
||||
}
|
||||
// PrivateKeySize returns the size of ML-KEM-768 decapsulation key seeds.
|
||||
func (m *MLKEM768Impl) PrivateKeySize() int { return mlkem.SeedSize }
|
||||
|
||||
// CiphertextSize returns the size of ciphertexts
|
||||
func (m *MLKEM768Impl) CiphertextSize() int {
|
||||
return mlkem768CiphertextSize
|
||||
}
|
||||
// CiphertextSize returns the size of ML-KEM-768 ciphertexts.
|
||||
func (m *MLKEM768Impl) CiphertextSize() int { return mlkem.CiphertextSize768 }
|
||||
|
||||
// SharedSecretSize returns the size of shared secrets
|
||||
func (m *MLKEM768Impl) SharedSecretSize() int {
|
||||
return mlkem768SharedSecretSize
|
||||
}
|
||||
// SharedSecretSize returns the size of ML-KEM-768 shared secrets.
|
||||
func (m *MLKEM768Impl) SharedSecretSize() int { return mlkem.SharedKeySize }
|
||||
|
||||
// Bytes returns the raw bytes of the public key
|
||||
func (pk *MLKEM768PublicKey) Bytes() []byte {
|
||||
return pk.data
|
||||
}
|
||||
// Bytes returns the raw bytes of the public key.
|
||||
func (pk *MLKEM768PublicKey) Bytes() []byte { return pk.data }
|
||||
|
||||
// Equal checks if two public keys are equal
|
||||
// Equal checks if two public keys are equal in constant time.
|
||||
func (pk *MLKEM768PublicKey) Equal(other PublicKey) bool {
|
||||
otherPK, ok := other.(*MLKEM768PublicKey)
|
||||
if !ok {
|
||||
@@ -131,17 +97,13 @@ func (pk *MLKEM768PublicKey) Equal(other PublicKey) bool {
|
||||
return subtle.ConstantTimeCompare(pk.data, otherPK.data) == 1
|
||||
}
|
||||
|
||||
// Bytes returns the raw bytes of the private key
|
||||
func (sk *MLKEM768PrivateKey) Bytes() []byte {
|
||||
return sk.data
|
||||
}
|
||||
// Bytes returns the raw bytes of the private key seed.
|
||||
func (sk *MLKEM768PrivateKey) Bytes() []byte { return sk.data }
|
||||
|
||||
// Public returns the public key corresponding to the private key
|
||||
func (sk *MLKEM768PrivateKey) Public() PublicKey {
|
||||
return sk.pk
|
||||
}
|
||||
// Public returns the public key corresponding to the private key.
|
||||
func (sk *MLKEM768PrivateKey) Public() PublicKey { return sk.pk }
|
||||
|
||||
// Equal checks if two private keys are equal
|
||||
// Equal checks if two private keys are equal in constant time.
|
||||
func (sk *MLKEM768PrivateKey) Equal(other PrivateKey) bool {
|
||||
otherSK, ok := other.(*MLKEM768PrivateKey)
|
||||
if !ok {
|
||||
@@ -150,84 +112,86 @@ func (sk *MLKEM768PrivateKey) Equal(other PrivateKey) bool {
|
||||
return subtle.ConstantTimeCompare(sk.data, otherSK.data) == 1
|
||||
}
|
||||
|
||||
// MLKEM1024 implementation (similar structure, different parameters)
|
||||
type MLKEM1024Impl struct {
|
||||
k int // k parameter (4 for ML-KEM-1024)
|
||||
}
|
||||
// MLKEM1024Impl implements ML-KEM-1024 using Go's crypto/mlkem (FIPS 203)
|
||||
type MLKEM1024Impl struct{}
|
||||
|
||||
// MLKEM1024PublicKey represents an ML-KEM-1024 public key
|
||||
// MLKEM1024PublicKey represents an ML-KEM-1024 encapsulation key
|
||||
type MLKEM1024PublicKey struct {
|
||||
data []byte
|
||||
}
|
||||
|
||||
// MLKEM1024PrivateKey represents an ML-KEM-1024 private key
|
||||
// MLKEM1024PrivateKey represents an ML-KEM-1024 decapsulation key (seed form)
|
||||
type MLKEM1024PrivateKey struct {
|
||||
data []byte
|
||||
pk *MLKEM1024PublicKey
|
||||
}
|
||||
|
||||
// GenerateKeyPair generates a new ML-KEM-1024 key pair
|
||||
// GenerateKeyPair generates a new ML-KEM-1024 key pair using crypto/mlkem.
|
||||
func (m *MLKEM1024Impl) GenerateKeyPair() (PublicKey, PrivateKey, error) {
|
||||
// Similar to ML-KEM-768 but with different sizes
|
||||
pk := &MLKEM1024PublicKey{
|
||||
data: make([]byte, mlkem1024PublicKeySize),
|
||||
}
|
||||
sk := &MLKEM1024PrivateKey{
|
||||
data: make([]byte, mlkem1024PrivateKeySize),
|
||||
pk: pk,
|
||||
}
|
||||
|
||||
if _, err := rand.Read(pk.data); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if _, err := rand.Read(sk.data); err != nil {
|
||||
dk, err := mlkem.GenerateKey1024()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
ekBytes := dk.EncapsulationKey().Bytes()
|
||||
dkBytes := dk.Bytes()
|
||||
|
||||
pk := &MLKEM1024PublicKey{data: ekBytes}
|
||||
sk := &MLKEM1024PrivateKey{data: dkBytes, pk: pk}
|
||||
return pk, sk, nil
|
||||
}
|
||||
|
||||
// Encapsulate for ML-KEM-1024
|
||||
// Encapsulate generates a shared secret and ciphertext from an encapsulation key.
|
||||
func (m *MLKEM1024Impl) Encapsulate(pk PublicKey) ([]byte, []byte, error) {
|
||||
ciphertext := make([]byte, mlkem1024CiphertextSize)
|
||||
sharedSecret := make([]byte, mlkem1024SharedSecretSize)
|
||||
|
||||
if _, err := rand.Read(ciphertext); err != nil {
|
||||
return nil, nil, err
|
||||
mlkemPK, ok := pk.(*MLKEM1024PublicKey)
|
||||
if !ok {
|
||||
return nil, nil, errors.New("invalid public key type")
|
||||
}
|
||||
if _, err := rand.Read(sharedSecret); err != nil {
|
||||
|
||||
ek, err := mlkem.NewEncapsulationKey1024(mlkemPK.data)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
sharedSecret, ciphertext := ek.Encapsulate()
|
||||
return ciphertext, sharedSecret, nil
|
||||
}
|
||||
|
||||
// Decapsulate for ML-KEM-1024
|
||||
// Decapsulate recovers the shared secret from ciphertext using the decapsulation key.
|
||||
func (m *MLKEM1024Impl) Decapsulate(sk PrivateKey, ciphertext []byte) ([]byte, error) {
|
||||
if len(ciphertext) != mlkem1024CiphertextSize {
|
||||
mlkemSK, ok := sk.(*MLKEM1024PrivateKey)
|
||||
if !ok {
|
||||
return nil, errors.New("invalid private key type")
|
||||
}
|
||||
|
||||
if len(ciphertext) != mlkem.CiphertextSize1024 {
|
||||
return nil, errors.New("invalid ciphertext size")
|
||||
}
|
||||
|
||||
sharedSecret := make([]byte, mlkem1024SharedSecretSize)
|
||||
if _, err := rand.Read(sharedSecret); err != nil {
|
||||
dk, err := mlkem.NewDecapsulationKey1024(mlkemSK.data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return sharedSecret, nil
|
||||
return dk.Decapsulate(ciphertext)
|
||||
}
|
||||
|
||||
// Size methods for ML-KEM-1024
|
||||
func (m *MLKEM1024Impl) PublicKeySize() int { return mlkem1024PublicKeySize }
|
||||
func (m *MLKEM1024Impl) PrivateKeySize() int { return mlkem1024PrivateKeySize }
|
||||
func (m *MLKEM1024Impl) CiphertextSize() int { return mlkem1024CiphertextSize }
|
||||
func (m *MLKEM1024Impl) SharedSecretSize() int { return mlkem1024SharedSecretSize }
|
||||
// PublicKeySize returns the size of ML-KEM-1024 encapsulation keys.
|
||||
func (m *MLKEM1024Impl) PublicKeySize() int { return mlkem.EncapsulationKeySize1024 }
|
||||
|
||||
// Bytes returns the raw bytes of the public key
|
||||
func (pk *MLKEM1024PublicKey) Bytes() []byte {
|
||||
return pk.data
|
||||
}
|
||||
// PrivateKeySize returns the size of ML-KEM-1024 decapsulation key seeds.
|
||||
func (m *MLKEM1024Impl) PrivateKeySize() int { return mlkem.SeedSize }
|
||||
|
||||
// Equal checks if two public keys are equal
|
||||
// CiphertextSize returns the size of ML-KEM-1024 ciphertexts.
|
||||
func (m *MLKEM1024Impl) CiphertextSize() int { return mlkem.CiphertextSize1024 }
|
||||
|
||||
// SharedSecretSize returns the size of ML-KEM-1024 shared secrets.
|
||||
func (m *MLKEM1024Impl) SharedSecretSize() int { return mlkem.SharedKeySize }
|
||||
|
||||
// Bytes returns the raw bytes of the public key.
|
||||
func (pk *MLKEM1024PublicKey) Bytes() []byte { return pk.data }
|
||||
|
||||
// Equal checks if two public keys are equal in constant time.
|
||||
func (pk *MLKEM1024PublicKey) Equal(other PublicKey) bool {
|
||||
otherPK, ok := other.(*MLKEM1024PublicKey)
|
||||
if !ok {
|
||||
@@ -236,17 +200,13 @@ func (pk *MLKEM1024PublicKey) Equal(other PublicKey) bool {
|
||||
return subtle.ConstantTimeCompare(pk.data, otherPK.data) == 1
|
||||
}
|
||||
|
||||
// Bytes returns the raw bytes of the private key
|
||||
func (sk *MLKEM1024PrivateKey) Bytes() []byte {
|
||||
return sk.data
|
||||
}
|
||||
// Bytes returns the raw bytes of the private key seed.
|
||||
func (sk *MLKEM1024PrivateKey) Bytes() []byte { return sk.data }
|
||||
|
||||
// Public returns the public key corresponding to the private key
|
||||
func (sk *MLKEM1024PrivateKey) Public() PublicKey {
|
||||
return sk.pk
|
||||
}
|
||||
// Public returns the public key corresponding to the private key.
|
||||
func (sk *MLKEM1024PrivateKey) Public() PublicKey { return sk.pk }
|
||||
|
||||
// Equal checks if two private keys are equal
|
||||
// Equal checks if two private keys are equal in constant time.
|
||||
func (sk *MLKEM1024PrivateKey) Equal(other PrivateKey) bool {
|
||||
otherSK, ok := other.(*MLKEM1024PrivateKey)
|
||||
if !ok {
|
||||
|
||||
@@ -16,7 +16,9 @@ func GetAllPostQuantumPrecompiles() map[Address]PrecompiledContract {
|
||||
// Register all precompile types
|
||||
RegisterSHAKE(registry)
|
||||
RegisterLamport(registry)
|
||||
// ML-DSA, ML-KEM, SLH-DSA would be registered here when moved to this package
|
||||
RegisterMLDSA(registry)
|
||||
RegisterMLKEM(registry)
|
||||
RegisterSLHDSA(registry)
|
||||
|
||||
return registry.Contracts()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
||||
// ML-DSA (FIPS 204) precompiled contracts for EVM
|
||||
// Provides on-chain post-quantum signature verification
|
||||
|
||||
package precompile
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
|
||||
"github.com/luxfi/crypto/mldsa"
|
||||
)
|
||||
|
||||
// ML-DSA precompile addresses (0x0110-0x0119)
|
||||
var (
|
||||
MLDSA44VerifyAddress = HexToAddress("0x0000000000000000000000000000000000000110")
|
||||
MLDSA65VerifyAddress = HexToAddress("0x0000000000000000000000000000000000000111")
|
||||
MLDSA87VerifyAddress = HexToAddress("0x0000000000000000000000000000000000000112")
|
||||
MLDSA44SignAddress = HexToAddress("0x0000000000000000000000000000000000000113")
|
||||
MLDSA65SignAddress = HexToAddress("0x0000000000000000000000000000000000000114")
|
||||
MLDSA87SignAddress = HexToAddress("0x0000000000000000000000000000000000000115")
|
||||
MLDSA44KeyGenAddress = HexToAddress("0x0000000000000000000000000000000000000116")
|
||||
MLDSA65KeyGenAddress = HexToAddress("0x0000000000000000000000000000000000000117")
|
||||
MLDSA87KeyGenAddress = HexToAddress("0x0000000000000000000000000000000000000118")
|
||||
)
|
||||
|
||||
// Gas costs for ML-DSA operations
|
||||
const (
|
||||
mldsa44VerifyGas = 120000
|
||||
mldsa65VerifyGas = 180000
|
||||
mldsa87VerifyGas = 250000
|
||||
mldsa44SignGas = 200000
|
||||
mldsa65SignGas = 300000
|
||||
mldsa87SignGas = 400000
|
||||
mldsaKeyGenGas = 500000
|
||||
)
|
||||
|
||||
// mldsaVerify implements ML-DSA signature verification for a given mode.
|
||||
type mldsaVerify struct {
|
||||
mode mldsa.Mode
|
||||
gas uint64
|
||||
}
|
||||
|
||||
func (v *mldsaVerify) RequiredGas(input []byte) uint64 { return v.gas }
|
||||
|
||||
func (v *mldsaVerify) Run(input []byte) ([]byte, error) {
|
||||
// Input: [public_key][signature][message]
|
||||
pubKeySize := mldsa.GetPublicKeySize(v.mode)
|
||||
sigSize := mldsa.GetSignatureSize(v.mode)
|
||||
|
||||
if len(input) < pubKeySize+sigSize+1 {
|
||||
return nil, errors.New("input too short")
|
||||
}
|
||||
|
||||
pubKeyBytes := input[:pubKeySize]
|
||||
sigBytes := input[pubKeySize : pubKeySize+sigSize]
|
||||
message := input[pubKeySize+sigSize:]
|
||||
|
||||
pubKey, err := mldsa.PublicKeyFromBytes(pubKeyBytes, v.mode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
valid := pubKey.VerifySignature(message, sigBytes)
|
||||
|
||||
result := make([]byte, 32)
|
||||
if valid {
|
||||
result[31] = 0x01
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// mldsaSign implements ML-DSA signing for a given mode.
|
||||
type mldsaSign struct {
|
||||
mode mldsa.Mode
|
||||
gas uint64
|
||||
}
|
||||
|
||||
func (s *mldsaSign) RequiredGas(input []byte) uint64 { return s.gas }
|
||||
|
||||
func (s *mldsaSign) Run(input []byte) ([]byte, error) {
|
||||
// Input: [private_key][message]
|
||||
privKeySize := 0
|
||||
switch s.mode {
|
||||
case mldsa.MLDSA44:
|
||||
privKeySize = mldsa.MLDSA44PrivateKeySize
|
||||
case mldsa.MLDSA65:
|
||||
privKeySize = mldsa.MLDSA65PrivateKeySize
|
||||
case mldsa.MLDSA87:
|
||||
privKeySize = mldsa.MLDSA87PrivateKeySize
|
||||
default:
|
||||
return nil, errors.New("invalid ML-DSA mode")
|
||||
}
|
||||
|
||||
if len(input) < privKeySize+1 {
|
||||
return nil, errors.New("input too short")
|
||||
}
|
||||
|
||||
privKeyBytes := input[:privKeySize]
|
||||
message := input[privKeySize:]
|
||||
|
||||
privKey, err := mldsa.PrivateKeyFromBytes(s.mode, privKeyBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return privKey.Sign(rand.Reader, message, nil)
|
||||
}
|
||||
|
||||
// mldsaKeyGen implements ML-DSA key pair generation for a given mode.
|
||||
type mldsaKeyGen struct {
|
||||
mode mldsa.Mode
|
||||
}
|
||||
|
||||
func (k *mldsaKeyGen) RequiredGas(input []byte) uint64 { return mldsaKeyGenGas }
|
||||
|
||||
func (k *mldsaKeyGen) Run(input []byte) ([]byte, error) {
|
||||
privKey, err := mldsa.GenerateKey(rand.Reader, k.mode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pubBytes := privKey.PublicKey.Bytes()
|
||||
privBytes := privKey.Bytes()
|
||||
|
||||
// Output: [public_key][private_key]
|
||||
result := make([]byte, len(pubBytes)+len(privBytes))
|
||||
copy(result, pubBytes)
|
||||
copy(result[len(pubBytes):], privBytes)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// RegisterMLDSA registers all ML-DSA precompiles.
|
||||
func RegisterMLDSA(registry *Registry) {
|
||||
registry.Register(MLDSA44VerifyAddress, &mldsaVerify{mode: mldsa.MLDSA44, gas: mldsa44VerifyGas})
|
||||
registry.Register(MLDSA65VerifyAddress, &mldsaVerify{mode: mldsa.MLDSA65, gas: mldsa65VerifyGas})
|
||||
registry.Register(MLDSA87VerifyAddress, &mldsaVerify{mode: mldsa.MLDSA87, gas: mldsa87VerifyGas})
|
||||
registry.Register(MLDSA44SignAddress, &mldsaSign{mode: mldsa.MLDSA44, gas: mldsa44SignGas})
|
||||
registry.Register(MLDSA65SignAddress, &mldsaSign{mode: mldsa.MLDSA65, gas: mldsa65SignGas})
|
||||
registry.Register(MLDSA87SignAddress, &mldsaSign{mode: mldsa.MLDSA87, gas: mldsa87SignGas})
|
||||
registry.Register(MLDSA44KeyGenAddress, &mldsaKeyGen{mode: mldsa.MLDSA44})
|
||||
registry.Register(MLDSA65KeyGenAddress, &mldsaKeyGen{mode: mldsa.MLDSA65})
|
||||
registry.Register(MLDSA87KeyGenAddress, &mldsaKeyGen{mode: mldsa.MLDSA87})
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterMLDSA(PostQuantumRegistry)
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
||||
// ML-KEM (FIPS 203) precompiled contracts for EVM
|
||||
// Provides on-chain post-quantum key encapsulation
|
||||
|
||||
package precompile
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/luxfi/crypto/mlkem"
|
||||
)
|
||||
|
||||
// ML-KEM precompile addresses (0x0120-0x0129)
|
||||
var (
|
||||
MLKEM512EncapsulateAddress = HexToAddress("0x0000000000000000000000000000000000000120")
|
||||
MLKEM768EncapsulateAddress = HexToAddress("0x0000000000000000000000000000000000000121")
|
||||
MLKEM1024EncapsulateAddress = HexToAddress("0x0000000000000000000000000000000000000122")
|
||||
MLKEM512DecapsulateAddress = HexToAddress("0x0000000000000000000000000000000000000123")
|
||||
MLKEM768DecapsulateAddress = HexToAddress("0x0000000000000000000000000000000000000124")
|
||||
MLKEM1024DecapsulateAddress = HexToAddress("0x0000000000000000000000000000000000000125")
|
||||
MLKEM512KeyGenAddress = HexToAddress("0x0000000000000000000000000000000000000126")
|
||||
MLKEM768KeyGenAddress = HexToAddress("0x0000000000000000000000000000000000000127")
|
||||
MLKEM1024KeyGenAddress = HexToAddress("0x0000000000000000000000000000000000000128")
|
||||
)
|
||||
|
||||
// Gas costs for ML-KEM operations
|
||||
const (
|
||||
mlkem512EncapsulateGas = 80000
|
||||
mlkem768EncapsulateGas = 100000
|
||||
mlkem1024EncapsulateGas = 130000
|
||||
mlkem512DecapsulateGas = 80000
|
||||
mlkem768DecapsulateGas = 100000
|
||||
mlkem1024DecapsulateGas = 130000
|
||||
mlkemKeyGenGas = 200000
|
||||
)
|
||||
|
||||
// mlkemEncapsulate implements ML-KEM encapsulation for a given mode.
|
||||
type mlkemEncapsulate struct {
|
||||
mode mlkem.Mode
|
||||
gas uint64
|
||||
}
|
||||
|
||||
func (e *mlkemEncapsulate) RequiredGas(input []byte) uint64 { return e.gas }
|
||||
|
||||
func (e *mlkemEncapsulate) Run(input []byte) ([]byte, error) {
|
||||
// Input: [encapsulation_key]
|
||||
expectedSize := mlkem.GetPublicKeySize(e.mode)
|
||||
if len(input) != expectedSize {
|
||||
return nil, errors.New("invalid encapsulation key size")
|
||||
}
|
||||
|
||||
pk, err := mlkem.PublicKeyFromBytes(input, e.mode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ciphertext, sharedKey, err := pk.Encapsulate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Output: [shared_key (32)][ciphertext]
|
||||
result := make([]byte, len(sharedKey)+len(ciphertext))
|
||||
copy(result, sharedKey)
|
||||
copy(result[len(sharedKey):], ciphertext)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// mlkemDecapsulate implements ML-KEM decapsulation for a given mode.
|
||||
type mlkemDecapsulate struct {
|
||||
mode mlkem.Mode
|
||||
gas uint64
|
||||
}
|
||||
|
||||
func (d *mlkemDecapsulate) RequiredGas(input []byte) uint64 { return d.gas }
|
||||
|
||||
func (d *mlkemDecapsulate) Run(input []byte) ([]byte, error) {
|
||||
// Input: [private_key][ciphertext]
|
||||
privKeySize := mlkem.GetPrivateKeySize(d.mode)
|
||||
ctSize := mlkem.GetCiphertextSize(d.mode)
|
||||
|
||||
if len(input) != privKeySize+ctSize {
|
||||
return nil, errors.New("invalid input size")
|
||||
}
|
||||
|
||||
privKeyBytes := input[:privKeySize]
|
||||
ciphertext := input[privKeySize:]
|
||||
|
||||
sk, err := mlkem.PrivateKeyFromBytes(privKeyBytes, d.mode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return sk.Decapsulate(ciphertext)
|
||||
}
|
||||
|
||||
// mlkemKeyGen implements ML-KEM key pair generation for a given mode.
|
||||
type mlkemKeyGen struct {
|
||||
mode mlkem.Mode
|
||||
}
|
||||
|
||||
func (k *mlkemKeyGen) RequiredGas(input []byte) uint64 { return mlkemKeyGenGas }
|
||||
|
||||
func (k *mlkemKeyGen) Run(input []byte) ([]byte, error) {
|
||||
pk, sk, err := mlkem.GenerateKey(k.mode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pubBytes := pk.Bytes()
|
||||
privBytes := sk.Bytes()
|
||||
|
||||
// Output: [public_key][private_key]
|
||||
result := make([]byte, len(pubBytes)+len(privBytes))
|
||||
copy(result, pubBytes)
|
||||
copy(result[len(pubBytes):], privBytes)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// RegisterMLKEM registers all ML-KEM precompiles.
|
||||
func RegisterMLKEM(registry *Registry) {
|
||||
registry.Register(MLKEM512EncapsulateAddress, &mlkemEncapsulate{mode: mlkem.MLKEM512, gas: mlkem512EncapsulateGas})
|
||||
registry.Register(MLKEM768EncapsulateAddress, &mlkemEncapsulate{mode: mlkem.MLKEM768, gas: mlkem768EncapsulateGas})
|
||||
registry.Register(MLKEM1024EncapsulateAddress, &mlkemEncapsulate{mode: mlkem.MLKEM1024, gas: mlkem1024EncapsulateGas})
|
||||
registry.Register(MLKEM512DecapsulateAddress, &mlkemDecapsulate{mode: mlkem.MLKEM512, gas: mlkem512DecapsulateGas})
|
||||
registry.Register(MLKEM768DecapsulateAddress, &mlkemDecapsulate{mode: mlkem.MLKEM768, gas: mlkem768DecapsulateGas})
|
||||
registry.Register(MLKEM1024DecapsulateAddress, &mlkemDecapsulate{mode: mlkem.MLKEM1024, gas: mlkem1024DecapsulateGas})
|
||||
registry.Register(MLKEM512KeyGenAddress, &mlkemKeyGen{mode: mlkem.MLKEM512})
|
||||
registry.Register(MLKEM768KeyGenAddress, &mlkemKeyGen{mode: mlkem.MLKEM768})
|
||||
registry.Register(MLKEM1024KeyGenAddress, &mlkemKeyGen{mode: mlkem.MLKEM1024})
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterMLKEM(PostQuantumRegistry)
|
||||
}
|
||||
@@ -647,6 +647,12 @@ func TestPrecompileRegistry(t *testing.T) {
|
||||
t.Run("AllPrecompilesRegistered", func(t *testing.T) {
|
||||
// Check that all expected addresses are registered
|
||||
expectedAddresses := []string{
|
||||
// ML-DSA (FIPS 204)
|
||||
"0x0110", "0x0111", "0x0112", "0x0113", "0x0114", "0x0115", "0x0116", "0x0117", "0x0118",
|
||||
// ML-KEM (FIPS 203)
|
||||
"0x0120", "0x0121", "0x0122", "0x0123", "0x0124", "0x0125", "0x0126", "0x0127", "0x0128",
|
||||
// SLH-DSA (FIPS 205)
|
||||
"0x0130", "0x0131", "0x0132", "0x0133", "0x0134", "0x0135", "0x0136", "0x0137", "0x0138",
|
||||
// SHAKE
|
||||
"0x0140", "0x0141", "0x0142", "0x0143", "0x0144", "0x0145", "0x0146", "0x0147", "0x0148",
|
||||
// Lamport
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
||||
// SLH-DSA (FIPS 205) precompiled contracts for EVM
|
||||
// Provides on-chain post-quantum stateless hash-based signature verification
|
||||
|
||||
package precompile
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
|
||||
"github.com/luxfi/crypto/slhdsa"
|
||||
)
|
||||
|
||||
// SLH-DSA precompile addresses (0x0130-0x0139)
|
||||
var (
|
||||
SLHDSA128sVerifyAddress = HexToAddress("0x0000000000000000000000000000000000000130")
|
||||
SLHDSA128fVerifyAddress = HexToAddress("0x0000000000000000000000000000000000000131")
|
||||
SLHDSA192sVerifyAddress = HexToAddress("0x0000000000000000000000000000000000000132")
|
||||
SLHDSA192fVerifyAddress = HexToAddress("0x0000000000000000000000000000000000000133")
|
||||
SLHDSA256sVerifyAddress = HexToAddress("0x0000000000000000000000000000000000000134")
|
||||
SLHDSA256fVerifyAddress = HexToAddress("0x0000000000000000000000000000000000000135")
|
||||
SLHDSA128sSignAddress = HexToAddress("0x0000000000000000000000000000000000000136")
|
||||
SLHDSA192sSignAddress = HexToAddress("0x0000000000000000000000000000000000000137")
|
||||
SLHDSA256sSignAddress = HexToAddress("0x0000000000000000000000000000000000000138")
|
||||
)
|
||||
|
||||
// Gas costs for SLH-DSA operations
|
||||
const (
|
||||
slhdsa128sVerifyGas = 300000
|
||||
slhdsa128fVerifyGas = 200000
|
||||
slhdsa192sVerifyGas = 400000
|
||||
slhdsa192fVerifyGas = 300000
|
||||
slhdsa256sVerifyGas = 500000
|
||||
slhdsa256fVerifyGas = 400000
|
||||
slhdsaSignGas = 1000000
|
||||
)
|
||||
|
||||
// slhdsaVerify implements SLH-DSA signature verification for a given mode.
|
||||
type slhdsaVerify struct {
|
||||
mode slhdsa.Mode
|
||||
gas uint64
|
||||
}
|
||||
|
||||
func (v *slhdsaVerify) RequiredGas(input []byte) uint64 { return v.gas }
|
||||
|
||||
func (v *slhdsaVerify) Run(input []byte) ([]byte, error) {
|
||||
// Input: [public_key][signature][message]
|
||||
pubKeySize := slhdsa.GetPublicKeySize(v.mode)
|
||||
sigSize := slhdsa.GetSignatureSize(v.mode)
|
||||
|
||||
if len(input) < pubKeySize+sigSize+1 {
|
||||
return nil, errors.New("input too short")
|
||||
}
|
||||
|
||||
pubKeyBytes := input[:pubKeySize]
|
||||
sigBytes := input[pubKeySize : pubKeySize+sigSize]
|
||||
message := input[pubKeySize+sigSize:]
|
||||
|
||||
pubKey, err := slhdsa.PublicKeyFromBytes(pubKeyBytes, v.mode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
valid := pubKey.VerifySignature(message, sigBytes)
|
||||
|
||||
result := make([]byte, 32)
|
||||
if valid {
|
||||
result[31] = 0x01
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// slhdsaSign implements SLH-DSA signing for a given mode.
|
||||
type slhdsaSign struct {
|
||||
mode slhdsa.Mode
|
||||
gas uint64
|
||||
}
|
||||
|
||||
func (s *slhdsaSign) RequiredGas(input []byte) uint64 { return s.gas }
|
||||
|
||||
func (s *slhdsaSign) Run(input []byte) ([]byte, error) {
|
||||
// Input: [private_key_bytes][message]
|
||||
// SLH-DSA private keys are mode-dependent in size; we need the full
|
||||
// serialized private key as produced by PrivateKey.Bytes().
|
||||
// Since we don't have a PrivateKeyFromBytes that detects size, callers
|
||||
// must provide the correctly-sized key for the mode.
|
||||
pubKeySize := slhdsa.GetPublicKeySize(s.mode)
|
||||
if pubKeySize == 0 {
|
||||
return nil, errors.New("invalid SLH-DSA mode")
|
||||
}
|
||||
|
||||
// SLH-DSA private key is 4*n bytes where n depends on security level:
|
||||
// 128-bit: n=16 -> 64 bytes, 192-bit: n=24 -> 96 bytes, 256-bit: n=32 -> 128 bytes
|
||||
var privKeySize int
|
||||
switch s.mode {
|
||||
case slhdsa.SHA2_128s, slhdsa.SHAKE_128s, slhdsa.SHA2_128f, slhdsa.SHAKE_128f:
|
||||
privKeySize = 64
|
||||
case slhdsa.SHA2_192s, slhdsa.SHAKE_192s, slhdsa.SHA2_192f, slhdsa.SHAKE_192f:
|
||||
privKeySize = 96
|
||||
case slhdsa.SHA2_256s, slhdsa.SHAKE_256s, slhdsa.SHA2_256f, slhdsa.SHAKE_256f:
|
||||
privKeySize = 128
|
||||
default:
|
||||
return nil, errors.New("invalid SLH-DSA mode")
|
||||
}
|
||||
|
||||
if len(input) < privKeySize+1 {
|
||||
return nil, errors.New("input too short")
|
||||
}
|
||||
|
||||
privKeyBytes := input[:privKeySize]
|
||||
message := input[privKeySize:]
|
||||
|
||||
privKey, err := slhdsa.PrivateKeyFromBytes(s.mode, privKeyBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return privKey.Sign(rand.Reader, message, nil)
|
||||
}
|
||||
|
||||
// RegisterSLHDSA registers all SLH-DSA precompiles.
|
||||
func RegisterSLHDSA(registry *Registry) {
|
||||
// Verification precompiles (small and fast variants)
|
||||
registry.Register(SLHDSA128sVerifyAddress, &slhdsaVerify{mode: slhdsa.SHA2_128s, gas: slhdsa128sVerifyGas})
|
||||
registry.Register(SLHDSA128fVerifyAddress, &slhdsaVerify{mode: slhdsa.SHA2_128f, gas: slhdsa128fVerifyGas})
|
||||
registry.Register(SLHDSA192sVerifyAddress, &slhdsaVerify{mode: slhdsa.SHA2_192s, gas: slhdsa192sVerifyGas})
|
||||
registry.Register(SLHDSA192fVerifyAddress, &slhdsaVerify{mode: slhdsa.SHA2_192f, gas: slhdsa192fVerifyGas})
|
||||
registry.Register(SLHDSA256sVerifyAddress, &slhdsaVerify{mode: slhdsa.SHA2_256s, gas: slhdsa256sVerifyGas})
|
||||
registry.Register(SLHDSA256fVerifyAddress, &slhdsaVerify{mode: slhdsa.SHA2_256f, gas: slhdsa256fVerifyGas})
|
||||
|
||||
// Signing precompiles (small signature variants only -- fast variants are for
|
||||
// off-chain use where gas is irrelevant)
|
||||
registry.Register(SLHDSA128sSignAddress, &slhdsaSign{mode: slhdsa.SHA2_128s, gas: slhdsaSignGas})
|
||||
registry.Register(SLHDSA192sSignAddress, &slhdsaSign{mode: slhdsa.SHA2_192s, gas: slhdsaSignGas})
|
||||
registry.Register(SLHDSA256sSignAddress, &slhdsaSign{mode: slhdsa.SHA2_256s, gas: slhdsaSignGas})
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterSLHDSA(PostQuantumRegistry)
|
||||
}
|
||||
Reference in New Issue
Block a user