mirror of
https://github.com/luxfi/precompile.git
synced 2026-09-24 00:26:21 +00:00
302 lines
9.5 KiB
Go
302 lines
9.5 KiB
Go
// Copyright (C) 2025, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package slhdsa
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/binary"
|
|
"testing"
|
|
|
|
"github.com/luxfi/crypto/slhdsa"
|
|
"github.com/luxfi/geth/common"
|
|
"github.com/luxfi/precompile/contract"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// createTestSignature creates test keys and signatures using the specified mode.
|
|
// Signs with the precompile domain-separation context so verification matches.
|
|
func createTestSignature(t testing.TB, mode slhdsa.Mode) ([]byte, []byte, []byte, []byte) {
|
|
priv, err := slhdsa.GenerateKey(rand.Reader, mode)
|
|
require.NoError(t, err)
|
|
|
|
message := []byte("test message for SLH-DSA signature verification")
|
|
signature, err := priv.SignCtx(rand.Reader, message, precompileCtx)
|
|
require.NoError(t, err)
|
|
|
|
return priv.PublicKey.Bytes(), signature, message, nil
|
|
}
|
|
|
|
// prepareInputWithMode creates precompile input with mode byte
|
|
// Format: [mode(1)][pubKeyLen(2)][pubKey][msgLen(2)][message][signature]
|
|
func prepareInputWithMode(mode uint8, publicKey, message, signature []byte) []byte {
|
|
input := make([]byte, 0)
|
|
|
|
// Mode byte
|
|
input = append(input, mode)
|
|
|
|
// Public key length (2 bytes, big-endian)
|
|
pubKeyLen := make([]byte, 2)
|
|
binary.BigEndian.PutUint16(pubKeyLen, uint16(len(publicKey)))
|
|
input = append(input, pubKeyLen...)
|
|
|
|
// Public key
|
|
input = append(input, publicKey...)
|
|
|
|
// Message length (2 bytes, big-endian)
|
|
msgLen := make([]byte, 2)
|
|
binary.BigEndian.PutUint16(msgLen, uint16(len(message)))
|
|
input = append(input, msgLen...)
|
|
|
|
// Message
|
|
input = append(input, message...)
|
|
|
|
// Signature
|
|
input = append(input, signature...)
|
|
|
|
return input
|
|
}
|
|
|
|
// TestSLHDSAVerify_ValidSignature tests successful signature verification
|
|
func TestSLHDSAVerify_ValidSignature_SHA2_128s(t *testing.T) {
|
|
pk, signature, message, _ := createTestSignature(t, slhdsa.SHA2_128s)
|
|
|
|
input := prepareInputWithMode(ModeSHA2_128s, pk, message, signature)
|
|
|
|
gas := SLHDSAVerifyPrecompile.RequiredGas(input)
|
|
result, _, err := SLHDSAVerifyPrecompile.Run(
|
|
nil, common.Address{}, ContractSLHDSAVerifyAddress,
|
|
input, gas, true,
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, byte(1), result[31], "signature should be valid")
|
|
}
|
|
|
|
func TestSLHDSAVerify_ValidSignature_SHAKE_128f(t *testing.T) {
|
|
pk, signature, message, _ := createTestSignature(t, slhdsa.SHAKE_128f)
|
|
|
|
input := prepareInputWithMode(ModeSHAKE_128f, pk, message, signature)
|
|
|
|
gas := SLHDSAVerifyPrecompile.RequiredGas(input)
|
|
result, _, err := SLHDSAVerifyPrecompile.Run(
|
|
nil, common.Address{}, ContractSLHDSAVerifyAddress,
|
|
input, gas, true,
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, byte(1), result[31], "signature should be valid")
|
|
}
|
|
|
|
// TestSLHDSAVerify_InvalidSignature tests rejection of invalid signatures
|
|
func TestSLHDSAVerify_InvalidSignature(t *testing.T) {
|
|
pk, signature, message, _ := createTestSignature(t, slhdsa.SHA2_128s)
|
|
|
|
// Corrupt signature
|
|
signature[0] ^= 0xFF
|
|
|
|
input := prepareInputWithMode(ModeSHA2_128s, pk, message, signature)
|
|
|
|
gas := SLHDSAVerifyPrecompile.RequiredGas(input)
|
|
result, _, err := SLHDSAVerifyPrecompile.Run(
|
|
nil, common.Address{}, ContractSLHDSAVerifyAddress,
|
|
input, gas, true,
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, byte(0), result[31], "corrupted signature should be invalid")
|
|
}
|
|
|
|
// TestSLHDSAVerify_WrongMessage tests rejection when message doesn't match
|
|
func TestSLHDSAVerify_WrongMessage(t *testing.T) {
|
|
pk, signature, _, _ := createTestSignature(t, slhdsa.SHA2_128s)
|
|
wrongMessage := []byte("wrong message!!!")
|
|
|
|
input := prepareInputWithMode(ModeSHA2_128s, pk, wrongMessage, signature)
|
|
|
|
gas := SLHDSAVerifyPrecompile.RequiredGas(input)
|
|
result, _, err := SLHDSAVerifyPrecompile.Run(
|
|
nil, common.Address{}, ContractSLHDSAVerifyAddress,
|
|
input, gas, true,
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, byte(0), result[31], "signature for different message should be invalid")
|
|
}
|
|
|
|
// TestSLHDSAVerify_InputTooShort tests error handling for insufficient input
|
|
func TestSLHDSAVerify_InputTooShort(t *testing.T) {
|
|
input := make([]byte, 2) // Too short - just mode and partial pubKeyLen
|
|
input[0] = ModeSHA2_128s
|
|
|
|
gas := SLHDSAVerifyPrecompile.RequiredGas(input)
|
|
_, _, err := SLHDSAVerifyPrecompile.Run(
|
|
nil, common.Address{}, ContractSLHDSAVerifyAddress,
|
|
input, gas, true,
|
|
)
|
|
|
|
require.Error(t, err)
|
|
require.ErrorIs(t, err, contract.ErrInvalidInput)
|
|
}
|
|
|
|
// TestSLHDSAVerify_InvalidMode tests rejection of invalid mode byte
|
|
func TestSLHDSAVerify_InvalidMode(t *testing.T) {
|
|
// Create a valid-looking input with invalid mode
|
|
input := make([]byte, 10000)
|
|
input[0] = 0xFF // Invalid mode
|
|
|
|
gas := SLHDSAVerifyPrecompile.RequiredGas(input)
|
|
_, _, err := SLHDSAVerifyPrecompile.Run(
|
|
nil, common.Address{}, ContractSLHDSAVerifyAddress,
|
|
input, gas, true,
|
|
)
|
|
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "unsupported")
|
|
}
|
|
|
|
// TestSLHDSAVerify_EmptyMessage tests verification with empty message
|
|
func TestSLHDSAVerify_EmptyMessage(t *testing.T) {
|
|
priv, err := slhdsa.GenerateKey(rand.Reader, slhdsa.SHA2_128s)
|
|
require.NoError(t, err)
|
|
|
|
message := []byte{}
|
|
signature, err := priv.SignCtx(rand.Reader, message, precompileCtx)
|
|
require.NoError(t, err)
|
|
|
|
input := prepareInputWithMode(ModeSHA2_128s, priv.PublicKey.Bytes(), message, signature)
|
|
|
|
gas := SLHDSAVerifyPrecompile.RequiredGas(input)
|
|
result, _, err := SLHDSAVerifyPrecompile.Run(
|
|
nil, common.Address{}, ContractSLHDSAVerifyAddress,
|
|
input, gas, true,
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, byte(1), result[31], "signature for empty message should be valid")
|
|
}
|
|
|
|
// TestSLHDSAVerify_LargeMessage tests verification with large message
|
|
func TestSLHDSAVerify_LargeMessage(t *testing.T) {
|
|
priv, err := slhdsa.GenerateKey(rand.Reader, slhdsa.SHA2_128s)
|
|
require.NoError(t, err)
|
|
|
|
// Create 10KB message
|
|
message := make([]byte, 10*1024)
|
|
for i := range message {
|
|
message[i] = byte(i % 256)
|
|
}
|
|
|
|
signature, err := priv.SignCtx(rand.Reader, message, precompileCtx)
|
|
require.NoError(t, err)
|
|
|
|
input := prepareInputWithMode(ModeSHA2_128s, priv.PublicKey.Bytes(), message, signature)
|
|
|
|
gas := SLHDSAVerifyPrecompile.RequiredGas(input)
|
|
result, _, err := SLHDSAVerifyPrecompile.Run(
|
|
nil, common.Address{}, ContractSLHDSAVerifyAddress,
|
|
input, gas, true,
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, byte(1), result[31], "signature for large message should be valid")
|
|
}
|
|
|
|
// TestSLHDSAVerify_GasCost tests per-mode gas cost calculation
|
|
func TestSLHDSAVerify_GasCost(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
mode uint8
|
|
minGas uint64
|
|
}{
|
|
{"SHA2_128s", ModeSHA2_128s, SLH128sVerifyBaseGas},
|
|
{"SHA2_128f", ModeSHA2_128f, SLH128fVerifyBaseGas},
|
|
{"SHAKE_128s", ModeSHAKE_128s, SLH128sVerifyBaseGas},
|
|
{"SHAKE_128f", ModeSHAKE_128f, SLH128fVerifyBaseGas},
|
|
{"SHA2_192s", ModeSHA2_192s, SLH192sVerifyBaseGas},
|
|
{"SHA2_192f", ModeSHA2_192f, SLH192fVerifyBaseGas},
|
|
{"SHA2_256s", ModeSHA2_256s, SLH256sVerifyBaseGas},
|
|
{"SHA2_256f", ModeSHA2_256f, SLH256fVerifyBaseGas},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Create minimal valid input for the mode
|
|
// Just mode + pubKeyLen (set to expected size for the mode)
|
|
input := make([]byte, 3)
|
|
input[0] = tt.mode
|
|
|
|
// Set correct pubkey size for mode
|
|
var pubKeySize uint16
|
|
switch tt.mode {
|
|
case ModeSHA2_128s, ModeSHA2_128f, ModeSHAKE_128s, ModeSHAKE_128f:
|
|
pubKeySize = SLH128PublicKeySize
|
|
case ModeSHA2_192s, ModeSHA2_192f, ModeSHAKE_192s, ModeSHAKE_192f:
|
|
pubKeySize = SLH192PublicKeySize
|
|
case ModeSHA2_256s, ModeSHA2_256f, ModeSHAKE_256s, ModeSHAKE_256f:
|
|
pubKeySize = SLH256PublicKeySize
|
|
}
|
|
binary.BigEndian.PutUint16(input[1:3], pubKeySize)
|
|
|
|
gas := SLHDSAVerifyPrecompile.RequiredGas(input)
|
|
require.GreaterOrEqual(t, gas, tt.minGas, "gas cost should be at least base gas for mode")
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestSLHDSAPrecompile_Address tests precompile address
|
|
func TestSLHDSAPrecompile_Address(t *testing.T) {
|
|
expectedAddress := ContractSLHDSAVerifyAddress
|
|
require.Equal(t, expectedAddress, SLHDSAVerifyPrecompile.Address())
|
|
}
|
|
|
|
// TestSLHDSAVerify_OutOfGas tests out of gas error
|
|
func TestSLHDSAVerify_OutOfGas(t *testing.T) {
|
|
pk, signature, message, _ := createTestSignature(t, slhdsa.SHA2_128s)
|
|
|
|
input := prepareInputWithMode(ModeSHA2_128s, pk, message, signature)
|
|
|
|
_, _, err := SLHDSAVerifyPrecompile.Run(
|
|
nil, common.Address{}, ContractSLHDSAVerifyAddress,
|
|
input, 1000, true, // Insufficient gas
|
|
)
|
|
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "out of gas")
|
|
}
|
|
|
|
// BenchmarkSLHDSAVerify benchmarks verification for different modes
|
|
func BenchmarkSLHDSAVerify_SHA2_128s(b *testing.B) {
|
|
priv, _ := slhdsa.GenerateKey(rand.Reader, slhdsa.SHA2_128s)
|
|
message := []byte("benchmark message")
|
|
signature, _ := priv.SignCtx(rand.Reader, message, precompileCtx)
|
|
input := prepareInputWithMode(ModeSHA2_128s, priv.PublicKey.Bytes(), message, signature)
|
|
|
|
gas := SLHDSAVerifyPrecompile.RequiredGas(input)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, _, _ = SLHDSAVerifyPrecompile.Run(
|
|
nil, common.Address{}, ContractSLHDSAVerifyAddress,
|
|
input, gas, true,
|
|
)
|
|
}
|
|
}
|
|
|
|
func BenchmarkSLHDSAVerify_SHA2_128f(b *testing.B) {
|
|
priv, _ := slhdsa.GenerateKey(rand.Reader, slhdsa.SHA2_128f)
|
|
message := []byte("benchmark message")
|
|
signature, _ := priv.SignCtx(rand.Reader, message, precompileCtx)
|
|
input := prepareInputWithMode(ModeSHA2_128f, priv.PublicKey.Bytes(), message, signature)
|
|
|
|
gas := SLHDSAVerifyPrecompile.RequiredGas(input)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, _, _ = SLHDSAVerifyPrecompile.Run(
|
|
nil, common.Address{}, ContractSLHDSAVerifyAddress,
|
|
input, gas, true,
|
|
)
|
|
}
|
|
}
|