Files
zeekay ca0bf6b303 precompile: seven packages to 96-99%, from lanes killed before they could commit
slhdsa 97.9 · mlkem 97.4 · mldsa 96.8 · xwing 96.2 · bls12381 97.2 ·
kzg4844 97.8 · v3 98.7. All build, vet and gofmt clean, all green under
-count=1, and preserved here rather than lost to a session limit.

v3's lane did report before it died: 78.1% to 98.7%, 70 of 70 mutations caught,
four of which survived a first pass and needed tests written. The two that
matter are rounding: burn rounding principal UP, and fee growth rounding UP,
neither of which any behaviour test could see -- a Q128.128 off-by-one is
2^-128 of a token and floors to zero through every payout path, so it took an
exact-value assertion. It also found that a full drain of a pool with
overlapping positions REVERTS rather than paying out, because deposits round
down once per position while payouts round down once per step over combined
liquidity, and floor(a+b) >= floor(a)+floor(b). Custody holds; not one unbacked
wei leaves. Liveness, not theft.

The other six carry no report -- their lanes were killed mid-run. Build, vet,
gofmt and the suite are all I can vouch for.
2026-08-28 13:27:00 -07:00

308 lines
11 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package slhdsa
import (
"encoding/binary"
"errors"
"fmt"
"github.com/luxfi/crypto/slhdsa"
"github.com/luxfi/geth/common"
"github.com/luxfi/precompile/contract"
)
var (
// ContractSLHDSAVerifyAddress is the address of the SLH-DSA verify precompile.
// LP-3501/LP-4200 unified PQCrypto block: 0x012203.
ContractSLHDSAVerifyAddress = common.HexToAddress("0x0000000000000000000000000000000000012203")
// Singleton instance
SLHDSAVerifyPrecompile = &slhdsaVerifyPrecompile{}
_ contract.StatefulPrecompiledContract = &slhdsaVerifyPrecompile{}
ErrInvalidInputLength = contract.ErrInvalidInput
ErrInvalidMode = errors.New("invalid SLH-DSA mode")
ErrUnsupportedMode = errors.New("unsupported SLH-DSA mode")
// precompileCtx is the domain-separation context for EVM precompile signature
// verification. FIPS 205: prevents cross-protocol replay between
// EVM precompile verify and other SLH-DSA uses (UTXO, Warp, MPC).
precompileCtx = []byte("lux-evm-precompile-slhdsa-v1")
)
// SLH-DSA modes supported by this precompile (12 parameter sets)
// Mode byte encoding: high nibble = hash (0=SHA2, 1=SHAKE), low nibble = size/variant
const (
// SHA2 modes
ModeSHA2_128s uint8 = 0x00 // SHA2, 128-bit security, small signatures
ModeSHA2_128f uint8 = 0x01 // SHA2, 128-bit security, fast signing
ModeSHA2_192s uint8 = 0x02 // SHA2, 192-bit security, small signatures
ModeSHA2_192f uint8 = 0x03 // SHA2, 192-bit security, fast signing
ModeSHA2_256s uint8 = 0x04 // SHA2, 256-bit security, small signatures
ModeSHA2_256f uint8 = 0x05 // SHA2, 256-bit security, fast signing
// SHAKE modes
ModeSHAKE_128s uint8 = 0x10 // SHAKE, 128-bit security, small signatures
ModeSHAKE_128f uint8 = 0x11 // SHAKE, 128-bit security, fast signing
ModeSHAKE_192s uint8 = 0x12 // SHAKE, 192-bit security, small signatures
ModeSHAKE_192f uint8 = 0x13 // SHAKE, 192-bit security, fast signing
ModeSHAKE_256s uint8 = 0x14 // SHAKE, 256-bit security, small signatures
ModeSHAKE_256f uint8 = 0x15 // SHAKE, 256-bit security, fast signing
)
// Size constants for each mode
const (
// Public key sizes (2*n where n is security level parameter)
SLH128PublicKeySize = 32 // 128-bit security
SLH192PublicKeySize = 48 // 192-bit security
SLH256PublicKeySize = 64 // 256-bit security
// Signature sizes vary significantly by mode
SLHSHA2_128sSignatureSize = 7856
SLHSHA2_128fSignatureSize = 17088
SLHSHA2_192sSignatureSize = 16224
SLHSHA2_192fSignatureSize = 35664
SLHSHA2_256sSignatureSize = 29792
SLHSHA2_256fSignatureSize = 49856
SLHSHAKE_128sSignatureSize = 7856
SLHSHAKE_128fSignatureSize = 17088
SLHSHAKE_192sSignatureSize = 16224
SLHSHAKE_192fSignatureSize = 35664
SLHSHAKE_256sSignatureSize = 29792
SLHSHAKE_256fSignatureSize = 49856
// Input format fields
ModeByte = 1 // Mode indicator byte
PubKeyLenSize = 2 // Size of public key length field (uint16)
MessageLenSize = 2 // Size of message length field (uint16)
)
// Gas costs - adjusted per mode based on computational complexity
// Larger signatures require more verification work
const (
// Base gas costs per parameter set
SLH128sVerifyBaseGas uint64 = 50_000 // Small, fastest
SLH128fVerifyBaseGas uint64 = 75_000 // Larger signature
SLH192sVerifyBaseGas uint64 = 100_000 // Medium
SLH192fVerifyBaseGas uint64 = 150_000 // Larger
SLH256sVerifyBaseGas uint64 = 175_000 // Large
SLH256fVerifyBaseGas uint64 = 250_000 // Largest signature
// Per-byte gas for message
SLHDSAVerifyPerByteGas uint64 = 10
// Default gas for invalid input
SLHDSADefaultGas uint64 = 100_000
)
type slhdsaVerifyPrecompile struct{}
// Address returns the address of the SLH-DSA verify precompile
func (p *slhdsaVerifyPrecompile) Address() common.Address {
return ContractSLHDSAVerifyAddress
}
// getModeParams returns the parameters for a given SLH-DSA mode
func getModeParams(mode uint8) (pubKeySize, sigSize int, baseGas uint64, slhdsaMode slhdsa.Mode, err error) {
switch mode {
case ModeSHA2_128s:
return SLH128PublicKeySize, SLHSHA2_128sSignatureSize, SLH128sVerifyBaseGas, slhdsa.SHA2_128s, nil
case ModeSHA2_128f:
return SLH128PublicKeySize, SLHSHA2_128fSignatureSize, SLH128fVerifyBaseGas, slhdsa.SHA2_128f, nil
case ModeSHA2_192s:
return SLH192PublicKeySize, SLHSHA2_192sSignatureSize, SLH192sVerifyBaseGas, slhdsa.SHA2_192s, nil
case ModeSHA2_192f:
return SLH192PublicKeySize, SLHSHA2_192fSignatureSize, SLH192fVerifyBaseGas, slhdsa.SHA2_192f, nil
case ModeSHA2_256s:
return SLH256PublicKeySize, SLHSHA2_256sSignatureSize, SLH256sVerifyBaseGas, slhdsa.SHA2_256s, nil
case ModeSHA2_256f:
return SLH256PublicKeySize, SLHSHA2_256fSignatureSize, SLH256fVerifyBaseGas, slhdsa.SHA2_256f, nil
case ModeSHAKE_128s:
return SLH128PublicKeySize, SLHSHAKE_128sSignatureSize, SLH128sVerifyBaseGas, slhdsa.SHAKE_128s, nil
case ModeSHAKE_128f:
return SLH128PublicKeySize, SLHSHAKE_128fSignatureSize, SLH128fVerifyBaseGas, slhdsa.SHAKE_128f, nil
case ModeSHAKE_192s:
return SLH192PublicKeySize, SLHSHAKE_192sSignatureSize, SLH192sVerifyBaseGas, slhdsa.SHAKE_192s, nil
case ModeSHAKE_192f:
return SLH192PublicKeySize, SLHSHAKE_192fSignatureSize, SLH192fVerifyBaseGas, slhdsa.SHAKE_192f, nil
case ModeSHAKE_256s:
return SLH256PublicKeySize, SLHSHAKE_256sSignatureSize, SLH256sVerifyBaseGas, slhdsa.SHAKE_256s, nil
case ModeSHAKE_256f:
return SLH256PublicKeySize, SLHSHAKE_256fSignatureSize, SLH256fVerifyBaseGas, slhdsa.SHAKE_256f, nil
default:
return 0, 0, 0, 0, ErrUnsupportedMode
}
}
// RequiredGas calculates the gas required for SLH-DSA verification
func (p *slhdsaVerifyPrecompile) RequiredGas(input []byte) uint64 {
if len(input) < ModeByte {
return SLHDSADefaultGas
}
mode := input[0]
pubKeySize, _, baseGas, _, err := getModeParams(mode)
if err != nil {
return SLHDSADefaultGas
}
// Check if we have enough bytes to read message length
// Format: [mode(1)][pubKeyLen(2)][pubKey][msgLen(2)][message][signature]
headerSize := ModeByte + PubKeyLenSize
if len(input) < headerSize {
return baseGas
}
pubKeyLen := int(binary.BigEndian.Uint16(input[ModeByte : ModeByte+PubKeyLenSize]))
if pubKeyLen != pubKeySize {
return baseGas // Invalid pubkey size for mode
}
msgLenOffset := headerSize + pubKeyLen
if len(input) < msgLenOffset+MessageLenSize {
return baseGas
}
msgLen := binary.BigEndian.Uint16(input[msgLenOffset : msgLenOffset+MessageLenSize])
// Base cost + per-byte cost for message
return baseGas + (uint64(msgLen) * SLHDSAVerifyPerByteGas)
}
// Run implements the SLH-DSA signature verification precompile
// Input format:
//
// [0] = mode byte (defines parameter set)
// [1:3] = public key length as uint16
// [3:pubKeyEnd] = public key
// [pubKeyEnd:+2] = message length as uint16
// [+2:msgEnd] = message
// [msgEnd:...] = signature
//
// Output: 32-byte word (1 = valid, 0 = invalid)
func (p *slhdsaVerifyPrecompile) 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)
remainingGas, err := contract.DeductGas(suppliedGas, gasCost)
if err != nil {
return nil, 0, err
}
// Minimum: mode byte + pubkey length
minHeader := ModeByte + PubKeyLenSize
if len(input) < minHeader {
return nil, remainingGas, fmt.Errorf("%w: need at least %d bytes", ErrInvalidInputLength, minHeader)
}
// Parse mode
mode := input[0]
pubKeySize, sigSize, _, slhdsaMode, err := getModeParams(mode)
if err != nil {
return nil, remainingGas, fmt.Errorf("%w: 0x%02x", ErrUnsupportedMode, mode)
}
// Parse public key length
pubKeyLen := int(binary.BigEndian.Uint16(input[ModeByte : ModeByte+PubKeyLenSize]))
if pubKeyLen != pubKeySize {
return nil, remainingGas, fmt.Errorf("%w: expected pubkey size %d for mode 0x%02x, got %d",
ErrInvalidInputLength, pubKeySize, mode, pubKeyLen)
}
// Calculate offsets
pubKeyStart := ModeByte + PubKeyLenSize
pubKeyEnd := pubKeyStart + pubKeyLen
msgLenStart := pubKeyEnd
msgLenEnd := msgLenStart + MessageLenSize
// Check we have enough input
if len(input) < msgLenEnd {
return nil, remainingGas, fmt.Errorf("%w: input too short for message length", ErrInvalidInputLength)
}
// Parse message length
msgLen := int(binary.BigEndian.Uint16(input[msgLenStart:msgLenEnd]))
msgStart := msgLenEnd
msgEnd := msgStart + msgLen
sigStart := msgEnd
sigEnd := sigStart + sigSize
// Validate total input size. Exact, not "at least": trailing bytes would
// let one signature be presented under many distinct calldatas, and the
// ML-DSA precompile next door already pins its frame exactly.
if len(input) != sigEnd {
return nil, remainingGas, fmt.Errorf("%w: expected exactly %d bytes, got %d",
ErrInvalidInputLength, sigEnd, len(input))
}
// Extract components
publicKey := input[pubKeyStart:pubKeyEnd]
message := input[msgStart:msgEnd]
signature := input[sigStart:sigEnd]
// SLH-DSA (FIPS 205) is hash-based (SPHINCS+ WOTS+ / FORS / Merkle), not
// lattice-based — there is no NTT in the verifier. The luxfi/accel
// `Lattice` op set exposes a Dilithium kernel, NOT an SLH-DSA kernel.
// A previous version dispatched to lattice.DilithiumVerify here, which
// silently verified a completely different algorithm against the input
// bytes whenever a GPU backend was present — a forgery / oracle hazard.
//
// Until accel ships a real SLH-DSA WOTS+/FORS/Merkle verifier kernel,
// the precompile uses the CPU verifier exclusively. The CPU path is
// constant-time and FIPS 205 compliant via luxfi/crypto/slhdsa.
pub, err := slhdsa.PublicKeyFromBytes(publicKey, slhdsaMode)
if err != nil {
return nil, remainingGas, fmt.Errorf("invalid public key: %w", err)
}
valid := pub.VerifySignatureCtx(message, signature, precompileCtx)
// Return result as 32-byte word (1 = valid, 0 = invalid)
result := make([]byte, 32)
if valid {
result[31] = 1
}
return result, remainingGas, nil
}
// ModeName returns a human-readable name for the mode
func ModeName(mode uint8) string {
switch mode {
case ModeSHA2_128s:
return "SLH-DSA-SHA2-128s"
case ModeSHA2_128f:
return "SLH-DSA-SHA2-128f"
case ModeSHA2_192s:
return "SLH-DSA-SHA2-192s"
case ModeSHA2_192f:
return "SLH-DSA-SHA2-192f"
case ModeSHA2_256s:
return "SLH-DSA-SHA2-256s"
case ModeSHA2_256f:
return "SLH-DSA-SHA2-256f"
case ModeSHAKE_128s:
return "SLH-DSA-SHAKE-128s"
case ModeSHAKE_128f:
return "SLH-DSA-SHAKE-128f"
case ModeSHAKE_192s:
return "SLH-DSA-SHAKE-192s"
case ModeSHAKE_192f:
return "SLH-DSA-SHAKE-192f"
case ModeSHAKE_256s:
return "SLH-DSA-SHAKE-256s"
case ModeSHAKE_256f:
return "SLH-DSA-SHAKE-256f"
default:
return "unknown"
}
}