[circuits/float]: rework of x mod 1

This commit is contained in:
Jean-Philippe Bossuat
2023-08-11 17:28:17 +02:00
parent 2a7c4549ab
commit 806a3564d5
15 changed files with 249 additions and 231 deletions

View File

@@ -15,7 +15,7 @@ import (
type Bootstrapper struct {
*ckks.Evaluator
*float.DFTEvaluator
*float.HModEvaluator
*float.Mod1Evaluator
*bootstrapperBase
}
@@ -27,9 +27,9 @@ type bootstrapperBase struct {
dslots int // Number of plaintext slots after the re-encoding
logdslots int
evalModPoly float.EvalModPoly
stcMatrices float.DFTMatrix
ctsMatrices float.DFTMatrix
mod1Parameters float.Mod1Parameters
stcMatrices float.DFTMatrix
ctsMatrices float.DFTMatrix
q0OverMessageRatio float64
}
@@ -45,19 +45,19 @@ type EvaluationKeySet struct {
// NewBootstrapper creates a new Bootstrapper.
func NewBootstrapper(params ckks.Parameters, btpParams Parameters, btpKeys *EvaluationKeySet) (btp *Bootstrapper, err error) {
if btpParams.EvalModParameters.SineType == float.SinContinuous && btpParams.EvalModParameters.DoubleAngle != 0 {
if btpParams.Mod1ParametersLiteral.SineType == float.SinContinuous && btpParams.Mod1ParametersLiteral.DoubleAngle != 0 {
return nil, fmt.Errorf("cannot use double angle formul for SineType = Sin -> must use SineType = Cos")
}
if btpParams.EvalModParameters.SineType == float.CosDiscrete && btpParams.EvalModParameters.SineDegree < 2*(btpParams.EvalModParameters.K-1) {
if btpParams.Mod1ParametersLiteral.SineType == float.CosDiscrete && btpParams.Mod1ParametersLiteral.SineDegree < 2*(btpParams.Mod1ParametersLiteral.K-1) {
return nil, fmt.Errorf("SineType 'ckks.CosDiscrete' uses a minimum degree of 2*(K-1) but EvalMod degree is smaller")
}
if btpParams.CoeffsToSlotsParameters.LevelStart-btpParams.CoeffsToSlotsParameters.Depth(true) != btpParams.EvalModParameters.LevelStart {
if btpParams.CoeffsToSlotsParameters.LevelStart-btpParams.CoeffsToSlotsParameters.Depth(true) != btpParams.Mod1ParametersLiteral.LevelStart {
return nil, fmt.Errorf("starting level and depth of CoeffsToSlotsParameters inconsistent starting level of SineEvalParameters")
}
if btpParams.EvalModParameters.LevelStart-btpParams.EvalModParameters.Depth() != btpParams.SlotsToCoeffsParameters.LevelStart {
if btpParams.Mod1ParametersLiteral.LevelStart-btpParams.Mod1ParametersLiteral.Depth() != btpParams.SlotsToCoeffsParameters.LevelStart {
return nil, fmt.Errorf("starting level and depth of SineEvalParameters inconsistent starting level of CoeffsToSlotsParameters")
}
@@ -76,7 +76,7 @@ func NewBootstrapper(params ckks.Parameters, btpParams Parameters, btpKeys *Eval
btp.DFTEvaluator = float.NewDFTEvaluator(params, btp.Evaluator)
btp.HModEvaluator = float.NewHModEvaluator(btp.Evaluator)
btp.Mod1Evaluator = float.NewMod1Evaluator(btp.Evaluator, btp.bootstrapperBase.mod1Parameters)
return
}
@@ -168,26 +168,26 @@ func newBootstrapperBase(params ckks.Parameters, btpParams Parameters, btpKey *E
bb.logdslots++
}
if bb.evalModPoly, err = float.NewEvalModPolyFromLiteral(params, btpParams.EvalModParameters); err != nil {
if bb.mod1Parameters, err = float.NewMod1ParametersFromLiteral(params, btpParams.Mod1ParametersLiteral); err != nil {
return nil, err
}
scFac := bb.evalModPoly.ScFac()
K := bb.evalModPoly.K() / scFac
scFac := bb.mod1Parameters.ScFac()
K := bb.mod1Parameters.K() / scFac
// 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()
qDiff := bb.mod1Parameters.QDiff()
Q0 := params.Q()[0]
// Q0/|m|
bb.q0OverMessageRatio = math.Exp2(math.Round(math.Log2(float64(Q0) / bb.evalModPoly.MessageRatio())))
bb.q0OverMessageRatio = math.Exp2(math.Round(math.Log2(float64(Q0) / bb.mod1Parameters.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 := bb.evalModPoly.ScalingFactor().Float64() / math.Exp2(math.Round(math.Log2(float64(Q0))))
qDiv := bb.mod1Parameters.ScalingFactor().Float64() / math.Exp2(math.Round(math.Log2(float64(Q0))))
// Sets qDiv to 1 if there is enough room for the division to happen using scale manipulation.
if qDiv > 1 {
@@ -213,9 +213,9 @@ func newBootstrapperBase(params ckks.Parameters, btpParams Parameters, btpKey *E
// Rescaling factor to set the final ciphertext to the desired scale
if bb.SlotsToCoeffsParameters.Scaling == nil {
bb.SlotsToCoeffsParameters.Scaling = new(big.Float).SetFloat64(bb.params.DefaultScale().Float64() / (bb.evalModPoly.ScalingFactor().Float64() / bb.evalModPoly.MessageRatio()) * qDiff)
bb.SlotsToCoeffsParameters.Scaling = new(big.Float).SetFloat64(bb.params.DefaultScale().Float64() / (bb.mod1Parameters.ScalingFactor().Float64() / bb.mod1Parameters.MessageRatio()) * qDiff)
} else {
bb.SlotsToCoeffsParameters.Scaling.Mul(bb.SlotsToCoeffsParameters.Scaling, new(big.Float).SetFloat64(bb.params.DefaultScale().Float64()/(bb.evalModPoly.ScalingFactor().Float64()/bb.evalModPoly.MessageRatio())*qDiff))
bb.SlotsToCoeffsParameters.Scaling.Mul(bb.SlotsToCoeffsParameters.Scaling, new(big.Float).SetFloat64(bb.params.DefaultScale().Float64()/(bb.mod1Parameters.ScalingFactor().Float64()/bb.mod1Parameters.MessageRatio())*qDiff))
}
if bb.stcMatrices, err = float.NewDFTMatrixFromLiteral(params, bb.SlotsToCoeffsParameters, encoder); err != nil {

View File

@@ -43,7 +43,7 @@ func (btp *Bootstrapper) Bootstrap(ctIn *rlwe.Ciphertext) (opOut *rlwe.Ciphertex
// Does an integer constant mult by round((Q0/Delta_m)/ctscale)
if scale := ctDiff.Scale.Float64(); scale != math.Exp2(math.Round(math.Log2(scale))) || btp.q0OverMessageRatio < scale {
msgRatio := btp.EvalModParameters.LogMessageRatio
msgRatio := btp.Mod1ParametersLiteral.LogMessageRatio
return nil, fmt.Errorf("cannot Bootstrap: ciphertext scale must be a power of two smaller than Q[0]/2^{LogMessageRatio=%d} = %f but is %f", msgRatio, float64(btp.params.Q()[0])/math.Exp2(float64(msgRatio)), scale)
}
@@ -53,7 +53,7 @@ func (btp *Bootstrapper) Bootstrap(ctIn *rlwe.Ciphertext) (opOut *rlwe.Ciphertex
}
// Scales the message to Q0/|m|, which is the maximum possible before ModRaise to avoid plaintext overflow.
if scale := math.Round((float64(btp.params.Q()[0]) / btp.evalModPoly.MessageRatio()) / ctDiff.Scale.Float64()); scale > 1 {
if scale := math.Round((float64(btp.params.Q()[0]) / btp.mod1Parameters.MessageRatio()) / ctDiff.Scale.Float64()); scale > 1 {
if err = btp.ScaleUp(ctDiff, rlwe.NewScale(scale), ctDiff); err != nil {
return nil, fmt.Errorf("cannot Bootstrap: %w", err)
}
@@ -107,7 +107,7 @@ func (btp *Bootstrapper) bootstrap(ctIn *rlwe.Ciphertext) (opOut *rlwe.Ciphertex
}
// Scale the message from Q0/|m| to QL/|m|, where QL is the largest modulus used during the bootstrapping.
if scale := (btp.evalModPoly.ScalingFactor().Float64() / btp.evalModPoly.MessageRatio()) / opOut.Scale.Float64(); scale > 1 {
if scale := (btp.mod1Parameters.ScalingFactor().Float64() / btp.mod1Parameters.MessageRatio()) / opOut.Scale.Float64(); scale > 1 {
if err = btp.ScaleUp(opOut, rlwe.NewScale(scale), opOut); err != nil {
return nil, err
}
@@ -128,13 +128,13 @@ func (btp *Bootstrapper) bootstrap(ctIn *rlwe.Ciphertext) (opOut *rlwe.Ciphertex
// ctReal = Ecd(real)
// ctImag = Ecd(imag)
// If n < N/2 then ctReal = Ecd(real|imag)
if ctReal, err = btp.EvalModNew(ctReal, btp.evalModPoly); err != nil {
if ctReal, err = btp.Mod1Evaluator.EvaluateNew(ctReal); err != nil {
return nil, err
}
ctReal.Scale = btp.params.DefaultScale()
if ctImag != nil {
if ctImag, err = btp.EvalModNew(ctImag, btp.evalModPoly); err != nil {
if ctImag, err = btp.Mod1Evaluator.EvaluateNew(ctImag); err != nil {
return nil, err
}
ctImag.Scale = btp.params.DefaultScale()

View File

@@ -32,7 +32,7 @@ func BenchmarkBootstrap(b *testing.B) {
for i := 0; i < b.N; i++ {
bootstrappingScale := rlwe.NewScale(math.Exp2(math.Round(math.Log2(float64(btp.params.Q()[0]) / btp.evalModPoly.MessageRatio()))))
bootstrappingScale := rlwe.NewScale(math.Exp2(math.Round(math.Log2(float64(btp.params.Q()[0]) / btp.mod1Parameters.MessageRatio()))))
b.StopTimer()
ct := ckks.NewCiphertext(params, 1, 0)
@@ -61,12 +61,12 @@ func BenchmarkBootstrap(b *testing.B) {
// Part 2 : SineEval
t = time.Now()
ct0, err = btp.EvalModNew(ct0, btp.evalModPoly)
ct0, err = btp.Mod1Evaluator.EvaluateNew(ct0)
require.NoError(b, err)
ct0.Scale = btp.params.DefaultScale()
if ct1 != nil {
ct1, err = btp.EvalModNew(ct1, btp.evalModPoly)
ct1, err = btp.Mod1Evaluator.EvaluateNew(ct1)
require.NoError(b, err)
ct1.Scale = btp.params.DefaultScale()
}

View File

@@ -99,7 +99,7 @@ func TestBootstrap(t *testing.T) {
// Insecure params for fast testing only
if !*flagLongTest {
// Corrects the message ratio to take into account the smaller number of slots and keep the same precision
btpParams.EvalModParameters.LogMessageRatio += utils.Min(utils.Max(15-LogSlots, 0), 8)
btpParams.Mod1ParametersLiteral.LogMessageRatio += utils.Min(utils.Max(15-LogSlots, 0), 8)
}
if !encapsulation {

View File

@@ -13,7 +13,7 @@ import (
// Parameters is a struct for the default bootstrapping parameters
type Parameters struct {
SlotsToCoeffsParameters float.DFTMatrixLiteral
EvalModParameters float.EvalModLiteral
Mod1ParametersLiteral float.Mod1ParametersLiteral
CoeffsToSlotsParameters float.DFTMatrixLiteral
Iterations int
EphemeralSecretWeight int // Hamming weight of the ephemeral secret. If 0, no ephemeral secret is used during the bootstrapping.
@@ -97,7 +97,7 @@ func NewParametersFromLiteral(ckksLit ckks.ParametersLiteral, btpLit ParametersL
return ckks.ParametersLiteral{}, Parameters{}, err
}
EvalModParams := float.EvalModLiteral{
Mod1ParametersLiteral := float.Mod1ParametersLiteral{
LogScale: EvalModLogScale,
SineType: SineType,
SineDegree: SineDegree,
@@ -113,7 +113,7 @@ func NewParametersFromLiteral(ckksLit ckks.ParametersLiteral, btpLit ParametersL
}
// Coeffs To Slots params
EvalModParams.LevelStart = S2CParams.LevelStart + EvalModParams.Depth()
Mod1ParametersLiteral.LevelStart = S2CParams.LevelStart + Mod1ParametersLiteral.Depth()
CoeffsToSlotsLevels := make([]int, len(CoeffsToSlotsFactorizationDepthAndLogScales))
for i := range CoeffsToSlotsLevels {
@@ -124,7 +124,7 @@ func NewParametersFromLiteral(ckksLit ckks.ParametersLiteral, btpLit ParametersL
Type: float.HomomorphicEncode,
LogSlots: LogSlots,
RepackImag2Real: true,
LevelStart: EvalModParams.LevelStart + len(CoeffsToSlotsFactorizationDepthAndLogScales),
LevelStart: Mod1ParametersLiteral.LevelStart + len(CoeffsToSlotsFactorizationDepthAndLogScales),
LogBSGSRatio: 1,
Levels: CoeffsToSlotsLevels,
}
@@ -149,7 +149,7 @@ func NewParametersFromLiteral(ckksLit ckks.ParametersLiteral, btpLit ParametersL
LogQ = append(LogQ, qi)
}
for i := 0; i < EvalModParams.Depth(); i++ {
for i := 0; i < Mod1ParametersLiteral.Depth(); i++ {
LogQ = append(LogQ, EvalModLogScale)
}
@@ -181,7 +181,7 @@ func NewParametersFromLiteral(ckksLit ckks.ParametersLiteral, btpLit ParametersL
Parameters{
EphemeralSecretWeight: EphemeralSecretWeight,
SlotsToCoeffsParameters: S2CParams,
EvalModParameters: EvalModParams,
Mod1ParametersLiteral: Mod1ParametersLiteral,
CoeffsToSlotsParameters: C2SParams,
Iterations: Iterations,
}, nil
@@ -199,7 +199,7 @@ func (p *Parameters) DepthCoeffsToSlots() (depth int) {
// DepthEvalMod returns the depth of the EvalMod step of the CKKS bootstrapping.
func (p *Parameters) DepthEvalMod() (depth int) {
return p.EvalModParameters.Depth()
return p.Mod1ParametersLiteral.Depth()
}
// DepthSlotsToCoeffs returns the depth of the Slots to Coeffs step of the CKKS bootstrapping.

View File

@@ -14,6 +14,7 @@ import (
"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
@@ -25,7 +26,7 @@ type DFTEvaluatorInterface interface {
Rescale(op0 *rlwe.Ciphertext, opOut *rlwe.Ciphertext) (err error)
}
// DFTType is a type used to distinguish different linear transformations.
// 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.
@@ -35,7 +36,7 @@ const (
)
// DFTMatrix is a struct storing the factorized IDFT, DFT matrices, which are
// used to hommorphically encode and decode a ciphertext respectively.
// used to homomorphically encode and decode a ciphertext respectively.
type DFTMatrix struct {
DFTMatrixLiteral
Matrices []LinearTransformation

View File

@@ -12,7 +12,7 @@ import (
// EvaluatorForMinimaxCompositePolynomial defines a set of common and scheme agnostic method that are necessary to instantiate a MinimaxCompositePolynomialEvaluator.
type EvaluatorForMinimaxCompositePolynomial interface {
circuits.EvaluatorForPolynomialEvaluation
circuits.EvaluatorForPolynomial
circuits.Evaluator
ConjugateNew(ct *rlwe.Ciphertext) (ctConj *rlwe.Ciphertext, err error)
}

View File

@@ -0,0 +1,123 @@
package float
import (
"fmt"
"math/big"
"github.com/tuneinsight/lattigo/v4/circuits"
"github.com/tuneinsight/lattigo/v4/ckks"
"github.com/tuneinsight/lattigo/v4/rlwe"
)
type EvaluatorForMod1 interface {
circuits.Evaluator
circuits.EvaluatorForPolynomial
DropLevel(*rlwe.Ciphertext, int)
GetParameters() *ckks.Parameters
}
type Mod1Evaluator struct {
EvaluatorForMod1
PolynomialEvaluator PolynomialEvaluator
Mod1Parameters Mod1Parameters
}
func NewMod1Evaluator(eval EvaluatorForMod1, Mod1Parameters Mod1Parameters) *Mod1Evaluator {
return &Mod1Evaluator{EvaluatorForMod1: eval, PolynomialEvaluator: *NewPolynomialEvaluator(*eval.GetParameters(), eval), Mod1Parameters: Mod1Parameters}
}
// EvaluateNew 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 Mod1Evaluator) EvaluateNew(ct *rlwe.Ciphertext) (*rlwe.Ciphertext, error) {
var err error
evm := eval.Mod1Parameters
if ct.Level() < evm.LevelStart() {
return nil, fmt.Errorf("cannot Evaluate: ct.Level() < Mod1Parameters.LevelStart")
}
if ct.Level() > evm.LevelStart() {
eval.DropLevel(ct, ct.Level()-evm.LevelStart())
}
// Stores default scales
prevScaleCt := ct.Scale
// Normalize the modular reduction to mod by 1 (division by Q)
ct.Scale = evm.ScalingFactor()
// 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
Qi := eval.GetParameters().Q()
targetScale := ct.Scale
for i := 0; i < evm.doubleAngle; i++ {
targetScale = targetScale.Mul(rlwe.NewScale(Qi[evm.levelStart-evm.sinePoly.Depth()-evm.doubleAngle+i+1]))
targetScale.Value.Sqrt(&targetScale.Value)
}
// Division by 1/2^r and change of variable for the Chebyshev evaluation
if evm.sineType == CosDiscrete || evm.sineType == CosContinuous {
offset := new(big.Float).Sub(&evm.sinePoly.B, &evm.sinePoly.A)
offset.Mul(offset, new(big.Float).SetFloat64(evm.scFac))
offset.Quo(new(big.Float).SetFloat64(-0.5), offset)
if err = eval.Add(ct, offset, ct); err != nil {
return nil, fmt.Errorf("cannot Evaluate: %w", err)
}
}
// Chebyshev evaluation
if ct, err = eval.PolynomialEvaluator.Evaluate(ct, evm.sinePoly, rlwe.NewScale(targetScale)); err != nil {
return nil, fmt.Errorf("cannot Evaluate: %w", err)
}
// Double angle
sqrt2pi := evm.sqrt2Pi
for i := 0; i < evm.doubleAngle; i++ {
sqrt2pi *= sqrt2pi
if err = eval.MulRelin(ct, ct, ct); err != nil {
return nil, fmt.Errorf("cannot Evaluate: %w", err)
}
if err = eval.Add(ct, ct, ct); err != nil {
return nil, fmt.Errorf("cannot Evaluate: %w", err)
}
if err = eval.Add(ct, -sqrt2pi, ct); err != nil {
return nil, fmt.Errorf("cannot Evaluate: %w", err)
}
if err = eval.Rescale(ct, ct); err != nil {
return nil, fmt.Errorf("cannot Evaluate: %w", err)
}
}
// ArcSine
if evm.arcSinePoly != nil {
if ct, err = eval.PolynomialEvaluator.Evaluate(ct, *evm.arcSinePoly, ct.Scale); err != nil {
return nil, fmt.Errorf("cannot Evaluate: %w", err)
}
}
// Multiplies back by q
ct.Scale = prevScaleCt
return ct, nil
}

View File

@@ -18,21 +18,6 @@ import (
// for the homomorphic modular reduction
type SineType uint64
func sin2pi(x *big.Float) (y *big.Float) {
y = new(big.Float).Set(x)
y.Mul(y, new(big.Float).SetFloat64(2))
y.Mul(y, bignum.Pi(x.Prec()))
return bignum.Sin(y)
}
func cos2pi(x *big.Float) (y *big.Float) {
y = new(big.Float).Set(x)
y.Mul(y, new(big.Float).SetFloat64(2))
y.Mul(y, bignum.Pi(x.Prec()))
y = bignum.Cos(y)
return y
}
// Sin and Cos are the two proposed functions for SineType.
// These trigonometric functions offer a good approximation of the function x mod 1 when the values are close to the origin.
const (
@@ -41,13 +26,13 @@ const (
CosContinuous = SineType(2) // Standard Chebyshev approximation of pow((1/2pi), 1/2^r) * cos(2pi(x-0.25)/2^r) on the full interval
)
// EvalModLiteral a struct for the parameters of the EvalMod procedure.
// The EvalMod procedure goal is to homomorphically evaluate a modular reduction by Q[0] (the first prime of the moduli chain) on the encrypted plaintext.
// This struct is consumed by `NewEvalModPolyFromLiteral` to generate the `EvalModPoly` struct, which notably stores
// Mod1ParametersLiteral a struct for the parameters of the mod 1 procedure.
// The x mod 1 procedure goal is to homomorphically evaluate a modular reduction by Q[0] (the first prime of the moduli chain) on the encrypted plaintext.
// This struct is consumed by `NewMod1ParametersLiteralFromLiteral` to generate the `Mod1ParametersLiteral` struct, which notably stores
// the coefficient of the polynomial approximating the function x mod Q[0].
type EvalModLiteral struct {
LevelStart int // Starting level of EvalMod
LogScale int // Log2 of the scaling factor used during EvalMod
type Mod1ParametersLiteral struct {
LevelStart int // Starting level of x mod 1
LogScale int // Log2 of the scaling factor used during x mod 1
SineType SineType // Chose between [Sin(2*pi*x)] or [cos(2*pi*x/r) with double angle formula]
LogMessageRatio int // Log2 of the ratio between Q0 and m, i.e. Q[0]/|m|
K int // K parameter (interpolation in the range -K to K)
@@ -56,20 +41,37 @@ type EvalModLiteral struct {
ArcSineDegree int // Degree of the Taylor arcsine composed with f(2*pi*x) (if zero then not used)
}
// MarshalBinary returns a JSON representation of the the target EvalModLiteral struct on a slice of bytes.
// MarshalBinary returns a JSON representation of the the target Mod1ParametersLiteral struct on a slice of bytes.
// See `Marshal` from the `encoding/json` package.
func (evm EvalModLiteral) MarshalBinary() (data []byte, err error) {
func (evm Mod1ParametersLiteral) MarshalBinary() (data []byte, err error) {
return json.Marshal(evm)
}
// UnmarshalBinary reads a JSON representation on the target EvalModLiteral struct.
// UnmarshalBinary reads a JSON representation on the target Mod1ParametersLiteral struct.
// See `Unmarshal` from the `encoding/json` package.
func (evm *EvalModLiteral) UnmarshalBinary(data []byte) (err error) {
func (evm *Mod1ParametersLiteral) UnmarshalBinary(data []byte) (err error) {
return json.Unmarshal(data, evm)
}
// EvalModPoly is a struct storing the parameters and polynomials approximating the function x mod Q[0] (the first prime of the moduli chain).
type EvalModPoly struct {
// Depth returns the depth required to evaluate x mod 1.
func (evm Mod1ParametersLiteral) Depth() (depth int) {
if evm.SineType == CosDiscrete { // this method requires a minimum degree of 2*K-1.
depth += int(bits.Len64(uint64(utils.Max(evm.SineDegree, 2*evm.K-1))))
} else {
depth += int(bits.Len64(uint64(evm.SineDegree)))
}
if evm.SineType != SinContinuous {
depth += evm.DoubleAngle
}
depth += int(bits.Len64(uint64(evm.ArcSineDegree)))
return depth
}
// Mod1Parameters is a struct storing the parameters and polynomials approximating the function x mod Q[0] (the first prime of the moduli chain).
type Mod1Parameters struct {
levelStart int
LogDefaultScale int
sineType SineType
@@ -83,41 +85,40 @@ type EvalModPoly struct {
k float64
}
// LevelStart returns the starting level of the EvalMod.
func (evp EvalModPoly) LevelStart() int {
// LevelStart returns the starting level of the x mod 1.
func (evp Mod1Parameters) LevelStart() int {
return evp.levelStart
}
// ScalingFactor returns scaling factor used during the EvalMod.
func (evp EvalModPoly) ScalingFactor() rlwe.Scale {
// ScalingFactor returns scaling factor used during the x mod 1.
func (evp Mod1Parameters) ScalingFactor() rlwe.Scale {
return rlwe.NewScale(math.Exp2(float64(evp.LogDefaultScale)))
}
// ScFac returns 1/2^r where r is the number of double angle evaluation.
func (evp EvalModPoly) ScFac() float64 {
func (evp Mod1Parameters) ScFac() float64 {
return evp.scFac
}
// MessageRatio returns the pre-set ratio Q[0]/|m|.
func (evp EvalModPoly) MessageRatio() float64 {
func (evp Mod1Parameters) MessageRatio() float64 {
return float64(uint(1 << evp.LogMessageRatio))
}
// K return the sine approximation range.
func (evp EvalModPoly) K() float64 {
func (evp Mod1Parameters) K() float64 {
return evp.k * evp.scFac
}
// QDiff return Q[0]/ClosetPow2
// This is the error introduced by the approximate division by Q[0].
func (evp EvalModPoly) QDiff() float64 {
func (evp Mod1Parameters) QDiff() float64 {
return evp.qDiff
}
// NewEvalModPolyFromLiteral generates an EvalModPoly struct from the EvalModLiteral struct.
// The EvalModPoly struct is used by the `EvalModNew` method from the `Evaluator`, which
// homomorphically evaluates x mod Q[0] (the first prime of the moduli chain) on the ciphertext.
func NewEvalModPolyFromLiteral(params ckks.Parameters, evm EvalModLiteral) (EvalModPoly, error) {
// NewMod1ParametersFromLiteral generates an Mod1Parameters struct from the Mod1ParametersLiteral struct.
// The Mod1Parameters struct is to instantiates a Mod1Evaluator, which homomorphically evaluates x mod 1.
func NewMod1ParametersFromLiteral(params ckks.Parameters, evm Mod1ParametersLiteral) (Mod1Parameters, error) {
var arcSinePoly *bignum.Polynomial
var sinePoly bignum.Polynomial
@@ -203,7 +204,7 @@ func NewEvalModPolyFromLiteral(params ckks.Parameters, evm EvalModLiteral) (Eval
}
default:
return EvalModPoly{}, fmt.Errorf("invalid SineType")
return Mod1Parameters{}, fmt.Errorf("invalid SineType")
}
sqrt2piBig := new(big.Float).SetFloat64(sqrt2pi)
@@ -214,7 +215,7 @@ func NewEvalModPolyFromLiteral(params ckks.Parameters, evm EvalModLiteral) (Eval
}
}
return EvalModPoly{
return Mod1Parameters{
levelStart: evm.LevelStart,
LogDefaultScale: evm.LogScale,
sineType: evm.SineType,
@@ -229,122 +230,17 @@ func NewEvalModPolyFromLiteral(params ckks.Parameters, evm EvalModLiteral) (Eval
}, nil
}
// Depth returns the depth of the SineEval.
func (evm EvalModLiteral) Depth() (depth int) {
if evm.SineType == CosDiscrete { // this method requires a minimum degree of 2*K-1.
depth += int(bits.Len64(uint64(utils.Max(evm.SineDegree, 2*evm.K-1))))
} else {
depth += int(bits.Len64(uint64(evm.SineDegree)))
}
if evm.SineType != SinContinuous {
depth += evm.DoubleAngle
}
depth += int(bits.Len64(uint64(evm.ArcSineDegree)))
return depth
func sin2pi(x *big.Float) (y *big.Float) {
y = new(big.Float).Set(x)
y.Mul(y, new(big.Float).SetFloat64(2))
y.Mul(y, bignum.Pi(x.Prec()))
return bignum.Sin(y)
}
type HModEvaluator struct {
*ckks.Evaluator
PolynomialEvaluator
}
func NewHModEvaluator(eval *ckks.Evaluator) *HModEvaluator {
return &HModEvaluator{Evaluator: eval, PolynomialEvaluator: *NewPolynomialEvaluator(*eval.GetParameters(), eval)}
}
// 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 *HModEvaluator) EvalModNew(ct *rlwe.Ciphertext, evalModPoly EvalModPoly) (*rlwe.Ciphertext, error) {
var err error
if ct.Level() < evalModPoly.LevelStart() {
return nil, fmt.Errorf("cannot EvalModNew: 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()
// 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
Qi := eval.GetParameters().Q()
targetScale := ct.Scale
for i := 0; i < evalModPoly.doubleAngle; i++ {
targetScale = targetScale.Mul(rlwe.NewScale(Qi[evalModPoly.levelStart-evalModPoly.sinePoly.Depth()-evalModPoly.doubleAngle+i+1]))
targetScale.Value.Sqrt(&targetScale.Value)
}
// Division by 1/2^r and change of variable for the Chebyshev evaluation
if evalModPoly.sineType == CosDiscrete || evalModPoly.sineType == CosContinuous {
offset := new(big.Float).Sub(&evalModPoly.sinePoly.B, &evalModPoly.sinePoly.A)
offset.Mul(offset, new(big.Float).SetFloat64(evalModPoly.scFac))
offset.Quo(new(big.Float).SetFloat64(-0.5), offset)
if err = eval.Add(ct, offset, ct); err != nil {
return nil, fmt.Errorf("cannot EvalModNew: %w", err)
}
}
// Chebyshev evaluation
if ct, err = eval.Evaluate(ct, evalModPoly.sinePoly, rlwe.NewScale(targetScale)); err != nil {
return nil, fmt.Errorf("cannot EvalModNew: %w", err)
}
// Double angle
sqrt2pi := evalModPoly.sqrt2Pi
for i := 0; i < evalModPoly.doubleAngle; i++ {
sqrt2pi *= sqrt2pi
if err = eval.MulRelin(ct, ct, ct); err != nil {
return nil, fmt.Errorf("cannot EvalModNew: %w", err)
}
if err = eval.Add(ct, ct, ct); err != nil {
return nil, fmt.Errorf("cannot EvalModNew: %w", err)
}
if err = eval.Add(ct, -sqrt2pi, ct); err != nil {
return nil, fmt.Errorf("cannot EvalModNew: %w", err)
}
if err = eval.RescaleTo(ct, rlwe.NewScale(targetScale), ct); err != nil {
return nil, fmt.Errorf("cannot EvalModNew: %w", err)
}
}
// ArcSine
if evalModPoly.arcSinePoly != nil {
if ct, err = eval.Evaluate(ct, *evalModPoly.arcSinePoly, ct.Scale); err != nil {
return nil, fmt.Errorf("cannot EvalModNew: %w", err)
}
}
// Multiplies back by q
ct.Scale = prevScaleCt
return ct, nil
func cos2pi(x *big.Float) (y *big.Float) {
y = new(big.Float).Set(x)
y.Mul(y, new(big.Float).SetFloat64(2))
y.Mul(y, bignum.Pi(x.Prec()))
y = bignum.Cos(y)
return y
}

View File

@@ -13,7 +13,7 @@ import (
"github.com/tuneinsight/lattigo/v4/utils/sampling"
)
func TestHomomorphicMod(t *testing.T) {
func TestMod1(t *testing.T) {
var err error
if runtime.GOARCH == "wasm" {
@@ -28,7 +28,7 @@ func TestHomomorphicMod(t *testing.T) {
LogDefaultScale: 45,
}
testEvalModMarshalling(t)
testMod1Marhsalling(t)
var params ckks.Parameters
if params, err = ckks.NewParametersFromLiteral(ParametersLiteral); err != nil {
@@ -36,17 +36,17 @@ func TestHomomorphicMod(t *testing.T) {
}
for _, testSet := range []func(params ckks.Parameters, t *testing.T){
testEvalMod,
testMod1,
} {
testSet(params, t)
runtime.GC()
}
}
func testEvalModMarshalling(t *testing.T) {
func testMod1Marhsalling(t *testing.T) {
t.Run("Marshalling", func(t *testing.T) {
evm := EvalModLiteral{
evm := Mod1ParametersLiteral{
LevelStart: 12,
SineType: SinContinuous,
LogMessageRatio: 8,
@@ -59,7 +59,7 @@ func testEvalModMarshalling(t *testing.T) {
data, err := evm.MarshalBinary()
assert.Nil(t, err)
evmNew := new(EvalModLiteral)
evmNew := new(Mod1ParametersLiteral)
if err := evmNew.UnmarshalBinary(data); err != nil {
assert.Nil(t, err)
}
@@ -67,7 +67,7 @@ func testEvalModMarshalling(t *testing.T) {
})
}
func testEvalMod(params ckks.Parameters, t *testing.T) {
func testMod1(params ckks.Parameters, t *testing.T) {
kgen := ckks.NewKeyGenerator(params)
sk := kgen.GenSecretKeyNew()
@@ -76,11 +76,9 @@ func testEvalMod(params ckks.Parameters, t *testing.T) {
dec := ckks.NewDecryptor(params, sk)
eval := ckks.NewEvaluator(params, rlwe.NewMemEvaluationKeySet(kgen.GenRelinearizationKeyNew(sk)))
modEval := NewHModEvaluator(eval)
t.Run("SineContinuousWithArcSine", func(t *testing.T) {
evm := EvalModLiteral{
evm := Mod1ParametersLiteral{
LevelStart: 12,
SineType: SinContinuous,
LogMessageRatio: 8,
@@ -90,14 +88,14 @@ func testEvalMod(params ckks.Parameters, t *testing.T) {
LogScale: 60,
}
values, ciphertext := evaluatexmod1(evm, params, ecd, enc, modEval, t)
values, ciphertext := evaluateMod1(evm, params, ecd, enc, eval, t)
ckks.VerifyTestVectors(params, ecd, dec, values, ciphertext, params.LogDefaultScale(), nil, *printPrecisionStats, t)
})
t.Run("CosDiscrete", func(t *testing.T) {
evm := EvalModLiteral{
evm := Mod1ParametersLiteral{
LevelStart: 12,
SineType: CosDiscrete,
LogMessageRatio: 8,
@@ -107,14 +105,14 @@ func testEvalMod(params ckks.Parameters, t *testing.T) {
LogScale: 60,
}
values, ciphertext := evaluatexmod1(evm, params, ecd, enc, modEval, t)
values, ciphertext := evaluateMod1(evm, params, ecd, enc, eval, t)
ckks.VerifyTestVectors(params, ecd, dec, values, ciphertext, params.LogDefaultScale(), nil, *printPrecisionStats, t)
})
t.Run("CosContinuous", func(t *testing.T) {
evm := EvalModLiteral{
evm := Mod1ParametersLiteral{
LevelStart: 12,
SineType: CosContinuous,
LogMessageRatio: 4,
@@ -124,51 +122,51 @@ func testEvalMod(params ckks.Parameters, t *testing.T) {
LogScale: 60,
}
values, ciphertext := evaluatexmod1(evm, params, ecd, enc, modEval, t)
values, ciphertext := evaluateMod1(evm, params, ecd, enc, eval, t)
ckks.VerifyTestVectors(params, ecd, dec, values, ciphertext, params.LogDefaultScale(), nil, *printPrecisionStats, t)
})
}
func evaluatexmod1(evm EvalModLiteral, params ckks.Parameters, ecd *ckks.Encoder, enc *rlwe.Encryptor, eval *HModEvaluator, t *testing.T) ([]float64, *rlwe.Ciphertext) {
func evaluateMod1(evm Mod1ParametersLiteral, params ckks.Parameters, ecd *ckks.Encoder, enc *rlwe.Encryptor, eval *ckks.Evaluator, t *testing.T) ([]float64, *rlwe.Ciphertext) {
EvalModPoly, err := NewEvalModPolyFromLiteral(params, evm)
mod1Parameters, err := NewMod1ParametersFromLiteral(params, evm)
require.NoError(t, err)
values, _, ciphertext := newTestVectorsEvalMod(params, enc, ecd, EvalModPoly, t)
values, _, ciphertext := newTestVectorsMod1(params, enc, ecd, mod1Parameters, t)
// Scale the message to Delta = Q/MessageRatio
scale := rlwe.NewScale(math.Exp2(math.Round(math.Log2(float64(params.Q()[0]) / EvalModPoly.MessageRatio()))))
scale := rlwe.NewScale(math.Exp2(math.Round(math.Log2(float64(params.Q()[0]) / mod1Parameters.MessageRatio()))))
scale = scale.Div(ciphertext.Scale)
eval.ScaleUp(ciphertext, rlwe.NewScale(math.Round(scale.Float64())), ciphertext)
// Scale the message up to Sine/MessageRatio
scale = EvalModPoly.ScalingFactor().Div(ciphertext.Scale)
scale = scale.Div(rlwe.NewScale(EvalModPoly.MessageRatio()))
scale = mod1Parameters.ScalingFactor().Div(ciphertext.Scale)
scale = scale.Div(rlwe.NewScale(mod1Parameters.MessageRatio()))
eval.ScaleUp(ciphertext, rlwe.NewScale(math.Round(scale.Float64())), ciphertext)
// Normalization
require.NoError(t, eval.Mul(ciphertext, 1/(float64(EvalModPoly.K())*EvalModPoly.QDiff()), ciphertext))
require.NoError(t, eval.Mul(ciphertext, 1/(float64(mod1Parameters.K())*mod1Parameters.QDiff()), ciphertext))
require.NoError(t, eval.Rescale(ciphertext, ciphertext))
// EvalMod
ciphertext, err = eval.EvalModNew(ciphertext, EvalModPoly)
ciphertext, err = NewMod1Evaluator(eval, mod1Parameters).EvaluateNew(ciphertext)
require.NoError(t, err)
// PlaintextCircuit
for i := range values {
x := values[i]
x /= EvalModPoly.MessageRatio()
x /= EvalModPoly.QDiff()
x /= mod1Parameters.MessageRatio()
x /= mod1Parameters.QDiff()
x = math.Sin(6.28318530717958 * x)
if evm.ArcSineDegree > 0 {
x = math.Asin(x)
}
x *= EvalModPoly.MessageRatio()
x *= EvalModPoly.QDiff()
x *= mod1Parameters.MessageRatio()
x *= mod1Parameters.QDiff()
x /= 6.28318530717958
values[i] = x
@@ -177,7 +175,7 @@ func evaluatexmod1(evm EvalModLiteral, params ckks.Parameters, ecd *ckks.Encoder
return values, ciphertext
}
func newTestVectorsEvalMod(params ckks.Parameters, encryptor *rlwe.Encryptor, encoder *ckks.Encoder, evm EvalModPoly, t *testing.T) (values []float64, plaintext *rlwe.Plaintext, ciphertext *rlwe.Ciphertext) {
func newTestVectorsMod1(params ckks.Parameters, encryptor *rlwe.Encryptor, encoder *ckks.Encoder, evm Mod1Parameters, t *testing.T) (values []float64, plaintext *rlwe.Plaintext, ciphertext *rlwe.Ciphertext) {
logSlots := params.LogMaxDimensions().Cols

View File

@@ -25,9 +25,9 @@ func NewPowerBasis(ct *rlwe.Ciphertext, basis bignum.Basis) circuits.PowerBasis
}
// NewPolynomialEvaluator instantiates a new PolynomialEvaluator.
func NewPolynomialEvaluator(params ckks.Parameters, eval circuits.EvaluatorForPolynomialEvaluation) *PolynomialEvaluator {
func NewPolynomialEvaluator(params ckks.Parameters, eval circuits.EvaluatorForPolynomial) *PolynomialEvaluator {
e := new(PolynomialEvaluator)
e.PolynomialEvaluator = circuits.PolynomialEvaluator{EvaluatorForPolynomialEvaluation: eval, EvaluatorBuffers: eval.GetEvaluatorBuffer()}
e.PolynomialEvaluator = circuits.PolynomialEvaluator{EvaluatorForPolynomial: eval, EvaluatorBuffers: eval.GetEvaluatorBuffer()}
e.Parameters = params
return e
}

View File

@@ -26,9 +26,9 @@ func NewPolynomialEvaluator(params bgv.Parameters, eval *bgv.Evaluator, Invarian
e := new(PolynomialEvaluator)
if InvariantTensoring {
e.PolynomialEvaluator = circuits.PolynomialEvaluator{EvaluatorForPolynomialEvaluation: scaleInvariantEvaluator{eval}, EvaluatorBuffers: eval.GetEvaluatorBuffer()}
e.PolynomialEvaluator = circuits.PolynomialEvaluator{EvaluatorForPolynomial: scaleInvariantEvaluator{eval}, EvaluatorBuffers: eval.GetEvaluatorBuffer()}
} else {
e.PolynomialEvaluator = circuits.PolynomialEvaluator{EvaluatorForPolynomialEvaluation: eval, EvaluatorBuffers: eval.GetEvaluatorBuffer()}
e.PolynomialEvaluator = circuits.PolynomialEvaluator{EvaluatorForPolynomial: eval, EvaluatorBuffers: eval.GetEvaluatorBuffer()}
}
e.InvariantTensoring = InvariantTensoring

View File

@@ -8,8 +8,8 @@ import (
"github.com/tuneinsight/lattigo/v4/utils/bignum"
)
// EvaluatorForPolynomialEvaluation defines a set of common and scheme agnostic method that are necessary to instantiate a PolynomialVectorEvaluator.
type EvaluatorForPolynomialEvaluation interface {
// EvaluatorForPolynomial defines a set of common and scheme agnostic method that are necessary to instantiate a PolynomialVectorEvaluator.
type EvaluatorForPolynomial interface {
rlwe.ParameterProvider
Evaluator
Encode(values interface{}, pt *rlwe.Plaintext) (err error)
@@ -23,7 +23,7 @@ type PolynomialVectorEvaluator interface {
// PolynomialEvaluator is an evaluator used to evaluate polynomials on ciphertexts.
type PolynomialEvaluator struct {
EvaluatorForPolynomialEvaluation
EvaluatorForPolynomial
*rlwe.EvaluatorBuffers
}

View File

@@ -76,7 +76,7 @@ func main() {
if *flagShort {
// Corrects the message ratio to take into account the smaller number of slots and keep the same precision
btpParams.EvalModParameters.LogMessageRatio += 3
btpParams.Mod1ParametersLiteral.LogMessageRatio += 3
}
// This generate ckks.Parameters, with the NTT tables and other pre-computations from the ckks.ParametersLiteral (which is only a template).

View File

@@ -87,14 +87,14 @@ func NewRemez(p RemezParameters) (r *Remez) {
r.Coeffs[i] = new(big.Float)
}
r.extrempoints = make([]point, 2*r.Degree)
r.extrempoints = make([]point, 3*r.Degree)
for i := range r.extrempoints {
r.extrempoints[i].x = new(big.Float)
r.extrempoints[i].y = new(big.Float)
}
r.localExtrempoints = make([]point, 2*r.Degree)
r.localExtrempoints = make([]point, 3*r.Degree)
for i := range r.localExtrempoints {
r.localExtrempoints[i].x = new(big.Float)
r.localExtrempoints[i].y = new(big.Float)