mirror of
https://github.com/luxfi/precompile.git
synced 2026-09-24 00:26:21 +00:00
428 lines
13 KiB
Go
428 lines
13 KiB
Go
// Copyright (C) 2025, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package hpke
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/binary"
|
|
"testing"
|
|
|
|
"github.com/cloudflare/circl/hpke"
|
|
"github.com/luxfi/geth/common"
|
|
"github.com/luxfi/precompile/precompileconfig"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// --- Config tests ---
|
|
|
|
func TestConfig_Key(t *testing.T) {
|
|
require.Equal(t, ConfigKey, (&Config{}).Key())
|
|
}
|
|
|
|
func TestConfig_Timestamp_Nil(t *testing.T) {
|
|
require.Nil(t, (&Config{}).Timestamp())
|
|
}
|
|
|
|
func TestConfig_Timestamp_Set(t *testing.T) {
|
|
ts := uint64(999)
|
|
c := &Config{Upgrade: precompileconfig.Upgrade{BlockTimestamp: &ts}}
|
|
require.Equal(t, &ts, c.Timestamp())
|
|
}
|
|
|
|
func TestConfig_IsDisabled(t *testing.T) {
|
|
require.False(t, (&Config{}).IsDisabled())
|
|
require.True(t, (&Config{Upgrade: precompileconfig.Upgrade{Disable: true}}).IsDisabled())
|
|
}
|
|
|
|
func TestConfig_Equal_Same(t *testing.T) {
|
|
ts := uint64(100)
|
|
c1 := &Config{Upgrade: precompileconfig.Upgrade{BlockTimestamp: &ts}}
|
|
c2 := &Config{Upgrade: precompileconfig.Upgrade{BlockTimestamp: &ts}}
|
|
require.True(t, c1.Equal(c2))
|
|
}
|
|
|
|
func TestConfig_Equal_Different(t *testing.T) {
|
|
ts1, ts2 := uint64(100), uint64(200)
|
|
c1 := &Config{Upgrade: precompileconfig.Upgrade{BlockTimestamp: &ts1}}
|
|
c2 := &Config{Upgrade: precompileconfig.Upgrade{BlockTimestamp: &ts2}}
|
|
require.False(t, c1.Equal(c2))
|
|
}
|
|
|
|
func TestConfig_Equal_WrongType(t *testing.T) {
|
|
require.False(t, (&Config{}).Equal(nil))
|
|
}
|
|
|
|
func TestConfig_Verify(t *testing.T) {
|
|
require.NoError(t, (&Config{}).Verify(nil))
|
|
}
|
|
|
|
func TestConfigurator_MakeConfig(t *testing.T) {
|
|
cfg := (&configurator{}).MakeConfig()
|
|
_, ok := cfg.(*Config)
|
|
require.True(t, ok)
|
|
}
|
|
|
|
func TestConfigurator_Configure(t *testing.T) {
|
|
require.NoError(t, (&configurator{}).Configure(nil, nil, nil, nil))
|
|
}
|
|
|
|
// --- deterministicReader ---
|
|
|
|
func TestDeterministicReader_Determinism(t *testing.T) {
|
|
seed := sha256.Sum256([]byte("test seed"))
|
|
r1 := newDeterministicReader(seed)
|
|
r2 := newDeterministicReader(seed)
|
|
|
|
buf1 := make([]byte, 100)
|
|
buf2 := make([]byte, 100)
|
|
n1, err1 := r1.Read(buf1)
|
|
n2, err2 := r2.Read(buf2)
|
|
require.NoError(t, err1)
|
|
require.NoError(t, err2)
|
|
require.Equal(t, n1, n2)
|
|
require.Equal(t, buf1, buf2)
|
|
}
|
|
|
|
func TestDeterministicReader_LargeRead(t *testing.T) {
|
|
seed := sha256.Sum256([]byte("large read"))
|
|
r := newDeterministicReader(seed)
|
|
|
|
// Read more than 32 bytes (forces state refresh)
|
|
buf := make([]byte, 100)
|
|
n, err := r.Read(buf)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 100, n)
|
|
|
|
// Ensure no zero runs (deterministic but not trivially zero)
|
|
allZero := true
|
|
for _, b := range buf {
|
|
if b != 0 {
|
|
allZero = false
|
|
break
|
|
}
|
|
}
|
|
require.False(t, allZero)
|
|
}
|
|
|
|
// --- kemGas ---
|
|
|
|
func TestKemGas_AllKEMs(t *testing.T) {
|
|
tests := []struct {
|
|
kemID uint16
|
|
gas uint64
|
|
}{
|
|
{KEMP256, GasKEMEncapsP256},
|
|
{KEMP384, GasKEMEncapsP384},
|
|
{KEMP521, GasKEMEncapsP521},
|
|
{KEMX25519, GasKEMEncapsX25519},
|
|
{KEMX25519Kyber768, GasKEMEncapsX25519Kyber768},
|
|
{KEMXWing, GasKEMEncapsXWing},
|
|
{0xFFFF, GasKEMEncapsX25519}, // unknown defaults to X25519
|
|
}
|
|
for _, tt := range tests {
|
|
require.Equal(t, tt.gas, kemGas(tt.kemID))
|
|
}
|
|
}
|
|
|
|
// --- isKyberKEM ---
|
|
|
|
func TestIsKyberKEM(t *testing.T) {
|
|
require.True(t, isKyberKEM(KEMX25519Kyber768))
|
|
require.True(t, isKyberKEM(KEMXWing))
|
|
require.False(t, isKyberKEM(KEMX25519))
|
|
require.False(t, isKyberKEM(KEMP256))
|
|
}
|
|
|
|
// --- parseSuite edge cases ---
|
|
|
|
func TestParseSuite_TooShort(t *testing.T) {
|
|
p := HPKEPrecompile
|
|
_, err := p.parseSuite([]byte{0x00, 0x01, 0x02})
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestParseSuite_InvalidKEM(t *testing.T) {
|
|
p := HPKEPrecompile
|
|
input := []byte{0xFF, 0xFF, 0x00, 0x01, 0x00, 0x01}
|
|
_, err := p.parseSuite(input)
|
|
require.Error(t, err)
|
|
require.ErrorIs(t, err, ErrInvalidCipherSuite)
|
|
}
|
|
|
|
func TestParseSuite_InvalidKDF(t *testing.T) {
|
|
p := HPKEPrecompile
|
|
input := []byte{0x00, 0x20, 0xFF, 0xFF, 0x00, 0x01} // valid KEM, invalid KDF
|
|
_, err := p.parseSuite(input)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestParseSuite_InvalidAEAD(t *testing.T) {
|
|
p := HPKEPrecompile
|
|
input := []byte{0x00, 0x20, 0x00, 0x01, 0xFF, 0xFF} // valid KEM+KDF, invalid AEAD
|
|
_, err := p.parseSuite(input)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestParseSuite_AllKEMs(t *testing.T) {
|
|
p := HPKEPrecompile
|
|
kems := []uint16{KEMP256, KEMP384, KEMP521, KEMX25519, KEMX25519Kyber768, KEMXWing}
|
|
for _, kemID := range kems {
|
|
input := make([]byte, 6)
|
|
binary.BigEndian.PutUint16(input[0:], kemID)
|
|
input[2], input[3] = 0x00, 0x01 // KDF_HKDF_SHA256
|
|
input[4], input[5] = 0x00, 0x01 // AEAD_AES128GCM
|
|
_, err := p.parseSuite(input)
|
|
require.NoError(t, err, "KEM 0x%04x", kemID)
|
|
}
|
|
}
|
|
|
|
func TestParseSuite_AllKDFs(t *testing.T) {
|
|
p := HPKEPrecompile
|
|
kdfs := []uint16{0x0001, 0x0002, 0x0003}
|
|
for _, kdf := range kdfs {
|
|
input := make([]byte, 6)
|
|
input[0], input[1] = 0x00, 0x20 // KEMX25519
|
|
binary.BigEndian.PutUint16(input[2:], kdf)
|
|
input[4], input[5] = 0x00, 0x01 // AEAD_AES128GCM
|
|
_, err := p.parseSuite(input)
|
|
require.NoError(t, err, "KDF 0x%04x", kdf)
|
|
}
|
|
}
|
|
|
|
func TestParseSuite_AllAEADs(t *testing.T) {
|
|
p := HPKEPrecompile
|
|
aeads := []uint16{0x0001, 0x0002, 0x0003}
|
|
for _, aead := range aeads {
|
|
input := make([]byte, 6)
|
|
input[0], input[1] = 0x00, 0x20 // KEMX25519
|
|
input[2], input[3] = 0x00, 0x01 // KDF_HKDF_SHA256
|
|
binary.BigEndian.PutUint16(input[4:], aead)
|
|
_, err := p.parseSuite(input)
|
|
require.NoError(t, err, "AEAD 0x%04x", aead)
|
|
}
|
|
}
|
|
|
|
// --- parseSealParams edge cases ---
|
|
|
|
func TestParseSealParams_TooShort(t *testing.T) {
|
|
p := HPKEPrecompile
|
|
_, err := p.parseSealParams([]byte{0x00})
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestParseSealParams_ShortRecipientPK(t *testing.T) {
|
|
p := HPKEPrecompile
|
|
input := make([]byte, 6+SeedSize+2)
|
|
input[0], input[1] = 0x00, 0x20 // KEMX25519
|
|
input[2], input[3] = 0x00, 0x01 // KDF
|
|
input[4], input[5] = 0x00, 0x01 // AEAD
|
|
// seed (32 bytes zero)
|
|
// pkLen = 100 but no data
|
|
input[6+SeedSize] = 0
|
|
input[6+SeedSize+1] = 100
|
|
_, err := p.parseSealParams(input)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestParseSealParams_ShortInfoLen(t *testing.T) {
|
|
p := HPKEPrecompile
|
|
suite := hpke.NewSuite(hpke.KEM_X25519_HKDF_SHA256, hpke.KDF_HKDF_SHA256, hpke.AEAD_AES128GCM)
|
|
kemID, _, _ := suite.Params()
|
|
pkSize := kemID.Scheme().PublicKeySize()
|
|
|
|
// Build input with pk but missing info length
|
|
input := make([]byte, 6+SeedSize+2+pkSize)
|
|
input[0], input[1] = 0x00, 0x20 // KEMX25519
|
|
input[2], input[3] = 0x00, 0x01
|
|
input[4], input[5] = 0x00, 0x01
|
|
binary.BigEndian.PutUint16(input[6+SeedSize:], uint16(pkSize))
|
|
_, err := p.parseSealParams(input)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestParseSealParams_ShortInfoData(t *testing.T) {
|
|
p := HPKEPrecompile
|
|
suite := hpke.NewSuite(hpke.KEM_X25519_HKDF_SHA256, hpke.KDF_HKDF_SHA256, hpke.AEAD_AES128GCM)
|
|
kemID, _, _ := suite.Params()
|
|
pkSize := kemID.Scheme().PublicKeySize()
|
|
|
|
input := make([]byte, 6+SeedSize+2+pkSize+2)
|
|
input[0], input[1] = 0x00, 0x20
|
|
input[2], input[3] = 0x00, 0x01
|
|
input[4], input[5] = 0x00, 0x01
|
|
binary.BigEndian.PutUint16(input[6+SeedSize:], uint16(pkSize))
|
|
// info len = 10 but no data
|
|
offset := 6 + SeedSize + 2 + pkSize
|
|
binary.BigEndian.PutUint16(input[offset:], 10)
|
|
_, err := p.parseSealParams(input)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestParseSealParams_ShortAADLen(t *testing.T) {
|
|
p := HPKEPrecompile
|
|
suite := hpke.NewSuite(hpke.KEM_X25519_HKDF_SHA256, hpke.KDF_HKDF_SHA256, hpke.AEAD_AES128GCM)
|
|
kemID, _, _ := suite.Params()
|
|
pkSize := kemID.Scheme().PublicKeySize()
|
|
|
|
// input with pk + infoLen=0 but missing aadLen
|
|
input := make([]byte, 6+SeedSize+2+pkSize+2)
|
|
input[0], input[1] = 0x00, 0x20
|
|
input[2], input[3] = 0x00, 0x01
|
|
input[4], input[5] = 0x00, 0x01
|
|
binary.BigEndian.PutUint16(input[6+SeedSize:], uint16(pkSize))
|
|
// info len = 0 -> next should be aad len, but not enough room
|
|
_, err := p.parseSealParams(input)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestParseSealParams_ShortAADData(t *testing.T) {
|
|
p := HPKEPrecompile
|
|
suite := hpke.NewSuite(hpke.KEM_X25519_HKDF_SHA256, hpke.KDF_HKDF_SHA256, hpke.AEAD_AES128GCM)
|
|
kemID, _, _ := suite.Params()
|
|
pkSize := kemID.Scheme().PublicKeySize()
|
|
|
|
input := make([]byte, 6+SeedSize+2+pkSize+2+2)
|
|
input[0], input[1] = 0x00, 0x20
|
|
input[2], input[3] = 0x00, 0x01
|
|
input[4], input[5] = 0x00, 0x01
|
|
binary.BigEndian.PutUint16(input[6+SeedSize:], uint16(pkSize))
|
|
offset := 6 + SeedSize + 2 + pkSize
|
|
binary.BigEndian.PutUint16(input[offset:], 0) // info len = 0
|
|
binary.BigEndian.PutUint16(input[offset+2:], 10) // aad len = 10 but no data
|
|
_, err := p.parseSealParams(input)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
// --- RequiredGas edge cases ---
|
|
|
|
func TestRequiredGas_EmptyInput(t *testing.T) {
|
|
require.Equal(t, uint64(0), HPKEPrecompile.RequiredGas(nil))
|
|
}
|
|
|
|
func TestRequiredGas_UnknownOp(t *testing.T) {
|
|
require.Equal(t, uint64(0), HPKEPrecompile.RequiredGas([]byte{0xFF}))
|
|
}
|
|
|
|
func TestRequiredGas_SealShortInput(t *testing.T) {
|
|
// Op = Seal but too short for suite+seed — exact value fixed by contract.go.
|
|
gas := HPKEPrecompile.RequiredGas([]byte{OpSingleShotSeal, 0x00})
|
|
require.Greater(t, gas, uint64(0), "short seal input still consumes gas")
|
|
}
|
|
|
|
func TestRequiredGas_SealWithKEM(t *testing.T) {
|
|
input := make([]byte, 1+6+SeedSize+6)
|
|
input[0] = OpSingleShotSeal
|
|
input[1], input[2] = 0x00, 0x10 // KEMP256
|
|
input[3], input[4] = 0x00, 0x01
|
|
input[5], input[6] = 0x00, 0x01
|
|
gas := HPKEPrecompile.RequiredGas(input)
|
|
require.True(t, gas >= GasKEMEncapsP256+GasKDFExtract+GasAEADBase)
|
|
}
|
|
|
|
// --- Run edge cases ---
|
|
|
|
func TestRun_EmptyInput(t *testing.T) {
|
|
_, _, err := HPKEPrecompile.Run(nil, common.Address{}, ContractAddress, nil, 1_000_000, true)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestRun_UnknownOp(t *testing.T) {
|
|
_, _, err := HPKEPrecompile.Run(nil, common.Address{}, ContractAddress, []byte{0xFF}, 1_000_000, true)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestRun_SealInvalidPK(t *testing.T) {
|
|
seed := sha256.Sum256([]byte("invalid pk test"))
|
|
sealData := buildSealInput(KEMX25519, 0x0001, 0x0001, make([]byte, 32), seed, nil, nil, []byte("plaintext"))
|
|
input := append([]byte{OpSingleShotSeal}, sealData...)
|
|
_, _, err := HPKEPrecompile.Run(nil, common.Address{}, ContractAddress, input, 1_000_000, true)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestRun_SealValid_X25519(t *testing.T) {
|
|
suite := hpke.NewSuite(hpke.KEM_X25519_HKDF_SHA256, hpke.KDF_HKDF_SHA256, hpke.AEAD_AES128GCM)
|
|
kemID, _, _ := suite.Params()
|
|
pk, _, err := kemID.Scheme().GenerateKeyPair()
|
|
require.NoError(t, err)
|
|
pkBytes, err := pk.MarshalBinary()
|
|
require.NoError(t, err)
|
|
|
|
seed := sha256.Sum256([]byte("deterministic seed"))
|
|
sealData := buildSealInput(KEMX25519, 0x0001, 0x0001, pkBytes, seed, []byte("info"), []byte("aad"), []byte("hello"))
|
|
input := append([]byte{OpSingleShotSeal}, sealData...)
|
|
result, _, err := HPKEPrecompile.Run(nil, common.Address{}, ContractAddress, input, 1_000_000, true)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, result)
|
|
require.True(t, len(result) > 0)
|
|
}
|
|
|
|
func TestRun_SealValid_P256(t *testing.T) {
|
|
suite := hpke.NewSuite(hpke.KEM_P256_HKDF_SHA256, hpke.KDF_HKDF_SHA256, hpke.AEAD_AES128GCM)
|
|
kemID, _, _ := suite.Params()
|
|
pk, _, err := kemID.Scheme().GenerateKeyPair()
|
|
require.NoError(t, err)
|
|
pkBytes, err := pk.MarshalBinary()
|
|
require.NoError(t, err)
|
|
|
|
seed := sha256.Sum256([]byte("p256 seed"))
|
|
sealData := buildSealInput(KEMP256, 0x0001, 0x0001, pkBytes, seed, nil, nil, []byte("encrypted"))
|
|
input := append([]byte{OpSingleShotSeal}, sealData...)
|
|
result, _, err := HPKEPrecompile.Run(nil, common.Address{}, ContractAddress, input, 1_000_000, true)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, result)
|
|
}
|
|
|
|
func TestRun_SealValid_P384(t *testing.T) {
|
|
suite := hpke.NewSuite(hpke.KEM_P384_HKDF_SHA384, hpke.KDF_HKDF_SHA384, hpke.AEAD_AES256GCM)
|
|
kemID, _, _ := suite.Params()
|
|
pk, _, err := kemID.Scheme().GenerateKeyPair()
|
|
require.NoError(t, err)
|
|
pkBytes, err := pk.MarshalBinary()
|
|
require.NoError(t, err)
|
|
|
|
seed := sha256.Sum256([]byte("p384 seed"))
|
|
sealData := buildSealInput(KEMP384, 0x0002, 0x0002, pkBytes, seed, nil, nil, []byte("data"))
|
|
input := append([]byte{OpSingleShotSeal}, sealData...)
|
|
result, _, err := HPKEPrecompile.Run(nil, common.Address{}, ContractAddress, input, 1_000_000, true)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, result)
|
|
}
|
|
|
|
func TestRun_SealValid_P521(t *testing.T) {
|
|
suite := hpke.NewSuite(hpke.KEM_P521_HKDF_SHA512, hpke.KDF_HKDF_SHA512, hpke.AEAD_ChaCha20Poly1305)
|
|
kemID, _, _ := suite.Params()
|
|
pk, _, err := kemID.Scheme().GenerateKeyPair()
|
|
require.NoError(t, err)
|
|
pkBytes, err := pk.MarshalBinary()
|
|
require.NoError(t, err)
|
|
|
|
seed := sha256.Sum256([]byte("p521 seed"))
|
|
sealData := buildSealInput(KEMP521, 0x0003, 0x0003, pkBytes, seed, nil, nil, []byte("secret"))
|
|
input := append([]byte{OpSingleShotSeal}, sealData...)
|
|
result, _, err := HPKEPrecompile.Run(nil, common.Address{}, ContractAddress, input, 1_000_000, true)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, result)
|
|
}
|
|
|
|
func TestRun_SealDeterministic(t *testing.T) {
|
|
suite := hpke.NewSuite(hpke.KEM_X25519_HKDF_SHA256, hpke.KDF_HKDF_SHA256, hpke.AEAD_AES128GCM)
|
|
kemID, _, _ := suite.Params()
|
|
pk, _, err := kemID.Scheme().GenerateKeyPair()
|
|
require.NoError(t, err)
|
|
pkBytes, err := pk.MarshalBinary()
|
|
require.NoError(t, err)
|
|
|
|
seed := sha256.Sum256([]byte("deterministic"))
|
|
sealData := buildSealInput(KEMX25519, 0x0001, 0x0001, pkBytes, seed, nil, nil, []byte("test"))
|
|
input := append([]byte{OpSingleShotSeal}, sealData...)
|
|
|
|
r1, _, err1 := HPKEPrecompile.Run(nil, common.Address{}, ContractAddress, input, 1_000_000, true)
|
|
r2, _, err2 := HPKEPrecompile.Run(nil, common.Address{}, ContractAddress, input, 1_000_000, true)
|
|
require.NoError(t, err1)
|
|
require.NoError(t, err2)
|
|
require.Equal(t, r1, r2)
|
|
}
|
|
|
|
// buildSealInput is defined in contract_test.go
|