mirror of
https://github.com/tuneinsight/lattigo.git
synced 2025-09-13 03:27:14 +00:00
872 lines
25 KiB
Go
872 lines
25 KiB
Go
package float
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"math"
|
|
"math/big"
|
|
|
|
"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/bignum"
|
|
)
|
|
|
|
// DFTEvaluatorInterface is an interface defining the set of methods required to instantiate a DFTEvaluator.
|
|
type DFTEvaluatorInterface interface {
|
|
rlwe.ParameterProvider
|
|
circuits.EvaluatorForLinearTransformation
|
|
Add(op0 *rlwe.Ciphertext, op1 interface{}, opOut *rlwe.Ciphertext) (err error)
|
|
Sub(op0 *rlwe.Ciphertext, op1 interface{}, opOut *rlwe.Ciphertext) (err error)
|
|
Mul(op0 *rlwe.Ciphertext, op1 interface{}, opOut *rlwe.Ciphertext) (err error)
|
|
Conjugate(op0 *rlwe.Ciphertext, opOut *rlwe.Ciphertext) (err error)
|
|
Rotate(op0 *rlwe.Ciphertext, k int, opOut *rlwe.Ciphertext) (err error)
|
|
Rescale(op0 *rlwe.Ciphertext, opOut *rlwe.Ciphertext) (err error)
|
|
}
|
|
|
|
// DFTType is a type used to distinguish between different discrete Fourier transformations.
|
|
type DFTType int
|
|
|
|
// HomomorphicEncode (IDFT) and HomomorphicDecode (DFT) are two available linear transformations for homomorphic encoding and decoding.
|
|
const (
|
|
HomomorphicEncode = DFTType(0) // Homomorphic Encoding (IDFT)
|
|
HomomorphicDecode = DFTType(1) // Homomorphic Decoding (DFT)
|
|
)
|
|
|
|
// DFTMatrix is a struct storing the factorized IDFT, DFT matrices, which are
|
|
// used to homomorphically encode and decode a ciphertext respectively.
|
|
type DFTMatrix struct {
|
|
DFTMatrixLiteral
|
|
Matrices []LinearTransformation
|
|
}
|
|
|
|
// DFTMatrixLiteral is a struct storing the parameters to generate the factorized DFT/IDFT matrices.
|
|
// This struct has mandatory and optional fields.
|
|
//
|
|
// Mandatory:
|
|
// - DFTType: Encode (a.k.a. CoeffsToSlots) or Decode (a.k.a. SlotsToCoeffs)
|
|
// - LogN: log2(RingDegree)
|
|
// - LogSlots: log2(slots)
|
|
// - LevelStart: starting level of the linear transformation
|
|
// - Levels: depth of the linear transform (i.e. the degree of factorization of the encoding matrix)
|
|
//
|
|
// Optional:
|
|
// - RepackImag2Real: if true, the imaginary part is repacked into the right n slots of the real part
|
|
// - Scaling: constant by which the matrix is multiplied
|
|
// - BitReversed: if true, then applies the transformation bit-reversed and expects bit-reversed inputs
|
|
// - LogBSGSRatio: log2 of the ratio between the inner and outer loop of the baby-step giant-step algorithm
|
|
type DFTMatrixLiteral struct {
|
|
// Mandatory
|
|
Type DFTType
|
|
LogSlots int
|
|
LevelStart int
|
|
Levels []int
|
|
// Optional
|
|
RepackImag2Real bool // Default: False.
|
|
Scaling *big.Float // Default 1.0.
|
|
BitReversed bool // Default: False.
|
|
LogBSGSRatio int // Default: 0.
|
|
}
|
|
|
|
// Depth returns the number of levels allocated to the linear transform.
|
|
// If actual == true then returns the number of moduli consumed, else
|
|
// returns the factorization depth.
|
|
func (d DFTMatrixLiteral) Depth(actual bool) (depth int) {
|
|
if actual {
|
|
depth = len(d.Levels)
|
|
} else {
|
|
for _, d := range d.Levels {
|
|
depth += d
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// GaloisElements returns the list of rotations performed during the CoeffsToSlot operation.
|
|
func (d DFTMatrixLiteral) GaloisElements(params ckks.Parameters) (galEls []uint64) {
|
|
rotations := []int{}
|
|
|
|
logSlots := d.LogSlots
|
|
logN := params.LogN()
|
|
slots := 1 << logSlots
|
|
dslots := slots
|
|
if logSlots < logN-1 && d.RepackImag2Real {
|
|
dslots <<= 1
|
|
if d.Type == HomomorphicEncode {
|
|
rotations = append(rotations, slots)
|
|
}
|
|
}
|
|
|
|
indexCtS := d.computeBootstrappingDFTIndexMap(logN)
|
|
|
|
// Coeffs to Slots rotations
|
|
for i, pVec := range indexCtS {
|
|
N1 := circuits.FindBestBSGSRatio(utils.GetKeys(pVec), dslots, d.LogBSGSRatio)
|
|
rotations = addMatrixRotToList(pVec, rotations, N1, slots, d.Type == HomomorphicDecode && logSlots < logN-1 && i == 0 && d.RepackImag2Real)
|
|
}
|
|
|
|
return params.GaloisElements(rotations)
|
|
}
|
|
|
|
// MarshalBinary returns a JSON representation of the the target DFTMatrixLiteral on a slice of bytes.
|
|
// See `Marshal` from the `encoding/json` package.
|
|
func (d DFTMatrixLiteral) MarshalBinary() (data []byte, err error) {
|
|
return json.Marshal(d)
|
|
}
|
|
|
|
// UnmarshalBinary reads a JSON representation on the target DFTMatrixLiteral struct.
|
|
// See `Unmarshal` from the `encoding/json` package.
|
|
func (d *DFTMatrixLiteral) UnmarshalBinary(data []byte) error {
|
|
return json.Unmarshal(data, d)
|
|
}
|
|
|
|
type DFTEvaluator struct {
|
|
DFTEvaluatorInterface
|
|
*LinearTransformationEvaluator
|
|
parameters ckks.Parameters
|
|
}
|
|
|
|
func NewDFTEvaluator(params ckks.Parameters, eval DFTEvaluatorInterface) *DFTEvaluator {
|
|
dfteval := new(DFTEvaluator)
|
|
dfteval.DFTEvaluatorInterface = eval
|
|
dfteval.LinearTransformationEvaluator = NewLinearTransformationEvaluator(eval)
|
|
dfteval.parameters = params
|
|
return dfteval
|
|
}
|
|
|
|
// NewDFTMatrixFromLiteral generates the factorized DFT/IDFT matrices for the homomorphic encoding/decoding.
|
|
func NewDFTMatrixFromLiteral(params ckks.Parameters, d DFTMatrixLiteral, encoder *ckks.Encoder) (DFTMatrix, error) {
|
|
|
|
logSlots := d.LogSlots
|
|
logdSlots := logSlots
|
|
if maxLogSlots := params.LogMaxDimensions().Cols; logdSlots < maxLogSlots && d.RepackImag2Real {
|
|
logdSlots++
|
|
}
|
|
|
|
// CoeffsToSlots vectors
|
|
matrices := []LinearTransformation{}
|
|
pVecDFT := d.GenMatrices(params.LogN(), params.EncodingPrecision())
|
|
|
|
nbModuliPerRescale := params.LevelsConsummedPerRescaling()
|
|
|
|
level := d.LevelStart
|
|
var idx int
|
|
for i := range d.Levels {
|
|
|
|
scale := rlwe.NewScale(params.Q()[level])
|
|
|
|
for j := 1; j < nbModuliPerRescale; j++ {
|
|
scale = scale.Mul(rlwe.NewScale(params.Q()[level-j]))
|
|
}
|
|
|
|
if d.Levels[i] > 1 {
|
|
y := new(big.Float).SetPrec(scale.Value.Prec()).SetInt64(1)
|
|
y.Quo(y, new(big.Float).SetPrec(scale.Value.Prec()).SetInt64(int64(d.Levels[i])))
|
|
|
|
scale.Value = *bignum.Pow(&scale.Value, y)
|
|
}
|
|
|
|
for j := 0; j < d.Levels[i]; j++ {
|
|
|
|
ltparams := LinearTransformationParameters{
|
|
DiagonalsIndexList: pVecDFT[idx].DiagonalsIndexList(),
|
|
Level: level,
|
|
Scale: scale,
|
|
LogDimensions: ring.Dimensions{Rows: 0, Cols: logdSlots},
|
|
LogBabyStepGianStepRatio: d.LogBSGSRatio,
|
|
}
|
|
|
|
mat := NewLinearTransformation(params, ltparams)
|
|
|
|
if err := EncodeLinearTransformation[*bignum.Complex](ltparams, encoder, pVecDFT[idx], mat); err != nil {
|
|
return DFTMatrix{}, fmt.Errorf("cannot NewDFTMatrixFromLiteral: %w", err)
|
|
}
|
|
|
|
matrices = append(matrices, mat)
|
|
idx++
|
|
}
|
|
|
|
level -= nbModuliPerRescale
|
|
}
|
|
|
|
return DFTMatrix{DFTMatrixLiteral: d, Matrices: matrices}, nil
|
|
}
|
|
|
|
// CoeffsToSlotsNew applies the homomorphic encoding and returns the result on new ciphertexts.
|
|
// Homomorphically encodes a complex vector vReal + i*vImag.
|
|
// 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 *DFTEvaluator) CoeffsToSlotsNew(ctIn *rlwe.Ciphertext, ctsMatrices DFTMatrix) (ctReal, ctImag *rlwe.Ciphertext, err error) {
|
|
ctReal = ckks.NewCiphertext(eval.parameters, 1, ctsMatrices.LevelStart)
|
|
|
|
if ctsMatrices.LogSlots == eval.parameters.LogMaxSlots() {
|
|
ctImag = ckks.NewCiphertext(eval.parameters, 1, ctsMatrices.LevelStart)
|
|
}
|
|
|
|
return ctReal, ctImag, eval.CoeffsToSlots(ctIn, ctsMatrices, ctReal, ctImag)
|
|
}
|
|
|
|
// CoeffsToSlots applies the homomorphic encoding and returns the results on the provided ciphertexts.
|
|
// Homomorphically encodes a complex vector vReal + i*vImag of size n on a real vector of size 2n.
|
|
// 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 *DFTEvaluator) CoeffsToSlots(ctIn *rlwe.Ciphertext, ctsMatrices DFTMatrix, ctReal, ctImag *rlwe.Ciphertext) (err error) {
|
|
|
|
if ctsMatrices.RepackImag2Real {
|
|
|
|
zV := ctIn.CopyNew()
|
|
|
|
if err = eval.dft(ctIn, ctsMatrices.Matrices, zV); err != nil {
|
|
return fmt.Errorf("cannot CoeffsToSlots: %w", err)
|
|
}
|
|
|
|
if err = eval.Conjugate(zV, ctReal); err != nil {
|
|
return fmt.Errorf("cannot CoeffsToSlots: %w", err)
|
|
}
|
|
|
|
var tmp *rlwe.Ciphertext
|
|
if ctImag != nil {
|
|
tmp = ctImag
|
|
} else {
|
|
tmp, err = rlwe.NewCiphertextAtLevelFromPoly(ctReal.Level(), eval.GetBuffCt().Value[:2])
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
tmp.IsNTT = true
|
|
}
|
|
|
|
// Imag part
|
|
if err = eval.Sub(zV, ctReal, tmp); err != nil {
|
|
return fmt.Errorf("cannot CoeffsToSlots: %w", err)
|
|
}
|
|
|
|
if err = eval.Mul(tmp, -1i, tmp); err != nil {
|
|
return fmt.Errorf("cannot CoeffsToSlots: %w", err)
|
|
}
|
|
|
|
// Real part
|
|
if err = eval.Add(ctReal, zV, ctReal); err != nil {
|
|
return fmt.Errorf("cannot CoeffsToSlots: %w", err)
|
|
}
|
|
|
|
// If repacking, then ct0 and ct1 right n/2 slots are zero.
|
|
if ctsMatrices.LogSlots < eval.parameters.LogMaxSlots() {
|
|
if err = eval.Rotate(tmp, 1<<ctIn.LogDimensions.Cols, tmp); err != nil {
|
|
return fmt.Errorf("cannot CoeffsToSlots: %w", err)
|
|
}
|
|
|
|
if err = eval.Add(ctReal, tmp, ctReal); err != nil {
|
|
return fmt.Errorf("cannot CoeffsToSlots: %w", err)
|
|
}
|
|
}
|
|
|
|
zV = nil
|
|
|
|
} else {
|
|
if err = eval.dft(ctIn, ctsMatrices.Matrices, ctReal); err != nil {
|
|
return fmt.Errorf("cannot CoeffsToSlots: %w", err)
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// SlotsToCoeffsNew applies the homomorphic decoding and returns the result on a new ciphertext.
|
|
// Homomorphically decodes a real vector of size 2n on a complex vector vReal + i*vImag of size n.
|
|
// If the packing is sparse (n < N/2) then ctReal = Ecd(vReal || vImag) and ctImag = nil.
|
|
// If the packing is dense (n == N/2), then ctReal = Ecd(vReal) and ctImag = Ecd(vImag).
|
|
func (eval *DFTEvaluator) SlotsToCoeffsNew(ctReal, ctImag *rlwe.Ciphertext, stcMatrices DFTMatrix) (opOut *rlwe.Ciphertext, err error) {
|
|
|
|
if ctReal.Level() < stcMatrices.LevelStart || (ctImag != nil && ctImag.Level() < stcMatrices.LevelStart) {
|
|
return nil, fmt.Errorf("ctReal.Level() or ctImag.Level() < DFTMatrix.LevelStart")
|
|
}
|
|
|
|
opOut = ckks.NewCiphertext(eval.parameters, 1, stcMatrices.LevelStart)
|
|
return opOut, eval.SlotsToCoeffs(ctReal, ctImag, stcMatrices, opOut)
|
|
|
|
}
|
|
|
|
// SlotsToCoeffs applies the homomorphic decoding and returns the result on the provided ciphertext.
|
|
// Homomorphically decodes a real vector of size 2n on a complex vector vReal + i*vImag of size n.
|
|
// If the packing is sparse (n < N/2) then ctReal = Ecd(vReal || vImag) and ctImag = nil.
|
|
// If the packing is dense (n == N/2), then ctReal = Ecd(vReal) and ctImag = Ecd(vImag).
|
|
func (eval *DFTEvaluator) SlotsToCoeffs(ctReal, ctImag *rlwe.Ciphertext, stcMatrices DFTMatrix, opOut *rlwe.Ciphertext) (err error) {
|
|
// If full packing, the repacking can be done directly using ct0 and ct1.
|
|
if ctImag != nil {
|
|
|
|
if err = eval.Mul(ctImag, 1i, opOut); err != nil {
|
|
return fmt.Errorf("cannot SlotsToCoeffs: %w", err)
|
|
}
|
|
|
|
if err = eval.Add(opOut, ctReal, opOut); err != nil {
|
|
return fmt.Errorf("cannot SlotsToCoeffs: %w", err)
|
|
}
|
|
|
|
if err = eval.dft(opOut, stcMatrices.Matrices, opOut); err != nil {
|
|
return fmt.Errorf("cannot SlotsToCoeffs: %w", err)
|
|
}
|
|
} else {
|
|
if err = eval.dft(ctReal, stcMatrices.Matrices, opOut); err != nil {
|
|
return fmt.Errorf("cannot SlotsToCoeffs: %w", err)
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (eval *DFTEvaluator) dft(ctIn *rlwe.Ciphertext, matrices []LinearTransformation, opOut *rlwe.Ciphertext) (err error) {
|
|
|
|
inputLogSlots := ctIn.LogDimensions
|
|
|
|
// Sequentially multiplies w with the provided dft matrices.
|
|
if err = eval.LinearTransformationEvaluator.EvaluateSequential(ctIn, matrices, opOut); err != nil {
|
|
return
|
|
}
|
|
|
|
// Encoding matrices are a special case of `fractal` linear transform
|
|
// that doesn't change the underlying plaintext polynomial Y = X^{N/n}
|
|
// of the input ciphertext.
|
|
opOut.LogDimensions = inputLogSlots
|
|
|
|
return
|
|
}
|
|
|
|
func fftPlainVec(logN, dslots int, roots []*bignum.Complex, pow5 []int) (a, b, c [][]*bignum.Complex) {
|
|
|
|
var N, m, index, tt, gap, k, mask, idx1, idx2 int
|
|
|
|
N = 1 << logN
|
|
|
|
a = make([][]*bignum.Complex, logN)
|
|
b = make([][]*bignum.Complex, logN)
|
|
c = make([][]*bignum.Complex, logN)
|
|
|
|
var size int
|
|
if 2*N == dslots {
|
|
size = 2
|
|
} else {
|
|
size = 1
|
|
}
|
|
|
|
prec := roots[0].Prec()
|
|
|
|
index = 0
|
|
for m = 2; m <= N; m <<= 1 {
|
|
|
|
aM := make([]*bignum.Complex, dslots)
|
|
bM := make([]*bignum.Complex, dslots)
|
|
cM := make([]*bignum.Complex, dslots)
|
|
|
|
for i := 0; i < dslots; i++ {
|
|
aM[i] = bignum.NewComplex().SetPrec(prec)
|
|
bM[i] = bignum.NewComplex().SetPrec(prec)
|
|
cM[i] = bignum.NewComplex().SetPrec(prec)
|
|
}
|
|
|
|
tt = m >> 1
|
|
|
|
for i := 0; i < N; i += m {
|
|
|
|
gap = N / m
|
|
mask = (m << 2) - 1
|
|
|
|
for j := 0; j < m>>1; j++ {
|
|
|
|
k = (pow5[j] & mask) * gap
|
|
|
|
idx1 = i + j
|
|
idx2 = i + j + tt
|
|
|
|
for u := 0; u < size; u++ {
|
|
aM[idx1+u*N].Set(roots[0])
|
|
aM[idx2+u*N].Neg(roots[k])
|
|
bM[idx1+u*N].Set(roots[k])
|
|
cM[idx2+u*N].Set(roots[0])
|
|
}
|
|
}
|
|
}
|
|
|
|
a[index] = aM
|
|
b[index] = bM
|
|
c[index] = cM
|
|
|
|
index++
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func ifftPlainVec(logN, dslots int, roots []*bignum.Complex, pow5 []int) (a, b, c [][]*bignum.Complex) {
|
|
|
|
var N, m, index, tt, gap, k, mask, idx1, idx2 int
|
|
|
|
N = 1 << logN
|
|
|
|
a = make([][]*bignum.Complex, logN)
|
|
b = make([][]*bignum.Complex, logN)
|
|
c = make([][]*bignum.Complex, logN)
|
|
|
|
var size int
|
|
if 2*N == dslots {
|
|
size = 2
|
|
} else {
|
|
size = 1
|
|
}
|
|
|
|
prec := roots[0].Prec()
|
|
|
|
index = 0
|
|
for m = N; m >= 2; m >>= 1 {
|
|
|
|
aM := make([]*bignum.Complex, dslots)
|
|
bM := make([]*bignum.Complex, dslots)
|
|
cM := make([]*bignum.Complex, dslots)
|
|
|
|
for i := 0; i < dslots; i++ {
|
|
aM[i] = bignum.NewComplex().SetPrec(prec)
|
|
bM[i] = bignum.NewComplex().SetPrec(prec)
|
|
cM[i] = bignum.NewComplex().SetPrec(prec)
|
|
}
|
|
|
|
tt = m >> 1
|
|
|
|
for i := 0; i < N; i += m {
|
|
|
|
gap = N / m
|
|
mask = (m << 2) - 1
|
|
|
|
for j := 0; j < m>>1; j++ {
|
|
|
|
k = ((m << 2) - (pow5[j] & mask)) * gap
|
|
|
|
idx1 = i + j
|
|
idx2 = i + j + tt
|
|
|
|
for u := 0; u < size; u++ {
|
|
aM[idx1+u*N].Set(roots[0])
|
|
aM[idx2+u*N].Neg(roots[k])
|
|
bM[idx1+u*N].Set(roots[0])
|
|
cM[idx2+u*N].Set(roots[k])
|
|
}
|
|
}
|
|
}
|
|
|
|
a[index] = aM
|
|
b[index] = bM
|
|
c[index] = cM
|
|
|
|
index++
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func addMatrixRotToList(pVec map[int]bool, rotations []int, N1, slots int, repack bool) []int {
|
|
|
|
if len(pVec) < 3 {
|
|
for j := range pVec {
|
|
if !utils.IsInSlice(j, rotations) {
|
|
rotations = append(rotations, j)
|
|
}
|
|
}
|
|
} else {
|
|
var index int
|
|
for j := range pVec {
|
|
|
|
index = (j / N1) * N1
|
|
|
|
if repack {
|
|
// Sparse repacking, occurring during the first DFT matrix of the CoeffsToSlots.
|
|
index &= (2*slots - 1)
|
|
} else {
|
|
// Other cases
|
|
index &= (slots - 1)
|
|
}
|
|
|
|
if index != 0 && !utils.IsInSlice(index, rotations) {
|
|
rotations = append(rotations, index)
|
|
}
|
|
|
|
index = j & (N1 - 1)
|
|
|
|
if index != 0 && !utils.IsInSlice(index, rotations) {
|
|
rotations = append(rotations, index)
|
|
}
|
|
}
|
|
}
|
|
|
|
return rotations
|
|
}
|
|
|
|
func (d DFTMatrixLiteral) computeBootstrappingDFTIndexMap(logN int) (rotationMap []map[int]bool) {
|
|
|
|
logSlots := d.LogSlots
|
|
ltType := d.Type
|
|
repacki2r := d.RepackImag2Real
|
|
bitreversed := d.BitReversed
|
|
maxDepth := d.Depth(false)
|
|
|
|
var level, depth, nextLevel int
|
|
|
|
level = logSlots
|
|
|
|
rotationMap = make([]map[int]bool, maxDepth)
|
|
|
|
// We compute the chain of merge in order or reverse order depending if its DFT or InvDFT because
|
|
// the way the levels are collapsed has an impact on the total number of rotations and keys to be
|
|
// stored. Ex. instead of using 255 + 64 plaintext vectors, we can use 127 + 128 plaintext vectors
|
|
// by reversing the order of the merging.
|
|
merge := make([]int, maxDepth)
|
|
for i := 0; i < maxDepth; i++ {
|
|
|
|
depth = int(math.Ceil(float64(level) / float64(maxDepth-i)))
|
|
|
|
if ltType == HomomorphicEncode {
|
|
merge[i] = depth
|
|
} else {
|
|
merge[len(merge)-i-1] = depth
|
|
|
|
}
|
|
|
|
level -= depth
|
|
}
|
|
|
|
level = logSlots
|
|
for i := 0; i < maxDepth; i++ {
|
|
|
|
if logSlots < logN-1 && ltType == HomomorphicDecode && i == 0 && repacki2r {
|
|
|
|
// Special initial matrix for the repacking before Decode
|
|
rotationMap[i] = genWfftRepackIndexMap(logSlots, level)
|
|
|
|
// Merges this special initial matrix with the first layer of Decode DFT
|
|
rotationMap[i] = nextLevelfftIndexMap(rotationMap[i], logSlots, 2<<logSlots, level, ltType, bitreversed)
|
|
|
|
// Continues the merging with the next layers if the total depth requires it.
|
|
nextLevel = level - 1
|
|
for j := 0; j < merge[i]-1; j++ {
|
|
rotationMap[i] = nextLevelfftIndexMap(rotationMap[i], logSlots, 2<<logSlots, nextLevel, ltType, bitreversed)
|
|
nextLevel--
|
|
}
|
|
|
|
} else {
|
|
|
|
// First layer of the i-th level of the DFT
|
|
rotationMap[i] = genWfftIndexMap(logSlots, level, ltType, bitreversed)
|
|
|
|
// Merges the layer with the next levels of the DFT if the total depth requires it.
|
|
nextLevel = level - 1
|
|
for j := 0; j < merge[i]-1; j++ {
|
|
rotationMap[i] = nextLevelfftIndexMap(rotationMap[i], logSlots, 1<<logSlots, nextLevel, ltType, bitreversed)
|
|
nextLevel--
|
|
}
|
|
}
|
|
|
|
level -= merge[i]
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func genWfftIndexMap(logL, level int, ltType DFTType, bitreversed bool) (vectors map[int]bool) {
|
|
|
|
var rot int
|
|
|
|
if ltType == HomomorphicEncode && !bitreversed || ltType == HomomorphicDecode && bitreversed {
|
|
rot = 1 << (level - 1)
|
|
} else {
|
|
rot = 1 << (logL - level)
|
|
}
|
|
|
|
vectors = make(map[int]bool)
|
|
vectors[0] = true
|
|
vectors[rot] = true
|
|
vectors[(1<<logL)-rot] = true
|
|
return
|
|
}
|
|
|
|
func genWfftRepackIndexMap(logL, level int) (vectors map[int]bool) {
|
|
vectors = make(map[int]bool)
|
|
vectors[0] = true
|
|
vectors[(1 << logL)] = true
|
|
return
|
|
}
|
|
|
|
func nextLevelfftIndexMap(vec map[int]bool, logL, N, nextLevel int, ltType DFTType, bitreversed bool) (newVec map[int]bool) {
|
|
|
|
var rot int
|
|
|
|
newVec = make(map[int]bool)
|
|
|
|
if ltType == HomomorphicEncode && !bitreversed || ltType == HomomorphicDecode && bitreversed {
|
|
rot = (1 << (nextLevel - 1)) & (N - 1)
|
|
} else {
|
|
rot = (1 << (logL - nextLevel)) & (N - 1)
|
|
}
|
|
|
|
for i := range vec {
|
|
newVec[i] = true
|
|
newVec[(i+rot)&(N-1)] = true
|
|
newVec[(i-rot)&(N-1)] = true
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// GenMatrices returns the ordered list of factors of the non-zero diagonales of the IDFT (encoding) or DFT (decoding) matrix.
|
|
func (d DFTMatrixLiteral) GenMatrices(LogN int, prec uint) (plainVector []Diagonals[*bignum.Complex]) {
|
|
|
|
logSlots := d.LogSlots
|
|
slots := 1 << logSlots
|
|
maxDepth := d.Depth(false)
|
|
ltType := d.Type
|
|
bitreversed := d.BitReversed
|
|
|
|
logdSlots := logSlots
|
|
if logdSlots < LogN-1 && d.RepackImag2Real {
|
|
logdSlots++
|
|
}
|
|
|
|
roots := ckks.GetRootsBigComplex(slots<<2, prec)
|
|
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 [][]*bignum.Complex
|
|
if ltType == HomomorphicEncode {
|
|
a, b, c = ifftPlainVec(logSlots, 1<<logdSlots, roots, pow5)
|
|
} else {
|
|
a, b, c = fftPlainVec(logSlots, 1<<logdSlots, roots, pow5)
|
|
}
|
|
|
|
plainVector = make([]Diagonals[*bignum.Complex], maxDepth)
|
|
|
|
// We compute the chain of merge in order or reverse order depending if its DFT or InvDFT because
|
|
// the way the levels are collapsed has an impact on the total number of rotations and keys to be
|
|
// stored. Ex. instead of using 255 + 64 plaintext vectors, we can use 127 + 128 plaintext vectors
|
|
// by reversing the order of the merging.
|
|
merge := make([]int, maxDepth)
|
|
for i := 0; i < maxDepth; i++ {
|
|
|
|
depth = int(math.Ceil(float64(fftLevel) / float64(maxDepth-i)))
|
|
|
|
if ltType == HomomorphicEncode {
|
|
merge[i] = depth
|
|
} else {
|
|
merge[len(merge)-i-1] = depth
|
|
|
|
}
|
|
|
|
fftLevel -= depth
|
|
}
|
|
|
|
fftLevel = logSlots
|
|
for i := 0; i < maxDepth; i++ {
|
|
|
|
if logSlots != logdSlots && ltType == HomomorphicDecode && i == 0 && d.RepackImag2Real {
|
|
|
|
// Special initial matrix for the repacking before DFT
|
|
plainVector[i] = genRepackMatrix(logSlots, prec, bitreversed)
|
|
|
|
// Merges this special initial matrix with the first layer of DFT
|
|
plainVector[i] = multiplyFFTMatrixWithNextFFTLevel(plainVector[i], logSlots, 2*slots, fftLevel, a[logSlots-fftLevel], b[logSlots-fftLevel], c[logSlots-fftLevel], ltType, bitreversed)
|
|
|
|
// Continues the merging with the next layers if the total depth requires it.
|
|
nextfftLevel = fftLevel - 1
|
|
for j := 0; j < merge[i]-1; j++ {
|
|
plainVector[i] = multiplyFFTMatrixWithNextFFTLevel(plainVector[i], logSlots, 2*slots, nextfftLevel, a[logSlots-nextfftLevel], b[logSlots-nextfftLevel], c[logSlots-nextfftLevel], ltType, bitreversed)
|
|
nextfftLevel--
|
|
}
|
|
|
|
} else {
|
|
// First layer of the i-th level of the DFT
|
|
plainVector[i] = genFFTDiagMatrix(logSlots, fftLevel, a[logSlots-fftLevel], b[logSlots-fftLevel], c[logSlots-fftLevel], ltType, bitreversed)
|
|
|
|
// Merges the layer with the next levels of the DFT if the total depth requires it.
|
|
nextfftLevel = fftLevel - 1
|
|
for j := 0; j < merge[i]-1; j++ {
|
|
plainVector[i] = multiplyFFTMatrixWithNextFFTLevel(plainVector[i], logSlots, slots, nextfftLevel, a[logSlots-nextfftLevel], b[logSlots-nextfftLevel], c[logSlots-nextfftLevel], ltType, bitreversed)
|
|
nextfftLevel--
|
|
}
|
|
}
|
|
|
|
fftLevel -= merge[i]
|
|
}
|
|
|
|
// Repacking after the IDFT (we multiply the last matrix with the vector [1, 1, ..., 1, 1, 0, 0, ..., 0, 0]).
|
|
if logSlots != logdSlots && ltType == HomomorphicEncode && d.RepackImag2Real {
|
|
for j := range plainVector[maxDepth-1] {
|
|
v := plainVector[maxDepth-1][j]
|
|
for x := 0; x < slots; x++ {
|
|
v[x+slots] = bignum.NewComplex().SetPrec(prec)
|
|
}
|
|
}
|
|
}
|
|
|
|
scaling := new(big.Float).SetPrec(prec)
|
|
if d.Scaling == nil {
|
|
scaling.SetFloat64(1)
|
|
} else {
|
|
scaling.Set(d.Scaling)
|
|
}
|
|
|
|
// If DFT matrix, rescale by 1/N
|
|
if ltType == HomomorphicEncode {
|
|
// Real/Imag extraction 1/2 factor
|
|
if d.RepackImag2Real {
|
|
scaling.Quo(scaling, new(big.Float).SetFloat64(float64(2*slots)))
|
|
} else {
|
|
scaling.Quo(scaling, new(big.Float).SetFloat64(float64(slots)))
|
|
}
|
|
}
|
|
|
|
// Spreads the scale accross the matrices
|
|
scaling = bignum.Pow(scaling, new(big.Float).Quo(new(big.Float).SetPrec(prec).SetFloat64(1), new(big.Float).SetPrec(prec).SetFloat64(float64(d.Depth(false)))))
|
|
|
|
for j := range plainVector {
|
|
for x := range plainVector[j] {
|
|
v := plainVector[j][x]
|
|
for i := range v {
|
|
v[i][0].Mul(v[i][0], scaling)
|
|
v[i][1].Mul(v[i][1], scaling)
|
|
}
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func genFFTDiagMatrix(logL, fftLevel int, a, b, c []*bignum.Complex, ltType DFTType, bitreversed bool) (vectors map[int][]*bignum.Complex) {
|
|
|
|
var rot int
|
|
|
|
if ltType == HomomorphicEncode && !bitreversed || ltType == HomomorphicDecode && bitreversed {
|
|
rot = 1 << (fftLevel - 1)
|
|
} else {
|
|
rot = 1 << (logL - fftLevel)
|
|
}
|
|
|
|
vectors = make(map[int][]*bignum.Complex)
|
|
|
|
if bitreversed {
|
|
utils.BitReverseInPlaceSlice(a, 1<<logL)
|
|
utils.BitReverseInPlaceSlice(b, 1<<logL)
|
|
utils.BitReverseInPlaceSlice(c, 1<<logL)
|
|
|
|
if len(a) > 1<<logL {
|
|
utils.BitReverseInPlaceSlice(a[1<<logL:], 1<<logL)
|
|
utils.BitReverseInPlaceSlice(b[1<<logL:], 1<<logL)
|
|
utils.BitReverseInPlaceSlice(c[1<<logL:], 1<<logL)
|
|
}
|
|
}
|
|
|
|
addToDiagMatrix(vectors, 0, a)
|
|
addToDiagMatrix(vectors, rot, b)
|
|
addToDiagMatrix(vectors, (1<<logL)-rot, c)
|
|
|
|
return
|
|
}
|
|
|
|
func genRepackMatrix(logL int, prec uint, bitreversed bool) (vectors map[int][]*bignum.Complex) {
|
|
|
|
vectors = make(map[int][]*bignum.Complex)
|
|
|
|
a := make([]*bignum.Complex, 2<<logL)
|
|
b := make([]*bignum.Complex, 2<<logL)
|
|
|
|
for i := 0; i < 1<<logL; i++ {
|
|
a[i] = bignum.NewComplex().SetPrec(prec)
|
|
a[i][0].SetFloat64(1)
|
|
a[i+(1<<logL)] = bignum.NewComplex().SetPrec(prec)
|
|
a[i+(1<<logL)][1].SetFloat64(1)
|
|
|
|
b[i] = bignum.NewComplex().SetPrec(prec)
|
|
b[i][1].SetFloat64(1)
|
|
b[i+(1<<logL)] = bignum.NewComplex().SetPrec(prec)
|
|
b[i+(1<<logL)][0].SetFloat64(1)
|
|
}
|
|
|
|
addToDiagMatrix(vectors, 0, a)
|
|
addToDiagMatrix(vectors, (1 << logL), b)
|
|
|
|
return
|
|
}
|
|
|
|
func multiplyFFTMatrixWithNextFFTLevel(vec map[int][]*bignum.Complex, logL, N, nextLevel int, a, b, c []*bignum.Complex, ltType DFTType, bitreversed bool) (newVec map[int][]*bignum.Complex) {
|
|
|
|
var rot int
|
|
|
|
newVec = make(map[int][]*bignum.Complex)
|
|
|
|
if ltType == HomomorphicEncode && !bitreversed || ltType == HomomorphicDecode && bitreversed {
|
|
rot = (1 << (nextLevel - 1)) & (N - 1)
|
|
} else {
|
|
rot = (1 << (logL - nextLevel)) & (N - 1)
|
|
}
|
|
|
|
if bitreversed {
|
|
utils.BitReverseInPlaceSlice(a, 1<<logL)
|
|
utils.BitReverseInPlaceSlice(b, 1<<logL)
|
|
utils.BitReverseInPlaceSlice(c, 1<<logL)
|
|
|
|
if len(a) > 1<<logL {
|
|
utils.BitReverseInPlaceSlice(a[1<<logL:], 1<<logL)
|
|
utils.BitReverseInPlaceSlice(b[1<<logL:], 1<<logL)
|
|
utils.BitReverseInPlaceSlice(c[1<<logL:], 1<<logL)
|
|
}
|
|
}
|
|
|
|
for i := range vec {
|
|
addToDiagMatrix(newVec, i, rotateAndMulNew(vec[i], 0, a))
|
|
addToDiagMatrix(newVec, (i+rot)&(N-1), rotateAndMulNew(vec[i], rot, b))
|
|
addToDiagMatrix(newVec, (i-rot)&(N-1), rotateAndMulNew(vec[i], -rot, c))
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func addToDiagMatrix(diagMat map[int][]*bignum.Complex, index int, vec []*bignum.Complex) {
|
|
if diagMat[index] == nil {
|
|
diagMat[index] = make([]*bignum.Complex, len(vec))
|
|
for i := range vec {
|
|
diagMat[index][i] = vec[i].Clone()
|
|
}
|
|
} else {
|
|
add(diagMat[index], vec, diagMat[index])
|
|
}
|
|
}
|
|
|
|
func rotateAndMulNew(a []*bignum.Complex, k int, b []*bignum.Complex) (c []*bignum.Complex) {
|
|
multiplier := bignum.NewComplexMultiplier()
|
|
|
|
c = make([]*bignum.Complex, len(a))
|
|
for i := range c {
|
|
c[i] = b[i].Clone()
|
|
}
|
|
|
|
mask := int(len(a) - 1)
|
|
|
|
for i := 0; i < len(a); i++ {
|
|
multiplier.Mul(c[i], a[(i+k)&mask], c[i])
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func add(a, b, c []*bignum.Complex) {
|
|
for i := 0; i < len(a); i++ {
|
|
c[i].Add(a[i], b[i])
|
|
}
|
|
}
|