Lattigo CKKS Bootstrap

2022-07-05

- advanced
	- evaluator.go // CtoS, StoC, EvalModの実装
	- homomorphic_encoding.go // EncodingMatrix関連
	- homomorphic_mod.go // EvalMod関連
- bootstrapping
	- bootstrap.go // Bootstrapの大枠の処理
	- bootstrapper.go // evkの拡張の定義とか作成処理
	- default_params.go // 用意されているbootstrap時のパラメータ
- evaluator.go // 基本演算
func (btp *Bootstrapper) Bootstrapp(ctIn *ckks.Ciphertext) (ctOut *ckks.Ciphertext) {
	ctOut = ctIn.CopyNew()
 
	// 省略: レベルとスケールの調整
 
	// Step 1 : Extend the basis from q to Q
	ctOut = btp.modUpFromQ0(ctOut)
 
	// SubSum X -> (N/dslots) * Y^dslots
	btp.Trace(ctOut, btp.params.LogSlots(), btp.params.LogN()-1, ctOut)
 
	// Step 2 : CoeffsToSlots (Homomorphic encoding)
	ctReal, ctImag := btp.CoeffsToSlotsNew(ctOut, btp.ctsMatrices)
 
	// Step 3 : EvalMod (Homomorphic modular reduction)
	ctReal = btp.EvalModNew(ctReal, btp.evalModPoly)
	ctReal.Scale = btp.params.DefaultScale()
 
	if ctImag != nil {
		ctImag = btp.EvalModNew(ctImag, btp.evalModPoly)
		ctImag.Scale = btp.params.DefaultScale()
	}
 
	// Step 4 : SlotsToCoeffs (Homomorphic decoding)
	ctOut = btp.SlotsToCoeffsNew(ctReal, ctImag, btp.stcMatrices)
 
	return
}

Model

bootstrapperBase

// Bootstrapper is a struct to store a memory buffer with the plaintext matrices,
// the polynomial approximation, and the keys for the bootstrapping.
type Bootstrapper struct {
	advanced.Evaluator
	*bootstrapperBase
}
 
type bootstrapperBase struct {
	Parameters
	params ckks.Parameters
 
	dslots    int // Number of plaintext slots after the re-encoding
	logdslots int
 
	evalModPoly advanced.EvalModPoly
	stcMatrices advanced.EncodingMatrix
	ctsMatrices advanced.EncodingMatrix
 
	q0OverMessageRatio float64
 
	swkDtS *rlwe.SwitchingKey
	swkStD *rlwe.SwitchingKey
}
 
// EvaluationKeys is a type for a CKKS bootstrapping key, which
// regroups the necessary public relinearization and rotation keys.
type EvaluationKeys struct {
	rlwe.EvaluationKey
	SwkDtS *rlwe.SwitchingKey
	SwkStD *rlwe.SwitchingKey
}
func newBootstrapperBase(params ckks.Parameters, btpParams Parameters, btpKey EvaluationKeys) (bb *bootstrapperBase) {
	bb = new(bootstrapperBase)
	bb.params = params
	bb.Parameters = btpParams
 
	bb.dslots = params.Slots()
	bb.logdslots = params.LogSlots()
	if params.LogSlots() < params.MaxLogSlots() {
		bb.dslots <<= 1
		bb.logdslots++
	}
 
	bb.evalModPoly = advanced.NewEvalModPolyFromLiteral(btpParams.EvalModParameters)
 
	scFac := bb.evalModPoly.ScFac()
	K := bb.evalModPoly.K() / scFac
	n := float64(2 * params.Slots())
 
	// Correcting factor for approximate division by Q
	// The second correcting factor for approximate multiplication by Q is included in the coefficients of the EvalMod polynomials
	qDiff := bb.evalModPoly.QDiff()
 
	// Q0/|m|
	bb.q0OverMessageRatio = math.Exp2(math.Round(math.Log2(params.QiFloat64(0) / bb.evalModPoly.MessageRatio())))
 
	// If the scale used during the EvalMod step is smaller than Q0, then we cannot increase the scale during
	// the EvalMod step to get a free division by MessageRatio, and we need to do this division (totally or partly)
	// during the CoeffstoSlots step
	qDiv := btpParams.EvalModParameters.ScalingFactor / math.Exp2(math.Round(math.Log2(params.QiFloat64(0))))
 
	// Sets qDiv to 1 if there is enough room for the division to happen using scale manipulation.
	if qDiv > 1 {
		qDiv = 1
	}
 
	encoder := ckks.NewEncoder(bb.params)
 
	// CoeffsToSlots vectors
	// Change of variable for the evaluation of the Chebyshev polynomial + cancelling factor for the DFT and SubSum + eventual scaling factor for the double angle formula
	bb.CoeffsToSlotsParameters.LogN = params.LogN()
	bb.CoeffsToSlotsParameters.LogSlots = params.LogSlots()
	bb.CoeffsToSlotsParameters.Scaling = qDiv / (K * n * scFac * qDiff)
	bb.ctsMatrices = advanced.NewHomomorphicEncodingMatrixFromLiteral(bb.CoeffsToSlotsParameters, encoder)
 
	// SlotsToCoeffs vectors
	// Rescaling factor to set the final ciphertext to the desired scale
	bb.SlotsToCoeffsParameters.LogN = params.LogN()
	bb.SlotsToCoeffsParameters.LogSlots = params.LogSlots()
	bb.SlotsToCoeffsParameters.Scaling = bb.params.DefaultScale() / (bb.evalModPoly.ScalingFactor() / bb.evalModPoly.MessageRatio())
	bb.stcMatrices = advanced.NewHomomorphicEncodingMatrixFromLiteral(bb.SlotsToCoeffsParameters, encoder)
 
	encoder = nil
 
	return
}
  • NewHomomorphicEncodingMatrixFromLiteral
// NewHomomorphicEncodingMatrixFromLiteral generates the factorized encoding matrix.
// scaling : constant by witch the all the matrices will be multuplied by.
// encoder : ckks.Encoder.
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
	}
 
	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)
	cnt := 0
	trueDepth := mParams.Depth(true)
	for i := range mParams.ScalingFactor {
		for j := range mParams.ScalingFactor[trueDepth-i-1] {
			matrices[cnt] = ckks.GenLinearTransformBSGS(encoder, pVecDFT[cnt], ctsLevels[cnt], mParams.ScalingFactor[trueDepth-i-1][j], mParams.BSGSRatio, logdSlots)
			cnt++
		}
	}
 
	return EncodingMatrix{EncodingMatrixLiteral: mParams, matrices: matrices}
}

EvalModLiteral

// Sin and Cos are the two proposed functions for SineType
const (
	Sin  = SineType(0) // Standard Chebyshev approximation of (1/2pi) * sin(2pix)
	Cos1 = SineType(1) // Special approximation (Han and Ki) of pow((1/2pi), 1/2^r) * cos(2pi(x-0.25)/2^r); this method requires a minimum degree of 2*(K-1).
	Cos2 = SineType(2) // Standard Chebyshev approximation of pow((1/2pi), 1/2^r) * cos(2pi(x-0.25)/2^r)
)
 
// EvalModLiteral a struct for the paramters of the EvalMod step
// of the bootstrapping
type EvalModLiteral struct {
	Q             uint64   // Q to reduce by during EvalMod
	LevelStart    int      // Starting level of EvalMod
	ScalingFactor float64  // Scaling factor used during EvalMod
	SineType      SineType // Chose betwenn [Sin(2*pi*x)] or [cos(2*pi*x/r) with double angle formula]
	MessageRatio  float64  // Ratio between Q0 and m, i.e. Q[0]/|m|
	K             int      // K parameter (interpolation in the range -K to K)
	SineDeg       int      // Degree of the interpolation
	DoubleAngle   int      // Number of rescale and double angle formula (only applies for cos)
	ArcSineDeg    int      // Degree of the Taylor arcsine composed with f(2*pi*x) (if zero then not used)
}
  • Sine: のChebychev補間
  • Cos1: 2倍角

EncodingMatrix

// LinearTransformType is a type used to distinguish different linear transformations.
type LinearTransformType int
 
// CoeffsToSlots and SlotsToCoeffs are two linear transformations.
const (
	CoeffsToSlots = LinearTransformType(0) // Homomorphic Encoding
	SlotsToCoeffs = LinearTransformType(1) // Homomorphic Decoding
)
 
type EncodingMatrix struct {
	EncodingMatrixLiteral
	matrices []ckks.LinearTransform
}
 
// EncodingMatrixLiteral is a struct storing the parameters to generate the factorized DFT matrix.
type EncodingMatrixLiteral struct {
	LinearTransformType LinearTransformType
	LogN                int     // log(RingDegree)
	LogSlots            int     // log(slots)
	Scaling             float64 // constant by which the matrix is multiplied with
	LevelStart          int     // Encoding level
	BitReversed         bool    // Flag for bit-reverseed input to the DFT (with bit-reversed output), by default false.
	BSGSRatio           float64 // n1/n2 ratio for the bsgs algo for matrix x vector eval
	ScalingFactor       [][]float64
}
  • lvl 1 から max level k まで上げる
func (btp *Bootstrapper) Bootstrapp(ctIn *ckks.Ciphertext) (ctOut *ckks.Ciphertext) {

Modup

// Step 1 : Extend the basis from q to Q
ctOut = btp.modUpFromQ0(ctOut)

lattigo/bootstrap.go at 8df1d3bed79e315f527c1c5fc9704f5df9de0f11 · tuneinsight/lattigo · GitHub

func (btp *Bootstrapper) modUpFromQ0(ct *ckks.Ciphertext) *ckks.Ciphertext {
 
	if btp.swkDtS != nil {
		btp.SwitchKeys(ct, btp.swkDtS, ct)
	}
 
	ringQ := btp.params.RingQ()
	ringP := btp.params.RingP()
 
	for i := range ct.Value {
		ringQ.InvNTTLvl(ct.Level(), ct.Value[i], ct.Value[i])
	}
 
	// Extend the ciphertext with zero polynomials.
	for u := range ct.Value {
		ct.Value[u].Coeffs = append(ct.Value[u].Coeffs, make([][]uint64, btp.params.MaxLevel())...)
		for i := 1; i < btp.params.MaxLevel()+1; i++ {
			ct.Value[u].Coeffs[i] = make([]uint64, btp.params.N())
		}
	}
 
	levelQ := btp.params.QCount() - 1
	levelP := btp.params.PCount() - 1
 
	Q := ringQ.Modulus
	P := ringP.Modulus
	q := Q[0]
	bredparamsQ := ringQ.BredParams
	bredparamsP := ringP.BredParams
 
	var coeff, tmp, pos, neg uint64
 
	// ModUp q->Q for ct[0] centered around q
	for j := 0; j < btp.params.N(); j++ {
 
		coeff = ct.Value[0].Coeffs[0][j]
		pos, neg = 1, 0
		if coeff >= (q >> 1) {
			coeff = q - coeff
			pos, neg = 0, 1
		}
 
		for i := 1; i < levelQ+1; i++ {
			tmp = ring.BRedAdd(coeff, Q[i], bredparamsQ[i])
			ct.Value[0].Coeffs[i][j] = tmp*pos + (Q[i]-tmp)*neg
		}
	}
 
	if btp.swkStD != nil {
 
		ks := btp.GetKeySwitcher()
 
		// ModUp q->QP for ct[1] centered around q
		for j := 0; j < btp.params.N(); j++ {
 
			coeff = ct.Value[1].Coeffs[0][j]
			pos, neg = 1, 0
			if coeff > (q >> 1) {
				coeff = q - coeff
				pos, neg = 0, 1
			}
 
			for i := 0; i < levelQ+1; i++ {
				tmp = ring.BRedAdd(coeff, Q[i], bredparamsQ[i])
				ks.BuffDecompQP[0].Q.Coeffs[i][j] = tmp*pos + (Q[i]-tmp)*neg
 
			}
 
			for i := 0; i < levelP+1; i++ {
				tmp = ring.BRedAdd(coeff, P[i], bredparamsP[i])
				ks.BuffDecompQP[0].P.Coeffs[i][j] = tmp*pos + (P[i]-tmp)*neg
			}
		}
 
		for i := len(ks.BuffDecompQP) - 1; i >= 0; i-- {
			ringQ.NTTLvl(levelQ, ks.BuffDecompQP[0].Q, ks.BuffDecompQP[i].Q)
		}
 
		for i := len(ks.BuffDecompQP) - 1; i >= 0; i-- {
			ringP.NTTLvl(levelP, ks.BuffDecompQP[0].P, ks.BuffDecompQP[i].P)
		}
 
		ringQ.NTTLvl(levelQ, ct.Value[0], ct.Value[0])
 
		ks.KeyswitchHoisted(levelQ, ks.BuffDecompQP, btp.swkStD, ks.BuffQP[1].Q, ct.Value[1], ks.BuffQP[1].P, ks.BuffQP[2].P)
		ringQ.AddLvl(levelQ, ct.Value[0], ks.BuffQP[1].Q, ct.Value[0])
 
	} else {
 
		for j := 0; j < btp.params.N(); j++ {
 
			coeff = ct.Value[1].Coeffs[0][j]
			pos, neg = 1, 0
			if coeff >= (q >> 1) {
				coeff = q - coeff
				pos, neg = 0, 1
			}
 
			for i := 1; i < levelQ+1; i++ {
				tmp = ring.BRedAdd(coeff, Q[i], bredparamsQ[i])
				ct.Value[1].Coeffs[i][j] = tmp*pos + (Q[i]-tmp)*neg
			}
		}
 
		ringQ.NTTLvl(levelQ, ct.Value[0], ct.Value[0])
		ringQ.NTTLvl(levelQ, ct.Value[1], ct.Value[1])
	}
 
	return ct
}

Subsum

// SubSum X -> (N/dslots) * Y^dslots
btp.Trace(ctOut, btp.params.LogSlots(), btp.params.LogN()-1, ctOut)

lattigo/linear_transform.go at 8df1d3bed79e315f527c1c5fc9704f5df9de0f11 · tuneinsight/lattigo · GitHub

// Trace maps X -> sum((-1)^i * X^{i*n+1}) for 0 <= i < N
// For log(n) = logSlotStart and log(N/2) = logSlotsEnd
// Monomial X^k vanishes if k is not divisible by (N/n), else it is multiplied by (N/n).
// Ciphertext is pre-multiplied by (N/n)^-1 to remove the (N/n) factor.
// Examples of full Trace for [0 + 1X + 2X^2 + 3X^3 + 4X^4 + 5X^5 + 6X^6 + 7X^7]
//
// 1)	   [1 + 2X + 3X^2 + 4X^3 + 5X^4 + 6X^5 + 7X^6 + 8X^7]
//       + [1 - 6X - 3X^2 + 8X^3 + 5X^4 + 2X^5 - 7X^6 - 4X^7]  {X-> X^(i * 5^1)}
//   	 = [2 - 4X + 0X^2 +12X^3 +10X^4 + 8X^5 - 0X^6 + 4X^7]
//
// 2)      [2 - 4X + 0X^2 +12X^3 +10X^4 + 8X^5 - 0X^6 + 4X^7]
//       + [2 + 4X + 0X^2 -12X^3 +10X^4 - 8X^5 + 0X^6 - 4X^7]  {X-> X^(i * 5^2)}
//       = [4 + 0X + 0X^2 - 0X^3 +20X^4 + 0X^5 + 0X^6 - 0X^7]
//
// 3)      [4 + 0X + 0X^2 - 0X^3 +20X^4 + 0X^5 + 0X^6 - 0X^7]
//       + [4 + 4X + 0X^2 - 0X^3 -20X^4 + 0X^5 + 0X^6 - 0X^7]  {X-> X^(i * -1)}
//       = [8 + 0X + 0X^2 - 0X^3 + 0X^4 + 0X^5 + 0X^6 - 0X^7]
func (eval *evaluator) Trace(ctIn *Ciphertext, logSlotsStart, logSlotsEnd int, ctOut *Ciphertext) {
 
	levelQ := utils.MinInt(ctIn.Level(), ctOut.Level())
 
	ctOut.Value[0].Coeffs = ctOut.Value[0].Coeffs[:levelQ+1]
	ctOut.Value[1].Coeffs = ctOut.Value[1].Coeffs[:levelQ+1]
	ctOut.Scale = ctIn.Scale
 
	n := 1 << (logSlotsEnd - logSlotsStart)
 
	if n > 1 {
 
		ringQ := eval.params.RingQ()
 
		// pre-multiplication by (N/n)^-1
		for i := 0; i < levelQ+1; i++ {
			Q := ringQ.Modulus[i]
			bredParams := ringQ.BredParams[i]
			mredparams := ringQ.MredParams[i]
			invN := ring.ModExp(uint64(n), Q-2, Q)
			invN = ring.MForm(invN, Q, bredParams)
 
			ring.MulScalarMontgomeryVec(ctIn.Value[0].Coeffs[i], ctOut.Value[0].Coeffs[i], invN, Q, mredparams)
			ring.MulScalarMontgomeryVec(ctIn.Value[1].Coeffs[i], ctOut.Value[1].Coeffs[i], invN, Q, mredparams)
		}
 
		for i := logSlotsStart; i < logSlotsEnd; i++ {
			eval.permuteNTT(ctOut, eval.params.GaloisElementForColumnRotationBy(1<<i), eval.buffCt)
			ctBuff := &Ciphertext{Ciphertext: eval.buffCt.Ciphertext, Scale: ctOut.Scale}
			ctBuff.Value = ctBuff.Value[:2]
			eval.Add(ctOut, ctBuff, ctOut)
		}
	} else {
		if ctIn != ctOut {
			ctOut.Copy(ctIn)
		}
	}
}

coeffsToSlots

lattigo/bootstrap.go at 8df1d3bed79e315f527c1c5fc9704f5df9de0f11 · tuneinsight/lattigo · GitHub

// Step 2 : CoeffsToSlots (Homomorphic encoding)
ctReal, ctImag := btp.CoeffsToSlotsNew(ctOut, btp.ctsMatrices)

lattigo/evaluator.go at 8df1d3bed79e315f527c1c5fc9704f5df9de0f11 · tuneinsight/lattigo · GitHub

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)
 
	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
}

EvalMod

// Step 3 : EvalMod (Homomorphic modular reduction)
// ctReal = Ecd(real)
// ctImag = Ecd(imag)
// If n < N/2 then ctReal = Ecd(real|imag)
ctReal = btp.EvalModNew(ctReal, btp.evalModPoly)
ctReal.Scale = btp.params.DefaultScale()
 
if ctImag != nil {
	ctImag = btp.EvalModNew(ctImag, btp.evalModPoly)
	ctImag.Scale = btp.params.DefaultScale()
}
// EvalModNew applies a homomorphic mod Q on a vector scaled by Delta, scaled down to mod 1 :
//
//	1) Delta * (Q/Delta * I(X) + m(X)) (Delta = scaling factor, I(X) integer poly, m(X) message)
//	2) Delta * (I(X) + Delta/Q * m(X)) (divide by Q/Delta)
//	3) Delta * (Delta/Q * m(X)) (x mod 1)
//	4) Delta * (m(X)) (multiply back by Q/Delta)
//
// Since Q is not a power of two, but Delta is, then does an approximate division by the closest
// power of two to Q instead. Hence, it assumes that the input plaintext is already scaled by
// the correcting factor Q/2^{round(log(Q))}.
//
// !! Assumes that the input is normalized by 1/K for K the range of the approximation.
//
// Scaling back error correction by 2^{round(log(Q))}/Q afterward is included in the polynomial
func (eval *evaluator) EvalModNew(ct *ckks.Ciphertext, evalModPoly EvalModPoly) *ckks.Ciphertext {
 
	if ct.Level() < evalModPoly.LevelStart() {
		panic("ct.Level() < evalModPoly.LevelStart")
	}
 
	if ct.Level() > evalModPoly.LevelStart() {
		eval.DropLevel(ct, ct.Level()-evalModPoly.LevelStart())
	}
 
	// Stores default scales
	prevScaleCt := ct.Scale
 
	// Normalize the modular reduction to mod by 1 (division by Q)
	ct.Scale = evalModPoly.scalingFactor
 
	var err error
 
	// Compute the scales that the ciphertext should have before the double angle
	// formula such that after it it has the scale it had before the polynomial
	// evaluation
	targetScale := ct.Scale
	for i := 0; i < evalModPoly.doubleAngle; i++ {
		targetScale = math.Sqrt(targetScale * eval.params.QiFloat64(evalModPoly.levelStart-evalModPoly.sinePoly.Depth()-evalModPoly.doubleAngle+i+1))
	}
 
	// Division by 1/2^r and change of variable for the Chebysehev evaluation
	if evalModPoly.sineType == Cos1 || evalModPoly.sineType == Cos2 {
		eval.AddConst(ct, -0.5/(evalModPoly.scFac*(evalModPoly.sinePoly.B-evalModPoly.sinePoly.A)), ct)
	}
 
	// Chebyshev evaluation
	if ct, err = eval.EvaluatePoly(ct, evalModPoly.sinePoly, targetScale); err != nil {
		panic(err)
	}
 
	// Double angle
	sqrt2pi := evalModPoly.sqrt2Pi
	for i := 0; i < evalModPoly.doubleAngle; i++ {
		sqrt2pi *= sqrt2pi
		eval.MulRelin(ct, ct, ct)
		eval.Add(ct, ct, ct)
		eval.AddConst(ct, -sqrt2pi, ct)
		if err := eval.Rescale(ct, targetScale, ct); err != nil {
			panic(err)
		}
	}
 
	// ArcSine
	if evalModPoly.arcSinePoly != nil {
		if ct, err = eval.EvaluatePoly(ct, evalModPoly.arcSinePoly, ct.Scale); err != nil {
			panic(err)
		}
	}
 
	// Multiplies back by q
	ct.Scale = prevScaleCt
	return ct
}

SlotsToCoeffs

// Step 4 : SlotsToCoeffs (Homomorphic decoding)
ctOut = btp.SlotsToCoeffsNew(ctReal, ctImag, btp.stcMatrices)
// SlotsToCoeffsNew 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 *evaluator) SlotsToCoeffs(ctReal, ctImag *ckks.Ciphertext, stcMatrices EncodingMatrix, ctOut *ckks.Ciphertext) {
	// If full packing, the repacking can be done directly using ct0 and ct1.
	if ctImag != nil {
		eval.MultByi(ctImag, ctOut)
		eval.Add(ctOut, ctReal, ctOut)
		eval.dft(ctOut, stcMatrices.matrices, ctOut)
	} else {
		eval.dft(ctReal, stcMatrices.matrices, ctOut)
	}
 
	return
}

BootstrappingParams

// N16QP1546H192H32 is a default bootstrapping parameters for a main secret with H=192 and an ephemeral secret with H=32.
// Residual Q : 420 bits.
// Precision : 26.6 bits for 2^{15} slots.
// Failure : 2^{-138.7} for 2^{15} slots.
N16QP1546H192H32 = defaultParametersLiteral{
	ckks.ParametersLiteral{...},
	Parameters{
			EphemeralSecretWeight: 32,
			SlotsToCoeffsParameters: advanced.EncodingMatrixLiteral{
				LinearTransformType: advanced.SlotsToCoeffs,
				LevelStart:          12,
				BSGSRatio:           2.0,
				BitReversed:         false,
				ScalingFactor: [][]float64{
					{0x7fffe60001},
					{0x7fffe40001},
					{0x7fffe00001},
				},
			},
			EvalModParameters: advanced.EvalModLiteral{
				Q:             0x10000000006e0001,
				LevelStart:    20,
				SineType:      advanced.Cos1,
				MessageRatio:  256.0,
				K:             16,
				SineDeg:       30,
				DoubleAngle:   3,
				ArcSineDeg:    0,
				ScalingFactor: 1 << 60,
			},
			CoeffsToSlotsParameters: advanced.EncodingMatrixLiteral{
				LinearTransformType: advanced.CoeffsToSlots,
				LevelStart:          24,
				BSGSRatio:           2.0,
				BitReversed:         false,
				ScalingFactor: [][]float64{
					{0x100000000060001},
					{0xfffffffff00001},
					{0xffffffffd80001},
					{0x1000000002a0001},
				},
			},
		},
	},