diff --git a/ckks/advanced/evaluator.go b/ckks/advanced/evaluator.go index 5ab13ce0..5e377370 100644 --- a/ckks/advanced/evaluator.go +++ b/ckks/advanced/evaluator.go @@ -142,32 +142,39 @@ func (eval *evaluator) CoeffsToSlotsNew(ctIn *ckks.Ciphertext, ctsMatrices Encod // If the packing is sparse (n < N/2), then returns ctReal = Ecd(vReal || vImag) and ctImag = nil. // If the packing is dense (n == N/2), then returns ctReal = Ecd(vReal) and ctImag = Ecd(vImag). func (eval *evaluator) CoeffsToSlots(ctIn *ckks.Ciphertext, ctsMatrices EncodingMatrix, ctReal, ctImag *ckks.Ciphertext) { - zV := ctIn.CopyNew() - eval.dft(ctIn, ctsMatrices.matrices, zV) - eval.Conjugate(zV, ctReal) + if ctsMatrices.RepackImag2Real { - var tmp *ckks.Ciphertext - if ctImag != nil { - tmp = ctImag + zV := ctIn.CopyNew() + + eval.dft(ctIn, ctsMatrices.matrices, zV) + + eval.Conjugate(zV, ctReal) + + var tmp *ckks.Ciphertext + if ctImag != nil { + tmp = ctImag + } else { + tmp = ckks.NewCiphertextAtLevelFromPoly(ctReal.Level(), [2]*ring.Poly{eval.BuffCt().Value[0], eval.BuffCt().Value[1]}) + } + + // Imag part + eval.Sub(zV, ctReal, tmp) + eval.DivByi(tmp, tmp) + + // Real part + eval.Add(ctReal, zV, ctReal) + + // If repacking, then ct0 and ct1 right n/2 slots are zero. + if eval.params.LogSlots() < eval.params.LogN()-1 { + eval.Rotate(tmp, eval.params.Slots(), tmp) + eval.Add(ctReal, tmp, ctReal) + } + + zV = nil } else { - tmp = ckks.NewCiphertextAtLevelFromPoly(ctReal.Level(), [2]*ring.Poly{eval.BuffCt().Value[0], eval.BuffCt().Value[1]}) + eval.dft(ctIn, ctsMatrices.matrices, ctReal) } - - // Imag part - eval.Sub(zV, ctReal, tmp) - eval.DivByi(tmp, tmp) - - // Real part - eval.Add(ctReal, zV, ctReal) - - // If repacking, then ct0 and ct1 right n/2 slots are zero. - if eval.params.LogSlots() < eval.params.LogN()-1 { - eval.Rotate(tmp, eval.params.Slots(), tmp) - eval.Add(ctReal, tmp, ctReal) - } - - zV = nil } // SlotsToCoeffsNew applies the homomorphic decoding and returns the result on a new ciphertext. diff --git a/ckks/advanced/homomorphic_encoding.go b/ckks/advanced/homomorphic_encoding.go index 85f04371..ca2d793f 100644 --- a/ckks/advanced/homomorphic_encoding.go +++ b/ckks/advanced/homomorphic_encoding.go @@ -25,6 +25,7 @@ type EncodingMatrix struct { // EncodingMatrixLiteral is a struct storing the parameters to generate the factorized DFT matrix. type EncodingMatrixLiteral struct { LinearTransformType LinearTransformType + RepackImag2Real bool // Repack imag into the right n slots or reals. LogN int // log(RingDegree) LogSlots int // log(slots) Scaling float64 // constant by which the matrix is multiplied with @@ -64,24 +65,26 @@ func (mParams *EncodingMatrixLiteral) Levels() (levels []int) { } // Rotations returns the list of rotations performed during the CoeffsToSlot operation. -func (mParams *EncodingMatrixLiteral) Rotations(logN, logSlots int) (rotations []int) { +func (mParams *EncodingMatrixLiteral) Rotations() (rotations []int) { rotations = []int{} + logSlots := mParams.LogSlots + logN := mParams.LogN slots := 1 << logSlots dslots := slots - if logSlots < logN-1 { + if logSlots < logN-1 && mParams.RepackImag2Real { dslots <<= 1 if mParams.LinearTransformType == CoeffsToSlots { rotations = append(rotations, slots) } } - indexCtS := computeBootstrappingDFTIndexMap(logN, logSlots, mParams.Depth(false), mParams.LinearTransformType, mParams.BitReversed) + indexCtS := mParams.computeBootstrappingDFTIndexMap() // Coeffs to Slots rotations for i, pVec := range indexCtS { N1 := ckks.FindBestBSGSSplit(pVec, dslots, mParams.BSGSRatio) - rotations = addMatrixRotToList(pVec, rotations, N1, slots, mParams.LinearTransformType == SlotsToCoeffs && logSlots < logN-1 && i == 0) + rotations = addMatrixRotToList(pVec, rotations, N1, slots, mParams.LinearTransformType == SlotsToCoeffs && logSlots < logN-1 && i == 0 && mParams.RepackImag2Real) } return @@ -93,28 +96,16 @@ func (mParams *EncodingMatrixLiteral) Rotations(logN, logSlots int) (rotations [ func NewHomomorphicEncodingMatrixFromLiteral(mParams EncodingMatrixLiteral, encoder ckks.Encoder) EncodingMatrix { logSlots := mParams.LogSlots - slots := 1 << logSlots - depth := mParams.Depth(false) - logdSlots := mParams.LogSlots + 1 - if logdSlots == mParams.LogN { - logdSlots-- - } - - roots := computeRoots(slots << 1) - pow5 := make([]int, (slots<<1)+1) - pow5[0] = 1 - for i := 1; i < (slots<<1)+1; i++ { - pow5[i] = pow5[i-1] * 5 - pow5[i] &= (slots << 2) - 1 + logdSlots := logSlots + if logdSlots < mParams.LogN-1 && mParams.RepackImag2Real { + logdSlots++ } ctsLevels := mParams.Levels() - scaling := complex(math.Pow(mParams.Scaling, 1.0/float64(mParams.Depth(false))), 0) - // CoeffsToSlots vectors matrices := make([]ckks.LinearTransform, len(ctsLevels)) - pVecDFT := computeDFTMatrices(logSlots, logdSlots, depth, roots, pow5, scaling, mParams.LinearTransformType, mParams.BitReversed) + pVecDFT := mParams.computeDFTMatrices() cnt := 0 trueDepth := mParams.Depth(true) for i := range mParams.ScalingFactor { @@ -289,7 +280,14 @@ func addMatrixRotToList(pVec map[int]bool, rotations []int, N1, slots int, repac return rotations } -func computeBootstrappingDFTIndexMap(logN, logSlots, maxDepth int, ltType LinearTransformType, bitreversed bool) (rotationMap []map[int]bool) { +func (mParams *EncodingMatrixLiteral) computeBootstrappingDFTIndexMap() (rotationMap []map[int]bool) { + + logN := mParams.LogN + logSlots := mParams.LogSlots + ltType := mParams.LinearTransformType + repacki2r := mParams.RepackImag2Real + bitreversed := mParams.BitReversed + maxDepth := mParams.Depth(false) var level, depth, nextLevel int @@ -319,7 +317,7 @@ func computeBootstrappingDFTIndexMap(logN, logSlots, maxDepth int, ltType Linear level = logSlots for i := 0; i < maxDepth; i++ { - if logSlots < logN-1 && ltType == SlotsToCoeffs && i == 0 { + if logSlots < logN-1 && ltType == SlotsToCoeffs && i == 0 && repacki2r { // Special initial matrix for the repacking before SlotsToCoeffs rotationMap[i] = genWfftRepackIndexMap(logSlots, level) @@ -335,6 +333,7 @@ func computeBootstrappingDFTIndexMap(logN, logSlots, maxDepth int, ltType Linear } } else { + // First layer of the i-th level of the DFT rotationMap[i] = genWfftIndexMap(logSlots, level, ltType, bitreversed) @@ -397,14 +396,32 @@ func nextLevelfftIndexMap(vec map[int]bool, logL, N, nextLevel int, ltType Linea return } -func computeDFTMatrices(logSlots, logdSlots, maxDepth int, roots []complex128, pow5 []int, diffscale complex128, ltType LinearTransformType, bitreversed bool) (plainVector []map[int][]complex128) { +func (mParams *EncodingMatrixLiteral) computeDFTMatrices() (plainVector []map[int][]complex128) { + + logSlots := mParams.LogSlots + slots := 1 << logSlots + maxDepth := mParams.Depth(false) + ltType := mParams.LinearTransformType + bitreversed := mParams.BitReversed + + logdSlots := logSlots + if logdSlots < mParams.LogN-1 && mParams.RepackImag2Real { + logdSlots++ + } + + roots := computeRoots(slots << 1) + pow5 := make([]int, (slots<<1)+1) + pow5[0] = 1 + for i := 1; i < (slots<<1)+1; i++ { + pow5[i] = pow5[i-1] * 5 + pow5[i] &= (slots << 2) - 1 + } var fftLevel, depth, nextfftLevel int fftLevel = logSlots var a, b, c [][]complex128 - if ltType == CoeffsToSlots { a, b, c = fftInvPlainVec(logSlots, 1<>>>>>> [ckks/advanced]: better StC & CtS if btp, err = NewBootstrapper(params, btpParams, evk); err != nil { panic(err) diff --git a/ckks/bootstrapping/bootstrap_params.go b/ckks/bootstrapping/bootstrap_params.go index e300ddb3..022a7dbc 100644 --- a/ckks/bootstrapping/bootstrap_params.go +++ b/ckks/bootstrapping/bootstrap_params.go @@ -84,26 +84,433 @@ func (p *Parameters) UnmarshalBinary(data []byte) (err error) { } // RotationsForBootstrapping returns the list of rotations performed during the Bootstrapping operation. -func (p *Parameters) RotationsForBootstrapping(LogN, LogSlots int) (rotations []int) { +func (p *Parameters) RotationsForBootstrapping(params ckks.Parameters) (rotations []int) { + + logN := params.LogN() + logSlots := params.LogSlots() // List of the rotation key values to needed for the bootstrapp rotations = []int{} - slots := 1 << LogSlots - dslots := slots - if LogSlots < LogN-1 { - dslots <<= 1 - } - //SubSum rotation needed X -> Y^slots rotations - for i := LogSlots; i < LogN-1; i++ { + for i := logSlots; i < logN-1; i++ { if !utils.IsInSliceInt(1<>>>>>> [ckks/advanced]: better StC & CtS +>>>>>>> [ckks/advanced]: better StC & CtS diff --git a/ckks/bootstrapping/bootstrap_test.go b/ckks/bootstrapping/bootstrap_test.go index 558b5f60..afc1a7b1 100644 --- a/ckks/bootstrapping/bootstrap_test.go +++ b/ckks/bootstrapping/bootstrap_test.go @@ -109,7 +109,12 @@ func testbootstrap(params ckks.Parameters, original bool, btpParams Parameters, encryptor := ckks.NewEncryptor(params, sk) decryptor := ckks.NewDecryptor(params, sk) +<<<<<<< btp_eprint evk := GenEvaluationKeys(btpParams, params, sk) +======= + rotations := btpParams.RotationsForBootstrapping(params) + rotkeys := kgen.GenRotationKeysForRotations(rotations, true, sk) +>>>>>>> [ckks/advanced]: better StC & CtS btp, err := NewBootstrapper(params, btpParams, evk) if err != nil { diff --git a/ckks/bootstrapping/bootstrapper.go b/ckks/bootstrapping/bootstrapper.go index 21f7af58..ca5637f1 100644 --- a/ckks/bootstrapping/bootstrapper.go +++ b/ckks/bootstrapping/bootstrapper.go @@ -147,8 +147,8 @@ func (bb *bootstrapperBase) CheckKeys(btpKeys EvaluationKeys) (err error) { rotKeyIndex := []int{} rotKeyIndex = append(rotKeyIndex, bb.params.RotationsForTrace(bb.params.LogSlots(), bb.params.MaxLogSlots())...) - rotKeyIndex = append(rotKeyIndex, bb.CoeffsToSlotsParameters.Rotations(bb.params.LogN(), bb.params.LogSlots())...) - rotKeyIndex = append(rotKeyIndex, bb.SlotsToCoeffsParameters.Rotations(bb.params.LogN(), bb.params.LogSlots())...) + rotKeyIndex = append(rotKeyIndex, bb.CoeffsToSlotsParameters.Rotations()...) + rotKeyIndex = append(rotKeyIndex, bb.SlotsToCoeffsParameters.Rotations()...) rotMissing := []int{} for _, i := range rotKeyIndex { diff --git a/ckks/ckks_vector_ops.go b/ckks/ckks_vector_ops.go index 06a6d7e0..13ab0e49 100644 --- a/ckks/ckks_vector_ops.go +++ b/ckks/ckks_vector_ops.go @@ -58,6 +58,7 @@ func SpecialFFTUL8Vec(values []complex128, N, M int, rotGroup []int, roots []com logM := int(bits.Len64(uint64(M))) - 1 for loglen := 1; loglen <= logN; loglen++ { + len := 1 << loglen lenh := len >> 1 lenq := len << 2 @@ -219,6 +220,7 @@ func SpecialiFFTUL8Vec(values []complex128, N, M int, rotGroup []int, roots []co logM := int(bits.Len64(uint64(M))) - 1 for loglen := logN; loglen > 0; loglen-- { + len := 1 << loglen lenh := len >> 1 lenq := len << 2 diff --git a/ckks/encoder.go b/ckks/encoder.go index edb99f27..5945b546 100644 --- a/ckks/encoder.go +++ b/ckks/encoder.go @@ -388,7 +388,7 @@ func (ecd *encoderComplex128) Embed(values interface{}, logSlots int, scale floa ecd.values[i] = 0 } - if logSlots < 3 { + if logSlots < 4 { SpecialiFFTVec(ecd.values, slots, ecd.m, ecd.rotGroup, ecd.roots) } else { SpecialiFFTUL8Vec(ecd.values, slots, ecd.m, ecd.rotGroup, ecd.roots) diff --git a/ckks/utils.go b/ckks/utils.go index 51d5da47..ec57f73e 100644 --- a/ckks/utils.go +++ b/ckks/utils.go @@ -271,6 +271,28 @@ func SliceBitReverseInPlaceComplex128(slice []complex128, N int) { } } +// SliceBitReverseInPlaceFloat64 applies an in-place bit-reverse permuation on the input slice. +func SliceBitReverseInPlaceFloat64(slice []float64, N int) { + + var bit, j int + + for i := 1; i < N; i++ { + + bit = N >> 1 + + for j >= bit { + j -= bit + bit >>= 1 + } + + j += bit + + if i < j { + slice[i], slice[j] = slice[j], slice[i] + } + } +} + // SliceBitReverseInPlaceRingComplex applies an in-place bit-reverse permuation on the input slice. func SliceBitReverseInPlaceRingComplex(slice []*ring.Complex, N int) { diff --git a/examples/main.go b/examples/main.go new file mode 100644 index 00000000..e15ffeca --- /dev/null +++ b/examples/main.go @@ -0,0 +1,145 @@ +package main + +import ( + "fmt" + "github.com/tuneinsight/lattigo/v3/ckks" + "github.com/tuneinsight/lattigo/v3/ring" + "github.com/tuneinsight/lattigo/v3/rlwe" + ckksAdvanced "github.com/tuneinsight/lattigo/v3/ckks/advanced" +) + +func main() { + LUT() +} + +// Q modulus Q +var Q = []uint64{0x80000000080001, 0x2000000e0001, 0x1fffffc20001} + +// P modulus P +var P = []uint64{0x4000000008a0001} + +var ckksParamsN12 = ckks.ParametersLiteral{ + LogN: 5, + LogSlots: 4, + Q: Q, + P: P, + DefaultScale: 1 << 40, + Sigma: rlwe.DefaultSigma, + RingType: ring.Standard, +} + +// LUT example +func LUT() { + var err error + var paramsN12 ckks.Parameters + + if paramsN12, err = ckks.NewParametersFromLiteral(ckksParamsN12); err != nil { + panic(err) + } + + kgenN12 := ckks.NewKeyGenerator(paramsN12) + skN12 := kgenN12.GenSecretKey() + encoderN12 := ckks.NewEncoder(paramsN12) + encryptorN12 := ckks.NewEncryptor(paramsN12, skN12) + decryptorN12 := ckks.NewDecryptor(paramsN12, skN12) + + fmt.Printf("Gen SlotsToCoeffs Matrices... ") + + // SlotsToCoeffsParameters homomorphic encoding parameters + var SlotsToCoeffsParameters = ckksAdvanced.EncodingMatrixLiteral{ + LogN: paramsN12.LogN(), + LogSlots: paramsN12.LogSlots(), + Scaling: 1.0, + LinearTransformType: ckksAdvanced.SlotsToCoeffs, + RepackImag2Real: false, + LevelStart: 2, // starting level + BSGSRatio: 4.0, // ratio between n1/n2 for n1*n2 = slots + BitReversed: false, // bit-reversed input + ScalingFactor: [][]float64{ // Decomposition level of the encoding matrix + {0x2000000e0001}, // Scale of the second matriox + {0x1fffffc20001}, // Scale of the first matrix + }, + } + + // CoeffsToSlotsParameters homomorphic decoding parameters + var CoeffsToSlotsParameters = ckksAdvanced.EncodingMatrixLiteral{ + LinearTransformType: ckksAdvanced.CoeffsToSlots, + RepackImag2Real: false, + LogN: paramsN12.LogN(), + LogSlots: paramsN12.LogSlots(), + Scaling: 1/16.0, + LevelStart: 2, // starting level + BSGSRatio: 4.0, // ratio between n1/n2 for n1*n2 = slots + BitReversed: false, // bit-reversed input + ScalingFactor: [][]float64{ // Decomposition level of the encoding matrix + {0x2000000e0001}, // Scale of the second matriox + {0x1fffffc20001}, // Scale of the first matrix + }, + } + + SlotsToCoeffsMatrix := ckksAdvanced.NewHomomorphicEncodingMatrixFromLiteral(SlotsToCoeffsParameters, encoderN12) + CoeffsToSlotsMatrix := ckksAdvanced.NewHomomorphicEncodingMatrixFromLiteral(CoeffsToSlotsParameters, encoderN12) + + // Rotation Keys + rotations := []int{} + for i := 1; i < paramsN12.N(); i <<= 1 { + rotations = append(rotations, i) + } + + rotations = append(rotations, SlotsToCoeffsParameters.Rotations()...) + rotations = append(rotations, CoeffsToSlotsParameters.Rotations()...) + + for i := 0; i < 32; i++{ + rotations = append(rotations, i) + } + + rotKey := kgenN12.GenRotationKeysForRotations(rotations, true, skN12) + + eval := ckksAdvanced.NewEvaluator(paramsN12, rlwe.EvaluationKey{Rlk: nil, Rtks: rotKey}) + + values := make([]complex128, paramsN12.Slots()) + + for i := 0; i < paramsN12.Slots(); i++ { + values[i] = complex(float64(i+1), float64(i+1+paramsN12.Slots())) + } + + fmt.Println("Before") + ckks.SliceBitReverseInPlaceComplex128(values, paramsN12.Slots()) + for i := range values{ + fmt.Printf("%2d: %7.4f\n", i, values[i]) + } + ckks.SliceBitReverseInPlaceComplex128(values, paramsN12.Slots()) + + pt := ckks.NewPlaintext(paramsN12, paramsN12.MaxLevel(), paramsN12.DefaultScale()) + encoderN12.EncodeSlots(values, pt, paramsN12.LogSlots()) + ctN12 := encryptorN12.EncryptNew(pt) + + + ctN12R := eval.SlotsToCoeffsNew(ctN12, nil, SlotsToCoeffsMatrix) + + valuesF := encoderN12.DecodeCoeffs(decryptorN12.DecryptNew(ctN12R)) + + fmt.Println("After") + for i, v := range valuesF{ + fmt.Printf("%2d: %7.4f\n", i, v) + } + + encoderN12.EncodeCoeffs(valuesF, pt) + ctN12 = encryptorN12.EncryptNew(pt) + + var ctN12I *ckks.Ciphertext + ctN12R, ctN12I = eval.CoeffsToSlotsNew(ctN12, CoeffsToSlotsMatrix) + + if ctN12I != nil{ + eval.MultByi(ctN12I, ctN12I) + eval.Add(ctN12R, ctN12I, ctN12R) + } + + values = encoderN12.DecodeSlots(decryptorN12.DecryptNew(ctN12R), paramsN12.LogSlots()) + + fmt.Println("After") + for i, v := range values{ + fmt.Printf("%2d: %7.4f\n", i, v) + } + +}