From 60a87b34d44ab449a943e4834fc2e218737a7fd1 Mon Sep 17 00:00:00 2001 From: lehugueni Date: Thu, 24 Oct 2024 12:19:03 +0200 Subject: [PATCH 1/4] packing cts in bootstrapping --- .../ckks/bootstrapping/bootstrapping_test.go | 176 +++++++++++++----- circuits/ckks/bootstrapping/evaluator.go | 153 ++++++++++----- 2 files changed, 242 insertions(+), 87 deletions(-) diff --git a/circuits/ckks/bootstrapping/bootstrapping_test.go b/circuits/ckks/bootstrapping/bootstrapping_test.go index 74bdc36de..80bade461 100644 --- a/circuits/ckks/bootstrapping/bootstrapping_test.go +++ b/circuits/ckks/bootstrapping/bootstrapping_test.go @@ -186,7 +186,7 @@ func TestBootstrapping(t *testing.T) { }) }) - t.Run("BootstrappingPackedWithRingDegreeSwitch", func(t *testing.T) { + t.Run("BootstrappingPackedWithoutRingDegreeSwitch", func(t *testing.T) { schemeParamsLit := testPrec45 btpParamsLit := ParametersLiteral{} @@ -196,75 +196,165 @@ func TestBootstrapping(t *testing.T) { } 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.BootstrappingParameters.LogN() - 1 - btpParams.CoeffsToSlotsParameters.LogSlots = btpParams.BootstrappingParameters.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() + btpParamsLit.LogMessageRatio = utils.Pointy(DefaultLogMessageRatio + (16 - params.LogN())) } - t.Logf("Params: LogN=%d/LogSlots=%d/LogQP=%f", btpParams.ResidualParameters.LogN(), btpParams.ResidualParameters.LogMaxSlots(), btpParams.ResidualParameters.LogQP()) - t.Logf("BTPParams: LogN=%d/LogSlots=%d/LogQP=%f", btpParams.BootstrappingParameters.LogN(), btpParams.BootstrappingParameters.LogMaxSlots(), btpParams.BootstrappingParameters.LogQP()) - sk := rlwe.NewKeyGenerator(params).GenSecretKeyNew() - t.Log("Generating Bootstrapping Keys") - btpKeys, _, err := btpParams.GenEvaluationKeys(sk) - require.Nil(t, err) + ecd := ckks.NewEncoder(params) + enc := rlwe.NewEncryptor(params, sk) + dec := rlwe.NewDecryptor(params, sk) - evaluator, err := NewEvaluator(btpParams, btpKeys) + for _, sparsity := range []int{0, 1, 2} { + btpParamsLit.LogSlots = utils.Pointy(*btpParamsLit.LogN - 1 - sparsity) + btpParams, err := NewParametersFromLiteral(params, btpParamsLit) + require.Nil(t, err) + + t.Logf("Params: LogN=%d/LogSlots=%d/LogQP=%f", btpParams.ResidualParameters.LogN(), btpParams.ResidualParameters.LogMaxSlots(), btpParams.ResidualParameters.LogQP()) + t.Logf("BTPParams: LogN=%d/LogSlots=%d/LogQP=%f", btpParams.BootstrappingParameters.LogN(), btpParams.BootstrappingParameters.LogMaxSlots(), btpParams.BootstrappingParameters.LogQP()) + + t.Log("Generating Bootstrapping Keys for LogSlots") + btpKeys, _, err := btpParams.GenEvaluationKeys(sk) + require.Nil(t, err) + + evaluator, err := NewEvaluator(btpParams, btpKeys) + require.Nil(t, err) + + for _, slotOffset := range []int{0, 1, 2, 3} { + logMaxSlots := params.LogMaxSlots() - slotOffset + logMaxSlots = utils.Min(logMaxSlots, btpParams.LogMaxSlots()) + values := make([]complex128, 1< 2 { + values[2] = complex(0.9238795325112867, 0.3826834323650898) + values[3] = complex(0.9238795325112867, 0.3826834323650898) + } + + pt := ckks.NewPlaintext(params, 0) + pt.LogDimensions = ring.Dimensions{Rows: 0, Cols: logMaxSlots} + + cts := make([]rlwe.Ciphertext, 11) + 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 = evaluator.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("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) + // 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 + btpParamsLit.LogMessageRatio = utils.Pointy(DefaultLogMessageRatio + (16 - params.LogN())) + } + + sk := rlwe.NewKeyGenerator(params).GenSecretKeyNew() + ecd := ckks.NewEncoder(params) enc := rlwe.NewEncryptor(params, sk) dec := rlwe.NewDecryptor(params, sk) - values := make([]complex128, params.MaxSlots()) - for i := range values { - values[i] = sampling.RandComplex128(-1, 1) - } + for _, sparsity := range []int{0, 1, 2} { + btpParamsLit.LogSlots = utils.Pointy(*btpParamsLit.LogN - 1 - sparsity) + btpParams, err := NewParametersFromLiteral(params, btpParamsLit) + require.Nil(t, err) - 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.Logf("Params: LogN=%d/LogSlots=%d/LogQP=%f", btpParams.ResidualParameters.LogN(), btpParams.ResidualParameters.LogMaxSlots(), btpParams.ResidualParameters.LogQP()) + t.Logf("BTPParams: LogN=%d/LogSlots=%d/LogQP=%f", btpParams.BootstrappingParameters.LogN(), btpParams.BootstrappingParameters.LogMaxSlots(), btpParams.BootstrappingParameters.LogQP()) - pt := ckks.NewPlaintext(params, 0) + t.Log("Generating Bootstrapping Keys for LogSlots") + btpKeys, _, err := btpParams.GenEvaluationKeys(sk) + require.Nil(t, err) - cts := make([]rlwe.Ciphertext, 7) - for i := range cts { + evaluator, err := NewEvaluator(btpParams, btpKeys) + require.Nil(t, err) - require.NoError(t, ecd.Encode(utils.RotateSlice(values, i), pt)) + for _, slotOffset := range []int{0, 1, 2, 3} { + logMaxSlots := params.LogMaxSlots() - slotOffset + logMaxSlots = utils.Min(logMaxSlots, btpParams.LogMaxSlots()) + values := make([]complex128, 1< 2 { + values[2] = complex(0.9238795325112867, 0.3826834323650898) + values[3] = complex(0.9238795325112867, 0.3826834323650898) + } - cts[i] = *ct - } + pt := ckks.NewPlaintext(params, 0) + pt.LogDimensions = ring.Dimensions{Rows: 0, Cols: logMaxSlots} - if cts, err = evaluator.BootstrapMany(cts); err != nil { - t.Fatal(err) - } + cts := make([]rlwe.Ciphertext, 11) + for i := range cts { - 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())) + require.NoError(t, ecd.Encode(utils.RotateSlice(values, i), pt)) + + ct, err := enc.EncryptNew(pt) + require.NoError(t, err) - verifyTestVectorsBootstrapping(params, ecd, dec, utils.RotateSlice(values, i), &cts[i], t) + cts[i] = *ct + } + + if cts, err = evaluator.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) + } + } } }) diff --git a/circuits/ckks/bootstrapping/evaluator.go b/circuits/ckks/bootstrapping/evaluator.go index 2f78cc8a5..6dc433c20 100644 --- a/circuits/ckks/bootstrapping/evaluator.go +++ b/circuits/ckks/bootstrapping/evaluator.go @@ -32,6 +32,8 @@ type Evaluator struct { xPow2N1 []ring.Poly // [1, x, x^2, x^4, ..., x^N2/2] / (X^N2 +1) xPow2N2 []ring.Poly + // [1, x^-1, x^-2, x^-4, ..., x^-N1/2] / (X^N1 +1) + xPow2InvN1 []ring.Poly // [1, x^-1, x^-2, x^-4, ..., x^-N2/2] / (X^N2 +1) xPow2InvN2 []ring.Poly @@ -73,9 +75,10 @@ func NewEvaluator(btpParams Parameters, evk *EvaluationKeys) (eval *Evaluator, e if paramsN1.N() != paramsN2.N() { eval.xPow2N1 = rlwe.GenXPow2NTT(paramsN1.RingQ().AtLevel(0), paramsN2.LogN(), false) - eval.xPow2N2 = rlwe.GenXPow2NTT(paramsN2.RingQ().AtLevel(0), paramsN2.LogN(), false) - eval.xPow2InvN2 = rlwe.GenXPow2NTT(paramsN2.RingQ(), paramsN2.LogN(), true) + eval.xPow2InvN1 = rlwe.GenXPow2NTT(paramsN1.RingQ(), paramsN1.LogN(), true) } + eval.xPow2N2 = rlwe.GenXPow2NTT(paramsN2.RingQ().AtLevel(0), paramsN2.LogN(), false) + eval.xPow2InvN2 = rlwe.GenXPow2NTT(paramsN2.RingQ(), paramsN2.LogN(), true) if btpParams.Mod1ParametersLiteral.Mod1Type == mod1.SinContinuous && btpParams.Mod1ParametersLiteral.DoubleAngle != 0 { return nil, fmt.Errorf("cannot use double angle formula for Mod1Type = Sin -> must use Mod1Type = Cos") @@ -283,13 +286,12 @@ func (eval Evaluator) BootstrapMany(cts []rlwe.Ciphertext) ([]rlwe.Ciphertext, e default: - LogSlots := cts[0].LogSlots() - nbCiphertexts := len(cts) - - if cts, err = eval.PackAndSwitchN1ToN2(cts); err != nil { + packedCTs, err := eval.PackAndSwitchN1ToN2(cts) + if err != nil { return nil, fmt.Errorf("cannot BootstrapMany: %w", err) } + cts = packedCTs.CTs for i := range cts { var ct *rlwe.Ciphertext if ct, err = eval.Evaluate(&cts[i]); err != nil { @@ -298,7 +300,7 @@ func (eval Evaluator) BootstrapMany(cts []rlwe.Ciphertext) ([]rlwe.Ciphertext, e cts[i] = *ct } - if cts, err = eval.UnpackAndSwitchN2Tn1(cts, LogSlots, nbCiphertexts); err != nil { + if cts, err = eval.UnpackAndSwitchN2Tn1(packedCTs); err != nil { return nil, fmt.Errorf("cannot BootstrapMany: %w", err) } } @@ -864,13 +866,35 @@ func (eval Evaluator) RealToComplexNew(ctReal *rlwe.Ciphertext) (ctCmplx *rlwe.C return } -func (eval Evaluator) PackAndSwitchN1ToN2(cts []rlwe.Ciphertext) ([]rlwe.Ciphertext, error) { +type packedCTs struct { + CTs []rlwe.Ciphertext + PackN1 packingContext // empty if N1 = N2 + PackN2 packingContext +} + +type packingContext struct { + Params *ckks.Parameters // Parameters of the ring we are packing to or unpacking from + LogMaxDimensions ring.Dimensions // maximum dimension of a packed ciphertext (logMaxDimensions <= params.LogMaxDimensions()) + LogSlots int // number of slots in a ct before packing (resp. after unpacking) + NbPackedCTs int // number of cts to be packed (resp. to be unpacked into) +} + +func (eval Evaluator) PackAndSwitchN1ToN2(cts []rlwe.Ciphertext) (packedCTs, error) { var err error + var packN1, packN2 packingContext + // If N1 < N2, we pack ciphertexts into N1 and then switch to N2 if eval.ResidualParameters.N() != eval.BootstrappingParameters.N() { - if cts, err = eval.Pack(cts, eval.ResidualParameters, eval.xPow2N1); err != nil { - return nil, fmt.Errorf("cannot PackAndSwitchN1ToN2: PackN1: %w", err) + + packN1 = packingContext{&eval.ResidualParameters, eval.ResidualParameters.LogMaxDimensions(), cts[0].LogSlots(), len(cts)} + + // If the bootstrapping max slots are smaller than the max slots of N1, we only pack up to the former + if eval.Parameters.LogMaxSlots() < eval.ResidualParameters.LogMaxSlots() { + packN1.LogMaxDimensions = eval.Parameters.LogMaxDimensions() + } + if cts, err = eval.Pack(cts, packN1, eval.xPow2N1); err != nil { + return packedCTs{}, fmt.Errorf("cannot PackAndSwitchN1ToN2: PackN1: %w", err) } for i := range cts { @@ -878,65 +902,99 @@ func (eval Evaluator) PackAndSwitchN1ToN2(cts []rlwe.Ciphertext) ([]rlwe.Ciphert } } - if cts, err = eval.Pack(cts, eval.BootstrappingParameters, eval.xPow2N2); err != nil { - return nil, fmt.Errorf("cannot PackAndSwitchN1ToN2: PackN2: %w", err) + // Packing ciphertexts into N2 (up to eval.Parameters.LogMaxDimensions()) + packN2 = packingContext{&eval.BootstrappingParameters, eval.Parameters.LogMaxDimensions(), cts[0].LogSlots(), len(cts)} + + if cts, err = eval.Pack(cts, packN2, eval.xPow2N2); err != nil { + return packedCTs{}, fmt.Errorf("cannot PackAndSwitchN1ToN2: PackN2: %w", err) } - return cts, nil + return packedCTs{cts, packN1, packN2}, nil } -func (eval Evaluator) UnpackAndSwitchN2Tn1(cts []rlwe.Ciphertext, LogSlots, Nb int) ([]rlwe.Ciphertext, error) { +func (eval Evaluator) UnpackAndSwitchN2Tn1(packedCTs packedCTs) ([]rlwe.Ciphertext, error) { - var err error + var ctsOut []rlwe.Ciphertext - if eval.ResidualParameters.N() != eval.BootstrappingParameters.N() { - if cts, err = eval.UnPack(cts, eval.BootstrappingParameters, LogSlots, Nb, eval.xPow2InvN2); err != nil { + LogSlots := packedCTs.PackN2.LogSlots + + // Unpack ciphertexts in N2 + for _, ct := range packedCTs.CTs { + ctsUnpack, err := eval.UnPack(&ct, packedCTs.PackN2, eval.xPow2InvN2) + + if err != nil { return nil, fmt.Errorf("cannot UnpackAndSwitchN2Tn1: UnpackN2: %w", err) } - for i := range cts { - cts[i] = *eval.SwitchRingDegreeN2ToN1New(&cts[i]) + ctsOut = append(ctsOut, ctsUnpack...) + packedCTs.PackN2.NbPackedCTs -= len(ctsUnpack) + } + + // If N1 != N2: 1) switch cts to N1 2) unpack the cts in N1 + if eval.ResidualParameters.N() != eval.BootstrappingParameters.N() { + var ctsN1 []rlwe.Ciphertext + LogSlots = packedCTs.PackN1.LogSlots + + for i := range ctsOut { + ctsOut[i] = *eval.SwitchRingDegreeN2ToN1New(&ctsOut[i]) + } + + for _, ct := range ctsOut { + ctsUnpack, err := eval.UnPack(&ct, packedCTs.PackN1, eval.xPow2InvN1) + if err != nil { + return nil, fmt.Errorf("cannot UnpackAndSwitchN2Tn1: UnpackN1: %w", err) + } + + ctsN1 = append(ctsN1, ctsUnpack...) + packedCTs.PackN1.NbPackedCTs -= len(ctsUnpack) } + + ctsOut = ctsN1 } - for i := range cts { - cts[i].LogDimensions.Cols = LogSlots + // Set back the dimension of cts to its original value + for i := range ctsOut { + ctsOut[i].LogDimensions.Cols = LogSlots } - return cts, nil + return ctsOut, nil } -func (eval Evaluator) UnPack(cts []rlwe.Ciphertext, params ckks.Parameters, LogSlots, Nb int, xPow2Inv []ring.Poly) ([]rlwe.Ciphertext, error) { - LogGap := params.LogMaxSlots() - LogSlots +// Unpack unpacks one sparse ciphertext of (log) dimension ctxt.logMaxDimensions +// into ctxt.NbPackedCTs ciphertexts of (log) dimension {0, ctxt.LogSlots} +func (eval Evaluator) UnPack(ct *rlwe.Ciphertext, ctxt packingContext, xPow2Inv []ring.Poly) ([]rlwe.Ciphertext, error) { + logPackCTs := ctxt.LogMaxDimensions.Cols - ctxt.LogSlots // log of number of CTs that can be packed in one ct - if LogGap == 0 { + cts := []rlwe.Ciphertext{*ct} + if logPackCTs == 0 { return cts, nil } - cts = append(cts, make([]rlwe.Ciphertext, Nb-1)...) + n := utils.Min(ctxt.NbPackedCTs, 1<> 1; k < step; k++ { - if (j + k) >= N { + if (j + k) >= n { break } - r.MulCoeffsMontgomery(cts[j+k].Value[0], xPow2Inv[i], cts[j+k].Value[0]) - r.MulCoeffsMontgomery(cts[j+k].Value[1], xPow2Inv[i], cts[j+k].Value[1]) + r.MulCoeffsMontgomery(cts[j+k].Value[0], xPow2Inv[logGap-i], cts[j+k].Value[0]) + r.MulCoeffsMontgomery(cts[j+k].Value[1], xPow2Inv[logGap-i], cts[j+k].Value[1]) } } } @@ -944,10 +1002,17 @@ func (eval Evaluator) UnPack(cts []rlwe.Ciphertext, params ckks.Parameters, LogS return cts, nil } -func (eval Evaluator) Pack(cts []rlwe.Ciphertext, params ckks.Parameters, xPow2 []ring.Poly) ([]rlwe.Ciphertext, error) { +// Pack packs ctxt.NbPackedCTs sparse ciphertexts of (log) dimension {0, ctxt.LogSlots} +// into one ciphertext of (log) dimension ctxt.logMaxDimensions +func (eval Evaluator) Pack(cts []rlwe.Ciphertext, ctxt packingContext, xPow2 []ring.Poly) ([]rlwe.Ciphertext, error) { var LogSlots = cts[0].LogSlots() - RingDegree := params.N() + var logMaxSlots = ctxt.LogMaxDimensions.Cols + RingDegree := ctxt.Params.N() + + if LogSlots > logMaxSlots { + return nil, fmt.Errorf("cannot Pack: cts[0].LogSlots()=%d > logMaxSlots=%d", LogSlots, logMaxSlots) + } for i, ct := range cts { if N := ct.LogSlots(); N != LogSlots { @@ -959,13 +1024,14 @@ func (eval Evaluator) Pack(cts []rlwe.Ciphertext, params ckks.Parameters, xPow2 } } - LogGap := params.LogMaxSlots() - LogSlots + logPackCTs := logMaxSlots - LogSlots // log of number of CTs that can be packed in one ct + logGapInN := (ctxt.Params.LogMaxSlots() - LogSlots - 1) - if LogGap == 0 { + if logPackCTs == 0 { return cts, nil } - for i := 0; i < LogGap; i++ { + for i := 0; i < logPackCTs; i++ { for j := 0; j < len(cts)>>1; j++ { @@ -974,10 +1040,10 @@ func (eval Evaluator) Pack(cts []rlwe.Ciphertext, params ckks.Parameters, xPow2 level := utils.Min(eve.Level(), odd.Level()) - r := params.RingQ().AtLevel(level) + r := ctxt.Params.RingQ().AtLevel(level) - r.MulCoeffsMontgomeryThenAdd(odd.Value[0], xPow2[i], eve.Value[0]) - r.MulCoeffsMontgomeryThenAdd(odd.Value[1], xPow2[i], eve.Value[1]) + r.MulCoeffsMontgomeryThenAdd(odd.Value[0], xPow2[logGapInN-i], eve.Value[0]) + r.MulCoeffsMontgomeryThenAdd(odd.Value[1], xPow2[logGapInN-i], eve.Value[1]) cts[j] = eve } @@ -990,9 +1056,8 @@ func (eval Evaluator) Pack(cts []rlwe.Ciphertext, params ckks.Parameters, xPow2 } } - LogMaxDimensions := params.LogMaxDimensions() for i := range cts { - cts[i].LogDimensions = LogMaxDimensions + cts[i].LogDimensions = ctxt.LogMaxDimensions } return cts, nil From 08823dd10bff28876fce519fef4c656b48ae8d03 Mon Sep 17 00:00:00 2001 From: lehugueni Date: Fri, 25 Oct 2024 12:27:30 +0200 Subject: [PATCH 2/4] doc + cleanup --- circuits/ckks/bootstrapping/evaluator.go | 86 +++++++++++++----------- 1 file changed, 47 insertions(+), 39 deletions(-) diff --git a/circuits/ckks/bootstrapping/evaluator.go b/circuits/ckks/bootstrapping/evaluator.go index 6dc433c20..19f511cc6 100644 --- a/circuits/ckks/bootstrapping/evaluator.go +++ b/circuits/ckks/bootstrapping/evaluator.go @@ -822,7 +822,7 @@ func (eval Evaluator) SlotsToCoeffs(ctReal, ctImag *rlwe.Ciphertext) (ctOut *rlw return eval.DFTEvaluator.SlotsToCoeffsNew(ctReal, ctImag, eval.S2CDFTMatrix) } -func (eval Evaluator) SwitchRingDegreeN1ToN2New(ctN1 *rlwe.Ciphertext) (ctN2 *rlwe.Ciphertext) { +func (eval Evaluator) switchRingDegreeN1ToN2New(ctN1 *rlwe.Ciphertext) (ctN2 *rlwe.Ciphertext) { ctN2 = ckks.NewCiphertext(eval.BootstrappingParameters, 1, ctN1.Level()) // Sanity check, this error should never happen unless this algorithm has been improperly @@ -833,7 +833,7 @@ func (eval Evaluator) SwitchRingDegreeN1ToN2New(ctN1 *rlwe.Ciphertext) (ctN2 *rl return } -func (eval Evaluator) SwitchRingDegreeN2ToN1New(ctN2 *rlwe.Ciphertext) (ctN1 *rlwe.Ciphertext) { +func (eval Evaluator) switchRingDegreeN2ToN1New(ctN2 *rlwe.Ciphertext) (ctN1 *rlwe.Ciphertext) { ctN1 = ckks.NewCiphertext(eval.ResidualParameters, 1, ctN2.Level()) // Sanity check, this error should never happen unless this algorithm has been improperly @@ -866,61 +866,69 @@ func (eval Evaluator) RealToComplexNew(ctReal *rlwe.Ciphertext) (ctCmplx *rlwe.C return } -type packedCTs struct { +// PackedCTs represents the list of ciphertexts to be bootstrapped. +// Each of these ciphertexts might pack sparse ciphertexts. +// PackN1/PackN2 contain the parameters needed to unpack once the bootstrapping is done. +type PackedCTs struct { CTs []rlwe.Ciphertext - PackN1 packingContext // empty if N1 = N2 - PackN2 packingContext + PackN1 PackingContext // empty if N1 = N2 + PackN2 PackingContext } -type packingContext struct { +// PackingContext contains the parameters used when packing (with Pack()) +type PackingContext struct { Params *ckks.Parameters // Parameters of the ring we are packing to or unpacking from LogMaxDimensions ring.Dimensions // maximum dimension of a packed ciphertext (logMaxDimensions <= params.LogMaxDimensions()) LogSlots int // number of slots in a ct before packing (resp. after unpacking) NbPackedCTs int // number of cts to be packed (resp. to be unpacked into) } -func (eval Evaluator) PackAndSwitchN1ToN2(cts []rlwe.Ciphertext) (packedCTs, error) { +// PackAndSwitchN1ToN2 packs the ciphertexts into N1 and switch to N2 if N1 < N2 +// then it packs the ciphertexts into N2. +func (eval Evaluator) PackAndSwitchN1ToN2(cts []rlwe.Ciphertext) (PackedCTs, error) { var err error - var packN1, packN2 packingContext + var packN1, packN2 PackingContext // If N1 < N2, we pack ciphertexts into N1 and then switch to N2 if eval.ResidualParameters.N() != eval.BootstrappingParameters.N() { - packN1 = packingContext{&eval.ResidualParameters, eval.ResidualParameters.LogMaxDimensions(), cts[0].LogSlots(), len(cts)} + packN1 = PackingContext{&eval.ResidualParameters, eval.ResidualParameters.LogMaxDimensions(), cts[0].LogSlots(), len(cts)} // If the bootstrapping max slots are smaller than the max slots of N1, we only pack up to the former if eval.Parameters.LogMaxSlots() < eval.ResidualParameters.LogMaxSlots() { packN1.LogMaxDimensions = eval.Parameters.LogMaxDimensions() } - if cts, err = eval.Pack(cts, packN1, eval.xPow2N1); err != nil { - return packedCTs{}, fmt.Errorf("cannot PackAndSwitchN1ToN2: PackN1: %w", err) + if cts, err = eval.pack(cts, packN1, eval.xPow2N1); err != nil { + return PackedCTs{}, fmt.Errorf("cannot PackAndSwitchN1ToN2: PackN1: %w", err) } for i := range cts { - cts[i] = *eval.SwitchRingDegreeN1ToN2New(&cts[i]) + cts[i] = *eval.switchRingDegreeN1ToN2New(&cts[i]) } } // Packing ciphertexts into N2 (up to eval.Parameters.LogMaxDimensions()) - packN2 = packingContext{&eval.BootstrappingParameters, eval.Parameters.LogMaxDimensions(), cts[0].LogSlots(), len(cts)} + packN2 = PackingContext{&eval.BootstrappingParameters, eval.Parameters.LogMaxDimensions(), cts[0].LogSlots(), len(cts)} - if cts, err = eval.Pack(cts, packN2, eval.xPow2N2); err != nil { - return packedCTs{}, fmt.Errorf("cannot PackAndSwitchN1ToN2: PackN2: %w", err) + if cts, err = eval.pack(cts, packN2, eval.xPow2N2); err != nil { + return PackedCTs{}, fmt.Errorf("cannot PackAndSwitchN1ToN2: PackN2: %w", err) } - return packedCTs{cts, packN1, packN2}, nil + return PackedCTs{cts, packN1, packN2}, nil } -func (eval Evaluator) UnpackAndSwitchN2Tn1(packedCTs packedCTs) ([]rlwe.Ciphertext, error) { +// UnpackAndSwitchN2Tn1 unpacks the ciphertexts into N2 and, if N1 < N2, it switch the ciphertexts +// to N1 and unpack further into N1 +func (eval Evaluator) UnpackAndSwitchN2Tn1(packedCTs PackedCTs) ([]rlwe.Ciphertext, error) { var ctsOut []rlwe.Ciphertext - LogSlots := packedCTs.PackN2.LogSlots + logSlots := packedCTs.PackN2.LogSlots // Unpack ciphertexts in N2 for _, ct := range packedCTs.CTs { - ctsUnpack, err := eval.UnPack(&ct, packedCTs.PackN2, eval.xPow2InvN2) + ctsUnpack, err := eval.unpack(&ct, packedCTs.PackN2, eval.xPow2InvN2) if err != nil { return nil, fmt.Errorf("cannot UnpackAndSwitchN2Tn1: UnpackN2: %w", err) @@ -933,14 +941,14 @@ func (eval Evaluator) UnpackAndSwitchN2Tn1(packedCTs packedCTs) ([]rlwe.Cipherte // If N1 != N2: 1) switch cts to N1 2) unpack the cts in N1 if eval.ResidualParameters.N() != eval.BootstrappingParameters.N() { var ctsN1 []rlwe.Ciphertext - LogSlots = packedCTs.PackN1.LogSlots + logSlots = packedCTs.PackN1.LogSlots for i := range ctsOut { - ctsOut[i] = *eval.SwitchRingDegreeN2ToN1New(&ctsOut[i]) + ctsOut[i] = *eval.switchRingDegreeN2ToN1New(&ctsOut[i]) } for _, ct := range ctsOut { - ctsUnpack, err := eval.UnPack(&ct, packedCTs.PackN1, eval.xPow2InvN1) + ctsUnpack, err := eval.unpack(&ct, packedCTs.PackN1, eval.xPow2InvN1) if err != nil { return nil, fmt.Errorf("cannot UnpackAndSwitchN2Tn1: UnpackN1: %w", err) } @@ -954,15 +962,15 @@ func (eval Evaluator) UnpackAndSwitchN2Tn1(packedCTs packedCTs) ([]rlwe.Cipherte // Set back the dimension of cts to its original value for i := range ctsOut { - ctsOut[i].LogDimensions.Cols = LogSlots + ctsOut[i].LogDimensions.Cols = logSlots } return ctsOut, nil } -// Unpack unpacks one sparse ciphertext of (log) dimension ctxt.logMaxDimensions +// unpack unpacks one sparse ciphertext of (log) dimension ctxt.logMaxDimensions // into ctxt.NbPackedCTs ciphertexts of (log) dimension {0, ctxt.LogSlots} -func (eval Evaluator) UnPack(ct *rlwe.Ciphertext, ctxt packingContext, xPow2Inv []ring.Poly) ([]rlwe.Ciphertext, error) { +func (eval Evaluator) unpack(ct *rlwe.Ciphertext, ctxt PackingContext, xPow2Inv []ring.Poly) ([]rlwe.Ciphertext, error) { logPackCTs := ctxt.LogMaxDimensions.Cols - ctxt.LogSlots // log of number of CTs that can be packed in one ct cts := []rlwe.Ciphertext{*ct} @@ -1002,30 +1010,30 @@ func (eval Evaluator) UnPack(ct *rlwe.Ciphertext, ctxt packingContext, xPow2Inv return cts, nil } -// Pack packs ctxt.NbPackedCTs sparse ciphertexts of (log) dimension {0, ctxt.LogSlots} +// pack packs ctxt.NbPackedCTs sparse ciphertexts of (log) dimension {0, ctxt.LogSlots} // into one ciphertext of (log) dimension ctxt.logMaxDimensions -func (eval Evaluator) Pack(cts []rlwe.Ciphertext, ctxt packingContext, xPow2 []ring.Poly) ([]rlwe.Ciphertext, error) { +func (eval Evaluator) pack(cts []rlwe.Ciphertext, ctxt PackingContext, xPow2 []ring.Poly) ([]rlwe.Ciphertext, error) { - var LogSlots = cts[0].LogSlots() + var logSlots = ctxt.LogSlots var logMaxSlots = ctxt.LogMaxDimensions.Cols - RingDegree := ctxt.Params.N() + ringDegree := ctxt.Params.N() - if LogSlots > logMaxSlots { - return nil, fmt.Errorf("cannot Pack: cts[0].LogSlots()=%d > logMaxSlots=%d", LogSlots, logMaxSlots) + if logSlots > logMaxSlots { + return nil, fmt.Errorf("cannot Pack: cts[0].LogSlots()=%d > logMaxSlots=%d", logSlots, logMaxSlots) } for i, ct := range cts { - if N := ct.LogSlots(); N != LogSlots { - return nil, fmt.Errorf("cannot Pack: cts[%d].PlaintextLogSlots()=%d != cts[0].PlaintextLogSlots=%d", i, N, LogSlots) + if s := ct.LogSlots(); s != logSlots { + return nil, fmt.Errorf("cannot Pack: cts[%d].PlaintextLogSlots()=%d != cts[0].PlaintextLogSlots=%d", i, s, logSlots) } - if N := ct.Value[0].N(); N != RingDegree { - return nil, fmt.Errorf("cannot Pack: cts[%d].Value[0].N()=%d != params.N()=%d", i, N, RingDegree) + if N := ct.Value[0].N(); N != ringDegree { + return nil, fmt.Errorf("cannot Pack: cts[%d].Value[0].N()=%d != params.N()=%d", i, N, ringDegree) } } - logPackCTs := logMaxSlots - LogSlots // log of number of CTs that can be packed in one ct - logGapInN := (ctxt.Params.LogMaxSlots() - LogSlots - 1) + logPackCTs := logMaxSlots - logSlots // log of number of CTs that can be packed in one ct + logGap := (ctxt.Params.LogMaxSlots() - logSlots - 1) if logPackCTs == 0 { return cts, nil @@ -1042,8 +1050,8 @@ func (eval Evaluator) Pack(cts []rlwe.Ciphertext, ctxt packingContext, xPow2 []r r := ctxt.Params.RingQ().AtLevel(level) - r.MulCoeffsMontgomeryThenAdd(odd.Value[0], xPow2[logGapInN-i], eve.Value[0]) - r.MulCoeffsMontgomeryThenAdd(odd.Value[1], xPow2[logGapInN-i], eve.Value[1]) + r.MulCoeffsMontgomeryThenAdd(odd.Value[0], xPow2[logGap-i], eve.Value[0]) + r.MulCoeffsMontgomeryThenAdd(odd.Value[1], xPow2[logGap-i], eve.Value[1]) cts[j] = eve } From 68b999e45aa7aca316cfbe944d05cd58ac570b72 Mon Sep 17 00:00:00 2001 From: lehugueni Date: Fri, 25 Oct 2024 13:57:55 +0200 Subject: [PATCH 3/4] typos --- circuits/ckks/bootstrapping/evaluator.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/circuits/ckks/bootstrapping/evaluator.go b/circuits/ckks/bootstrapping/evaluator.go index 19f511cc6..8db38f426 100644 --- a/circuits/ckks/bootstrapping/evaluator.go +++ b/circuits/ckks/bootstrapping/evaluator.go @@ -254,7 +254,7 @@ func (eval Evaluator) Bootstrap(ct *rlwe.Ciphertext) (*rlwe.Ciphertext, error) { return &cts[0], nil } -// BootstrapMany bootstraps a list of ciphertext and returns the list of bootstrapped ciphertexts. +// BootstrapMany bootstraps a list of ciphertexts and returns the list of bootstrapped ciphertexts. func (eval Evaluator) BootstrapMany(cts []rlwe.Ciphertext) ([]rlwe.Ciphertext, error) { var err error @@ -300,7 +300,7 @@ func (eval Evaluator) BootstrapMany(cts []rlwe.Ciphertext) ([]rlwe.Ciphertext, e cts[i] = *ct } - if cts, err = eval.UnpackAndSwitchN2Tn1(packedCTs); err != nil { + if cts, err = eval.UnpackAndSwitchN2ToN1(packedCTs); err != nil { return nil, fmt.Errorf("cannot BootstrapMany: %w", err) } } @@ -918,9 +918,9 @@ func (eval Evaluator) PackAndSwitchN1ToN2(cts []rlwe.Ciphertext) (PackedCTs, err return PackedCTs{cts, packN1, packN2}, nil } -// UnpackAndSwitchN2Tn1 unpacks the ciphertexts into N2 and, if N1 < N2, it switch the ciphertexts -// to N1 and unpack further into N1 -func (eval Evaluator) UnpackAndSwitchN2Tn1(packedCTs PackedCTs) ([]rlwe.Ciphertext, error) { +// UnpackAndSwitchN2ToN1 unpacks the ciphertexts into N2 and, if N1 < N2, it switches the ciphertexts +// to N1 and unpacks further into N1 +func (eval Evaluator) UnpackAndSwitchN2ToN1(packedCTs PackedCTs) ([]rlwe.Ciphertext, error) { var ctsOut []rlwe.Ciphertext From 76d8965ec49b52ea94aefcc85d9cabbeea49a77a Mon Sep 17 00:00:00 2001 From: lehugueni Date: Thu, 21 Nov 2024 10:22:32 +0100 Subject: [PATCH 4/4] fix params in bts shallowcopy --- circuits/ckks/bootstrapping/evaluator.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/circuits/ckks/bootstrapping/evaluator.go b/circuits/ckks/bootstrapping/evaluator.go index 8db38f426..606af81c6 100644 --- a/circuits/ckks/bootstrapping/evaluator.go +++ b/circuits/ckks/bootstrapping/evaluator.go @@ -134,11 +134,12 @@ func (eval Evaluator) ShallowCopy() *Evaluator { heEvaluator := eval.Evaluator.ShallowCopy() paramsN1 := eval.ResidualParameters + paramsN2 := eval.BootstrappingParameters var DomainSwitcher ckks.DomainSwitcher if paramsN1.RingType() == ring.ConjugateInvariant { var err error - if DomainSwitcher, err = ckks.NewDomainSwitcher(eval.Parameters.BootstrappingParameters, eval.EvkCmplxToReal, eval.EvkRealToCmplx); err != nil { + if DomainSwitcher, err = ckks.NewDomainSwitcher(paramsN2, eval.EvkCmplxToReal, eval.EvkRealToCmplx); err != nil { panic(fmt.Errorf("cannot NewBootstrapper: ckks.NewDomainSwitcher: %w", err)) } } @@ -153,8 +154,8 @@ func (eval Evaluator) ShallowCopy() *Evaluator { xPow2N2: eval.xPow2N2, xPow2InvN2: eval.xPow2InvN2, DomainSwitcher: DomainSwitcher, - DFTEvaluator: dft.NewEvaluator(paramsN1, heEvaluator), - Mod1Evaluator: mod1.NewEvaluator(heEvaluator, polynomial.NewEvaluator(paramsN1, heEvaluator), eval.Mod1Parameters), + DFTEvaluator: dft.NewEvaluator(paramsN2, heEvaluator), + Mod1Evaluator: mod1.NewEvaluator(heEvaluator, polynomial.NewEvaluator(paramsN2, heEvaluator), eval.Mod1Parameters), SkDebug: eval.SkDebug, } }