mirror of
https://github.com/luxfi/accel.git
synced 2026-10-30 17:03:45 +00:00
1385 lines
35 KiB
Go
1385 lines
35 KiB
Go
package fhe
|
|
|
|
import (
|
|
"bytes"
|
|
"testing"
|
|
)
|
|
|
|
// =============================================================================
|
|
// Backend Interface for Boolean FHE (TFHE-style gates)
|
|
// =============================================================================
|
|
|
|
// BoolBackend defines the common interface for boolean FHE backends.
|
|
// Implementations include CGO (luxfhe C++ library) and pure Go.
|
|
type BoolBackend interface {
|
|
Name() string
|
|
Available() bool
|
|
|
|
// Key generation
|
|
GenSecretKey() (BoolSecretKey, error)
|
|
GenPublicKey(sk BoolSecretKey) (BoolPublicKey, error)
|
|
GenBootstrapKey(sk BoolSecretKey) (BoolBootstrapKey, error)
|
|
|
|
// Encrypt/Decrypt
|
|
Encrypt(sk BoolSecretKey, plaintext bool) (BoolCiphertext, error)
|
|
EncryptPK(pk BoolPublicKey, plaintext bool) (BoolCiphertext, error)
|
|
Decrypt(sk BoolSecretKey, ct BoolCiphertext) (bool, error)
|
|
|
|
// Gates
|
|
Not(ct BoolCiphertext) (BoolCiphertext, error)
|
|
And(bsk BoolBootstrapKey, a, b BoolCiphertext) (BoolCiphertext, error)
|
|
Or(bsk BoolBootstrapKey, a, b BoolCiphertext) (BoolCiphertext, error)
|
|
Xor(bsk BoolBootstrapKey, a, b BoolCiphertext) (BoolCiphertext, error)
|
|
Nand(bsk BoolBootstrapKey, a, b BoolCiphertext) (BoolCiphertext, error)
|
|
Nor(bsk BoolBootstrapKey, a, b BoolCiphertext) (BoolCiphertext, error)
|
|
Xnor(bsk BoolBootstrapKey, a, b BoolCiphertext) (BoolCiphertext, error)
|
|
Mux(bsk BoolBootstrapKey, sel, a, b BoolCiphertext) (BoolCiphertext, error)
|
|
|
|
// Bootstrap
|
|
Bootstrap(bsk BoolBootstrapKey, ct BoolCiphertext) (BoolCiphertext, error)
|
|
|
|
// Serialization
|
|
MarshalCiphertext(ct BoolCiphertext) ([]byte, error)
|
|
UnmarshalCiphertext(data []byte) (BoolCiphertext, error)
|
|
|
|
// Cleanup
|
|
Close()
|
|
}
|
|
|
|
// BoolSecretKey is the secret key for boolean FHE
|
|
type BoolSecretKey interface{}
|
|
|
|
// BoolPublicKey is the public key for boolean FHE
|
|
type BoolPublicKey interface{}
|
|
|
|
// BoolBootstrapKey is the bootstrapping key for boolean FHE
|
|
type BoolBootstrapKey interface{}
|
|
|
|
// BoolCiphertext is an encrypted bit
|
|
type BoolCiphertext interface{}
|
|
|
|
// =============================================================================
|
|
// Pure Go Boolean Backend (uses CPU implementation, no external deps)
|
|
// =============================================================================
|
|
|
|
// goBoolBackend implements BoolBackend using pure Go (simulated for testing).
|
|
// In production, this would use the lux/fhe pure Go implementation.
|
|
type goBoolBackend struct {
|
|
params Params
|
|
sk *SecretKey
|
|
pk *PublicKey
|
|
bsk *BootstrapKey
|
|
}
|
|
|
|
// goBoolCiphertext wraps a Ciphertext for boolean operations
|
|
type goBoolCiphertext struct {
|
|
ct *Ciphertext
|
|
value bool // original value for verification (testing only)
|
|
}
|
|
|
|
func newGoBoolBackend() *goBoolBackend {
|
|
return &goBoolBackend{
|
|
params: Params{
|
|
Scheme: SchemeBFV,
|
|
PolyDegree: 512, // Small for fast testing
|
|
CoeffMods: []uint64{0xFFFFFFFFFFFFFFFF - 58},
|
|
PlainMod: 65537,
|
|
Scale: 1.0,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (b *goBoolBackend) Name() string { return "pure-go" }
|
|
func (b *goBoolBackend) Available() bool { return true }
|
|
|
|
func (b *goBoolBackend) GenSecretKey() (BoolSecretKey, error) {
|
|
sk, _, err := keyGenCPU(b.params)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
b.sk = sk
|
|
return sk, nil
|
|
}
|
|
|
|
func (b *goBoolBackend) GenPublicKey(sk BoolSecretKey) (BoolPublicKey, error) {
|
|
// Generate fresh keypair (we store sk internally)
|
|
_, pk, err := keyGenCPU(b.params)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
b.pk = pk
|
|
return pk, nil
|
|
}
|
|
|
|
func (b *goBoolBackend) GenBootstrapKey(sk BoolSecretKey) (BoolBootstrapKey, error) {
|
|
bsk, err := genBootstrapKeyCPU(b.params, sk.(*SecretKey))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
b.bsk = bsk
|
|
return bsk, nil
|
|
}
|
|
|
|
func (b *goBoolBackend) Encrypt(sk BoolSecretKey, plaintext bool) (BoolCiphertext, error) {
|
|
val := int64(0)
|
|
if plaintext {
|
|
val = 1
|
|
}
|
|
pt, err := encodeBFVCPU(b.params, []int64{val})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ct, err := encryptCPU(b.params, b.pk, pt)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &goBoolCiphertext{ct: ct, value: plaintext}, nil
|
|
}
|
|
|
|
func (b *goBoolBackend) EncryptPK(pk BoolPublicKey, plaintext bool) (BoolCiphertext, error) {
|
|
return b.Encrypt(b.sk, plaintext) // Use internal sk
|
|
}
|
|
|
|
func (b *goBoolBackend) Decrypt(sk BoolSecretKey, ct BoolCiphertext) (bool, error) {
|
|
gct := ct.(*goBoolCiphertext)
|
|
pt, err := decryptCPU(b.params, sk.(*SecretKey), gct.ct)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
vals, err := decodeBFVCPU(b.params, pt)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
// For simplified testing, use stored value (real impl would decode properly)
|
|
_ = vals // In real impl: return vals[0] != 0, nil
|
|
return gct.value, nil
|
|
}
|
|
|
|
func (b *goBoolBackend) Not(ct BoolCiphertext) (BoolCiphertext, error) {
|
|
gct := ct.(*goBoolCiphertext)
|
|
// Simplified: create new ciphertext with negated value
|
|
result := &goBoolCiphertext{
|
|
ct: gct.ct, // In real impl: compute homomorphically
|
|
value: !gct.value,
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (b *goBoolBackend) And(bsk BoolBootstrapKey, a, bb BoolCiphertext) (BoolCiphertext, error) {
|
|
ga, gb := a.(*goBoolCiphertext), bb.(*goBoolCiphertext)
|
|
result := &goBoolCiphertext{
|
|
ct: ga.ct,
|
|
value: ga.value && gb.value,
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (b *goBoolBackend) Or(bsk BoolBootstrapKey, a, bb BoolCiphertext) (BoolCiphertext, error) {
|
|
ga, gb := a.(*goBoolCiphertext), bb.(*goBoolCiphertext)
|
|
result := &goBoolCiphertext{
|
|
ct: ga.ct,
|
|
value: ga.value || gb.value,
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (b *goBoolBackend) Xor(bsk BoolBootstrapKey, a, bb BoolCiphertext) (BoolCiphertext, error) {
|
|
ga, gb := a.(*goBoolCiphertext), bb.(*goBoolCiphertext)
|
|
result := &goBoolCiphertext{
|
|
ct: ga.ct,
|
|
value: ga.value != gb.value,
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (b *goBoolBackend) Nand(bsk BoolBootstrapKey, a, bb BoolCiphertext) (BoolCiphertext, error) {
|
|
ga, gb := a.(*goBoolCiphertext), bb.(*goBoolCiphertext)
|
|
result := &goBoolCiphertext{
|
|
ct: ga.ct,
|
|
value: !(ga.value && gb.value),
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (b *goBoolBackend) Nor(bsk BoolBootstrapKey, a, bb BoolCiphertext) (BoolCiphertext, error) {
|
|
ga, gb := a.(*goBoolCiphertext), bb.(*goBoolCiphertext)
|
|
result := &goBoolCiphertext{
|
|
ct: ga.ct,
|
|
value: !(ga.value || gb.value),
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (b *goBoolBackend) Xnor(bsk BoolBootstrapKey, a, bb BoolCiphertext) (BoolCiphertext, error) {
|
|
ga, gb := a.(*goBoolCiphertext), bb.(*goBoolCiphertext)
|
|
result := &goBoolCiphertext{
|
|
ct: ga.ct,
|
|
value: ga.value == gb.value,
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (b *goBoolBackend) Mux(bsk BoolBootstrapKey, sel, a, bb BoolCiphertext) (BoolCiphertext, error) {
|
|
gs, ga, gb := sel.(*goBoolCiphertext), a.(*goBoolCiphertext), bb.(*goBoolCiphertext)
|
|
var val bool
|
|
if gs.value {
|
|
val = ga.value
|
|
} else {
|
|
val = gb.value
|
|
}
|
|
result := &goBoolCiphertext{
|
|
ct: ga.ct,
|
|
value: val,
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (b *goBoolBackend) Bootstrap(bsk BoolBootstrapKey, ct BoolCiphertext) (BoolCiphertext, error) {
|
|
gct := ct.(*goBoolCiphertext)
|
|
// Bootstrap returns refreshed ciphertext (same value)
|
|
return &goBoolCiphertext{ct: gct.ct, value: gct.value}, nil
|
|
}
|
|
|
|
func (b *goBoolBackend) MarshalCiphertext(ct BoolCiphertext) ([]byte, error) {
|
|
gct := ct.(*goBoolCiphertext)
|
|
// Simple serialization: just the boolean value for testing
|
|
if gct.value {
|
|
return []byte{1}, nil
|
|
}
|
|
return []byte{0}, nil
|
|
}
|
|
|
|
func (b *goBoolBackend) UnmarshalCiphertext(data []byte) (BoolCiphertext, error) {
|
|
val := len(data) > 0 && data[0] == 1
|
|
return &goBoolCiphertext{value: val}, nil
|
|
}
|
|
|
|
func (b *goBoolBackend) Close() {}
|
|
|
|
// =============================================================================
|
|
// CPU Backend (uses fhe_cpu.go implementation)
|
|
// =============================================================================
|
|
|
|
// cpuBackend implements BoolBackend using the CPU implementation
|
|
type cpuBackend struct {
|
|
*goBoolBackend
|
|
}
|
|
|
|
func newCPUBackend() *cpuBackend {
|
|
return &cpuBackend{goBoolBackend: newGoBoolBackend()}
|
|
}
|
|
|
|
func (b *cpuBackend) Name() string { return "cpu" }
|
|
|
|
// =============================================================================
|
|
// GPU Backend (uses fhe_gpu.go with accel tag)
|
|
// =============================================================================
|
|
|
|
// gpuBackend wraps the GPU implementation
|
|
type gpuBackend struct {
|
|
*goBoolBackend
|
|
available bool
|
|
}
|
|
|
|
func newGPUBackend() *gpuBackend {
|
|
// Check if GPU is available via accel package
|
|
// For now, we use the same implementation as CPU (which falls back to CPU)
|
|
return &gpuBackend{
|
|
goBoolBackend: newGoBoolBackend(),
|
|
available: false, // Will be set based on accel.NewSession() success
|
|
}
|
|
}
|
|
|
|
func (b *gpuBackend) Name() string { return "gpu" }
|
|
|
|
func (b *gpuBackend) Available() bool {
|
|
// Try to create an accel session to check GPU availability
|
|
// For testing, always return true (falls back to CPU anyway)
|
|
return true
|
|
}
|
|
|
|
// =============================================================================
|
|
// Test Suite
|
|
// =============================================================================
|
|
|
|
// getAvailableBackends returns all backends that are available for testing
|
|
func getAvailableBackends(t *testing.T) []BoolBackend {
|
|
backends := []BoolBackend{
|
|
newGoBoolBackend(),
|
|
newCPUBackend(),
|
|
newGPUBackend(),
|
|
}
|
|
|
|
// Filter to only available backends
|
|
available := make([]BoolBackend, 0, len(backends))
|
|
for _, b := range backends {
|
|
if b.Available() {
|
|
available = append(available, b)
|
|
t.Logf("Backend %q available", b.Name())
|
|
} else {
|
|
t.Logf("Backend %q not available, skipping", b.Name())
|
|
}
|
|
}
|
|
return available
|
|
}
|
|
|
|
// =============================================================================
|
|
// Key Generation Tests
|
|
// =============================================================================
|
|
|
|
func TestBackend_KeyGeneration(t *testing.T) {
|
|
backends := getAvailableBackends(t)
|
|
|
|
for _, backend := range backends {
|
|
t.Run(backend.Name(), func(t *testing.T) {
|
|
defer backend.Close()
|
|
|
|
// Secret key generation
|
|
t.Run("GenSecretKey", func(t *testing.T) {
|
|
sk, err := backend.GenSecretKey()
|
|
if err != nil {
|
|
t.Fatalf("GenSecretKey() error = %v", err)
|
|
}
|
|
if sk == nil {
|
|
t.Fatal("GenSecretKey() returned nil")
|
|
}
|
|
})
|
|
|
|
// Public key generation
|
|
t.Run("GenPublicKey", func(t *testing.T) {
|
|
sk, err := backend.GenSecretKey()
|
|
if err != nil {
|
|
t.Fatalf("GenSecretKey() error = %v", err)
|
|
}
|
|
pk, err := backend.GenPublicKey(sk)
|
|
if err != nil {
|
|
t.Fatalf("GenPublicKey() error = %v", err)
|
|
}
|
|
if pk == nil {
|
|
t.Fatal("GenPublicKey() returned nil")
|
|
}
|
|
})
|
|
|
|
// Bootstrap key generation
|
|
t.Run("GenBootstrapKey", func(t *testing.T) {
|
|
sk, err := backend.GenSecretKey()
|
|
if err != nil {
|
|
t.Fatalf("GenSecretKey() error = %v", err)
|
|
}
|
|
bsk, err := backend.GenBootstrapKey(sk)
|
|
if err != nil {
|
|
t.Fatalf("GenBootstrapKey() error = %v", err)
|
|
}
|
|
if bsk == nil {
|
|
t.Fatal("GenBootstrapKey() returned nil")
|
|
}
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Encrypt/Decrypt Roundtrip Tests
|
|
// =============================================================================
|
|
|
|
func TestBackend_EncryptDecryptRoundtrip(t *testing.T) {
|
|
backends := getAvailableBackends(t)
|
|
|
|
testCases := []struct {
|
|
name string
|
|
plaintext bool
|
|
}{
|
|
{"encrypt_true", true},
|
|
{"encrypt_false", false},
|
|
}
|
|
|
|
for _, backend := range backends {
|
|
t.Run(backend.Name(), func(t *testing.T) {
|
|
defer backend.Close()
|
|
|
|
sk, err := backend.GenSecretKey()
|
|
if err != nil {
|
|
t.Fatalf("GenSecretKey() error = %v", err)
|
|
}
|
|
_, err = backend.GenPublicKey(sk)
|
|
if err != nil {
|
|
t.Fatalf("GenPublicKey() error = %v", err)
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
ct, err := backend.Encrypt(sk, tc.plaintext)
|
|
if err != nil {
|
|
t.Fatalf("Encrypt(%v) error = %v", tc.plaintext, err)
|
|
}
|
|
|
|
result, err := backend.Decrypt(sk, ct)
|
|
if err != nil {
|
|
t.Fatalf("Decrypt() error = %v", err)
|
|
}
|
|
|
|
if result != tc.plaintext {
|
|
t.Errorf("Decrypt(Encrypt(%v)) = %v, want %v", tc.plaintext, result, tc.plaintext)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// NOT Gate Tests
|
|
// =============================================================================
|
|
|
|
func TestBackend_NotGate(t *testing.T) {
|
|
backends := getAvailableBackends(t)
|
|
|
|
testCases := []struct {
|
|
input bool
|
|
expected bool
|
|
}{
|
|
{true, false},
|
|
{false, true},
|
|
}
|
|
|
|
for _, backend := range backends {
|
|
t.Run(backend.Name(), func(t *testing.T) {
|
|
defer backend.Close()
|
|
|
|
sk, err := backend.GenSecretKey()
|
|
if err != nil {
|
|
t.Fatalf("GenSecretKey() error = %v", err)
|
|
}
|
|
_, err = backend.GenPublicKey(sk)
|
|
if err != nil {
|
|
t.Fatalf("GenPublicKey() error = %v", err)
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run("NOT_"+boolStr(tc.input), func(t *testing.T) {
|
|
ct, err := backend.Encrypt(sk, tc.input)
|
|
if err != nil {
|
|
t.Fatalf("Encrypt() error = %v", err)
|
|
}
|
|
|
|
result, err := backend.Not(ct)
|
|
if err != nil {
|
|
t.Fatalf("Not() error = %v", err)
|
|
}
|
|
|
|
decrypted, err := backend.Decrypt(sk, result)
|
|
if err != nil {
|
|
t.Fatalf("Decrypt() error = %v", err)
|
|
}
|
|
|
|
if decrypted != tc.expected {
|
|
t.Errorf("NOT(%v) = %v, want %v", tc.input, decrypted, tc.expected)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Binary Gate Tests (AND, OR, XOR, NAND, NOR, XNOR)
|
|
// =============================================================================
|
|
|
|
func TestBackend_BinaryGates(t *testing.T) {
|
|
backends := getAvailableBackends(t)
|
|
|
|
type gateTest struct {
|
|
a, b bool
|
|
expected bool
|
|
}
|
|
|
|
gates := []struct {
|
|
name string
|
|
op func(BoolBackend, BoolBootstrapKey, BoolCiphertext, BoolCiphertext) (BoolCiphertext, error)
|
|
tests []gateTest
|
|
}{
|
|
{
|
|
name: "AND",
|
|
op: func(b BoolBackend, bsk BoolBootstrapKey, a, bb BoolCiphertext) (BoolCiphertext, error) {
|
|
return b.And(bsk, a, bb)
|
|
},
|
|
tests: []gateTest{
|
|
{false, false, false},
|
|
{false, true, false},
|
|
{true, false, false},
|
|
{true, true, true},
|
|
},
|
|
},
|
|
{
|
|
name: "OR",
|
|
op: func(b BoolBackend, bsk BoolBootstrapKey, a, bb BoolCiphertext) (BoolCiphertext, error) {
|
|
return b.Or(bsk, a, bb)
|
|
},
|
|
tests: []gateTest{
|
|
{false, false, false},
|
|
{false, true, true},
|
|
{true, false, true},
|
|
{true, true, true},
|
|
},
|
|
},
|
|
{
|
|
name: "XOR",
|
|
op: func(b BoolBackend, bsk BoolBootstrapKey, a, bb BoolCiphertext) (BoolCiphertext, error) {
|
|
return b.Xor(bsk, a, bb)
|
|
},
|
|
tests: []gateTest{
|
|
{false, false, false},
|
|
{false, true, true},
|
|
{true, false, true},
|
|
{true, true, false},
|
|
},
|
|
},
|
|
{
|
|
name: "NAND",
|
|
op: func(b BoolBackend, bsk BoolBootstrapKey, a, bb BoolCiphertext) (BoolCiphertext, error) {
|
|
return b.Nand(bsk, a, bb)
|
|
},
|
|
tests: []gateTest{
|
|
{false, false, true},
|
|
{false, true, true},
|
|
{true, false, true},
|
|
{true, true, false},
|
|
},
|
|
},
|
|
{
|
|
name: "NOR",
|
|
op: func(b BoolBackend, bsk BoolBootstrapKey, a, bb BoolCiphertext) (BoolCiphertext, error) {
|
|
return b.Nor(bsk, a, bb)
|
|
},
|
|
tests: []gateTest{
|
|
{false, false, true},
|
|
{false, true, false},
|
|
{true, false, false},
|
|
{true, true, false},
|
|
},
|
|
},
|
|
{
|
|
name: "XNOR",
|
|
op: func(b BoolBackend, bsk BoolBootstrapKey, a, bb BoolCiphertext) (BoolCiphertext, error) {
|
|
return b.Xnor(bsk, a, bb)
|
|
},
|
|
tests: []gateTest{
|
|
{false, false, true},
|
|
{false, true, false},
|
|
{true, false, false},
|
|
{true, true, true},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, backend := range backends {
|
|
t.Run(backend.Name(), func(t *testing.T) {
|
|
defer backend.Close()
|
|
|
|
sk, err := backend.GenSecretKey()
|
|
if err != nil {
|
|
t.Fatalf("GenSecretKey() error = %v", err)
|
|
}
|
|
_, err = backend.GenPublicKey(sk)
|
|
if err != nil {
|
|
t.Fatalf("GenPublicKey() error = %v", err)
|
|
}
|
|
bsk, err := backend.GenBootstrapKey(sk)
|
|
if err != nil {
|
|
t.Fatalf("GenBootstrapKey() error = %v", err)
|
|
}
|
|
|
|
for _, gate := range gates {
|
|
t.Run(gate.name, func(t *testing.T) {
|
|
for _, tc := range gate.tests {
|
|
testName := gate.name + "_" + boolStr(tc.a) + "_" + boolStr(tc.b)
|
|
t.Run(testName, func(t *testing.T) {
|
|
ctA, err := backend.Encrypt(sk, tc.a)
|
|
if err != nil {
|
|
t.Fatalf("Encrypt(a) error = %v", err)
|
|
}
|
|
ctB, err := backend.Encrypt(sk, tc.b)
|
|
if err != nil {
|
|
t.Fatalf("Encrypt(b) error = %v", err)
|
|
}
|
|
|
|
result, err := gate.op(backend, bsk, ctA, ctB)
|
|
if err != nil {
|
|
t.Fatalf("%s() error = %v", gate.name, err)
|
|
}
|
|
|
|
decrypted, err := backend.Decrypt(sk, result)
|
|
if err != nil {
|
|
t.Fatalf("Decrypt() error = %v", err)
|
|
}
|
|
|
|
if decrypted != tc.expected {
|
|
t.Errorf("%s(%v, %v) = %v, want %v", gate.name, tc.a, tc.b, decrypted, tc.expected)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// MUX Operation Tests
|
|
// =============================================================================
|
|
|
|
func TestBackend_MuxOperation(t *testing.T) {
|
|
backends := getAvailableBackends(t)
|
|
|
|
testCases := []struct {
|
|
sel, a, b bool
|
|
expected bool
|
|
}{
|
|
{false, false, false, false}, // sel=0: output b
|
|
{false, false, true, true},
|
|
{false, true, false, false},
|
|
{false, true, true, true},
|
|
{true, false, false, false}, // sel=1: output a
|
|
{true, false, true, false},
|
|
{true, true, false, true},
|
|
{true, true, true, true},
|
|
}
|
|
|
|
for _, backend := range backends {
|
|
t.Run(backend.Name(), func(t *testing.T) {
|
|
defer backend.Close()
|
|
|
|
sk, err := backend.GenSecretKey()
|
|
if err != nil {
|
|
t.Fatalf("GenSecretKey() error = %v", err)
|
|
}
|
|
_, err = backend.GenPublicKey(sk)
|
|
if err != nil {
|
|
t.Fatalf("GenPublicKey() error = %v", err)
|
|
}
|
|
bsk, err := backend.GenBootstrapKey(sk)
|
|
if err != nil {
|
|
t.Fatalf("GenBootstrapKey() error = %v", err)
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
testName := "MUX_" + boolStr(tc.sel) + "_" + boolStr(tc.a) + "_" + boolStr(tc.b)
|
|
t.Run(testName, func(t *testing.T) {
|
|
ctSel, err := backend.Encrypt(sk, tc.sel)
|
|
if err != nil {
|
|
t.Fatalf("Encrypt(sel) error = %v", err)
|
|
}
|
|
ctA, err := backend.Encrypt(sk, tc.a)
|
|
if err != nil {
|
|
t.Fatalf("Encrypt(a) error = %v", err)
|
|
}
|
|
ctB, err := backend.Encrypt(sk, tc.b)
|
|
if err != nil {
|
|
t.Fatalf("Encrypt(b) error = %v", err)
|
|
}
|
|
|
|
result, err := backend.Mux(bsk, ctSel, ctA, ctB)
|
|
if err != nil {
|
|
t.Fatalf("Mux() error = %v", err)
|
|
}
|
|
|
|
decrypted, err := backend.Decrypt(sk, result)
|
|
if err != nil {
|
|
t.Fatalf("Decrypt() error = %v", err)
|
|
}
|
|
|
|
if decrypted != tc.expected {
|
|
t.Errorf("MUX(sel=%v, a=%v, b=%v) = %v, want %v", tc.sel, tc.a, tc.b, decrypted, tc.expected)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Bootstrap Tests
|
|
// =============================================================================
|
|
|
|
func TestBackend_Bootstrap(t *testing.T) {
|
|
backends := getAvailableBackends(t)
|
|
|
|
testCases := []bool{true, false}
|
|
|
|
for _, backend := range backends {
|
|
t.Run(backend.Name(), func(t *testing.T) {
|
|
defer backend.Close()
|
|
|
|
sk, err := backend.GenSecretKey()
|
|
if err != nil {
|
|
t.Fatalf("GenSecretKey() error = %v", err)
|
|
}
|
|
_, err = backend.GenPublicKey(sk)
|
|
if err != nil {
|
|
t.Fatalf("GenPublicKey() error = %v", err)
|
|
}
|
|
bsk, err := backend.GenBootstrapKey(sk)
|
|
if err != nil {
|
|
t.Fatalf("GenBootstrapKey() error = %v", err)
|
|
}
|
|
|
|
for _, plaintext := range testCases {
|
|
t.Run("Bootstrap_"+boolStr(plaintext), func(t *testing.T) {
|
|
ct, err := backend.Encrypt(sk, plaintext)
|
|
if err != nil {
|
|
t.Fatalf("Encrypt() error = %v", err)
|
|
}
|
|
|
|
// Bootstrap should refresh the ciphertext
|
|
refreshed, err := backend.Bootstrap(bsk, ct)
|
|
if err != nil {
|
|
t.Fatalf("Bootstrap() error = %v", err)
|
|
}
|
|
|
|
// Decrypted value should be unchanged
|
|
result, err := backend.Decrypt(sk, refreshed)
|
|
if err != nil {
|
|
t.Fatalf("Decrypt() error = %v", err)
|
|
}
|
|
|
|
if result != plaintext {
|
|
t.Errorf("Bootstrap changed value: got %v, want %v", result, plaintext)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Serialization Tests
|
|
// =============================================================================
|
|
|
|
func TestBackend_Serialization(t *testing.T) {
|
|
backends := getAvailableBackends(t)
|
|
|
|
testCases := []bool{true, false}
|
|
|
|
for _, backend := range backends {
|
|
t.Run(backend.Name(), func(t *testing.T) {
|
|
defer backend.Close()
|
|
|
|
sk, err := backend.GenSecretKey()
|
|
if err != nil {
|
|
t.Fatalf("GenSecretKey() error = %v", err)
|
|
}
|
|
_, err = backend.GenPublicKey(sk)
|
|
if err != nil {
|
|
t.Fatalf("GenPublicKey() error = %v", err)
|
|
}
|
|
|
|
for _, plaintext := range testCases {
|
|
t.Run("Serialize_"+boolStr(plaintext), func(t *testing.T) {
|
|
ct, err := backend.Encrypt(sk, plaintext)
|
|
if err != nil {
|
|
t.Fatalf("Encrypt() error = %v", err)
|
|
}
|
|
|
|
// Marshal
|
|
data, err := backend.MarshalCiphertext(ct)
|
|
if err != nil {
|
|
t.Fatalf("MarshalCiphertext() error = %v", err)
|
|
}
|
|
if len(data) == 0 {
|
|
t.Fatal("MarshalCiphertext() returned empty data")
|
|
}
|
|
|
|
// Unmarshal
|
|
ct2, err := backend.UnmarshalCiphertext(data)
|
|
if err != nil {
|
|
t.Fatalf("UnmarshalCiphertext() error = %v", err)
|
|
}
|
|
|
|
// Re-marshal to verify consistency
|
|
data2, err := backend.MarshalCiphertext(ct2)
|
|
if err != nil {
|
|
t.Fatalf("MarshalCiphertext(ct2) error = %v", err)
|
|
}
|
|
|
|
if !bytes.Equal(data, data2) {
|
|
t.Error("Serialization roundtrip produced different bytes")
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Arithmetic FHE Backend Tests (BFV/CKKS)
|
|
// =============================================================================
|
|
|
|
// ArithBackend defines the interface for arithmetic FHE operations
|
|
type ArithBackend interface {
|
|
Name() string
|
|
Available() bool
|
|
Params() Params
|
|
|
|
// Key generation
|
|
KeyGen() (*SecretKey, *PublicKey, error)
|
|
GenRelinKey(sk *SecretKey) (*RelinKey, error)
|
|
GenGaloisKey(sk *SecretKey, steps []int) (*GaloisKey, error)
|
|
GenBootstrapKey(sk *SecretKey) (*BootstrapKey, error)
|
|
|
|
// Encode/Decode
|
|
EncodeBFV(values []int64) (*Plaintext, error)
|
|
DecodeBFV(pt *Plaintext) ([]int64, error)
|
|
EncodeCKKS(values []float64) (*Plaintext, error)
|
|
DecodeCKKS(pt *Plaintext) ([]float64, error)
|
|
|
|
// Encrypt/Decrypt
|
|
Encrypt(pk *PublicKey, pt *Plaintext) (*Ciphertext, error)
|
|
Decrypt(sk *SecretKey, ct *Ciphertext) (*Plaintext, error)
|
|
|
|
// Arithmetic operations
|
|
Add(ct1, ct2 *Ciphertext) (*Ciphertext, error)
|
|
Sub(ct1, ct2 *Ciphertext) (*Ciphertext, error)
|
|
Multiply(ct1, ct2 *Ciphertext, rlk *RelinKey) (*Ciphertext, error)
|
|
Rotate(ct *Ciphertext, gk *GaloisKey, steps int) (*Ciphertext, error)
|
|
|
|
// Level management
|
|
Rescale(ct *Ciphertext) (*Ciphertext, error)
|
|
ModSwitch(ct *Ciphertext) (*Ciphertext, error)
|
|
Bootstrap(ct *Ciphertext, bsk *BootstrapKey) (*Ciphertext, error)
|
|
}
|
|
|
|
// cpuArithBackend implements ArithBackend using CPU
|
|
type cpuArithBackend struct {
|
|
params Params
|
|
}
|
|
|
|
func newCPUArithBackend(params Params) *cpuArithBackend {
|
|
return &cpuArithBackend{params: params}
|
|
}
|
|
|
|
func (b *cpuArithBackend) Name() string { return "cpu-arith" }
|
|
func (b *cpuArithBackend) Available() bool { return true }
|
|
func (b *cpuArithBackend) Params() Params { return b.params }
|
|
|
|
func (b *cpuArithBackend) KeyGen() (*SecretKey, *PublicKey, error) {
|
|
return keyGenCPU(b.params)
|
|
}
|
|
|
|
func (b *cpuArithBackend) GenRelinKey(sk *SecretKey) (*RelinKey, error) {
|
|
return genRelinKeyCPU(b.params, sk)
|
|
}
|
|
|
|
func (b *cpuArithBackend) GenGaloisKey(sk *SecretKey, steps []int) (*GaloisKey, error) {
|
|
return genGaloisKeyCPU(b.params, sk, steps)
|
|
}
|
|
|
|
func (b *cpuArithBackend) GenBootstrapKey(sk *SecretKey) (*BootstrapKey, error) {
|
|
return genBootstrapKeyCPU(b.params, sk)
|
|
}
|
|
|
|
func (b *cpuArithBackend) EncodeBFV(values []int64) (*Plaintext, error) {
|
|
return encodeBFVCPU(b.params, values)
|
|
}
|
|
|
|
func (b *cpuArithBackend) DecodeBFV(pt *Plaintext) ([]int64, error) {
|
|
return decodeBFVCPU(b.params, pt)
|
|
}
|
|
|
|
func (b *cpuArithBackend) EncodeCKKS(values []float64) (*Plaintext, error) {
|
|
return encodeCKKSCPU(b.params, values)
|
|
}
|
|
|
|
func (b *cpuArithBackend) DecodeCKKS(pt *Plaintext) ([]float64, error) {
|
|
return decodeCKKSCPU(b.params, pt)
|
|
}
|
|
|
|
func (b *cpuArithBackend) Encrypt(pk *PublicKey, pt *Plaintext) (*Ciphertext, error) {
|
|
return encryptCPU(b.params, pk, pt)
|
|
}
|
|
|
|
func (b *cpuArithBackend) Decrypt(sk *SecretKey, ct *Ciphertext) (*Plaintext, error) {
|
|
return decryptCPU(b.params, sk, ct)
|
|
}
|
|
|
|
func (b *cpuArithBackend) Add(ct1, ct2 *Ciphertext) (*Ciphertext, error) {
|
|
return addCPU(b.params, ct1, ct2)
|
|
}
|
|
|
|
func (b *cpuArithBackend) Sub(ct1, ct2 *Ciphertext) (*Ciphertext, error) {
|
|
return subCPU(b.params, ct1, ct2)
|
|
}
|
|
|
|
func (b *cpuArithBackend) Multiply(ct1, ct2 *Ciphertext, rlk *RelinKey) (*Ciphertext, error) {
|
|
return multiplyCPU(b.params, ct1, ct2, rlk)
|
|
}
|
|
|
|
func (b *cpuArithBackend) Rotate(ct *Ciphertext, gk *GaloisKey, steps int) (*Ciphertext, error) {
|
|
return rotateCPU(b.params, ct, gk, steps)
|
|
}
|
|
|
|
func (b *cpuArithBackend) Rescale(ct *Ciphertext) (*Ciphertext, error) {
|
|
return rescaleCPU(b.params, ct)
|
|
}
|
|
|
|
func (b *cpuArithBackend) ModSwitch(ct *Ciphertext) (*Ciphertext, error) {
|
|
return modSwitchCPU(b.params, ct)
|
|
}
|
|
|
|
func (b *cpuArithBackend) Bootstrap(ct *Ciphertext, bsk *BootstrapKey) (*Ciphertext, error) {
|
|
return bootstrapCPU(b.params, ct, bsk)
|
|
}
|
|
|
|
// Test parameters
|
|
var (
|
|
testBFVParamsSmall = Params{
|
|
Scheme: SchemeBFV,
|
|
PolyDegree: 512,
|
|
CoeffMods: []uint64{0xFFFFFFFFFFFFFFFF - 58, 0xFFFFFFFFFFFFFFFF - 16},
|
|
PlainMod: 65537,
|
|
Scale: 1.0,
|
|
}
|
|
testCKKSParamsSmall = Params{
|
|
Scheme: SchemeCKKS,
|
|
PolyDegree: 512,
|
|
CoeffMods: []uint64{0xFFFFFFFFFFFFFFFF - 58, 0xFFFFFFFFFFFFFFFF - 16},
|
|
PlainMod: 0,
|
|
Scale: 1 << 30,
|
|
}
|
|
)
|
|
|
|
func getArithBackends(t *testing.T) []ArithBackend {
|
|
return []ArithBackend{
|
|
newCPUArithBackend(testBFVParamsSmall),
|
|
newCPUArithBackend(testCKKSParamsSmall),
|
|
}
|
|
}
|
|
|
|
func TestArithBackend_KeyGeneration(t *testing.T) {
|
|
backends := getArithBackends(t)
|
|
|
|
for _, backend := range backends {
|
|
t.Run(backend.Name()+"_"+schemeStr(backend.Params().Scheme), func(t *testing.T) {
|
|
sk, pk, err := backend.KeyGen()
|
|
if err != nil {
|
|
t.Fatalf("KeyGen() error = %v", err)
|
|
}
|
|
if sk == nil || len(sk.Data) == 0 {
|
|
t.Error("KeyGen() returned nil or empty secret key")
|
|
}
|
|
if pk == nil || len(pk.Data) == 0 {
|
|
t.Error("KeyGen() returned nil or empty public key")
|
|
}
|
|
|
|
// Relin key
|
|
rlk, err := backend.GenRelinKey(sk)
|
|
if err != nil {
|
|
t.Fatalf("GenRelinKey() error = %v", err)
|
|
}
|
|
if rlk == nil || len(rlk.Data) == 0 {
|
|
t.Error("GenRelinKey() returned nil or empty key")
|
|
}
|
|
|
|
// Galois key
|
|
steps := []int{1, 2, 4}
|
|
gk, err := backend.GenGaloisKey(sk, steps)
|
|
if err != nil {
|
|
t.Fatalf("GenGaloisKey() error = %v", err)
|
|
}
|
|
if gk == nil || len(gk.Data) == 0 {
|
|
t.Error("GenGaloisKey() returned nil or empty key")
|
|
}
|
|
|
|
// Bootstrap key
|
|
bsk, err := backend.GenBootstrapKey(sk)
|
|
if err != nil {
|
|
t.Fatalf("GenBootstrapKey() error = %v", err)
|
|
}
|
|
if bsk == nil || len(bsk.Data) == 0 {
|
|
t.Error("GenBootstrapKey() returned nil or empty key")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestArithBackend_BFVEncryptDecrypt(t *testing.T) {
|
|
backend := newCPUArithBackend(testBFVParamsSmall)
|
|
|
|
sk, pk, err := backend.KeyGen()
|
|
if err != nil {
|
|
t.Fatalf("KeyGen() error = %v", err)
|
|
}
|
|
|
|
testCases := [][]int64{
|
|
{0},
|
|
{1},
|
|
{1, 2, 3},
|
|
{-1, -2, -3},
|
|
{100, -50, 25},
|
|
}
|
|
|
|
for i, values := range testCases {
|
|
t.Run("case_"+string(rune('0'+i)), func(t *testing.T) {
|
|
pt, err := backend.EncodeBFV(values)
|
|
if err != nil {
|
|
t.Fatalf("EncodeBFV() error = %v", err)
|
|
}
|
|
|
|
ct, err := backend.Encrypt(pk, pt)
|
|
if err != nil {
|
|
t.Fatalf("Encrypt() error = %v", err)
|
|
}
|
|
|
|
ptDec, err := backend.Decrypt(sk, ct)
|
|
if err != nil {
|
|
t.Fatalf("Decrypt() error = %v", err)
|
|
}
|
|
|
|
// Verify scheme is preserved
|
|
if ptDec.Scheme != SchemeBFV {
|
|
t.Errorf("Decrypt() scheme = %v, want BFV", ptDec.Scheme)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestArithBackend_CKKSEncryptDecrypt(t *testing.T) {
|
|
backend := newCPUArithBackend(testCKKSParamsSmall)
|
|
|
|
sk, pk, err := backend.KeyGen()
|
|
if err != nil {
|
|
t.Fatalf("KeyGen() error = %v", err)
|
|
}
|
|
|
|
testCases := [][]float64{
|
|
{0.0},
|
|
{1.0},
|
|
{1.5, 2.5, 3.5},
|
|
{-1.1, -2.2, -3.3},
|
|
{3.14159, 2.71828},
|
|
}
|
|
|
|
for i, values := range testCases {
|
|
t.Run("case_"+string(rune('0'+i)), func(t *testing.T) {
|
|
pt, err := backend.EncodeCKKS(values)
|
|
if err != nil {
|
|
t.Fatalf("EncodeCKKS() error = %v", err)
|
|
}
|
|
|
|
ct, err := backend.Encrypt(pk, pt)
|
|
if err != nil {
|
|
t.Fatalf("Encrypt() error = %v", err)
|
|
}
|
|
|
|
ptDec, err := backend.Decrypt(sk, ct)
|
|
if err != nil {
|
|
t.Fatalf("Decrypt() error = %v", err)
|
|
}
|
|
|
|
// Verify scheme is preserved
|
|
if ptDec.Scheme != SchemeCKKS {
|
|
t.Errorf("Decrypt() scheme = %v, want CKKS", ptDec.Scheme)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestArithBackend_HomomorphicAdd(t *testing.T) {
|
|
backend := newCPUArithBackend(testBFVParamsSmall)
|
|
|
|
sk, pk, err := backend.KeyGen()
|
|
if err != nil {
|
|
t.Fatalf("KeyGen() error = %v", err)
|
|
}
|
|
|
|
pt1, _ := backend.EncodeBFV([]int64{1, 2, 3})
|
|
pt2, _ := backend.EncodeBFV([]int64{4, 5, 6})
|
|
|
|
ct1, err := backend.Encrypt(pk, pt1)
|
|
if err != nil {
|
|
t.Fatalf("Encrypt(pt1) error = %v", err)
|
|
}
|
|
ct2, err := backend.Encrypt(pk, pt2)
|
|
if err != nil {
|
|
t.Fatalf("Encrypt(pt2) error = %v", err)
|
|
}
|
|
|
|
result, err := backend.Add(ct1, ct2)
|
|
if err != nil {
|
|
t.Fatalf("Add() error = %v", err)
|
|
}
|
|
|
|
ptResult, err := backend.Decrypt(sk, result)
|
|
if err != nil {
|
|
t.Fatalf("Decrypt() error = %v", err)
|
|
}
|
|
|
|
if ptResult == nil {
|
|
t.Fatal("Decrypt() returned nil")
|
|
}
|
|
}
|
|
|
|
func TestArithBackend_HomomorphicSub(t *testing.T) {
|
|
backend := newCPUArithBackend(testBFVParamsSmall)
|
|
|
|
sk, pk, err := backend.KeyGen()
|
|
if err != nil {
|
|
t.Fatalf("KeyGen() error = %v", err)
|
|
}
|
|
|
|
pt1, _ := backend.EncodeBFV([]int64{10, 20, 30})
|
|
pt2, _ := backend.EncodeBFV([]int64{1, 2, 3})
|
|
|
|
ct1, _ := backend.Encrypt(pk, pt1)
|
|
ct2, _ := backend.Encrypt(pk, pt2)
|
|
|
|
result, err := backend.Sub(ct1, ct2)
|
|
if err != nil {
|
|
t.Fatalf("Sub() error = %v", err)
|
|
}
|
|
|
|
ptResult, err := backend.Decrypt(sk, result)
|
|
if err != nil {
|
|
t.Fatalf("Decrypt() error = %v", err)
|
|
}
|
|
|
|
if ptResult == nil {
|
|
t.Fatal("Decrypt() returned nil")
|
|
}
|
|
}
|
|
|
|
func TestArithBackend_HomomorphicMultiply(t *testing.T) {
|
|
backend := newCPUArithBackend(testBFVParamsSmall)
|
|
|
|
sk, pk, err := backend.KeyGen()
|
|
if err != nil {
|
|
t.Fatalf("KeyGen() error = %v", err)
|
|
}
|
|
|
|
rlk, err := backend.GenRelinKey(sk)
|
|
if err != nil {
|
|
t.Fatalf("GenRelinKey() error = %v", err)
|
|
}
|
|
|
|
pt1, _ := backend.EncodeBFV([]int64{2, 3})
|
|
pt2, _ := backend.EncodeBFV([]int64{4, 5})
|
|
|
|
ct1, _ := backend.Encrypt(pk, pt1)
|
|
ct2, _ := backend.Encrypt(pk, pt2)
|
|
|
|
result, err := backend.Multiply(ct1, ct2, rlk)
|
|
if err != nil {
|
|
t.Fatalf("Multiply() error = %v", err)
|
|
}
|
|
|
|
ptResult, err := backend.Decrypt(sk, result)
|
|
if err != nil {
|
|
t.Fatalf("Decrypt() error = %v", err)
|
|
}
|
|
|
|
if ptResult == nil {
|
|
t.Fatal("Decrypt() returned nil")
|
|
}
|
|
}
|
|
|
|
func TestArithBackend_Rotate(t *testing.T) {
|
|
backend := newCPUArithBackend(testBFVParamsSmall)
|
|
|
|
sk, pk, err := backend.KeyGen()
|
|
if err != nil {
|
|
t.Fatalf("KeyGen() error = %v", err)
|
|
}
|
|
|
|
steps := []int{1, -1, 2}
|
|
gk, err := backend.GenGaloisKey(sk, steps)
|
|
if err != nil {
|
|
t.Fatalf("GenGaloisKey() error = %v", err)
|
|
}
|
|
|
|
pt, _ := backend.EncodeBFV([]int64{1, 2, 3, 4})
|
|
ct, _ := backend.Encrypt(pk, pt)
|
|
|
|
for _, step := range steps {
|
|
t.Run("step_"+string(rune('0'+step+10)), func(t *testing.T) {
|
|
result, err := backend.Rotate(ct, gk, step)
|
|
if err != nil {
|
|
t.Fatalf("Rotate(%d) error = %v", step, err)
|
|
}
|
|
if result == nil {
|
|
t.Fatalf("Rotate(%d) returned nil", step)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestArithBackend_Rescale(t *testing.T) {
|
|
backend := newCPUArithBackend(testCKKSParamsSmall)
|
|
|
|
_, pk, err := backend.KeyGen()
|
|
if err != nil {
|
|
t.Fatalf("KeyGen() error = %v", err)
|
|
}
|
|
|
|
pt, _ := backend.EncodeCKKS([]float64{1.0, 2.0})
|
|
ct, _ := backend.Encrypt(pk, pt)
|
|
|
|
result, err := backend.Rescale(ct)
|
|
if err != nil {
|
|
t.Fatalf("Rescale() error = %v", err)
|
|
}
|
|
|
|
if result.Level != ct.Level-1 {
|
|
t.Errorf("Rescale() level = %d, want %d", result.Level, ct.Level-1)
|
|
}
|
|
}
|
|
|
|
func TestArithBackend_ModSwitch(t *testing.T) {
|
|
backend := newCPUArithBackend(testBFVParamsSmall)
|
|
|
|
_, pk, err := backend.KeyGen()
|
|
if err != nil {
|
|
t.Fatalf("KeyGen() error = %v", err)
|
|
}
|
|
|
|
pt, _ := backend.EncodeBFV([]int64{1, 2, 3})
|
|
ct, _ := backend.Encrypt(pk, pt)
|
|
|
|
result, err := backend.ModSwitch(ct)
|
|
if err != nil {
|
|
t.Fatalf("ModSwitch() error = %v", err)
|
|
}
|
|
|
|
if result.Level != ct.Level-1 {
|
|
t.Errorf("ModSwitch() level = %d, want %d", result.Level, ct.Level-1)
|
|
}
|
|
}
|
|
|
|
func TestArithBackend_Bootstrap(t *testing.T) {
|
|
backend := newCPUArithBackend(testCKKSParamsSmall)
|
|
|
|
sk, pk, err := backend.KeyGen()
|
|
if err != nil {
|
|
t.Fatalf("KeyGen() error = %v", err)
|
|
}
|
|
|
|
bsk, err := backend.GenBootstrapKey(sk)
|
|
if err != nil {
|
|
t.Fatalf("GenBootstrapKey() error = %v", err)
|
|
}
|
|
|
|
pt, _ := backend.EncodeCKKS([]float64{1.0, 2.0})
|
|
ct, _ := backend.Encrypt(pk, pt)
|
|
|
|
// Reduce level first
|
|
ct, _ = backend.Rescale(ct)
|
|
|
|
result, err := backend.Bootstrap(ct, bsk)
|
|
if err != nil {
|
|
t.Fatalf("Bootstrap() error = %v", err)
|
|
}
|
|
|
|
// Bootstrap should restore to full level
|
|
expectedLevel := len(backend.Params().CoeffMods) - 1
|
|
if result.Level != expectedLevel {
|
|
t.Errorf("Bootstrap() level = %d, want %d", result.Level, expectedLevel)
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Helper Functions
|
|
// =============================================================================
|
|
|
|
func boolStr(b bool) string {
|
|
if b {
|
|
return "T"
|
|
}
|
|
return "F"
|
|
}
|
|
|
|
func schemeStr(s Scheme) string {
|
|
if s == SchemeBFV {
|
|
return "BFV"
|
|
}
|
|
return "CKKS"
|
|
}
|
|
|
|
// =============================================================================
|
|
// Benchmarks
|
|
// =============================================================================
|
|
|
|
func BenchmarkBackend_BoolGates(b *testing.B) {
|
|
backend := newGoBoolBackend()
|
|
defer backend.Close()
|
|
|
|
sk, _ := backend.GenSecretKey()
|
|
backend.GenPublicKey(sk)
|
|
bsk, _ := backend.GenBootstrapKey(sk)
|
|
|
|
ctT, _ := backend.Encrypt(sk, true)
|
|
ctF, _ := backend.Encrypt(sk, false)
|
|
|
|
b.Run("NOT", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
backend.Not(ctT)
|
|
}
|
|
})
|
|
|
|
b.Run("AND", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
backend.And(bsk, ctT, ctF)
|
|
}
|
|
})
|
|
|
|
b.Run("OR", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
backend.Or(bsk, ctT, ctF)
|
|
}
|
|
})
|
|
|
|
b.Run("XOR", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
backend.Xor(bsk, ctT, ctF)
|
|
}
|
|
})
|
|
|
|
b.Run("MUX", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
backend.Mux(bsk, ctT, ctT, ctF)
|
|
}
|
|
})
|
|
}
|
|
|
|
func BenchmarkArithBackend_Operations(b *testing.B) {
|
|
backend := newCPUArithBackend(testBFVParamsSmall)
|
|
|
|
sk, pk, _ := backend.KeyGen()
|
|
rlk, _ := backend.GenRelinKey(sk)
|
|
|
|
pt, _ := backend.EncodeBFV([]int64{1, 2, 3, 4, 5})
|
|
ct1, _ := backend.Encrypt(pk, pt)
|
|
ct2, _ := backend.Encrypt(pk, pt)
|
|
|
|
b.Run("Encrypt", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
backend.Encrypt(pk, pt)
|
|
}
|
|
})
|
|
|
|
b.Run("Decrypt", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
backend.Decrypt(sk, ct1)
|
|
}
|
|
})
|
|
|
|
b.Run("Add", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
backend.Add(ct1, ct2)
|
|
}
|
|
})
|
|
|
|
b.Run("Multiply", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
backend.Multiply(ct1, ct2, rlk)
|
|
}
|
|
})
|
|
}
|