Files
lattigo/circuits/float/bootstrapper/bootstrapper_test.go

379 lines
12 KiB
Go

package bootstrapper
import (
"flag"
"math"
"testing"
"github.com/stretchr/testify/require"
"github.com/tuneinsight/lattigo/v4/circuits"
"github.com/tuneinsight/lattigo/v4/ckks"
"github.com/tuneinsight/lattigo/v4/ring"
"github.com/tuneinsight/lattigo/v4/rlwe"
"github.com/tuneinsight/lattigo/v4/utils"
"github.com/tuneinsight/lattigo/v4/utils/sampling"
)
var flagLongTest = flag.Bool("long", false, "run the long test suite (all parameters + secure bootstrapping). Overrides -short and requires -timeout=0.")
var printPrecisionStats = flag.Bool("print-precision", false, "print precision stats")
var testPrec45 = ckks.ParametersLiteral{
LogN: 10,
LogQ: []int{60, 40},
LogP: []int{61},
LogDefaultScale: 40,
}
func TestBootstrapping(t *testing.T) {
// Check that the bootstrapper complies to the rlwe.Bootstrapper interface
var _ circuits.Bootstrapper[rlwe.Ciphertext] = (*Bootstrapper)(nil)
t.Run("BootstrapingWithoutRingDegreeSwitch", func(t *testing.T) {
schemeParamsLit := testPrec45
btpParamsLit := ParametersLiteral{}
if *flagLongTest {
schemeParamsLit.LogN = 16
}
params, err := ckks.NewParametersFromLiteral(schemeParamsLit)
require.Nil(t, err)
btpParamsLit.LogN = utils.Pointy(params.LogN())
btpParams, err := NewParametersFromLiteral(params, btpParamsLit)
require.Nil(t, err)
// Insecure params for fast testing only
if !*flagLongTest {
btpParams.SlotsToCoeffsParameters.LogSlots = btpParams.LogN() - 1
btpParams.CoeffsToSlotsParameters.LogSlots = btpParams.LogN() - 1
// Corrects the message ratio to take into account the smaller number of slots and keep the same precision
btpParams.Mod1ParametersLiteral.LogMessageRatio += 16 - params.LogN()
}
t.Logf("ParamsN2: LogN=%d/LogSlots=%d/LogQP=%f", params.LogN(), params.LogMaxSlots(), params.LogQP())
sk := ckks.NewKeyGenerator(btpParams.Parameters.Parameters).GenSecretKeyNew()
t.Log("Generating Bootstrapping Keys")
btpKeys, _, err := btpParams.GenBootstrappingKeys(sk)
require.NoError(t, err)
bootstrapper, err := NewBootstrapper(btpParams, btpKeys)
require.NoError(t, err)
ecd := ckks.NewEncoder(params)
enc := ckks.NewEncryptor(params, sk)
dec := ckks.NewDecryptor(params, sk)
values := make([]complex128, params.MaxSlots())
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 len(values) > 2 {
values[2] = complex(0.9238795325112867, 0.3826834323650898)
values[3] = complex(0.9238795325112867, 0.3826834323650898)
}
t.Run("Bootstrapping", func(t *testing.T) {
plaintext := ckks.NewPlaintext(params, 0)
ecd.Encode(values, plaintext)
ctQ0, err := enc.EncryptNew(plaintext)
require.NoError(t, err)
// Checks that the input ciphertext is at the level 0
require.True(t, ctQ0.Level() == 0)
// Bootstrapps the ciphertext
ctQL, err := bootstrapper.Bootstrap(ctQ0)
require.NoError(t, err)
// Checks that the output ciphertext is at the max level of paramsN1
require.True(t, ctQL.Level() == params.MaxLevel())
require.True(t, ctQL.Scale.Equal(params.DefaultScale()))
verifyTestVectorsBootstrapping(params, ecd, dec, values, ctQL, t)
})
})
t.Run("BootstrappingWithRingDegreeSwitch", func(t *testing.T) {
schemeParamsLit := testPrec45
btpParamsLit := ParametersLiteral{}
if *flagLongTest {
schemeParamsLit.LogN = 16
}
schemeParamsLit.LogNthRoot = schemeParamsLit.LogN + 1
schemeParamsLit.LogN--
params, err := ckks.NewParametersFromLiteral(schemeParamsLit)
require.Nil(t, err)
btpParamsLit.LogN = utils.Pointy(params.LogN() + 1)
btpParams, err := NewParametersFromLiteral(params, btpParamsLit)
require.Nil(t, err)
// Insecure params for fast testing only
if !*flagLongTest {
btpParams.SlotsToCoeffsParameters.LogSlots = btpParams.LogN() - 1
btpParams.CoeffsToSlotsParameters.LogSlots = btpParams.LogN() - 1
// Corrects the message ratio to take into account the smaller number of slots and keep the same precision
btpParams.Mod1ParametersLiteral.LogMessageRatio += 16 - params.LogN()
}
t.Logf("Params: LogN=%d/LogSlots=%d/LogQP=%f", params.LogN(), params.LogMaxSlots(), params.LogQP())
t.Logf("BTPParams: LogN=%d/LogSlots=%d/LogQP=%f", btpParams.LogN(), btpParams.LogMaxSlots(), btpParams.LogQP())
sk := ckks.NewKeyGenerator(params).GenSecretKeyNew()
t.Log("Generating Bootstrapping Keys")
btpKeys, _, err := btpParams.GenBootstrappingKeys(sk)
require.Nil(t, err)
bootstrapper, err := NewBootstrapper(btpParams, btpKeys)
require.Nil(t, err)
ecd := ckks.NewEncoder(params)
enc := ckks.NewEncryptor(params, sk)
dec := ckks.NewDecryptor(params, sk)
values := make([]complex128, params.MaxSlots())
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 len(values) > 2 {
values[2] = complex(0.9238795325112867, 0.3826834323650898)
values[3] = complex(0.9238795325112867, 0.3826834323650898)
}
t.Run("N1ToN2->Bootstrapping->N2ToN1", func(t *testing.T) {
plaintext := ckks.NewPlaintext(params, 0)
ecd.Encode(values, plaintext)
ctQ0, err := enc.EncryptNew(plaintext)
require.NoError(t, err)
// Checks that the input ciphertext is at the level 0
require.True(t, ctQ0.Level() == 0)
// Bootstrapps the ciphertext
ctQL, err := bootstrapper.Bootstrap(ctQ0)
if err != nil {
t.Fatal(err)
}
// Checks that the output ciphertext is at the max level of params
require.True(t, ctQL.Level() == params.MaxLevel())
require.True(t, ctQL.Scale.Equal(params.DefaultScale()))
verifyTestVectorsBootstrapping(params, ecd, dec, values, ctQL, t)
})
})
t.Run("BootstrappingPackedWithRingDegreeSwitch", func(t *testing.T) {
schemeParamsLit := testPrec45
btpParamsLit := ParametersLiteral{}
if *flagLongTest {
schemeParamsLit.LogN = 16
}
btpParamsLit.LogN = utils.Pointy(schemeParamsLit.LogN)
schemeParamsLit.LogNthRoot = schemeParamsLit.LogN + 1
schemeParamsLit.LogN -= 3
params, err := ckks.NewParametersFromLiteral(schemeParamsLit)
require.Nil(t, err)
btpParams, err := NewParametersFromLiteral(params, btpParamsLit)
require.Nil(t, err)
// Insecure params for fast testing only
if !*flagLongTest {
btpParams.SlotsToCoeffsParameters.LogSlots = btpParams.LogN() - 1
btpParams.CoeffsToSlotsParameters.LogSlots = btpParams.LogN() - 1
// Corrects the message ratio to take into account the smaller number of slots and keep the same precision
btpParams.Mod1ParametersLiteral.LogMessageRatio += 16 - params.LogN()
}
t.Logf("Params: LogN=%d/LogSlots=%d/LogQP=%f", params.LogN(), params.LogMaxSlots(), params.LogQP())
t.Logf("BTPParams: LogN=%d/LogSlots=%d/LogQP=%f", btpParams.LogN(), btpParams.LogMaxSlots(), btpParams.LogQP())
sk := ckks.NewKeyGenerator(params).GenSecretKeyNew()
t.Log("Generating Bootstrapping Keys")
btpKeys, _, err := btpParams.GenBootstrappingKeys(sk)
require.Nil(t, err)
bootstrapper, err := NewBootstrapper(btpParams, btpKeys)
require.Nil(t, err)
ecd := ckks.NewEncoder(params)
enc := ckks.NewEncryptor(params, sk)
dec := ckks.NewDecryptor(params, sk)
values := make([]complex128, params.MaxSlots())
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 len(values) > 2 {
values[2] = complex(0.9238795325112867, 0.3826834323650898)
values[3] = complex(0.9238795325112867, 0.3826834323650898)
}
pt := ckks.NewPlaintext(params, 0)
cts := make([]rlwe.Ciphertext, 7)
for i := range cts {
require.NoError(t, ecd.Encode(utils.RotateSlice(values, i), pt))
ct, err := enc.EncryptNew(pt)
require.NoError(t, err)
cts[i] = *ct
}
if cts, err = bootstrapper.BootstrapMany(cts); err != nil {
t.Fatal(err)
}
for i := range cts {
// Checks that the output ciphertext is at the max level of paramsN1
require.True(t, cts[i].Level() == params.MaxLevel())
require.True(t, cts[i].Scale.Equal(params.DefaultScale()))
verifyTestVectorsBootstrapping(params, ecd, dec, utils.RotateSlice(values, i), &cts[i], t)
}
})
t.Run("BootstrappingWithRingTypeSwitch", func(t *testing.T) {
schemeParamsLit := testPrec45
schemeParamsLit.RingType = ring.ConjugateInvariant
btpParamsLit := ParametersLiteral{}
if *flagLongTest {
schemeParamsLit.LogN = 16
}
btpParamsLit.LogN = utils.Pointy(schemeParamsLit.LogN)
schemeParamsLit.LogNthRoot = schemeParamsLit.LogN + 1
schemeParamsLit.LogN--
params, err := ckks.NewParametersFromLiteral(schemeParamsLit)
require.Nil(t, err)
btpParams, err := NewParametersFromLiteral(params, btpParamsLit)
require.Nil(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 += 16 - params.LogN()
}
t.Logf("Params: LogN=%d/LogSlots=%d/LogQP=%f", params.LogN(), params.LogMaxSlots(), params.LogQP())
t.Logf("BTPParams: LogN=%d/LogSlots=%d/LogQP=%f", btpParams.LogN(), btpParams.LogMaxSlots(), btpParams.LogQP())
sk := ckks.NewKeyGenerator(params).GenSecretKeyNew()
t.Log("Generating Bootstrapping Keys")
btpKeys, _, err := btpParams.GenBootstrappingKeys(sk)
require.Nil(t, err)
bootstrapper, err := NewBootstrapper(btpParams, btpKeys)
require.Nil(t, err)
ecd := ckks.NewEncoder(params)
enc := ckks.NewEncryptor(params, sk)
dec := ckks.NewDecryptor(params, sk)
values := make([]float64, params.MaxSlots())
for i := range values {
values[i] = sampling.RandFloat64(-1, 1)
}
values[0] = 0.9238795325112867
values[1] = 0.9238795325112867
if len(values) > 2 {
values[2] = 0.9238795325112867
values[3] = 0.9238795325112867
}
t.Run("ConjugateInvariant->Standard->Bootstrapping->Standard->ConjugateInvariant", func(t *testing.T) {
plaintext := ckks.NewPlaintext(params, 0)
require.NoError(t, ecd.Encode(values, plaintext))
ctLeftQ0, err := enc.EncryptNew(plaintext)
require.NoError(t, err)
ctRightQ0, err := enc.EncryptNew(plaintext)
require.NoError(t, err)
// Checks that the input ciphertext is at the level 0
require.True(t, ctLeftQ0.Level() == 0)
require.True(t, ctRightQ0.Level() == 0)
// Bootstrapps the ciphertext
ctLeftQL, ctRightQL, err := bootstrapper.refreshConjugateInvariant(ctLeftQ0, ctRightQ0)
require.NoError(t, err)
// Checks that the output ciphertext is at the max level of paramsN1
require.True(t, ctLeftQL.Level() == params.MaxLevel())
require.True(t, ctLeftQL.Scale.Equal(params.DefaultScale()))
verifyTestVectorsBootstrapping(params, ecd, dec, values, ctLeftQL, t)
require.True(t, ctRightQL.Level() == params.MaxLevel())
require.True(t, ctRightQL.Scale.Equal(params.DefaultScale()))
verifyTestVectorsBootstrapping(params, ecd, dec, values, ctRightQL, t)
})
})
}
func verifyTestVectorsBootstrapping(params ckks.Parameters, encoder *ckks.Encoder, decryptor *rlwe.Decryptor, valuesWant, element interface{}, t *testing.T) {
precStats := ckks.GetPrecisionStats(params, encoder, decryptor, valuesWant, element, nil, false)
if *printPrecisionStats {
t.Log(precStats.String())
}
rf64, _ := precStats.MeanPrecision.Real.Float64()
if64, _ := precStats.MeanPrecision.Imag.Float64()
minPrec := math.Log2(params.DefaultScale().Float64()) - float64(params.LogN()+2)
if minPrec < 0 {
minPrec = 0
}
minPrec -= 10
require.GreaterOrEqual(t, rf64, minPrec)
require.GreaterOrEqual(t, if64, minPrec)
}