mirror of
https://github.com/luxfi/precompile.git
synced 2026-07-27 03:33:45 +00:00
The precompile derived the FHE secret key from a fixed in-source seed (public secret = anyone decrypts offline) + performFHEDecrypt ignored caller (no ACL). FIX: remove the secret key entirely (keygen seed/secretKey/decryptor gone); public+bootstrap keys installed via SetNetworkKeys (F-Chain DKG); decrypt + sealOutput are now ACL-gated (owner-only) and FAIL CLOSED (ErrThresholdDecryptionRequired) — never return plaintext from a local key. Encrypt + all homomorphic compute ops (the 46 callers' safe path) unchanged (public bootstrap key). Real threshold decrypt = F-Chain via Warp (follow-up). Security tests prove: unauthorized decrypt rejected, no plaintext ever leaked.
716 lines
20 KiB
Go
716 lines
20 KiB
Go
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
|
//go:build cgo
|
|
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package fhe
|
|
|
|
import (
|
|
"math/big"
|
|
"testing"
|
|
|
|
"github.com/holiman/uint256"
|
|
"github.com/luxfi/fhe"
|
|
"github.com/luxfi/geth/common"
|
|
"github.com/luxfi/geth/core/tracing"
|
|
ethtypes "github.com/luxfi/geth/core/types"
|
|
"github.com/luxfi/precompile/contract"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// testStateDB is a minimal in-memory StateDB for FHE tests.
|
|
type testStateDB struct {
|
|
state map[common.Address]map[common.Hash]common.Hash
|
|
}
|
|
|
|
func newTestStateDB() *testStateDB {
|
|
return &testStateDB{state: make(map[common.Address]map[common.Hash]common.Hash)}
|
|
}
|
|
|
|
func (s *testStateDB) GetState(addr common.Address, key common.Hash) common.Hash {
|
|
if m, ok := s.state[addr]; ok {
|
|
return m[key]
|
|
}
|
|
return common.Hash{}
|
|
}
|
|
|
|
func (s *testStateDB) SetState(addr common.Address, key, value common.Hash) common.Hash {
|
|
if s.state[addr] == nil {
|
|
s.state[addr] = make(map[common.Hash]common.Hash)
|
|
}
|
|
prev := s.state[addr][key]
|
|
s.state[addr][key] = value
|
|
return prev
|
|
}
|
|
|
|
func (s *testStateDB) SetNonce(common.Address, uint64, tracing.NonceChangeReason) {}
|
|
func (s *testStateDB) GetNonce(common.Address) uint64 { return 0 }
|
|
func (s *testStateDB) GetBalance(common.Address) *uint256.Int { return uint256.NewInt(0) }
|
|
func (s *testStateDB) AddBalance(common.Address, *uint256.Int, tracing.BalanceChangeReason) uint256.Int {
|
|
return *uint256.NewInt(0)
|
|
}
|
|
func (s *testStateDB) SubBalance(common.Address, *uint256.Int, tracing.BalanceChangeReason) uint256.Int {
|
|
return *uint256.NewInt(0)
|
|
}
|
|
func (s *testStateDB) GetBalanceMultiCoin(common.Address, common.Hash) *big.Int { return big.NewInt(0) }
|
|
func (s *testStateDB) AddBalanceMultiCoin(common.Address, common.Hash, *big.Int) {}
|
|
func (s *testStateDB) SubBalanceMultiCoin(common.Address, common.Hash, *big.Int) {}
|
|
func (s *testStateDB) CreateAccount(common.Address) {}
|
|
func (s *testStateDB) Exist(common.Address) bool { return false }
|
|
func (s *testStateDB) AddLog(*ethtypes.Log) {}
|
|
func (s *testStateDB) Logs() []*ethtypes.Log { return nil }
|
|
func (s *testStateDB) GetPredicateStorageSlots(common.Address, int) ([]byte, bool) {
|
|
return nil, false
|
|
}
|
|
func (s *testStateDB) TxHash() common.Hash { return common.Hash{} }
|
|
func (s *testStateDB) Snapshot() int { return 0 }
|
|
func (s *testStateDB) RevertToSnapshot(int) {}
|
|
|
|
var _ contract.StateDB = (*testStateDB)(nil)
|
|
|
|
// TestTFHEInitialization tests that the precompile installs PUBLIC key material
|
|
// only. There is deliberately no secret key and no decryptor in the precompile;
|
|
// decryption is an F-Chain threshold ceremony.
|
|
func TestTFHEInitialization(t *testing.T) {
|
|
err := initTFHE()
|
|
require.NoError(t, err, "network key installation should succeed")
|
|
require.NotNil(t, evaluator, "evaluator (bootstrap key) should be installed")
|
|
require.NotNil(t, encryptor, "public-key encryptor should be installed")
|
|
require.NotNil(t, publicKey, "network public key should be installed")
|
|
require.NotNil(t, bootstrapKey, "network bootstrap key should be installed")
|
|
require.True(t, HasNetworkKeys(), "precompile should report installed keys")
|
|
}
|
|
|
|
// TestFheTypeMapping tests FHE type constant to TFHE type mapping
|
|
func TestFheTypeMapping(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
fheType uint8
|
|
expected fhe.FheUintType
|
|
}{
|
|
{"bool", TypeEbool, fhe.FheBool},
|
|
{"uint8", TypeEuint8, fhe.FheUint8},
|
|
{"uint16", TypeEuint16, fhe.FheUint16},
|
|
{"uint32", TypeEuint32, fhe.FheUint32},
|
|
{"uint64", TypeEuint64, fhe.FheUint64},
|
|
{"uint128", TypeEuint128, fhe.FheUint128},
|
|
{"uint256", TypeEuint256, fhe.FheUint256},
|
|
{"address", TypeEaddress, fhe.FheUint160},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := fheTypeToTFHEType(tt.fheType)
|
|
require.Equal(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestTrivialEncryptDecrypt tests encrypt-decrypt roundtrip
|
|
func TestTrivialEncryptDecrypt(t *testing.T) {
|
|
err := initTFHE()
|
|
require.NoError(t, err)
|
|
|
|
tests := []struct {
|
|
name string
|
|
value uint64
|
|
fheType uint8
|
|
}{
|
|
{"zero_uint8", 0, TypeEuint8},
|
|
{"one_uint8", 1, TypeEuint8},
|
|
{"max_uint8", 255, TypeEuint8},
|
|
{"uint32_42", 42, TypeEuint32},
|
|
{"uint64_large", 12345678, TypeEuint64},
|
|
{"bool_true", 1, TypeEbool},
|
|
{"bool_false", 0, TypeEbool},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Encrypt
|
|
ct := tfheTrivialEncrypt(big.NewInt(int64(tt.value)), tt.fheType)
|
|
require.NotNil(t, ct, "encryption should succeed")
|
|
require.Greater(t, len(ct), 0, "ciphertext should not be empty")
|
|
|
|
// Decrypt
|
|
decrypted := tfheDecrypt(ct, tt.fheType)
|
|
require.NotNil(t, decrypted)
|
|
require.Equal(t, tt.value, decrypted.Uint64(), "decrypted value should match")
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestBitCiphertextSerialization tests BitCiphertext serialization roundtrip
|
|
func TestBitCiphertextSerialization(t *testing.T) {
|
|
err := initTFHE()
|
|
require.NoError(t, err)
|
|
|
|
// Encrypt a value
|
|
value := uint64(42)
|
|
ct := tfheTrivialEncrypt(big.NewInt(int64(value)), TypeEuint8)
|
|
require.NotNil(t, ct)
|
|
|
|
// Deserialize to BitCiphertext and back
|
|
bc := deserializeBitCiphertext(ct)
|
|
require.NotNil(t, bc)
|
|
|
|
serialized := serializeBitCiphertext(bc)
|
|
require.NotNil(t, serialized)
|
|
|
|
// Deserialize again and verify
|
|
bc2 := deserializeBitCiphertext(serialized)
|
|
require.NotNil(t, bc2)
|
|
require.Equal(t, bc.NumBits(), bc2.NumBits())
|
|
}
|
|
|
|
// TestFHEAdd tests homomorphic addition
|
|
func TestFHEAdd(t *testing.T) {
|
|
err := initTFHE()
|
|
require.NoError(t, err)
|
|
|
|
tests := []struct {
|
|
name string
|
|
a, b uint64
|
|
fheType uint8
|
|
expected uint64
|
|
}{
|
|
{"zero_plus_zero", 0, 0, TypeEuint8, 0},
|
|
{"one_plus_one", 1, 1, TypeEuint8, 2},
|
|
{"3_plus_5", 3, 5, TypeEuint8, 8},
|
|
{"overflow_uint8", 200, 100, TypeEuint8, 44}, // 300 mod 256 = 44
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ctA := tfheTrivialEncrypt(big.NewInt(int64(tt.a)), tt.fheType)
|
|
ctB := tfheTrivialEncrypt(big.NewInt(int64(tt.b)), tt.fheType)
|
|
require.NotNil(t, ctA)
|
|
require.NotNil(t, ctB)
|
|
|
|
result := tfheAdd(ctA, ctB, tt.fheType)
|
|
require.NotNil(t, result, "addition should succeed")
|
|
|
|
decrypted := tfheDecrypt(result, tt.fheType)
|
|
require.Equal(t, tt.expected, decrypted.Uint64())
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestFHESub tests homomorphic subtraction
|
|
func TestFHESub(t *testing.T) {
|
|
err := initTFHE()
|
|
require.NoError(t, err)
|
|
|
|
tests := []struct {
|
|
name string
|
|
a, b uint64
|
|
fheType uint8
|
|
expected uint64
|
|
}{
|
|
{"5_minus_3", 5, 3, TypeEuint8, 2},
|
|
{"10_minus_0", 10, 0, TypeEuint8, 10},
|
|
{"underflow", 3, 5, TypeEuint8, 254}, // Two's complement wrap
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ctA := tfheTrivialEncrypt(big.NewInt(int64(tt.a)), tt.fheType)
|
|
ctB := tfheTrivialEncrypt(big.NewInt(int64(tt.b)), tt.fheType)
|
|
require.NotNil(t, ctA)
|
|
require.NotNil(t, ctB)
|
|
|
|
result := tfheSub(ctA, ctB, tt.fheType)
|
|
require.NotNil(t, result, "subtraction should succeed")
|
|
|
|
decrypted := tfheDecrypt(result, tt.fheType)
|
|
require.Equal(t, tt.expected, decrypted.Uint64())
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestFHEMul tests homomorphic multiplication
|
|
// Note: Schoolbook multiplication is O(n^2) in bootstrapping operations,
|
|
// so this test is very slow (~2 min for uint8). Skip in short mode.
|
|
func TestFHEMul(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping slow FHE multiplication test in short mode")
|
|
}
|
|
|
|
err := initTFHE()
|
|
require.NoError(t, err)
|
|
|
|
tests := []struct {
|
|
name string
|
|
a, b uint64
|
|
fheType uint8
|
|
expected uint64
|
|
}{
|
|
{"zero_times_anything", 0, 42, TypeEuint8, 0},
|
|
{"one_times_anything", 1, 42, TypeEuint8, 42},
|
|
{"3_times_4", 3, 4, TypeEuint8, 12},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ctA := tfheTrivialEncrypt(big.NewInt(int64(tt.a)), tt.fheType)
|
|
ctB := tfheTrivialEncrypt(big.NewInt(int64(tt.b)), tt.fheType)
|
|
require.NotNil(t, ctA)
|
|
require.NotNil(t, ctB)
|
|
|
|
result := tfheMul(ctA, ctB, tt.fheType)
|
|
require.NotNil(t, result, "multiplication should succeed")
|
|
|
|
decrypted := tfheDecrypt(result, tt.fheType)
|
|
require.Equal(t, tt.expected, decrypted.Uint64())
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestFHEComparisons tests comparison operations
|
|
// Note: Comparison on n-bit integers requires O(n) bootstrapping operations per compare.
|
|
// With 13 test cases on uint8, this takes several minutes. Skip in short mode.
|
|
func TestFHEComparisons(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping slow FHE comparison tests in short mode")
|
|
}
|
|
|
|
err := initTFHE()
|
|
require.NoError(t, err)
|
|
|
|
tests := []struct {
|
|
name string
|
|
op string
|
|
a, b uint64
|
|
expected bool
|
|
}{
|
|
{"lt_true", "lt", 3, 5, true},
|
|
{"lt_false", "lt", 5, 3, false},
|
|
{"lt_equal", "lt", 3, 3, false},
|
|
{"le_true", "le", 3, 5, true},
|
|
{"le_equal", "le", 3, 3, true},
|
|
{"le_false", "le", 5, 3, false},
|
|
{"gt_true", "gt", 5, 3, true},
|
|
{"gt_false", "gt", 3, 5, false},
|
|
{"gt_equal", "gt", 3, 3, false},
|
|
{"ge_true", "ge", 5, 3, true},
|
|
{"ge_equal", "ge", 3, 3, true},
|
|
{"ge_false", "ge", 3, 5, false},
|
|
{"eq_true", "eq", 5, 5, true},
|
|
{"eq_false", "eq", 3, 5, false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ctA := tfheTrivialEncrypt(big.NewInt(int64(tt.a)), TypeEuint8)
|
|
ctB := tfheTrivialEncrypt(big.NewInt(int64(tt.b)), TypeEuint8)
|
|
require.NotNil(t, ctA)
|
|
require.NotNil(t, ctB)
|
|
|
|
var result []byte
|
|
switch tt.op {
|
|
case "lt":
|
|
result = tfheLt(ctA, ctB, TypeEuint8)
|
|
case "le":
|
|
result = tfheLe(ctA, ctB, TypeEuint8)
|
|
case "gt":
|
|
result = tfheGt(ctA, ctB, TypeEuint8)
|
|
case "ge":
|
|
result = tfheGe(ctA, ctB, TypeEuint8)
|
|
case "eq":
|
|
result = tfheEq(ctA, ctB, TypeEuint8)
|
|
}
|
|
require.NotNil(t, result, "%s should succeed", tt.op)
|
|
|
|
// Comparison returns encrypted bool
|
|
decrypted := tfheDecrypt(result, TypeEbool)
|
|
expectedVal := uint64(0)
|
|
if tt.expected {
|
|
expectedVal = 1
|
|
}
|
|
require.Equal(t, expectedVal, decrypted.Uint64())
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestFHEBitwise tests bitwise operations
|
|
func TestFHEBitwise(t *testing.T) {
|
|
err := initTFHE()
|
|
require.NoError(t, err)
|
|
|
|
tests := []struct {
|
|
name string
|
|
op string
|
|
a, b uint64
|
|
expected uint64
|
|
}{
|
|
{"and_0x0F_0xF0", "and", 0x0F, 0xF0, 0x00},
|
|
{"and_0xFF_0x0F", "and", 0xFF, 0x0F, 0x0F},
|
|
{"or_0x0F_0xF0", "or", 0x0F, 0xF0, 0xFF},
|
|
{"or_0x00_0x00", "or", 0x00, 0x00, 0x00},
|
|
{"xor_0xFF_0xFF", "xor", 0xFF, 0xFF, 0x00},
|
|
{"xor_0x0F_0xF0", "xor", 0x0F, 0xF0, 0xFF},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ctA := tfheTrivialEncrypt(big.NewInt(int64(tt.a)), TypeEuint8)
|
|
ctB := tfheTrivialEncrypt(big.NewInt(int64(tt.b)), TypeEuint8)
|
|
require.NotNil(t, ctA)
|
|
require.NotNil(t, ctB)
|
|
|
|
var result []byte
|
|
switch tt.op {
|
|
case "and":
|
|
result = tfheAnd(ctA, ctB, TypeEuint8)
|
|
case "or":
|
|
result = tfheOr(ctA, ctB, TypeEuint8)
|
|
case "xor":
|
|
result = tfheXor(ctA, ctB, TypeEuint8)
|
|
}
|
|
require.NotNil(t, result, "%s should succeed", tt.op)
|
|
|
|
decrypted := tfheDecrypt(result, TypeEuint8)
|
|
require.Equal(t, tt.expected, decrypted.Uint64())
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestFHENot tests bitwise NOT
|
|
func TestFHENot(t *testing.T) {
|
|
err := initTFHE()
|
|
require.NoError(t, err)
|
|
|
|
tests := []struct {
|
|
name string
|
|
value uint64
|
|
expected uint64
|
|
}{
|
|
{"not_0x00", 0x00, 0xFF},
|
|
{"not_0xFF", 0xFF, 0x00},
|
|
{"not_0x0F", 0x0F, 0xF0},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ct := tfheTrivialEncrypt(big.NewInt(int64(tt.value)), TypeEuint8)
|
|
require.NotNil(t, ct)
|
|
|
|
result := tfheNot(ct, TypeEuint8)
|
|
require.NotNil(t, result)
|
|
|
|
decrypted := tfheDecrypt(result, TypeEuint8)
|
|
require.Equal(t, tt.expected, decrypted.Uint64())
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestFHEShift tests shift operations
|
|
func TestFHEShift(t *testing.T) {
|
|
err := initTFHE()
|
|
require.NoError(t, err)
|
|
|
|
tests := []struct {
|
|
name string
|
|
op string
|
|
value uint64
|
|
shift int
|
|
expected uint64
|
|
}{
|
|
{"shl_1_by_1", "shl", 1, 1, 2},
|
|
{"shl_1_by_3", "shl", 1, 3, 8},
|
|
{"shl_overflow", "shl", 128, 1, 0}, // 128 << 1 = 256, but uint8 wraps to 0
|
|
{"shr_8_by_1", "shr", 8, 1, 4},
|
|
{"shr_255_by_4", "shr", 255, 4, 15},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ct := tfheTrivialEncrypt(big.NewInt(int64(tt.value)), TypeEuint8)
|
|
require.NotNil(t, ct)
|
|
|
|
var result []byte
|
|
switch tt.op {
|
|
case "shl":
|
|
result = tfheShl(ct, tt.shift, TypeEuint8)
|
|
case "shr":
|
|
result = tfheShr(ct, tt.shift, TypeEuint8)
|
|
}
|
|
require.NotNil(t, result, "%s should succeed", tt.op)
|
|
|
|
decrypted := tfheDecrypt(result, TypeEuint8)
|
|
require.Equal(t, tt.expected, decrypted.Uint64())
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestFHEMinMax tests min/max operations
|
|
// Note: These operations are computationally expensive (involve comparison + selection)
|
|
// so we only test a single case of each
|
|
func TestFHEMinMax(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping slow FHE min/max tests in short mode")
|
|
}
|
|
|
|
err := initTFHE()
|
|
require.NoError(t, err)
|
|
|
|
// Test min: min(5, 3) = 3
|
|
t.Run("min", func(t *testing.T) {
|
|
ctA := tfheTrivialEncrypt(big.NewInt(5), TypeEuint8)
|
|
ctB := tfheTrivialEncrypt(big.NewInt(3), TypeEuint8)
|
|
require.NotNil(t, ctA)
|
|
require.NotNil(t, ctB)
|
|
|
|
result := tfheMin(ctA, ctB, TypeEuint8)
|
|
require.NotNil(t, result, "min should succeed")
|
|
|
|
decrypted := tfheDecrypt(result, TypeEuint8)
|
|
require.Equal(t, uint64(3), decrypted.Uint64())
|
|
})
|
|
|
|
// Test max: max(3, 5) = 5
|
|
t.Run("max", func(t *testing.T) {
|
|
ctA := tfheTrivialEncrypt(big.NewInt(3), TypeEuint8)
|
|
ctB := tfheTrivialEncrypt(big.NewInt(5), TypeEuint8)
|
|
require.NotNil(t, ctA)
|
|
require.NotNil(t, ctB)
|
|
|
|
result := tfheMax(ctA, ctB, TypeEuint8)
|
|
require.NotNil(t, result, "max should succeed")
|
|
|
|
decrypted := tfheDecrypt(result, TypeEuint8)
|
|
require.Equal(t, uint64(5), decrypted.Uint64())
|
|
})
|
|
}
|
|
|
|
// TestFHEScalarAdd tests scalar addition
|
|
func TestFHEScalarAdd(t *testing.T) {
|
|
err := initTFHE()
|
|
require.NoError(t, err)
|
|
|
|
tests := []struct {
|
|
name string
|
|
value uint64
|
|
scalar uint64
|
|
expected uint64
|
|
}{
|
|
{"add_0", 5, 0, 5},
|
|
{"add_1", 5, 1, 6},
|
|
{"add_10", 5, 10, 15},
|
|
{"overflow", 250, 10, 4}, // 260 mod 256 = 4
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ct := tfheTrivialEncrypt(big.NewInt(int64(tt.value)), TypeEuint8)
|
|
require.NotNil(t, ct)
|
|
|
|
result := tfheScalarAdd(ct, tt.scalar, TypeEuint8)
|
|
require.NotNil(t, result)
|
|
|
|
decrypted := tfheDecrypt(result, TypeEuint8)
|
|
require.Equal(t, tt.expected, decrypted.Uint64())
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestFHEScalarMul tests scalar multiplication
|
|
func TestFHEScalarMul(t *testing.T) {
|
|
err := initTFHE()
|
|
require.NoError(t, err)
|
|
|
|
tests := []struct {
|
|
name string
|
|
value uint64
|
|
scalar uint64
|
|
expected uint64
|
|
}{
|
|
{"mul_0", 5, 0, 0},
|
|
{"mul_1", 5, 1, 5},
|
|
{"mul_2", 5, 2, 10},
|
|
{"mul_10", 5, 10, 50},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ct := tfheTrivialEncrypt(big.NewInt(int64(tt.value)), TypeEuint8)
|
|
require.NotNil(t, ct)
|
|
|
|
result := tfheScalarMul(ct, tt.scalar, TypeEuint8)
|
|
require.NotNil(t, result)
|
|
|
|
decrypted := tfheDecrypt(result, TypeEuint8)
|
|
require.Equal(t, tt.expected, decrypted.Uint64())
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestFHECast tests type casting
|
|
func TestFHECast(t *testing.T) {
|
|
err := initTFHE()
|
|
require.NoError(t, err)
|
|
|
|
// Cast uint8 to uint16
|
|
ct8 := tfheTrivialEncrypt(big.NewInt(42), TypeEuint8)
|
|
require.NotNil(t, ct8)
|
|
|
|
ct16 := tfheCast(ct8, TypeEuint8, TypeEuint16)
|
|
require.NotNil(t, ct16)
|
|
|
|
decrypted := tfheDecrypt(ct16, TypeEuint16)
|
|
require.Equal(t, uint64(42), decrypted.Uint64())
|
|
}
|
|
|
|
// TestFHERandom tests random number generation
|
|
func TestFHERandom(t *testing.T) {
|
|
err := initTFHE()
|
|
require.NoError(t, err)
|
|
|
|
// Generate encrypted random
|
|
result := tfheRandom(TypeEuint8, 12345)
|
|
require.NotNil(t, result)
|
|
|
|
// Decrypt to verify it produces a value
|
|
decrypted := tfheDecrypt(result, TypeEuint8)
|
|
require.NotNil(t, decrypted)
|
|
// Value should be in range [0, 255] for uint8
|
|
require.True(t, decrypted.Uint64() <= 255)
|
|
}
|
|
|
|
// TestGetNetworkPublicKey tests public key retrieval
|
|
func TestGetNetworkPublicKey(t *testing.T) {
|
|
err := initTFHE()
|
|
require.NoError(t, err)
|
|
|
|
pk := tfheGetNetworkPublicKey()
|
|
require.NotNil(t, pk)
|
|
require.Greater(t, len(pk), 0)
|
|
}
|
|
|
|
// TestCiphertextStore tests the StateDB-backed ciphertext storage
|
|
func TestCiphertextStore(t *testing.T) {
|
|
err := initTFHE()
|
|
require.NoError(t, err)
|
|
db := newTestStateDB()
|
|
|
|
// Create ciphertext
|
|
ct := tfheTrivialEncrypt(big.NewInt(42), TypeEuint8)
|
|
require.NotNil(t, ct)
|
|
|
|
// Store it
|
|
handle := storeCiphertext(db, ct, TypeEuint8)
|
|
require.NotEqual(t, common.Hash{}, handle)
|
|
|
|
// Retrieve it
|
|
retrieved, ctType, ok := getCiphertext(db, handle)
|
|
require.True(t, ok)
|
|
require.Equal(t, TypeEuint8, ctType)
|
|
require.Equal(t, ct, retrieved)
|
|
}
|
|
|
|
// TestPerformFHEOperation tests the high-level operation dispatcher
|
|
func TestPerformFHEOperation(t *testing.T) {
|
|
err := initTFHE()
|
|
require.NoError(t, err)
|
|
db := newTestStateDB()
|
|
|
|
caller := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
|
|
|
// Store two ciphertexts
|
|
ct1 := tfheTrivialEncrypt(big.NewInt(10), TypeEuint8)
|
|
ct2 := tfheTrivialEncrypt(big.NewInt(3), TypeEuint8)
|
|
|
|
handle1 := storeCiphertext(db, ct1, TypeEuint8)
|
|
handle2 := storeCiphertext(db, ct2, TypeEuint8)
|
|
|
|
// Test add operation
|
|
resultHandle := performFHEOperation(db, "add", handle1, handle2, caller)
|
|
require.NotEqual(t, common.Hash{}, resultHandle)
|
|
|
|
resultCt, _, ok := getCiphertext(db, resultHandle)
|
|
require.True(t, ok)
|
|
|
|
decrypted := tfheDecrypt(resultCt, TypeEuint8)
|
|
require.Equal(t, uint64(13), decrypted.Uint64())
|
|
}
|
|
|
|
// TestEncryptValue tests the encryptValue helper
|
|
func TestEncryptValue(t *testing.T) {
|
|
err := initTFHE()
|
|
require.NoError(t, err)
|
|
db := newTestStateDB()
|
|
|
|
caller := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
|
|
|
handle := encryptValue(db, 42, TypeEuint8, caller)
|
|
require.NotEqual(t, common.Hash{}, handle)
|
|
|
|
ct, ctType, ok := getCiphertext(db, handle)
|
|
require.True(t, ok)
|
|
require.Equal(t, TypeEuint8, ctType)
|
|
|
|
decrypted := tfheDecrypt(ct, ctType)
|
|
require.Equal(t, uint64(42), decrypted.Uint64())
|
|
}
|
|
|
|
// TestEncryptAddress tests address encryption
|
|
func TestEncryptAddress(t *testing.T) {
|
|
err := initTFHE()
|
|
require.NoError(t, err)
|
|
db := newTestStateDB()
|
|
|
|
caller := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
|
// Use a small address that fits in uint64 for testing
|
|
// Full 160-bit addresses require proper radix integer encryption
|
|
addr := common.HexToAddress("0x0000000000000000000000000000000012345678")
|
|
|
|
handle := encryptAddress(db, addr, caller)
|
|
require.NotEqual(t, common.Hash{}, handle)
|
|
|
|
ct, ctType, ok := getCiphertext(db, handle)
|
|
require.True(t, ok)
|
|
require.Equal(t, TypeEaddress, ctType)
|
|
|
|
// Decrypt and verify the lower 64 bits
|
|
decrypted := tfheDecrypt(ct, ctType)
|
|
require.NotNil(t, decrypted)
|
|
// Verify the address value is preserved (lower 64 bits)
|
|
require.Equal(t, uint64(0x12345678), decrypted.Uint64())
|
|
}
|
|
|
|
// TestFHEVerify tests ciphertext verification
|
|
func TestFHEVerify(t *testing.T) {
|
|
err := initTFHE()
|
|
require.NoError(t, err)
|
|
|
|
// Create valid ciphertext
|
|
ct := tfheTrivialEncrypt(big.NewInt(42), TypeEuint8)
|
|
require.NotNil(t, ct)
|
|
|
|
// Verify should succeed for valid ciphertext
|
|
valid := tfheVerify(ct, TypeEuint8)
|
|
require.True(t, valid)
|
|
|
|
// Verify should fail for garbage data
|
|
garbage := []byte{0x00, 0x01, 0x02, 0x03}
|
|
invalid := tfheVerify(garbage, TypeEuint8)
|
|
require.False(t, invalid)
|
|
}
|
|
|
|
// TestNetworkPublicKeyStable verifies the precompile publishes a stable network
|
|
// PUBLIC key (so all validators encrypt under the same key) and that the key is
|
|
// the public half only — the secret key lives off-precompile, on the F-Chain
|
|
// threshold set.
|
|
func TestNetworkPublicKeyStable(t *testing.T) {
|
|
err := initTFHE()
|
|
require.NoError(t, err)
|
|
|
|
pk1 := tfheGetNetworkPublicKey()
|
|
require.NotNil(t, pk1)
|
|
require.Greater(t, len(pk1), 0)
|
|
|
|
// Repeated reads return identical bytes (same installed public key).
|
|
pk2 := tfheGetNetworkPublicKey()
|
|
require.Equal(t, pk1, pk2, "published network public key must be stable across calls")
|
|
}
|