Files
precompile/ai/ai_mining_test.go
zeekay 8ee8523f6e zk,ai: real fflonk pairing verify, Pallas point validation, TEE attestation
D1: verifyTEESignature now parses the receipt's X.509 chain, verifies it to an
embedded Intel SGX Root CA at the report's timestamp, and verifies the report
signature by the leaf key (ECDSA raw/ASN.1, Ed25519, RSA). Fails closed; never
returns true by default.

B2: fflonkVerify now performs two real BLS12-381 KZG pairing openings bound by
Fiat-Shamir so every commitment, opening proof, claim and bound evaluation is
load-bearing; off-curve/wrong-subgroup G1 points rejected via gnark-crypto.
isValidCompressedPoint now decodes the sign bit and checks x<p and that x^3+5
is a QR mod the Pallas field (on-curve == in-subgroup for cofactor 1).

Tests: KAT happy paths + tampered/wrong-signer/truncated/expired/empty-root and
soundness negatives; halo2 fixtures emit real on-curve points.
2026-06-26 18:15:00 -07:00

463 lines
12 KiB
Go

// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package ai
import (
"crypto/rand"
"math/big"
"testing"
"github.com/cloudflare/circl/sign/mldsa/mldsa65"
)
// MockStateDB implements StateDB interface for testing
type MockStateDB struct {
storage map[[20]byte]map[[32]byte][32]byte
}
func NewMockStateDB() *MockStateDB {
return &MockStateDB{
storage: make(map[[20]byte]map[[32]byte][32]byte),
}
}
func (m *MockStateDB) GetState(addr [20]byte, key [32]byte) [32]byte {
if m.storage[addr] == nil {
return [32]byte{}
}
return m.storage[addr][key]
}
func (m *MockStateDB) SetState(addr [20]byte, key [32]byte, value [32]byte) {
if m.storage[addr] == nil {
m.storage[addr] = make(map[[32]byte][32]byte)
}
m.storage[addr][key] = value
}
func TestVerifyMLDSA65(t *testing.T) {
// Generate test key pair
pub, priv, err := mldsa65.GenerateKey(rand.Reader)
if err != nil {
t.Fatalf("Failed to generate key: %v", err)
}
pubBytes, err := pub.MarshalBinary()
if err != nil {
t.Fatalf("Failed to marshal public key: %v", err)
}
// Test message
message := []byte("test message for AI mining verification")
// Sign message
sig := make([]byte, MLDSA65SignatureSize)
if err := mldsa65.SignTo(priv, message, nil, true, sig); err != nil {
t.Fatalf("Failed to sign message: %v", err)
}
// Verify valid signature
valid, err := VerifyMLDSA(pubBytes, message, sig)
if err != nil {
t.Fatalf("VerifyMLDSA error: %v", err)
}
if !valid {
t.Error("Expected valid signature")
}
// Verify with wrong message
wrongMessage := []byte("wrong message")
valid, err = VerifyMLDSA(pubBytes, wrongMessage, sig)
if err != nil {
t.Fatalf("VerifyMLDSA error: %v", err)
}
if valid {
t.Error("Expected invalid signature for wrong message")
}
// Verify with corrupted signature
corruptedSig := make([]byte, len(sig))
copy(corruptedSig, sig)
corruptedSig[0] ^= 0xFF
valid, err = VerifyMLDSA(pubBytes, message, corruptedSig)
if err != nil {
t.Fatalf("VerifyMLDSA error: %v", err)
}
if valid {
t.Error("Expected invalid signature for corrupted signature")
}
}
func TestVerifyMLDSAInvalidSizes(t *testing.T) {
// Invalid public key size
_, err := VerifyMLDSA([]byte("short"), []byte("msg"), []byte("sig"))
if err != ErrInvalidPublicKeySize {
t.Errorf("Expected ErrInvalidPublicKeySize, got %v", err)
}
// Valid public key size but invalid signature size
pubkey := make([]byte, MLDSA65PublicKeySize)
_, err = VerifyMLDSA(pubkey, []byte("msg"), []byte("short sig"))
if err != ErrInvalidSignatureSize {
t.Errorf("Expected ErrInvalidSignatureSize, got %v", err)
}
}
func TestCalculateReward(t *testing.T) {
tests := []struct {
name string
privacyLevel uint16
computeMinutes uint32
chainId uint64
expectedMult uint64 // expected multiplier in basis points
}{
{"Public 10 min", PrivacyPublic, 10, 96369, 2500},
{"Private 10 min", PrivacyPrivate, 10, 96369, 5000},
{"Confidential 10 min", PrivacyConfidential, 10, 96369, 10000},
{"Sovereign 10 min", PrivacySovereign, 10, 96369, 15000},
{"Public 60 min", PrivacyPublic, 60, 36963, 2500},
{"Confidential 120 min", PrivacyConfidential, 120, 200200, 10000},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
deviceId := [32]byte{1, 2, 3}
nonce := [32]byte{4, 5, 6}
timestamp := uint64(1700000000)
workProof := BuildWorkProof(deviceId, nonce, timestamp, tt.privacyLevel, tt.computeMinutes, nil)
reward, err := CalculateReward(workProof, tt.chainId)
if err != nil {
t.Fatalf("CalculateReward error: %v", err)
}
// Expected: baseReward * computeMinutes * multiplier / 10000
expected := new(big.Int).Set(baseRewardPerMinute)
expected.Mul(expected, big.NewInt(int64(tt.computeMinutes)))
expected.Mul(expected, big.NewInt(int64(tt.expectedMult)))
expected.Div(expected, big.NewInt(10000))
if reward.Cmp(expected) != 0 {
t.Errorf("Expected reward %s, got %s", expected.String(), reward.String())
}
})
}
}
func TestCalculateRewardInvalidInput(t *testing.T) {
// Too short work proof
_, err := CalculateReward([]byte("short"), 96369)
if err != ErrInvalidWorkProof {
t.Errorf("Expected ErrInvalidWorkProof, got %v", err)
}
// Invalid privacy level
deviceId := [32]byte{1}
nonce := [32]byte{2}
workProof := BuildWorkProof(deviceId, nonce, 1700000000, 99, 10, nil) // Invalid privacy level 99
_, err = CalculateReward(workProof, 96369)
if err != ErrInvalidPrivacyLevel {
t.Errorf("Expected ErrInvalidPrivacyLevel, got %v", err)
}
}
func TestComputeWorkId(t *testing.T) {
deviceId := [32]byte{0x01, 0x02, 0x03}
nonce := [32]byte{0x04, 0x05, 0x06}
chainId := uint64(96369)
workId1 := ComputeWorkId(deviceId, nonce, chainId)
workId2 := ComputeWorkId(deviceId, nonce, chainId)
// Same inputs should produce same output
if workId1 != workId2 {
t.Error("Same inputs should produce same work ID")
}
// Different inputs should produce different output
differentNonce := [32]byte{0x07, 0x08, 0x09}
workId3 := ComputeWorkId(deviceId, differentNonce, chainId)
if workId1 == workId3 {
t.Error("Different inputs should produce different work ID")
}
// Different chain ID should produce different output
workId4 := ComputeWorkId(deviceId, nonce, 36963)
if workId1 == workId4 {
t.Error("Different chain ID should produce different work ID")
}
}
func TestSpentSet(t *testing.T) {
stateDB := NewMockStateDB()
workId := [32]byte{0x01, 0x02, 0x03}
// Initially not spent
if IsSpent(stateDB, workId) {
t.Error("Work ID should not be spent initially")
}
// Mark as spent
err := MarkSpent(stateDB, workId)
if err != nil {
t.Fatalf("MarkSpent error: %v", err)
}
// Now should be spent
if !IsSpent(stateDB, workId) {
t.Error("Work ID should be spent after marking")
}
// Trying to mark again should fail
err = MarkSpent(stateDB, workId)
if err != ErrWorkAlreadySpent {
t.Errorf("Expected ErrWorkAlreadySpent, got %v", err)
}
}
func TestParseWorkProof(t *testing.T) {
expectedDeviceId := [32]byte{0x01, 0x02, 0x03}
expectedNonce := [32]byte{0x04, 0x05, 0x06}
expectedTimestamp := uint64(1700000000)
expectedPrivacy := uint16(PrivacyConfidential)
expectedComputeMins := uint32(60)
expectedTEEQuote := []byte("tee quote data")
workProof := BuildWorkProof(
expectedDeviceId,
expectedNonce,
expectedTimestamp,
expectedPrivacy,
expectedComputeMins,
expectedTEEQuote,
)
deviceId, nonce, timestamp, privacy, computeMins, teeQuote, err := ParseWorkProof(workProof)
if err != nil {
t.Fatalf("ParseWorkProof error: %v", err)
}
if deviceId != expectedDeviceId {
t.Error("Device ID mismatch")
}
if nonce != expectedNonce {
t.Error("Nonce mismatch")
}
if timestamp != expectedTimestamp {
t.Error("Timestamp mismatch")
}
if privacy != expectedPrivacy {
t.Error("Privacy level mismatch")
}
if computeMins != expectedComputeMins {
t.Error("Compute minutes mismatch")
}
if string(teeQuote) != string(expectedTEEQuote) {
t.Error("TEE quote mismatch")
}
}
func TestGetSecurityLevel(t *testing.T) {
tests := []struct {
size int
expected uint8
hasError bool
}{
{MLDSA44PublicKeySize, 2, false},
{MLDSA65PublicKeySize, 3, false},
{MLDSA87PublicKeySize, 5, false},
{100, 0, true},
}
for _, tt := range tests {
pubkey := make([]byte, tt.size)
level, err := GetSecurityLevel(pubkey)
if tt.hasError {
if err == nil {
t.Errorf("Expected error for size %d", tt.size)
}
} else {
if err != nil {
t.Errorf("Unexpected error for size %d: %v", tt.size, err)
}
if level != tt.expected {
t.Errorf("Expected level %d for size %d, got %d", tt.expected, tt.size, level)
}
}
}
}
func TestGetExpectedSignatureSize(t *testing.T) {
tests := []struct {
level uint8
expected int
hasError bool
}{
{2, MLDSA44SignatureSize, false},
{3, MLDSA65SignatureSize, false},
{5, MLDSA87SignatureSize, false},
{1, 0, true},
{4, 0, true},
}
for _, tt := range tests {
size, err := GetExpectedSignatureSize(tt.level)
if tt.hasError {
if err == nil {
t.Errorf("Expected error for level %d", tt.level)
}
} else {
if err != nil {
t.Errorf("Unexpected error for level %d: %v", tt.level, err)
}
if size != tt.expected {
t.Errorf("Expected size %d for level %d, got %d", tt.expected, tt.level, size)
}
}
}
}
func TestGetPrivacyMultiplier(t *testing.T) {
tests := []struct {
level uint16
expected uint64
hasError bool
}{
{PrivacyPublic, 2500, false},
{PrivacyPrivate, 5000, false},
{PrivacyConfidential, 10000, false},
{PrivacySovereign, 15000, false},
{0, 0, true},
{99, 0, true},
}
for _, tt := range tests {
mult, err := GetPrivacyMultiplier(tt.level)
if tt.hasError {
if err == nil {
t.Errorf("Expected error for level %d", tt.level)
}
} else {
if err != nil {
t.Errorf("Unexpected error for level %d: %v", tt.level, err)
}
if mult != tt.expected {
t.Errorf("Expected multiplier %d for level %d, got %d", tt.expected, tt.level, mult)
}
}
}
}
func TestVerifyTEE(t *testing.T) {
// Valid receipt structure (minimal)
deviceId := [32]byte{0x01, 0x02, 0x03}
timestamp := uint64(1700000000)
nonce := uint64(12345)
receipt := make([]byte, 48)
copy(receipt[0:32], deviceId[:])
copy(receipt[32:40], []byte{
byte(timestamp >> 56), byte(timestamp >> 48),
byte(timestamp >> 40), byte(timestamp >> 32),
byte(timestamp >> 24), byte(timestamp >> 16),
byte(timestamp >> 8), byte(timestamp),
})
copy(receipt[40:48], []byte{
byte(nonce >> 56), byte(nonce >> 48),
byte(nonce >> 40), byte(nonce >> 32),
byte(nonce >> 24), byte(nonce >> 16),
byte(nonce >> 8), byte(nonce),
})
signature := make([]byte, 64)
for i := range signature {
signature[i] = byte(i + 1)
}
// A bare 48-byte receipt carries no attestation certificate chain, so real
// verification rejects it. (Previously the stub accepted any >=64-byte
// signature — a forgeable hole.) The genuine happy path with a real chain
// is covered by TestTEEAttestationValid in tee_attest_test.go.
valid, err := VerifyTEE(receipt, signature)
if valid {
t.Error("fake receipt with no certificate chain must be rejected")
}
// Empty receipt should fail
_, err = VerifyTEE([]byte{}, signature)
if err != ErrInvalidTEEReceipt {
t.Errorf("Expected ErrInvalidTEEReceipt, got %v", err)
}
// Short receipt should fail
_, err = VerifyTEE([]byte("short"), signature)
if err != ErrInvalidTEEReceipt {
t.Errorf("Expected ErrInvalidTEEReceipt, got %v", err)
}
}
// Benchmark tests
func BenchmarkComputeWorkId(b *testing.B) {
deviceId := [32]byte{0x01, 0x02, 0x03}
nonce := [32]byte{0x04, 0x05, 0x06}
chainId := uint64(96369)
b.ResetTimer()
for i := 0; i < b.N; i++ {
ComputeWorkId(deviceId, nonce, chainId)
}
}
func BenchmarkCalculateReward(b *testing.B) {
deviceId := [32]byte{0x01}
nonce := [32]byte{0x02}
workProof := BuildWorkProof(deviceId, nonce, 1700000000, PrivacyConfidential, 60, nil)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = CalculateReward(workProof, 96369)
}
}
func BenchmarkIsSpent(b *testing.B) {
stateDB := NewMockStateDB()
workId := [32]byte{0x01, 0x02, 0x03}
b.ResetTimer()
for i := 0; i < b.N; i++ {
IsSpent(stateDB, workId)
}
}
func BenchmarkVerifyMLDSA65(b *testing.B) {
// Generate test key pair
pub, priv, err := mldsa65.GenerateKey(rand.Reader)
if err != nil {
b.Fatalf("Failed to generate key: %v", err)
}
pubBytes, err := pub.MarshalBinary()
if err != nil {
b.Fatalf("Failed to marshal public key: %v", err)
}
message := []byte("test message for benchmarking")
sig := make([]byte, MLDSA65SignatureSize)
if err := mldsa65.SignTo(priv, message, nil, true, sig); err != nil {
b.Fatalf("Failed to sign: %v", err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = VerifyMLDSA(pubBytes, message, sig)
}
}