mirror of
https://github.com/luxfi/crypto.git
synced 2026-07-27 01:54:50 +00:00
security: remove signing/keygen precompiles, add BLS PoP, cap aggregate sigs
Private key material must never appear in EVM calldata — it is visible to all nodes and permanently stored on-chain. - Remove ML-DSA Sign + KeyGen precompiles (0x0113-0x0118) - Remove ML-KEM Decapsulate + KeyGen precompiles (0x0123-0x0128) - Remove SLH-DSA Sign precompiles (0x0136-0x0138) - Add BLSVerifyPoP precompile (0x0167) to prevent rogue key attacks - Cap BLSFastAggregate and BLSAggregateVerify to 64 keys/sigs max - Precompile set is now verify-only + encapsulate-only
This commit is contained in:
+83
-1
@@ -8,6 +8,7 @@ package precompile
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381"
|
||||
@@ -28,6 +29,9 @@ var (
|
||||
// BLS key operations
|
||||
BLSPublicKeyAggregateAddress = HexToAddress("0x0000000000000000000000000000000000000165")
|
||||
BLSHashToPointAddress = HexToAddress("0x0000000000000000000000000000000000000166")
|
||||
|
||||
// Proof-of-possession
|
||||
BLSVerifyPoPAddress = HexToAddress("0x0000000000000000000000000000000000000167")
|
||||
)
|
||||
|
||||
// Gas costs for BLS operations
|
||||
@@ -39,6 +43,7 @@ const (
|
||||
blsThresholdCombineGas = 180000
|
||||
blsPublicKeyAggregateGas = 50000
|
||||
blsHashToPointGas = 80000
|
||||
blsVerifyPoPGas = 150000
|
||||
|
||||
// Per-item costs for aggregation
|
||||
blsPerSignatureGas = 30000
|
||||
@@ -47,6 +52,11 @@ const (
|
||||
// Point sizes
|
||||
g1Size = 48
|
||||
g2Size = 96
|
||||
|
||||
// maxAggregateSigs caps the number of signatures in fast aggregate verify
|
||||
// to bound gas consumption and prevent abuse. 64 covers any reasonable
|
||||
// consensus committee size.
|
||||
maxAggregateSigs = 64
|
||||
)
|
||||
|
||||
// BLSVerify implements single BLS signature verification
|
||||
@@ -128,6 +138,9 @@ func (b *BLSAggregateVerify) Run(input []byte) ([]byte, error) {
|
||||
if numSigs == 0 {
|
||||
return nil, errors.New("no signatures")
|
||||
}
|
||||
if numSigs > maxAggregateSigs {
|
||||
return nil, fmt.Errorf("too many signatures: %d > %d", numSigs, maxAggregateSigs)
|
||||
}
|
||||
|
||||
// Parse aggregate signature
|
||||
var aggSig bls12381.G2Affine
|
||||
@@ -201,7 +214,13 @@ func (b *BLSAggregateVerify) Run(input []byte) ([]byte, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// BLSFastAggregate verifies aggregate signature with same message
|
||||
// BLSFastAggregate verifies aggregate signature with same message.
|
||||
//
|
||||
// SECURITY: All public keys used in aggregation MUST have a verified
|
||||
// proof-of-possession (BLSVerifyPoP) before being included. Without PoP,
|
||||
// an attacker can perform a rogue key attack by choosing a crafted public
|
||||
// key that cancels out honest participants' keys. Verify PoP on-chain
|
||||
// before calling this precompile.
|
||||
type BLSFastAggregate struct{}
|
||||
|
||||
func (b *BLSFastAggregate) RequiredGas(input []byte) uint64 {
|
||||
@@ -222,6 +241,9 @@ func (b *BLSFastAggregate) Run(input []byte) ([]byte, error) {
|
||||
if numKeys == 0 {
|
||||
return nil, errors.New("no public keys")
|
||||
}
|
||||
if numKeys > maxAggregateSigs {
|
||||
return nil, fmt.Errorf("too many keys: %d > %d", numKeys, maxAggregateSigs)
|
||||
}
|
||||
|
||||
// Parse aggregate signature
|
||||
var aggSig bls12381.G2Affine
|
||||
@@ -540,6 +562,65 @@ func (b *BLSHashToPoint) Run(input []byte) ([]byte, error) {
|
||||
return bytes[:], nil
|
||||
}
|
||||
|
||||
// BLSVerifyPoP verifies proof-of-possession for a BLS public key.
|
||||
// A PoP is a BLS signature over the serialized public key itself, proving the
|
||||
// holder knows the corresponding secret key. This MUST be verified before any
|
||||
// key is used in aggregation (BLSFastAggregate, BLSPublicKeyAggregate) to
|
||||
// prevent rogue key attacks.
|
||||
type BLSVerifyPoP struct{}
|
||||
|
||||
func (b *BLSVerifyPoP) RequiredGas(input []byte) uint64 {
|
||||
return blsVerifyPoPGas
|
||||
}
|
||||
|
||||
func (b *BLSVerifyPoP) Run(input []byte) ([]byte, error) {
|
||||
// Input: [48 bytes public_key (G1)][96 bytes pop_signature (G2)]
|
||||
if len(input) < g1Size+g2Size {
|
||||
return nil, fmt.Errorf("input too short: need %d bytes, got %d", g1Size+g2Size, len(input))
|
||||
}
|
||||
|
||||
pubKeyBytes := input[:g1Size]
|
||||
popBytes := input[g1Size : g1Size+g2Size]
|
||||
|
||||
// Parse public key (G1 point)
|
||||
var pubKey bls12381.G1Affine
|
||||
if _, err := pubKey.SetBytes(pubKeyBytes); err != nil {
|
||||
return nil, errors.New("invalid public key point")
|
||||
}
|
||||
|
||||
// Parse PoP signature (G2 point)
|
||||
var popSig bls12381.G2Affine
|
||||
if _, err := popSig.SetBytes(popBytes); err != nil {
|
||||
return nil, errors.New("invalid PoP signature point")
|
||||
}
|
||||
|
||||
// PoP = Sign(sk, pubkey_bytes). Verify by checking the BLS signature
|
||||
// over the raw public key bytes using that same public key.
|
||||
msgPoint, err := bls12381.HashToG2(pubKeyBytes, nil)
|
||||
if err != nil {
|
||||
return nil, errors.New("hash to curve failed")
|
||||
}
|
||||
|
||||
// Verify: e(pubKey, H(pubKeyBytes)) == e(G1, popSig)
|
||||
_, _, g1Gen, _ := bls12381.Generators()
|
||||
var negG1 bls12381.G1Affine
|
||||
negG1.Neg(&g1Gen)
|
||||
|
||||
valid, err := bls12381.PairingCheck(
|
||||
[]bls12381.G1Affine{pubKey, negG1},
|
||||
[]bls12381.G2Affine{msgPoint, popSig},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]byte, 32)
|
||||
if valid {
|
||||
result[31] = 0x01
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// RegisterBLS registers all BLS precompiles
|
||||
func RegisterBLS(registry *Registry) {
|
||||
registry.Register(BLSVerifyAddress, &BLSVerify{})
|
||||
@@ -549,6 +630,7 @@ func RegisterBLS(registry *Registry) {
|
||||
registry.Register(BLSThresholdCombineAddress, &BLSThresholdCombine{})
|
||||
registry.Register(BLSPublicKeyAggregateAddress, &BLSPublicKeyAggregate{})
|
||||
registry.Register(BLSHashToPointAddress, &BLSHashToPoint{})
|
||||
registry.Register(BLSVerifyPoPAddress, &BLSVerifyPoP{})
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
+12
-9
@@ -8,17 +8,18 @@ import (
|
||||
// "github.com/luxfi/crypto" // removed to avoid import cycle
|
||||
)
|
||||
|
||||
// GetAllPostQuantumPrecompiles returns all post-quantum precompiles
|
||||
// This includes SHAKE, Lamport, and the three NIST standards
|
||||
// GetAllPostQuantumPrecompiles returns all post-quantum and BLS precompiles.
|
||||
// This includes SHAKE, Lamport, the three NIST standards (verify/encapsulate
|
||||
// only), and BLS12-381 operations.
|
||||
func GetAllPostQuantumPrecompiles() map[Address]PrecompiledContract {
|
||||
registry := NewRegistry()
|
||||
|
||||
// Register all precompile types
|
||||
RegisterSHAKE(registry)
|
||||
RegisterLamport(registry)
|
||||
RegisterMLDSA(registry)
|
||||
RegisterMLKEM(registry)
|
||||
RegisterSLHDSA(registry)
|
||||
RegisterBLS(registry)
|
||||
|
||||
return registry.Contracts()
|
||||
}
|
||||
@@ -54,17 +55,19 @@ func Info() map[string]interface{} {
|
||||
"cgo_enabled": EnableCGO(),
|
||||
"standards": []string{
|
||||
"FIPS 202 (SHAKE)",
|
||||
"FIPS 203 (ML-KEM)",
|
||||
"FIPS 204 (ML-DSA)",
|
||||
"FIPS 205 (SLH-DSA)",
|
||||
"FIPS 203 (ML-KEM, encapsulate only)",
|
||||
"FIPS 204 (ML-DSA, verify only)",
|
||||
"FIPS 205 (SLH-DSA, verify only)",
|
||||
"Lamport OTS",
|
||||
"BLS12-381",
|
||||
},
|
||||
"address_ranges": map[string]string{
|
||||
"ml_dsa": "0x0110-0x0119",
|
||||
"ml_kem": "0x0120-0x0129",
|
||||
"slh_dsa": "0x0130-0x0139",
|
||||
"ml_dsa": "0x0110-0x0112",
|
||||
"ml_kem": "0x0120-0x0122",
|
||||
"slh_dsa": "0x0130-0x0135",
|
||||
"shake": "0x0140-0x0149",
|
||||
"lamport": "0x0150-0x0159",
|
||||
"bls": "0x0160-0x0167",
|
||||
},
|
||||
"shake_precompiles": []string{
|
||||
"SHAKE128 (variable)",
|
||||
|
||||
+10
-83
@@ -1,38 +1,31 @@
|
||||
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
||||
// ML-DSA (FIPS 204) precompiled contracts for EVM
|
||||
// Provides on-chain post-quantum signature verification
|
||||
//
|
||||
// SECURITY: Only verification is exposed as a precompile. Signing and keygen
|
||||
// are deliberately excluded — private key material must never appear in EVM
|
||||
// calldata (it is visible to all nodes and permanently stored on-chain).
|
||||
|
||||
package precompile
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
|
||||
"github.com/luxfi/crypto/mldsa"
|
||||
)
|
||||
|
||||
// ML-DSA precompile addresses (0x0110-0x0119)
|
||||
// ML-DSA verification precompile addresses (0x0110-0x0112)
|
||||
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")
|
||||
MLDSA44VerifyAddress = HexToAddress("0x0000000000000000000000000000000000000110")
|
||||
MLDSA65VerifyAddress = HexToAddress("0x0000000000000000000000000000000000000111")
|
||||
MLDSA87VerifyAddress = HexToAddress("0x0000000000000000000000000000000000000112")
|
||||
)
|
||||
|
||||
// Gas costs for ML-DSA operations
|
||||
// Gas costs for ML-DSA verification
|
||||
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.
|
||||
@@ -70,77 +63,11 @@ func (v *mldsaVerify) Run(input []byte) ([]byte, error) {
|
||||
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.
|
||||
// RegisterMLDSA registers ML-DSA verification 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() {
|
||||
|
||||
+7
-70
@@ -1,6 +1,10 @@
|
||||
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
||||
// ML-KEM (FIPS 203) precompiled contracts for EVM
|
||||
// Provides on-chain post-quantum key encapsulation
|
||||
//
|
||||
// SECURITY: Only encapsulation (public-key operation) is exposed. Decapsulation
|
||||
// and keygen are deliberately excluded — private key material must never appear
|
||||
// in EVM calldata (it is visible to all nodes and permanently stored on-chain).
|
||||
|
||||
package precompile
|
||||
|
||||
@@ -10,28 +14,18 @@ import (
|
||||
"github.com/luxfi/crypto/mlkem"
|
||||
)
|
||||
|
||||
// ML-KEM precompile addresses (0x0120-0x0129)
|
||||
// ML-KEM encapsulation precompile addresses (0x0120-0x0122)
|
||||
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
|
||||
// Gas costs for ML-KEM encapsulation
|
||||
const (
|
||||
mlkem512EncapsulateGas = 80000
|
||||
mlkem768EncapsulateGas = 100000
|
||||
mlkem1024EncapsulateGas = 130000
|
||||
mlkem512DecapsulateGas = 80000
|
||||
mlkem768DecapsulateGas = 100000
|
||||
mlkem1024DecapsulateGas = 130000
|
||||
mlkemKeyGenGas = 200000
|
||||
)
|
||||
|
||||
// mlkemEncapsulate implements ML-KEM encapsulation for a given mode.
|
||||
@@ -66,68 +60,11 @@ func (e *mlkemEncapsulate) Run(input []byte) ([]byte, error) {
|
||||
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.
|
||||
// RegisterMLKEM registers ML-KEM encapsulation 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() {
|
||||
|
||||
@@ -647,18 +647,18 @@ 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",
|
||||
// ML-DSA (FIPS 204) — verify only
|
||||
"0x0110", "0x0111", "0x0112",
|
||||
// ML-KEM (FIPS 203) — encapsulate only
|
||||
"0x0120", "0x0121", "0x0122",
|
||||
// SLH-DSA (FIPS 205) — verify only
|
||||
"0x0130", "0x0131", "0x0132", "0x0133", "0x0134", "0x0135",
|
||||
// SHAKE
|
||||
"0x0140", "0x0141", "0x0142", "0x0143", "0x0144", "0x0145", "0x0146", "0x0147", "0x0148",
|
||||
// Lamport
|
||||
"0x0150", "0x0151", "0x0152", "0x0153", "0x0154",
|
||||
// BLS
|
||||
"0x0160", "0x0161", "0x0162", "0x0163", "0x0164", "0x0165", "0x0166",
|
||||
"0x0160", "0x0161", "0x0162", "0x0163", "0x0164", "0x0165", "0x0166", "0x0167",
|
||||
}
|
||||
|
||||
for _, addr := range expectedAddresses {
|
||||
|
||||
+7
-63
@@ -1,17 +1,20 @@
|
||||
// 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
|
||||
//
|
||||
// SECURITY: Only verification is exposed as a precompile. Signing is
|
||||
// deliberately excluded — private key material must never appear in EVM
|
||||
// calldata (it is visible to all nodes and permanently stored on-chain).
|
||||
|
||||
package precompile
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
|
||||
"github.com/luxfi/crypto/slhdsa"
|
||||
)
|
||||
|
||||
// SLH-DSA precompile addresses (0x0130-0x0139)
|
||||
// SLH-DSA verification precompile addresses (0x0130-0x0135)
|
||||
var (
|
||||
SLHDSA128sVerifyAddress = HexToAddress("0x0000000000000000000000000000000000000130")
|
||||
SLHDSA128fVerifyAddress = HexToAddress("0x0000000000000000000000000000000000000131")
|
||||
@@ -19,12 +22,9 @@ var (
|
||||
SLHDSA192fVerifyAddress = HexToAddress("0x0000000000000000000000000000000000000133")
|
||||
SLHDSA256sVerifyAddress = HexToAddress("0x0000000000000000000000000000000000000134")
|
||||
SLHDSA256fVerifyAddress = HexToAddress("0x0000000000000000000000000000000000000135")
|
||||
SLHDSA128sSignAddress = HexToAddress("0x0000000000000000000000000000000000000136")
|
||||
SLHDSA192sSignAddress = HexToAddress("0x0000000000000000000000000000000000000137")
|
||||
SLHDSA256sSignAddress = HexToAddress("0x0000000000000000000000000000000000000138")
|
||||
)
|
||||
|
||||
// Gas costs for SLH-DSA operations
|
||||
// Gas costs for SLH-DSA verification
|
||||
const (
|
||||
slhdsa128sVerifyGas = 300000
|
||||
slhdsa128fVerifyGas = 200000
|
||||
@@ -32,7 +32,6 @@ const (
|
||||
slhdsa192fVerifyGas = 300000
|
||||
slhdsa256sVerifyGas = 500000
|
||||
slhdsa256fVerifyGas = 400000
|
||||
slhdsaSignGas = 1000000
|
||||
)
|
||||
|
||||
// slhdsaVerify implements SLH-DSA signature verification for a given mode.
|
||||
@@ -70,69 +69,14 @@ func (v *slhdsaVerify) Run(input []byte) ([]byte, error) {
|
||||
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.
|
||||
// RegisterSLHDSA registers SLH-DSA verification 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() {
|
||||
|
||||
Reference in New Issue
Block a user