mirror of
https://github.com/luxfi/lattice.git
synced 2026-09-24 00:25:44 +00:00
320 lines
9.1 KiB
Go
320 lines
9.1 KiB
Go
package bootstrapping
|
|
|
|
import (
|
|
"fmt"
|
|
"runtime"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/luxfi/lattice/v5/core/rlwe"
|
|
"github.com/luxfi/lattice/v5/he/hefloat"
|
|
"github.com/luxfi/lattice/v5/utils"
|
|
"github.com/luxfi/lattice/v5/utils/sampling"
|
|
)
|
|
|
|
var minPrec float64 = 12.0
|
|
|
|
func ParamsToString(params hefloat.Parameters, LogSlots int, opname string) string {
|
|
return fmt.Sprintf("%slogN=%d/LogSlots=%d/logQP=%f/levels=%d/a=%d/b=%d",
|
|
opname,
|
|
params.LogN(),
|
|
LogSlots,
|
|
params.LogQP(),
|
|
params.MaxLevel()+1,
|
|
params.PCount(),
|
|
params.BaseRNSDecompositionVectorSize(params.MaxLevelQ(), params.MaxLevelP()))
|
|
}
|
|
|
|
func TestParametersMarshalling(t *testing.T) {
|
|
|
|
t.Run("ParametersLiteral", func(t *testing.T) {
|
|
|
|
paramsLit := ParametersLiteral{
|
|
CoeffsToSlotsFactorizationDepthAndLogScales: [][]int{{53}, {53}, {53}, {53}},
|
|
SlotsToCoeffsFactorizationDepthAndLogScales: [][]int{{30}, {30, 30}},
|
|
EvalModLogScale: utils.Pointy(59),
|
|
EphemeralSecretWeight: utils.Pointy(1),
|
|
IterationsParameters: &IterationsParameters{BootstrappingPrecision: []float64{20, 20}, ReservedPrimeBitSize: 20},
|
|
Mod1Degree: utils.Pointy(32),
|
|
Mod1InvDegree: utils.Pointy(7),
|
|
}
|
|
|
|
data, err := paramsLit.MarshalBinary()
|
|
require.Nil(t, err)
|
|
|
|
paramsLitNew := new(ParametersLiteral)
|
|
if err := paramsLitNew.UnmarshalBinary(data); err != nil {
|
|
require.Nil(t, err)
|
|
}
|
|
require.Equal(t, paramsLit, *paramsLitNew)
|
|
})
|
|
|
|
t.Run("Parameters", func(t *testing.T) {
|
|
paramSet := DefaultParametersSparse[0]
|
|
|
|
params, err := hefloat.NewParametersFromLiteral(paramSet.SchemeParams)
|
|
require.Nil(t, err)
|
|
|
|
btpParams, err := NewParametersFromLiteral(params, paramSet.BootstrappingParams)
|
|
require.Nil(t, err)
|
|
|
|
data, err := btpParams.MarshalBinary()
|
|
require.Nil(t, err)
|
|
btpParamsNew := new(Parameters)
|
|
if err := btpParamsNew.UnmarshalBinary(data); err != nil {
|
|
require.Nil(t, err)
|
|
}
|
|
|
|
require.True(t, btpParams.Equal(btpParamsNew))
|
|
})
|
|
}
|
|
|
|
func TestCircuitWithEncapsulation(t *testing.T) {
|
|
|
|
if runtime.GOARCH == "wasm" {
|
|
t.Skip("skipping bootstrapping tests for GOARCH=wasm")
|
|
}
|
|
|
|
paramSet := DefaultParametersSparse[0]
|
|
|
|
if !*flagLongTest {
|
|
paramSet.SchemeParams.LogN -= 3
|
|
}
|
|
|
|
paramSet.BootstrappingParams.LogN = utils.Pointy(paramSet.SchemeParams.LogN)
|
|
|
|
for _, LogSlots := range []int{1, paramSet.SchemeParams.LogN - 2, paramSet.SchemeParams.LogN - 1} {
|
|
paramsSetCpy := paramSet
|
|
|
|
level := utils.Min(1, len(paramSet.SchemeParams.LogQ))
|
|
|
|
paramsSetCpy.SchemeParams.LogQ = paramSet.SchemeParams.LogQ[:level+1]
|
|
|
|
paramsSetCpy.BootstrappingParams.LogSlots = &LogSlots
|
|
|
|
params, err := hefloat.NewParametersFromLiteral(paramsSetCpy.SchemeParams)
|
|
require.NoError(t, err)
|
|
|
|
btpParams, err := NewParametersFromLiteral(params, paramsSetCpy.BootstrappingParams)
|
|
require.NoError(t, err)
|
|
|
|
// Insecure params for fast testing only
|
|
if !*flagLongTest {
|
|
// Corrects the message ratio to take into account the smaller number of slots and keep the same precision
|
|
btpParams.Mod1ParametersLiteral.LogMessageRatio += utils.Min(utils.Max(15-LogSlots, 0), 8)
|
|
}
|
|
|
|
testRawCircuit(params, btpParams, level, t)
|
|
runtime.GC()
|
|
}
|
|
|
|
testRawCircuitHighPrecision(paramSet, t)
|
|
}
|
|
|
|
func TestCircuitOriginal(t *testing.T) {
|
|
|
|
if runtime.GOARCH == "wasm" {
|
|
t.Skip("skipping bootstrapping tests for GOARCH=wasm")
|
|
}
|
|
|
|
paramSet := DefaultParametersDense[0]
|
|
|
|
if !*flagLongTest {
|
|
paramSet.SchemeParams.LogN -= 3
|
|
}
|
|
|
|
paramSet.BootstrappingParams.LogN = utils.Pointy(paramSet.SchemeParams.LogN)
|
|
|
|
for _, LogSlots := range []int{1, paramSet.SchemeParams.LogN - 2, paramSet.SchemeParams.LogN - 1} {
|
|
|
|
paramsSetCpy := paramSet
|
|
|
|
level := utils.Min(1, len(paramSet.SchemeParams.LogQ))
|
|
|
|
paramsSetCpy.SchemeParams.LogQ = paramSet.SchemeParams.LogQ[:level+1]
|
|
|
|
paramsSetCpy.BootstrappingParams.LogSlots = &LogSlots
|
|
|
|
params, err := hefloat.NewParametersFromLiteral(paramsSetCpy.SchemeParams)
|
|
require.NoError(t, err)
|
|
|
|
btpParams, err := NewParametersFromLiteral(params, paramsSetCpy.BootstrappingParams)
|
|
require.NoError(t, err)
|
|
|
|
// Insecure params for fast testing only
|
|
if !*flagLongTest {
|
|
// Corrects the message ratio to take into account the smaller number of slots and keep the same precision
|
|
btpParams.Mod1ParametersLiteral.LogMessageRatio += utils.Min(utils.Max(15-LogSlots, 0), 8)
|
|
}
|
|
|
|
testRawCircuit(params, btpParams, level, t)
|
|
runtime.GC()
|
|
}
|
|
|
|
testRawCircuitHighPrecision(paramSet, t)
|
|
}
|
|
|
|
func testRawCircuit(params hefloat.Parameters, btpParams Parameters, level int, t *testing.T) {
|
|
|
|
t.Run(ParamsToString(params, btpParams.LogMaxSlots(), ""), func(t *testing.T) {
|
|
|
|
kgen := rlwe.NewKeyGenerator(btpParams.BootstrappingParameters)
|
|
sk := kgen.GenSecretKeyNew()
|
|
encoder := hefloat.NewEncoder(params)
|
|
|
|
encryptor := rlwe.NewEncryptor(params, sk)
|
|
decryptor := rlwe.NewDecryptor(params, sk)
|
|
|
|
evk, _, err := btpParams.GenEvaluationKeys(sk)
|
|
require.NoError(t, err)
|
|
|
|
eval, err := NewEvaluator(btpParams, evk)
|
|
require.NoError(t, err)
|
|
|
|
values := make([]complex128, 1<<btpParams.LogMaxSlots())
|
|
for i := range values {
|
|
values[i] = sampling.RandComplex128(-1, 1)
|
|
}
|
|
|
|
values[0] = complex(0.9238795325112867, 0.3826834323650898)
|
|
values[1] = complex(0.9238795325112867, 0.3826834323650898)
|
|
|
|
if btpParams.LogMaxSlots() > 1 {
|
|
values[2] = complex(0.9238795325112867, 0.3826834323650898)
|
|
values[3] = complex(0.9238795325112867, 0.3826834323650898)
|
|
}
|
|
|
|
plaintext := hefloat.NewPlaintext(params, 0)
|
|
plaintext.Scale = params.DefaultScale()
|
|
plaintext.LogDimensions = btpParams.LogMaxDimensions()
|
|
encoder.Encode(values, plaintext)
|
|
|
|
n := 1
|
|
|
|
ciphertexts := make([]*rlwe.Ciphertext, n)
|
|
evaluators := make([]*Evaluator, n)
|
|
evaluators[0] = eval
|
|
ciphertexts[0], err = encryptor.EncryptNew(plaintext)
|
|
require.NoError(t, err)
|
|
for i := 1; i < len(ciphertexts); i++ {
|
|
ciphertexts[i], err = encryptor.EncryptNew(plaintext)
|
|
require.NoError(t, err)
|
|
evaluators[i] = evaluators[0].ShallowCopy()
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(n)
|
|
for i := range ciphertexts {
|
|
go func(index int) {
|
|
var err error
|
|
ciphertexts[index], err = evaluators[index].Evaluate(ciphertexts[index])
|
|
require.NoError(t, err)
|
|
wg.Done()
|
|
}(i)
|
|
}
|
|
wg.Wait()
|
|
|
|
for i := range ciphertexts {
|
|
require.True(t, ciphertexts[i].Level() == level)
|
|
}
|
|
|
|
for i := range ciphertexts {
|
|
verifyTestVectors(params, encoder, decryptor, values, ciphertexts[i], t)
|
|
}
|
|
})
|
|
}
|
|
|
|
func testRawCircuitHighPrecision(paramSet defaultParametersLiteral, t *testing.T) {
|
|
|
|
t.Run("HighPrecision", func(t *testing.T) {
|
|
|
|
level := utils.Min(1, len(paramSet.SchemeParams.LogQ))
|
|
|
|
paramSet.SchemeParams.LogQ = paramSet.SchemeParams.LogQ[:level+1]
|
|
|
|
paramSet.SchemeParams.LogDefaultScale = 80
|
|
|
|
paramSet.BootstrappingParams.IterationsParameters = &IterationsParameters{
|
|
BootstrappingPrecision: []float64{25, 25},
|
|
ReservedPrimeBitSize: 28,
|
|
}
|
|
|
|
params, err := hefloat.NewParametersFromLiteral(paramSet.SchemeParams)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
btpParams, err := NewParametersFromLiteral(params, paramSet.BootstrappingParams)
|
|
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Insecure params for fast testing only
|
|
if !*flagLongTest {
|
|
// Corrects the message ratio to take into account the smaller number of slots and keep the same precision
|
|
btpParams.Mod1ParametersLiteral.LogMessageRatio += utils.Min(utils.Max(16-params.LogN(), 0), 8)
|
|
}
|
|
|
|
t.Run(ParamsToString(params, btpParams.LogMaxSlots(), ""), func(t *testing.T) {
|
|
|
|
kgen := rlwe.NewKeyGenerator(btpParams.BootstrappingParameters)
|
|
sk := kgen.GenSecretKeyNew()
|
|
encoder := hefloat.NewEncoder(params)
|
|
encryptor := rlwe.NewEncryptor(params, sk)
|
|
decryptor := rlwe.NewDecryptor(params, sk)
|
|
|
|
evk, _, err := btpParams.GenEvaluationKeys(sk)
|
|
require.NoError(t, err)
|
|
|
|
eval, err := NewEvaluator(btpParams, evk)
|
|
require.NoError(t, err)
|
|
|
|
values := make([]complex128, 1<<btpParams.LogMaxSlots())
|
|
for i := range values {
|
|
values[i] = sampling.RandComplex128(-1, 1)
|
|
}
|
|
|
|
values[0] = complex(0.9238795325112867, 0.3826834323650898)
|
|
values[1] = complex(0.9238795325112867, 0.3826834323650898)
|
|
|
|
if btpParams.LogMaxSlots() > 1 {
|
|
values[2] = complex(0.9238795325112867, 0.3826834323650898)
|
|
values[3] = complex(0.9238795325112867, 0.3826834323650898)
|
|
}
|
|
|
|
plaintext := hefloat.NewPlaintext(params, level)
|
|
plaintext.Scale = params.DefaultScale()
|
|
|
|
plaintext.LogDimensions = btpParams.LogMaxDimensions()
|
|
encoder.Encode(values, plaintext)
|
|
|
|
ciphertext, err := encryptor.EncryptNew(plaintext)
|
|
require.NoError(t, err)
|
|
|
|
ciphertext, err = eval.Evaluate(ciphertext)
|
|
require.NoError(t, err)
|
|
|
|
require.True(t, ciphertext.Level() == level)
|
|
|
|
verifyTestVectors(params, encoder, decryptor, values, ciphertext, t)
|
|
})
|
|
|
|
runtime.GC()
|
|
})
|
|
}
|
|
|
|
func verifyTestVectors(params hefloat.Parameters, encoder *hefloat.Encoder, decryptor *rlwe.Decryptor, valuesWant, valuesHave interface{}, t *testing.T) {
|
|
precStats := hefloat.GetPrecisionStats(params, encoder, decryptor, valuesWant, valuesHave, 0, false)
|
|
if *printPrecisionStats {
|
|
t.Log(precStats.String())
|
|
}
|
|
|
|
rf64, _ := precStats.MeanPrecision.Real.Float64()
|
|
if64, _ := precStats.MeanPrecision.Imag.Float64()
|
|
|
|
require.GreaterOrEqual(t, rf64, minPrec)
|
|
require.GreaterOrEqual(t, if64, minPrec)
|
|
}
|