Merge branch 'main' into Security_note_update

This commit is contained in:
lehugueni
2025-07-28 13:48:45 +02:00
committed by GitHub
73 changed files with 1990 additions and 1379 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.23.6'
go-version: '1.23.10'
- uses: actions/cache@v4
with:
+1
View File
@@ -2,6 +2,7 @@
All notable changes to this library are documented in this file.
## [Unreleased]
- Thread-safety: methods of a given structure (e.g. an evaluator) can be called concurrently. `ShallowCopy` methods are not needed anymore and have been removed.
- Refactoring of the InnerSum methods:
- `rlwe.Evaluator.InnerSum` has been replaced by `rlwe.Evaluator.PartialTracesSum`, which applies the automorphisms that correspond to rotations at the scheme level (and sum the results).
- Introduction of the `bgv.Evaluator.InnerSum` and `ckks.Evaluator.InnerSum` methods, which have the same behaviour as the old `InnerSum` method for parameters `n` and `batchSize` s.t. `0 < n*batchSize <= ctIn.Slots()` divides the number of slots. Parameters not satisfying these conditions are rejected.
+1 -3
View File
@@ -140,9 +140,7 @@ type Evaluator struct {
// The default [bgv.Evaluator] is compliant to the [schemes.Evaluator] interface.
func NewEvaluator(eval schemes.Evaluator) (linTransEval *Evaluator) {
return &Evaluator{
lintrans.Evaluator{
Evaluator: eval,
},
*lintrans.NewEvaluator(eval),
}
}
+22 -45
View File
@@ -42,6 +42,8 @@ type Evaluator struct {
C2SDFTMatrix dft.Matrix
SkDebug *rlwe.SecretKey
pool *rlwe.BufferPool
}
// NewEvaluator creates a new [Evaluator].
@@ -124,42 +126,10 @@ func NewEvaluator(btpParams Parameters, evk *EvaluationKeys) (eval *Evaluator, e
eval.Mod1Evaluator = mod1.NewEvaluator(eval.Evaluator, polynomial.NewEvaluator(params, eval.Evaluator), eval.Mod1Parameters)
eval.pool = rlwe.NewPool(eval.BootstrappingParameters.RingQP())
return
}
// ShallowCopy creates a shallow copy of this [Evaluator] in which all the read-only data-structures are
// shared with the receiver and the temporary buffers are reallocated. The receiver and the returned
// Evaluator can be used concurrently.
func (eval Evaluator) ShallowCopy() *Evaluator {
heEvaluator := eval.Evaluator.ShallowCopy()
paramsN1 := eval.ResidualParameters
paramsN2 := eval.BootstrappingParameters
var DomainSwitcher ckks.DomainSwitcher
if paramsN1.RingType() == ring.ConjugateInvariant {
var err error
if DomainSwitcher, err = ckks.NewDomainSwitcher(paramsN2, eval.EvkCmplxToReal, eval.EvkRealToCmplx); err != nil {
panic(fmt.Errorf("cannot NewBootstrapper: ckks.NewDomainSwitcher: %w", err))
}
}
return &Evaluator{
Parameters: eval.Parameters,
EvaluationKeys: eval.EvaluationKeys,
Mod1Parameters: eval.Mod1Parameters,
S2CDFTMatrix: eval.S2CDFTMatrix,
C2SDFTMatrix: eval.C2SDFTMatrix,
Evaluator: heEvaluator,
xPow2N1: eval.xPow2N1,
xPow2N2: eval.xPow2N2,
xPow2InvN2: eval.xPow2InvN2,
DomainSwitcher: DomainSwitcher,
DFTEvaluator: dft.NewEvaluator(paramsN2, heEvaluator),
Mod1Evaluator: mod1.NewEvaluator(heEvaluator, polynomial.NewEvaluator(paramsN2, heEvaluator), eval.Mod1Parameters),
SkDebug: eval.SkDebug,
}
}
// CheckKeys checks if all the necessary keys are present in the instantiated [Evaluator]
func (eval Evaluator) checkKeys(evk *EvaluationKeys) (err error) {
@@ -667,6 +637,7 @@ func (eval Evaluator) ModUp(ctIn *rlwe.Ciphertext) (ctOut *rlwe.Ciphertext, err
levelQ := params.QCount() - 1
levelP := params.PCount() - 1
poolQP := eval.pool.AtLevel(levelQ, levelP)
ringQ = ringQ.AtLevel(levelQ)
Q := ringQ.ModuliChain()
@@ -699,6 +670,9 @@ func (eval Evaluator) ModUp(ctIn *rlwe.Ciphertext) (ctOut *rlwe.Ciphertext, err
ks := eval.Evaluator.Evaluator
buffDecompQP := poolQP.GetBuffDecompQP(eval.ResidualParameters.Parameters, eval.BootstrappingParameters.MaxLevelQ(), 0)
defer eval.pool.RecycleBuffDecompQP(buffDecompQP)
// ModUp q->QP for ctIn[1] centered around q
for j := 0; j < N; j++ {
@@ -711,22 +685,22 @@ func (eval Evaluator) ModUp(ctIn *rlwe.Ciphertext) (ctOut *rlwe.Ciphertext, err
for i := 0; i < levelQ+1; i++ {
tmp = ring.BRedAdd(coeff, Q[i], BRCQ[i])
ks.BuffDecompQP[0].Q.Coeffs[i][j] = tmp*pos + (Q[i]-tmp)*neg
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], BRCP[i])
ks.BuffDecompQP[0].P.Coeffs[i][j] = tmp*pos + (P[i]-tmp)*neg
buffDecompQP[0].P.Coeffs[i][j] = tmp*pos + (P[i]-tmp)*neg
}
}
for i := len(ks.BuffDecompQP) - 1; i >= 0; i-- {
ringQ.NTT(ks.BuffDecompQP[0].Q, ks.BuffDecompQP[i].Q)
for i := len(buffDecompQP) - 1; i >= 0; i-- {
ringQ.NTT(buffDecompQP[0].Q, buffDecompQP[i].Q)
}
for i := len(ks.BuffDecompQP) - 1; i >= 0; i-- {
ringP.NTT(ks.BuffDecompQP[0].P, ks.BuffDecompQP[i].P)
for i := len(buffDecompQP) - 1; i >= 0; i-- {
ringP.NTT(buffDecompQP[0].P, buffDecompQP[i].P)
}
ringQ.NTT(ctIn.Value[0], ctIn.Value[0])
@@ -736,12 +710,12 @@ func (eval Evaluator) ModUp(ctIn *rlwe.Ciphertext) (ctOut *rlwe.Ciphertext, err
scalar := uint64(math.Round(scale))
for i := len(ks.BuffDecompQP) - 1; i >= 0; i-- {
ringQ.MulScalar(ks.BuffDecompQP[0].Q, scalar, ks.BuffDecompQP[i].Q)
for i := len(buffDecompQP) - 1; i >= 0; i-- {
ringQ.MulScalar(buffDecompQP[0].Q, scalar, buffDecompQP[i].Q)
}
for i := len(ks.BuffDecompQP) - 1; i >= 0; i-- {
ringP.MulScalar(ks.BuffDecompQP[0].P, scalar, ks.BuffDecompQP[i].P)
for i := len(buffDecompQP) - 1; i >= 0; i-- {
ringP.MulScalar(buffDecompQP[0].P, scalar, buffDecompQP[i].P)
}
ringQ.MulScalar(ctIn.Value[0], scalar, ctIn.Value[0])
@@ -749,12 +723,15 @@ func (eval Evaluator) ModUp(ctIn *rlwe.Ciphertext) (ctOut *rlwe.Ciphertext, err
ctIn.Scale = ctIn.Scale.Mul(rlwe.NewScale(scale))
}
buffQ1 := poolQP.GetBuffPoly()
defer poolQP.RecycleBuffPoly(buffQ1)
ctTmp := &rlwe.Ciphertext{}
ctTmp.Value = []ring.Poly{ks.BuffQP[1].Q, ctIn.Value[1]}
ctTmp.Value = []ring.Poly{*buffQ1, ctIn.Value[1]}
ctTmp.MetaData = ctIn.MetaData
// Switch back to the dense key
ks.GadgetProductHoisted(levelQ, ks.BuffDecompQP, &eval.EvkSparseToDense.GadgetCiphertext, ctTmp)
ks.GadgetProductHoisted(levelQ, buffDecompQP, &eval.EvkSparseToDense.GadgetCiphertext, ctTmp)
ringQ.Add(ctIn.Value[0], ctTmp.Value[0], ctIn.Value[0])
} else {
@@ -7,11 +7,11 @@ import (
"github.com/stretchr/testify/require"
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/schemes/ckks"
)
func BenchmarkBootstrap(b *testing.B) {
func BenchmarkConcurrentBootstrap(b *testing.B) {
paramSet := DefaultParametersDense[0]
params, err := ckks.NewParametersFromLiteral(paramSet.SchemeParams)
@@ -26,12 +26,44 @@ func BenchmarkBootstrap(b *testing.B) {
evk, _, err := btpParams.GenEvaluationKeys(sk)
require.NoError(b, err)
eval, err := NewEvaluator(btpParams, evk)
// Benchmark parallel bootstrapping
b.Run(ParamsToString(params, btpParams.LogMaxDimensions().Cols, "Bootstrap/"), func(b *testing.B) {
var err error
eval, err := NewEvaluator(btpParams, evk)
require.NoError(b, err)
ct1 := ckks.NewCiphertext(params, 1, 0)
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_, err = eval.Bootstrap(ct1)
}
})
})
}
func BenchmarkBootstrap(b *testing.B) {
b.Skip()
paramSet := DefaultParametersDense[0]
params, err := ckks.NewParametersFromLiteral(paramSet.SchemeParams)
require.NoError(b, err)
btpParams, err := NewParametersFromLiteral(params, paramSet.BootstrappingParams)
require.Nil(b, err)
kgen := rlwe.NewKeyGenerator(params)
sk := kgen.GenSecretKeyNew()
evk, _, err := btpParams.GenEvaluationKeys(sk)
require.NoError(b, err)
b.Run(ParamsToString(params, btpParams.LogMaxDimensions().Cols, "Bootstrap/"), func(b *testing.B) {
var err error
eval, err := NewEvaluator(btpParams, evk)
require.NoError(b, err)
for i := 0; i < b.N; i++ {
@@ -191,17 +191,14 @@ func testRawCircuit(params ckks.Parameters, btpParams Parameters, level int, t *
plaintext.LogDimensions = btpParams.LogMaxDimensions()
encoder.Encode(values, plaintext)
n := 1
n := 2
ciphertexts := make([]*rlwe.Ciphertext, n)
evaluators := make([]*Evaluator, n)
evaluators[0] = eval
ciphertexts[0], err = encryptor.EncryptNew(plaintext)
require.NoError(t, err)
for i := 1; i < len(ciphertexts); i++ {
ciphertexts[i], err = encryptor.EncryptNew(plaintext)
require.NoError(t, err)
evaluators[i] = evaluators[0].ShallowCopy()
}
var wg sync.WaitGroup
@@ -209,7 +206,7 @@ func testRawCircuit(params ckks.Parameters, btpParams Parameters, level int, t *
for i := range ciphertexts {
go func(index int) {
var err error
ciphertexts[index], err = evaluators[index].Evaluate(ciphertexts[index])
ciphertexts[index], err = eval.Evaluate(ciphertexts[index])
require.NoError(t, err)
wg.Done()
}(i)
@@ -14,7 +14,6 @@ type SecretKeyBootstrapper struct {
*rlwe.Decryptor
*rlwe.Encryptor
sk *rlwe.SecretKey
Values []*bignum.Complex
Counter int // records the number of bootstrapping
MinLevel int
}
@@ -26,11 +25,11 @@ func NewSecretKeyBootstrapper(params ckks.Parameters, sk *rlwe.SecretKey) *Secre
Decryptor: rlwe.NewDecryptor(params, sk),
Encryptor: rlwe.NewEncryptor(params, sk),
sk: sk,
Values: make([]*bignum.Complex, params.N())}
}
}
func (d *SecretKeyBootstrapper) Bootstrap(ct *rlwe.Ciphertext) (*rlwe.Ciphertext, error) {
values := d.Values[:1<<ct.LogDimensions.Cols]
values := make([]*bignum.Complex, 1<<ct.LogDimensions.Cols)
if err := d.Decode(d.DecryptNew(ct), values); err != nil {
return nil, err
}
@@ -49,6 +49,7 @@ func TestComparisons(t *testing.T) {
CmpEval := NewEvaluator(tc.Params, minimaxEvl, polys)
t.Run(name("Sign", tc), func(t *testing.T) {
t.Parallel()
values, _, ct := tc.NewTestVector(complex(-1, 0), complex(1, 0))
@@ -70,6 +71,7 @@ func TestComparisons(t *testing.T) {
})
t.Run(name("Step", tc), func(t *testing.T) {
t.Parallel()
values, _, ct := tc.NewTestVector(complex(-1, 0), complex(1, 0))
@@ -95,6 +97,7 @@ func TestComparisons(t *testing.T) {
})
t.Run(name("Max", tc), func(t *testing.T) {
t.Parallel()
values0, _, ct0 := tc.NewTestVector(complex(-0.5, 0), complex(0.5, 0))
values1, _, ct1 := tc.NewTestVector(complex(-0.5, 0), complex(0.5, 0))
@@ -122,6 +125,7 @@ func TestComparisons(t *testing.T) {
})
t.Run(name("Min", tc), func(t *testing.T) {
t.Parallel()
values0, _, ct0 := tc.NewTestVector(complex(-0.5, 0), complex(0.5, 0))
values1, _, ct1 := tc.NewTestVector(complex(-0.5, 0), complex(0.5, 0))
+6 -8
View File
@@ -144,6 +144,7 @@ type Evaluator struct {
*ckks.Evaluator
LTEvaluator *ltcommon.Evaluator
parameters ckks.Parameters
pool *rlwe.BufferPool
}
// NewEvaluator instantiates a new [Evaluator] from a [ckks.Evaluator].
@@ -152,6 +153,9 @@ func NewEvaluator(params ckks.Parameters, eval *ckks.Evaluator) *Evaluator {
dfteval.Evaluator = eval
dfteval.LTEvaluator = ltcommon.NewEvaluator(eval)
dfteval.parameters = params
dfteval.pool = rlwe.NewPool(params.RingQP())
return dfteval
}
@@ -251,14 +255,8 @@ func (eval *Evaluator) CoeffsToSlots(ctIn *rlwe.Ciphertext, ctsMatrices Matrix,
if ctImag != nil {
tmp = ctImag
} else {
tmp, err = rlwe.NewCiphertextAtLevelFromPoly(ctReal.Level(), eval.GetBuffCt().Value[:2])
// This error cannot happen unless the user improperly tempered the evaluators
// buffer. If it were to happen in that case, there is no way to recover from
// it, hence the panic.
if err != nil {
panic(err)
}
tmp = eval.pool.GetBuffCt(1, ctReal.Level())
defer eval.pool.RecycleBuffCt(tmp)
tmp.IsNTT = true
}
+6
View File
@@ -55,6 +55,7 @@ func TestInverse(t *testing.T) {
eval := tc.Evl.WithKey(evk)
t.Run(name("GoldschmidtDivisionNew", tc), func(t *testing.T) {
t.Parallel()
values, _, ciphertext := tc.NewTestVector(complex(min, 0), complex(2-min, 0))
@@ -76,7 +77,9 @@ func TestInverse(t *testing.T) {
})
t.Run(name("PositiveDomain", tc), func(t *testing.T) {
t.Parallel()
eval := tc.Evl.WithKey(evk)
values, _, ct := tc.NewTestVector(complex(0, 0), complex(max, 0))
minEvl := minimax.NewEvaluator(tc.Params, eval, btp)
@@ -105,7 +108,9 @@ func TestInverse(t *testing.T) {
})
t.Run(name("NegativeDomain", tc), func(t *testing.T) {
t.Parallel()
eval := tc.Evl.WithKey(evk)
values, _, ct := tc.NewTestVector(complex(-max, 0), complex(0, 0))
minEvl := minimax.NewEvaluator(tc.Params, eval, btp)
@@ -134,6 +139,7 @@ func TestInverse(t *testing.T) {
})
t.Run(name("FullDomain", tc), func(t *testing.T) {
t.Parallel()
values, _, ct := tc.NewTestVector(complex(-max, 0), complex(max, 0))
+2 -3
View File
@@ -137,10 +137,9 @@ type Evaluator struct {
// NewEvaluator instantiates a new [Evaluator] from a circuit.EvaluatorForLinearTransformation.
// This method is allocation free.
func NewEvaluator(eval schemes.Evaluator) (linTransEval *Evaluator) {
return &Evaluator{
lintrans.Evaluator{
Evaluator: eval,
},
*lintrans.NewEvaluator(eval),
}
}
+5
View File
@@ -104,6 +104,7 @@ func run(tc *ckks.TestContext, t *testing.T) {
}
t.Run(name("Average", tc), func(t *testing.T) {
t.Parallel()
values, _, ciphertext := tc.NewTestVector(-1-1i, 1+1i)
@@ -142,6 +143,7 @@ func run(tc *ckks.TestContext, t *testing.T) {
})
t.Run(name("LinearTransform/BSGS=True", tc), func(t *testing.T) {
t.Parallel()
values, _, ciphertext := tc.NewTestVector(-1-1i, 1+1i)
@@ -189,6 +191,7 @@ func run(tc *ckks.TestContext, t *testing.T) {
})
t.Run(name("LinearTransform/BSGS=False", tc), func(t *testing.T) {
t.Parallel()
values, _, ciphertext := tc.NewTestVector(-1-1i, 1+1i)
@@ -237,6 +240,8 @@ func run(tc *ckks.TestContext, t *testing.T) {
})
t.Run(name("LinearTransform/Permutation", tc), func(t *testing.T) {
t.Parallel()
idx := make([]int, params.MaxSlots())
for i := range idx {
idx[i] = i
@@ -75,6 +75,7 @@ func run(tc *ckks.TestContext, t *testing.T) {
polyEval := NewEvaluator(params, tc.Evl)
t.Run(name("EvaluatePoly/PolySingle/Exp", tc), func(t *testing.T) {
t.Parallel()
if params.MaxLevel() < 3 {
t.Skip("skipping test for params max level < 3")
@@ -109,6 +110,7 @@ func run(tc *ckks.TestContext, t *testing.T) {
})
t.Run(name("Polynomial/PolyVector/Exp", tc), func(t *testing.T) {
t.Parallel()
if params.MaxLevel() < 3 {
t.Skip("skipping test for params max level < 3")
+64 -27
View File
@@ -11,6 +11,15 @@ import (
type Evaluator struct {
schemes.Evaluator
pool *rlwe.BufferPool
}
func NewEvaluator(eval schemes.Evaluator) *Evaluator {
return &Evaluator{
Evaluator: eval,
pool: rlwe.NewPool(eval.GetRLWEParameters().RingQP()),
}
}
// EvaluateMany takes as input a ciphertext ctIn, a list of linear transformations [M0, M1, M2, ...] and a list of pre-allocated receiver opOut
@@ -38,23 +47,25 @@ func (eval Evaluator) EvaluateMany(ctIn *rlwe.Ciphertext, linearTransformations
levelQ = utils.Min(levelQ, ctIn.Level())
BuffDecompQP := eval.GetBuffDecompQP()
poolQP := eval.pool.AtLevel(levelQ, levelP)
buffDecompQP := poolQP.GetBuffDecompQP(*eval.GetRLWEParameters(), levelQ, levelP)
defer eval.pool.RecycleBuffDecompQP(buffDecompQP)
eval.DecomposeNTT(levelQ, levelP, levelP+1, ctIn.Value[1], ctIn.IsNTT, BuffDecompQP)
eval.DecomposeNTT(levelQ, levelP, levelP+1, ctIn.Value[1], ctIn.IsNTT, buffDecompQP)
ctPreRot := map[int]*rlwe.Element[ringqp.Poly]{}
for i, lt := range linearTransformations {
if lt.N1 == 0 {
if err = eval.MultiplyByDiagMatrix(ctIn, lt, BuffDecompQP, opOut[i]); err != nil {
if err = eval.MultiplyByDiagMatrix(ctIn, lt, buffDecompQP, opOut[i]); err != nil {
return
}
} else {
_, _, rotN2 := lt.BSGSIndex()
if err = eval.PreRotatedCiphertextForDiagonalMatrixMultiplication(levelQ, levelP, ctIn, BuffDecompQP, rotN2, ctPreRot); err != nil {
if err = eval.PreRotatedCiphertextForDiagonalMatrixMultiplication(levelQ, levelP, ctIn, buffDecompQP, rotN2, ctPreRot); err != nil {
return
}
@@ -130,9 +141,6 @@ func (eval Evaluator) EvaluateSequential(ctIn *rlwe.Ciphertext, linearTransforma
// for matrix of only a few non-zero diagonals but uses more keys.
func (eval Evaluator) MultiplyByDiagMatrix(ctIn *rlwe.Ciphertext, matrix LinearTransformation, BuffDecompQP []ringqp.Poly, opOut *rlwe.Ciphertext) (err error) {
BuffQP := eval.GetBuffQP()
BuffCt := eval.GetBuffCt()
*opOut.MetaData = *ctIn.MetaData
opOut.Scale = opOut.Scale.Mul(matrix.Scale)
@@ -142,30 +150,46 @@ func (eval Evaluator) MultiplyByDiagMatrix(ctIn *rlwe.Ciphertext, matrix LinearT
levelP := matrix.LevelP
ringQP := params.RingQP().AtLevel(levelQ, levelP)
poolQP := eval.pool.AtLevel(levelQ, levelP)
ringQ := ringQP.RingQ
ringP := ringQP.RingP
buffQP0 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP0)
buffQP1 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP1)
buffQP2 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP2)
buffQP3 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP3)
buffQP4 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP4)
buffQP5 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP5)
buffCt := eval.pool.GetBuffCt(1, ringQ.Level())
defer eval.pool.RecycleBuffCt(buffCt)
opOut.Resize(opOut.Degree(), levelQ)
QiOverF := params.QiOverflowMargin(levelQ)
PiOverF := params.PiOverflowMargin(levelP)
c0OutQP := ringqp.Poly{Q: opOut.Value[0], P: BuffQP[5].Q}
c1OutQP := ringqp.Poly{Q: opOut.Value[1], P: BuffQP[5].P}
c0OutQP := ringqp.Poly{Q: opOut.Value[0], P: (*buffQP5).Q}
c1OutQP := ringqp.Poly{Q: opOut.Value[1], P: (*buffQP5).P}
ct0TimesP := BuffQP[0].Q // ct0 * P mod Q
tmp0QP := BuffQP[1]
tmp1QP := BuffQP[2]
ct0TimesP := (*buffQP0).Q // ct0 * P mod Q
tmp0QP := *buffQP1
tmp1QP := *buffQP2
cQP := &rlwe.Element[ringqp.Poly]{}
cQP.Value = []ringqp.Poly{BuffQP[3], BuffQP[4]}
cQP.Value = []ringqp.Poly{*buffQP3, *buffQP4}
cQP.MetaData = &rlwe.MetaData{}
cQP.MetaData.IsNTT = true
BuffCt.Value[0].CopyLvl(levelQ, ctIn.Value[0])
BuffCt.Value[1].CopyLvl(levelQ, ctIn.Value[1])
buffCt.Value[0].CopyLvl(levelQ, ctIn.Value[0])
buffCt.Value[1].CopyLvl(levelQ, ctIn.Value[1])
ctInTmp0, ctInTmp1 := BuffCt.Value[0], BuffCt.Value[1]
ctInTmp0, ctInTmp1 := buffCt.Value[0], buffCt.Value[1]
ringQ.MulScalarBigint(ctInTmp0, ringP.ModulusAtLevel[levelP], ct0TimesP) // P*c0
@@ -257,9 +281,6 @@ func (eval Evaluator) MultiplyByDiagMatrixBSGS(ctIn *rlwe.Ciphertext, matrix Lin
params := eval.GetRLWEParameters()
BuffQP := eval.GetBuffQP()
BuffCt := eval.GetBuffCt()
*opOut.MetaData = *ctIn.MetaData
opOut.Scale = opOut.Scale.Mul(matrix.Scale)
@@ -267,9 +288,25 @@ func (eval Evaluator) MultiplyByDiagMatrixBSGS(ctIn *rlwe.Ciphertext, matrix Lin
levelP := matrix.LevelP
ringQP := params.RingQP().AtLevel(levelQ, levelP)
poolQP := eval.pool.AtLevel(levelQ, levelP)
ringQ := ringQP.RingQ
ringP := ringQP.RingP
buffQP1 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP1)
buffQP2 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP2)
buffQP3 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP3)
buffQP4 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP4)
buffP1 := poolQP.PoolP.GetBuffPoly()
defer poolQP.PoolP.RecycleBuffPoly(buffP1)
buffP2 := poolQP.PoolP.GetBuffPoly()
defer poolQP.PoolP.RecycleBuffPoly(buffP2)
buffCt := eval.pool.GetBuffCt(1, ringQ.Level())
defer eval.pool.RecycleBuffCt(buffCt)
opOut.Resize(opOut.Degree(), levelQ)
QiOverF := params.QiOverflowMargin(levelQ) >> 1
@@ -278,24 +315,24 @@ func (eval Evaluator) MultiplyByDiagMatrixBSGS(ctIn *rlwe.Ciphertext, matrix Lin
// Computes the N2 rotations indexes of the non-zero rows of the diagonalized DFT matrix for the baby-step giant-step algorithm
index, _, _ := matrix.BSGSIndex()
BuffCt.Value[0].CopyLvl(levelQ, ctIn.Value[0])
BuffCt.Value[1].CopyLvl(levelQ, ctIn.Value[1])
buffCt.Value[0].CopyLvl(levelQ, ctIn.Value[0])
buffCt.Value[1].CopyLvl(levelQ, ctIn.Value[1])
ctInTmp0, ctInTmp1 := BuffCt.Value[0], BuffCt.Value[1]
ctInTmp0, ctInTmp1 := buffCt.Value[0], buffCt.Value[1]
// Accumulator inner loop
tmp0QP := BuffQP[1]
tmp1QP := BuffQP[2]
tmp0QP := (*buffQP1)
tmp1QP := (*buffQP2)
// Accumulator outer loop
cQP := &rlwe.Element[ringqp.Poly]{}
cQP.Value = []ringqp.Poly{BuffQP[3], BuffQP[4]}
cQP.Value = []ringqp.Poly{*buffQP3, *buffQP4}
cQP.MetaData = &rlwe.MetaData{}
cQP.IsNTT = true
// Result in QP
c0OutQP := ringqp.Poly{Q: opOut.Value[0], P: BuffQP[5].Q}
c1OutQP := ringqp.Poly{Q: opOut.Value[1], P: BuffQP[5].P}
c0OutQP := ringqp.Poly{Q: opOut.Value[0], P: *buffP1}
c1OutQP := ringqp.Poly{Q: opOut.Value[1], P: *buffP2}
ringQ.MulScalarBigint(ctInTmp0, ringP.ModulusAtLevel[levelP], ctInTmp0) // P*c0
ringQ.MulScalarBigint(ctInTmp1, ringP.ModulusAtLevel[levelP], ctInTmp1) // P*c1
+11 -15
View File
@@ -11,13 +11,13 @@ import (
// types in addition to ciphertexts types in the rlwe package.
type Encryptor struct {
*rlwe.Encryptor
buffQP ringqp.Poly
pool *rlwe.BufferPool
}
// NewEncryptor creates a new Encryptor type. Note that only secret-key encryption is
// supported at the moment.
func NewEncryptor(params rlwe.ParameterProvider, key rlwe.EncryptionKey) *Encryptor {
return &Encryptor{rlwe.NewEncryptor(params, key), params.GetRLWEParameters().RingQP().NewPoly()}
return &Encryptor{rlwe.NewEncryptor(params, key), rlwe.NewPool(params.GetRLWEParameters().RingQP())}
}
// Encrypt encrypts a plaintext pt into a ciphertext ct, which can be a [rgsw.Ciphertext]
@@ -41,26 +41,29 @@ func (enc Encryptor) Encrypt(pt *rlwe.Plaintext, ct interface{}) (err error) {
if pt != nil {
buffQP := enc.pool.GetBuffPolyQP()
defer enc.pool.RecycleBuffPolyQP(buffQP)
if !pt.IsNTT {
ringQ.NTT(pt.Value, enc.buffQP.Q)
ringQ.NTT(pt.Value, buffQP.Q)
if !pt.IsMontgomery {
ringQ.MForm(enc.buffQP.Q, enc.buffQP.Q)
ringQ.MForm(buffQP.Q, buffQP.Q)
}
} else {
if !pt.IsMontgomery {
ringQ.MForm(pt.Value, enc.buffQP.Q)
ringQ.MForm(pt.Value, buffQP.Q)
} else {
pt.Value.CopyLvl(levelQ, enc.buffQP.Q)
pt.Value.CopyLvl(levelQ, buffQP.Q)
}
}
if err := rlwe.AddPolyTimesGadgetVectorToGadgetCiphertext(
enc.buffQP.Q,
buffQP.Q,
[]rlwe.GadgetCiphertext{rgswCt.Value[0], rgswCt.Value[1]},
*params.RingQP(),
enc.buffQP.Q); err != nil {
buffQP.Q); err != nil {
// Sanity check, this error should not happen.
panic(err)
}
@@ -116,10 +119,3 @@ func (enc Encryptor) EncryptZero(ct interface{}) (err error) {
return nil
}
// ShallowCopy creates a shallow copy of this [Encryptor] in which all the read-only data-structures are
// shared with the receiver and the temporary buffers are reallocated. The receiver and the returned
// Encryptors can be used concurrently.
func (enc Encryptor) ShallowCopy() *Encryptor {
return &Encryptor{Encryptor: enc.Encryptor.ShallowCopy(), buffQP: enc.GetRLWEParameters().RingQP().NewPoly()}
}
+47 -23
View File
@@ -11,25 +11,22 @@ import (
// [Evaluator.ExternalProduct]).
type Evaluator struct {
rlwe.Evaluator
pool *rlwe.BufferPool
}
// NewEvaluator creates a new [Evaluator] type supporting RGSW operations in addition
// to [rlwe.Evaluator] operations.
func NewEvaluator(params rlwe.ParameterProvider, evk rlwe.EvaluationKeySet) *Evaluator {
return &Evaluator{*rlwe.NewEvaluator(params, evk)}
}
// ShallowCopy creates a shallow copy of this [Evaluator] in which all the read-only data-structures are
// shared with the receiver and the temporary buffers are reallocated. The receiver and the returned
// Evaluators can be used concurrently.
func (eval Evaluator) ShallowCopy() *Evaluator {
return &Evaluator{*eval.Evaluator.ShallowCopy()}
return &Evaluator{
*rlwe.NewEvaluator(params, evk),
rlwe.NewPool(params.GetRLWEParameters().RingQP()),
}
}
// WithKey creates a shallow copy of the receiver [Evaluator] for which the evaluation key is set to the provided [rlwe.EvaluationKeySet]
// and where the temporary buffers are shared. The receiver and the returned Evaluators cannot be used concurrently.
func (eval Evaluator) WithKey(evk rlwe.EvaluationKeySet) *Evaluator {
return &Evaluator{*eval.Evaluator.WithKey(evk)}
return &Evaluator{*eval.Evaluator.WithKey(evk), eval.pool}
}
// ExternalProduct computes RLWE x RGSW -> RLWE
@@ -42,12 +39,17 @@ func (eval Evaluator) WithKey(evk rlwe.EvaluationKeySet) *Evaluator {
func (eval Evaluator) ExternalProduct(op0 *rlwe.Ciphertext, op1 *Ciphertext, opOut *rlwe.Ciphertext) {
levelQ, levelP := op1.LevelQ(), op1.LevelP()
poolQP := eval.pool.AtLevel(levelQ, levelP)
var c0QP, c1QP ringqp.Poly
buffQP1 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP1)
buffQP2 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP2)
if op0 == opOut {
c0QP, c1QP = eval.BuffQP[1], eval.BuffQP[2]
c0QP, c1QP = *buffQP1, *buffQP2
} else {
c0QP, c1QP = ringqp.Poly{Q: opOut.Value[0], P: eval.BuffQP[1].P}, ringqp.Poly{Q: opOut.Value[1], P: eval.BuffQP[2].P}
c0QP, c1QP = ringqp.Poly{Q: opOut.Value[0], P: (*buffQP1).P}, ringqp.Poly{Q: opOut.Value[1], P: (*buffQP2).P}
}
if levelP < 1 {
@@ -72,7 +74,7 @@ func (eval Evaluator) ExternalProduct(op0 *rlwe.Ciphertext, op1 *Ciphertext, opO
}
}
} else {
eval.externalProductInPlaceMultipleP(levelQ, levelP, op0, op1, eval.BuffQP[1].Q, eval.BuffQP[1].P, eval.BuffQP[2].Q, eval.BuffQP[2].P)
eval.externalProductInPlaceMultipleP(levelQ, levelP, op0, op1, (*buffQP1).Q, (*buffQP1).P, (*buffQP2).Q, (*buffQP2).P)
eval.BasisExtender.ModDownQPtoQNTT(levelQ, levelP, c0QP.Q, c0QP.P, opOut.Value[0])
eval.BasisExtender.ModDownQPtoQNTT(levelQ, levelP, c1QP.Q, c1QP.P, opOut.Value[1])
@@ -86,23 +88,32 @@ func (eval Evaluator) externalProduct32Bit(ct0 *rlwe.Ciphertext, rgsw *Ciphertex
// opOut = [<ct, rgsw[0]>, <ct, rgsw[1]>] = [ct[0] * rgsw[0][0] + ct[1] * rgsw[0][1], ct[0] * rgsw[1][0] + ct[1] * rgsw[1][1]]
params := eval.GetRLWEParameters()
ringQ := params.RingQ().AtLevel(0)
poolQ := eval.pool.AtLevel(0)
subRing := ringQ.SubRings[0]
pw2 := rgsw.Value[0].BaseTwoDecomposition
mask := uint64(((1 << pw2) - 1))
cw := eval.BuffQP[0].Q.Coeffs[0]
cwNTT := eval.BuffBitDecomp
buffQ1 := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(buffQ1)
cw := (*buffQ1).Coeffs[0]
buffBitDecomp := eval.pool.GetBuffUintArray()
defer eval.pool.RecycleBuffUintArray(buffBitDecomp)
cwNTT := *buffBitDecomp
acc0 := c0.Coeffs[0]
acc1 := c1.Coeffs[0]
buffQ := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(buffQ)
// (a, b) + (c0 * rgsw[0][0], c0 * rgsw[0][1])
// (a, b) + (c1 * rgsw[1][0], c1 * rgsw[1][1])
for i, el := range rgsw.Value {
ringQ.INTT(ct0.Value[i], eval.BuffInvNTT)
ringQ.INTT(ct0.Value[i], *buffQ)
for j := range el.Value[0] {
// TODO: center values if mask = 0
ring.MaskVec(eval.BuffInvNTT.Coeffs[0], j*pw2, mask, cw)
ring.MaskVec(buffQ.Coeffs[0], j*pw2, mask, cw)
if j == 0 && i == 0 {
subRing.NTTLazy(cw, cwNTT)
subRing.MulCoeffsLazy(el.Value[0][j][0].Q.Coeffs[0], cwNTT, acc0)
@@ -126,6 +137,7 @@ func (eval Evaluator) externalProductInPlaceSinglePAndBitDecomp(ct0 *rlwe.Cipher
params := eval.GetRLWEParameters()
ringQP := params.RingQP().AtLevel(levelQ, levelP)
poolQP := eval.pool.AtLevel(levelQ, levelP)
ringQ := ringQP.RingQ
ringP := ringQP.RingP
@@ -139,15 +151,21 @@ func (eval Evaluator) externalProductInPlaceSinglePAndBitDecomp(ct0 *rlwe.Cipher
BaseRNSDecompositionVectorSize := rgsw.Value[0].BaseRNSDecompositionVectorSize()
BaseTwoDecompositionVectorSize := rgsw.Value[0].BaseTwoDecompositionVectorSize()
buffQ := poolQP.GetBuffPoly()
defer poolQP.RecycleBuffPoly(buffQ)
buffQ1 := poolQP.BufferPool.AtLevel(0).GetBuffPoly()
defer poolQP.BufferPool.AtLevel(0).RecycleBuffPoly(buffQ1)
buffBitDecomp := eval.pool.GetBuffUintArray()
defer eval.pool.RecycleBuffUintArray(buffBitDecomp)
// (a, b) + (c0 * rgsw[k][0], c0 * rgsw[k][1])
for k, el := range rgsw.Value {
ringQ.INTT(ct0.Value[k], eval.BuffInvNTT)
cw := eval.BuffQP[0].Q.Coeffs[0]
cwNTT := eval.BuffBitDecomp
ringQ.INTT(ct0.Value[k], *buffQ)
cw := (*buffQ1).Coeffs[0]
cwNTT := *buffBitDecomp
for i := 0; i < BaseRNSDecompositionVectorSize; i++ {
for j := 0; j < BaseTwoDecompositionVectorSize[i]; j++ {
// TODO: center values if mask == 0
ring.MaskVec(eval.BuffInvNTT.Coeffs[i], j*pw2, mask, cw)
ring.MaskVec(buffQ.Coeffs[i], j*pw2, mask, cw)
if k == 0 && i == 0 && j == 0 {
for u, s := range ringQ.SubRings[:levelQ+1] {
@@ -190,10 +208,13 @@ func (eval Evaluator) externalProductInPlaceMultipleP(levelQ, levelP int, ct0 *r
params := eval.GetRLWEParameters()
ringQP := params.RingQP().AtLevel(levelQ, levelP)
poolQP := eval.pool.AtLevel(levelQ, levelP)
ringQ := ringQP.RingQ
ringP := ringQP.RingP
c2QP := eval.BuffQP[0]
buffQP1 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP1)
c2QP := (*buffQP1)
c0QP := ringqp.Poly{Q: c0OutQ, P: c0OutP}
c1QP := ringqp.Poly{Q: c1OutQ, P: c1OutP}
@@ -205,14 +226,17 @@ func (eval Evaluator) externalProductInPlaceMultipleP(levelQ, levelP int, ct0 *r
var c2NTT, c2InvNTT ring.Poly
buffQ := poolQP.GetBuffPoly()
defer poolQP.RecycleBuffPoly(buffQ)
for k, el := range rgsw.Value {
if ct0.IsNTT {
c2NTT = ct0.Value[k]
c2InvNTT = eval.BuffInvNTT
c2InvNTT = *buffQ
ringQ.INTT(c2NTT, c2InvNTT)
} else {
c2NTT = eval.BuffInvNTT
c2NTT = *buffQ
c2InvNTT = ct0.Value[k]
ringQ.NTT(c2InvNTT, c2NTT)
}
+9 -19
View File
@@ -11,7 +11,7 @@ import (
type Decryptor struct {
params Parameters
ringQ *ring.Ring
buff ring.Poly
poolQ *ring.BufferPool
sk *SecretKey
}
@@ -27,8 +27,8 @@ func NewDecryptor(params ParameterProvider, sk *SecretKey) *Decryptor {
return &Decryptor{
params: *p,
ringQ: p.RingQ(),
buff: p.RingQ().NewPoly(),
sk: sk,
poolQ: ring.NewPool(p.ringQ),
}
}
@@ -69,8 +69,11 @@ func (d Decryptor) Decrypt(ct *Ciphertext, pt *Plaintext) {
ringQ.MulCoeffsMontgomery(pt.Value, d.sk.Value.Q, pt.Value)
if !ct.IsNTT {
ringQ.NTTLazy(ct.Value[i-1], d.buff)
ringQ.Add(pt.Value, d.buff, pt.Value)
poolQ := d.poolQ.AtLevel(level)
buff := poolQ.GetBuffPoly()
ringQ.NTTLazy(ct.Value[i-1], *buff)
ringQ.Add(pt.Value, *buff, pt.Value)
poolQ.RecycleBuffPoly(buff)
} else {
ringQ.Add(pt.Value, ct.Value[i-1], pt.Value)
}
@@ -89,26 +92,13 @@ func (d Decryptor) Decrypt(ct *Ciphertext, pt *Plaintext) {
}
}
// ShallowCopy creates a shallow copy of [Decryptor] in which all the read-only data-structures are
// shared with the receiver and the temporary buffers are reallocated. The receiver and the returned
// [Decryptor] can be used concurrently.
func (d Decryptor) ShallowCopy() *Decryptor {
return &Decryptor{
params: d.params,
ringQ: d.ringQ,
buff: d.ringQ.NewPoly(),
sk: d.sk,
}
}
// WithKey creates a shallow copy of [Decryptor] with a new decryption key, in which all the
// read-only data-structures are shared with the receiver and the temporary buffers
// are reallocated. The receiver and the returned [Decryptor] can be used concurrently.
// data-structures are shared with the receiver.
// The receiver and the returned [Decryptor] can be used concurrently.
func (d Decryptor) WithKey(sk *SecretKey) *Decryptor {
return &Decryptor{
params: d.params,
ringQ: d.ringQ,
buff: d.ringQ.NewPoly(),
sk: sk,
}
}
+53 -55
View File
@@ -46,7 +46,6 @@ func NewEncryptor(params ParameterProvider, key EncryptionKey) *Encryptor {
type Encryptor struct {
params Parameters
*encryptorBuffers
encKey EncryptionKey
prng sampling.PRNG
@@ -54,6 +53,7 @@ type Encryptor struct {
xsSampler ring.Sampler
basisextender *ring.BasisExtender
uniformSampler ringqp.UniformSampler
pool *BufferPool
}
// GetRLWEParameters returns the underlying [Parameters].
@@ -65,8 +65,7 @@ func newEncryptor(params Parameters) *Encryptor {
prng, err := sampling.NewPRNG()
if err != nil {
// Sanity check, this error should not happen.
panic(err)
panic(fmt.Errorf("newEncryptor: %w", err))
}
var bc *ring.BasisExtender
@@ -89,19 +88,20 @@ func newEncryptor(params Parameters) *Encryptor {
}
return &Encryptor{
params: params,
prng: prng,
xeSampler: xeSampler,
xsSampler: xsSampler,
encryptorBuffers: newEncryptorBuffers(params),
uniformSampler: ringqp.NewUniformSampler(prng, *params.RingQP()),
basisextender: bc,
params: params,
prng: prng,
xeSampler: xeSampler,
xsSampler: xsSampler,
uniformSampler: ringqp.NewUniformSampler(prng, *params.RingQP()),
basisextender: bc,
pool: NewPool(params.RingQP()),
}
}
// NewTestEncryptorWithPRNG creates a new [Encryptor] that uses the provided prng for randomness.
// newTestEncryptorWithKeyedPRNG creates a new [Encryptor] that uses the provided prng for randomness.
// CAUTION: THIS FUNCTION SHOULD BE USED FOR TESTING PURPOSES ONLY.
func NewTestEncryptorWithPRNG(params ParameterProvider, key EncryptionKey, prng sampling.PRNG) *Encryptor {
// WARNING: The resulting encryptor is not meant to be used concurrently.
func newTestEncryptorWithKeyedPRNG(params ParameterProvider, key EncryptionKey, prng *sampling.KeyedPRNG) *Encryptor {
p := *params.GetRLWEParameters()
enc := NewEncryptor(params, key)
@@ -122,20 +122,6 @@ func NewTestEncryptorWithPRNG(params ParameterProvider, key EncryptionKey, prng
return enc
}
type encryptorBuffers struct {
buffQP [3]ringqp.Poly
}
func newEncryptorBuffers(params Parameters) *encryptorBuffers {
return &encryptorBuffers{
buffQP: [3]ringqp.Poly{
params.RingQP().NewPoly(),
params.RingQP().NewPoly(),
params.RingQP().NewPoly(),
},
}
}
// Encrypt encrypts the input plaintext using the stored encryption key and writes the result on ct.
// The method currently accepts only *[Ciphertext] as ct.
// If a [Plaintext] is given, then the output [Ciphertext] [MetaData] will match the [Plaintext] [MetaData].
@@ -229,9 +215,11 @@ func (enc Encryptor) encryptZeroPk(pk *PublicKey, ct interface{}) (err error) {
levelQ = ct.Level()
levelP = 0
buffQP := enc.pool.GetBuffPolyQP()
defer enc.pool.RecycleBuffPolyQP(buffQP)
ct0QP = ringqp.Poly{Q: ct.Value[0], P: enc.buffQP[0].Q}
ct1QP = ringqp.Poly{Q: ct.Value[1], P: enc.buffQP[0].P}
ct0QP = ringqp.Poly{Q: ct.Value[0], P: buffQP.Q}
ct1QP = ringqp.Poly{Q: ct.Value[1], P: buffQP.P}
case Element[ringqp.Poly]:
levelQ = ct.LevelQ()
@@ -245,7 +233,10 @@ func (enc Encryptor) encryptZeroPk(pk *PublicKey, ct interface{}) (err error) {
ringQP := enc.params.RingQP().AtLevel(levelQ, levelP)
u := enc.buffQP[1]
buffU := enc.pool.GetBuffPolyQP()
defer enc.pool.RecycleBuffPolyQP(buffU)
u := *buffU
// We sample a RLWE instance (encryption of zero) over the extended ring (ciphertext ring + special prime)
enc.xsSampler.AtLevel(levelQ).Read(u.Q)
@@ -313,23 +304,24 @@ func (enc Encryptor) encryptZeroPkNoP(pk *PublicKey, ct Element[ring.Poly]) (err
ringQ := enc.params.RingQ().AtLevel(levelQ)
buffQ0 := enc.buffQP[0].Q
buffQ0 := enc.pool.GetBuffPoly()
defer enc.pool.RecycleBuffPoly(buffQ0)
enc.xsSampler.AtLevel(levelQ).Read(buffQ0)
ringQ.NTT(buffQ0, buffQ0)
enc.xsSampler.AtLevel(levelQ).Read(*buffQ0)
ringQ.NTT(*buffQ0, *buffQ0)
c0, c1 := ct.Value[0], ct.Value[1]
// ct0 = NTT(u*pk0)
ringQ.MulCoeffsMontgomery(buffQ0, pk.Value[0].Q, c0)
ringQ.MulCoeffsMontgomery(*buffQ0, pk.Value[0].Q, c0)
// ct1 = NTT(u*pk1)
ringQ.MulCoeffsMontgomery(buffQ0, pk.Value[1].Q, c1)
ringQ.MulCoeffsMontgomery(*buffQ0, pk.Value[1].Q, c1)
// c0
if ct.IsNTT {
enc.xeSampler.AtLevel(levelQ).Read(buffQ0)
ringQ.NTT(buffQ0, buffQ0)
ringQ.Add(c0, buffQ0, c0)
enc.xeSampler.AtLevel(levelQ).Read(*buffQ0)
ringQ.NTT(*buffQ0, *buffQ0)
ringQ.Add(c0, *buffQ0, c0)
} else {
ringQ.INTT(c0, c0)
enc.xeSampler.AtLevel(levelQ).ReadAndAdd(c0)
@@ -337,9 +329,9 @@ func (enc Encryptor) encryptZeroPkNoP(pk *PublicKey, ct Element[ring.Poly]) (err
// c1
if ct.IsNTT {
enc.xeSampler.AtLevel(levelQ).Read(buffQ0)
ringQ.NTT(buffQ0, buffQ0)
ringQ.Add(c1, buffQ0, c1)
enc.xeSampler.AtLevel(levelQ).Read(*buffQ0)
ringQ.NTT(*buffQ0, *buffQ0)
ringQ.Add(c1, *buffQ0, c1)
} else {
ringQ.INTT(c1, c1)
@@ -361,7 +353,9 @@ func (enc Encryptor) encryptZeroSk(sk *SecretKey, ct interface{}) (err error) {
if ct.Degree() == 1 {
c1 = ct.Value[1]
} else {
c1 = enc.buffQP[1].Q
buffPolyQ := enc.pool.BufferPool.AtLevel(ct.Level()).GetBuffPoly()
defer enc.pool.RecycleBuffPoly(buffPolyQ)
c1 = *buffPolyQ
}
enc.uniformSampler.AtLevel(ct.Level(), -1).Read(ringqp.Poly{Q: c1})
@@ -379,7 +373,9 @@ func (enc Encryptor) encryptZeroSk(sk *SecretKey, ct interface{}) (err error) {
if ct.Degree() == 1 {
c1 = ct.Value[1]
} else {
c1 = enc.buffQP[1]
buffPolyQP := enc.pool.AtLevel(ct.LevelQ(), ct.LevelP()).GetBuffPolyQP()
defer enc.pool.RecycleBuffPolyQP(buffPolyQP)
c1 = *buffPolyQP
}
// ct = (e, a)
@@ -407,10 +403,11 @@ func (enc Encryptor) encryptZeroSkFromC1(sk *SecretKey, ct Element[ring.Poly], c
ringQ.Neg(c0, c0)
if ct.IsNTT {
e := enc.buffQP[0].Q
enc.xeSampler.AtLevel(levelQ).Read(e)
ringQ.NTT(e, e)
ringQ.Add(c0, e, c0)
e := enc.pool.GetBuffPoly()
defer enc.pool.RecycleBuffPoly(e)
enc.xeSampler.AtLevel(levelQ).Read(*e)
ringQ.NTT(*e, *e)
ringQ.Add(c0, *e, c0)
} else {
ringQ.INTT(c0, c0)
if ct.Degree() == 1 {
@@ -459,18 +456,14 @@ func (enc Encryptor) encryptZeroSkFromC1QP(sk *SecretKey, ct Element[ringqp.Poly
return
}
// WithPRNG returns this encryptor with prng as its source of randomness for the uniform
// withKeyedUniformSampling returns this encryptor with a keyed prng as its source of randomness for the uniform
// element c1.
// The returned encryptor isn't safe to use concurrently with the original encryptor.
func (enc Encryptor) WithPRNG(prng sampling.PRNG) *Encryptor {
// The returned encryptor is not thread safe (sampling will not be deterministic).
func (enc Encryptor) withKeyedUniformSampling(prng *sampling.KeyedPRNG) *Encryptor {
enc.uniformSampler = ringqp.NewUniformSampler(prng, *enc.params.RingQP())
return &enc
}
func (enc Encryptor) ShallowCopy() *Encryptor {
return NewEncryptor(enc.params, enc.encKey)
}
func (enc Encryptor) WithKey(key EncryptionKey) *Encryptor {
switch key := key.(type) {
case *SecretKey:
@@ -512,17 +505,22 @@ func (enc Encryptor) checkSk(sk *SecretKey) (err error) {
func (enc Encryptor) addPtToCt(level int, pt *Plaintext, ct *Ciphertext) {
ringQ := enc.params.RingQ().AtLevel(level)
poolQ := enc.pool.AtLevel(level)
var buff ring.Poly
if pt.IsNTT {
if ct.IsNTT {
buff = pt.Value
} else {
buff = enc.buffQP[0].Q
buffQ := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(buffQ)
buff = *buffQ
ringQ.NTT(pt.Value, buff)
}
} else {
if ct.IsNTT {
buff = enc.buffQP[0].Q
buffQ := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(buffQ)
buff = *buffQ
ringQ.INTT(pt.Value, buff)
} else {
buff = pt.Value
+4 -73
View File
@@ -4,7 +4,6 @@ import (
"fmt"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/ring/ringqp"
"github.com/tuneinsight/lattigo/v6/utils"
)
@@ -13,51 +12,12 @@ import (
type Evaluator struct {
params Parameters
EvaluationKeySet
*EvaluatorBuffers
automorphismIndex map[uint64][]uint64
BasisExtender *ring.BasisExtender
Decomposer *ring.Decomposer
}
type EvaluatorBuffers struct {
BuffCt *Ciphertext
// BuffQP[0-1]: Key-Switch output Key-Switch on the fly decomp(c2)
// BuffQP[2-5]: Available
BuffQP [6]ringqp.Poly
BuffInvNTT ring.Poly
BuffDecompQP []ringqp.Poly // Memory Buff for the basis extension in hoisting
BuffBitDecomp []uint64
}
func NewEvaluatorBuffers(params Parameters) *EvaluatorBuffers {
buff := new(EvaluatorBuffers)
BaseRNSDecompositionVectorSize := params.BaseRNSDecompositionVectorSize(params.MaxLevelQ(), 0)
ringQP := params.RingQP()
buff.BuffCt = NewCiphertext(params, 2, params.MaxLevel())
buff.BuffQP = [6]ringqp.Poly{
ringQP.NewPoly(),
ringQP.NewPoly(),
ringQP.NewPoly(),
ringQP.NewPoly(),
ringQP.NewPoly(),
ringQP.NewPoly(),
}
buff.BuffInvNTT = params.RingQ().NewPoly()
buff.BuffDecompQP = make([]ringqp.Poly, BaseRNSDecompositionVectorSize)
for i := 0; i < BaseRNSDecompositionVectorSize; i++ {
buff.BuffDecompQP[i] = ringQP.NewPoly()
}
buff.BuffBitDecomp = make([]uint64, params.RingQ().N())
return buff
pool *BufferPool
}
// NewEvaluator creates a new [Evaluator].
@@ -65,7 +25,8 @@ func NewEvaluator(params ParameterProvider, evk EvaluationKeySet) (eval *Evaluat
eval = new(Evaluator)
p := params.GetRLWEParameters()
eval.params = *p
eval.EvaluatorBuffers = NewEvaluatorBuffers(eval.params)
eval.pool = NewPool(p.RingQP())
if p.RingP() != nil {
eval.BasisExtender = ring.NewBasisExtender(p.RingQ(), p.RingP())
@@ -234,20 +195,6 @@ func (eval Evaluator) InitOutputUnaryOp(op0, opOut *Element[ring.Poly]) (degree,
return utils.Max(op0.Degree(), opOut.Degree()), utils.Min(op0.Level(), opOut.Level()), nil
}
// ShallowCopy creates a shallow copy of this [Evaluator] in which all the read-only data-structures are
// shared with the receiver and the temporary buffers are reallocated. The receiver and the returned
// evaluators can be used concurrently.
func (eval Evaluator) ShallowCopy() *Evaluator {
return &Evaluator{
params: eval.params,
Decomposer: eval.Decomposer,
BasisExtender: eval.BasisExtender.ShallowCopy(),
EvaluatorBuffers: NewEvaluatorBuffers(eval.params),
EvaluationKeySet: eval.EvaluationKeySet,
automorphismIndex: eval.automorphismIndex,
}
}
// WithKey creates a shallow copy of the receiver [Evaluator] for which the new [EvaluationKey] is evaluationKey
// and where the temporary buffers are shared. The receiver and the returned evaluators cannot be used concurrently.
func (eval Evaluator) WithKey(evk EvaluationKeySet) *Evaluator {
@@ -271,11 +218,11 @@ func (eval Evaluator) WithKey(evk EvaluationKeySet) *Evaluator {
return &Evaluator{
params: eval.params,
EvaluatorBuffers: eval.EvaluatorBuffers,
Decomposer: eval.Decomposer,
BasisExtender: eval.BasisExtender,
EvaluationKeySet: evk,
automorphismIndex: AutomorphismIndex,
pool: eval.pool,
}
}
@@ -283,22 +230,6 @@ func (eval Evaluator) AutomorphismIndex(galEl uint64) []uint64 {
return eval.automorphismIndex[galEl]
}
func (eval Evaluator) GetEvaluatorBuffer() *EvaluatorBuffers {
return eval.EvaluatorBuffers
}
func (eval Evaluator) GetBuffQP() [6]ringqp.Poly {
return eval.BuffQP
}
func (eval Evaluator) GetBuffCt() *Ciphertext {
return eval.BuffCt
}
func (eval Evaluator) GetBuffDecompQP() []ringqp.Poly {
return eval.BuffDecompQP
}
func (eval Evaluator) ModDownQPtoQNTT(levelQ, levelP int, p1Q, p1P, p2Q ring.Poly) {
eval.BasisExtender.ModDownQPtoQNTT(levelQ, levelP, p1Q, p1P, p2Q)
}
+12 -7
View File
@@ -3,7 +3,6 @@ package rlwe
import (
"fmt"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/ring/ringqp"
"github.com/tuneinsight/lattigo/v6/utils"
)
@@ -35,7 +34,8 @@ func (eval Evaluator) Automorphism(ctIn *Ciphertext, galEl uint64, opOut *Cipher
ringQ := eval.params.RingQ().AtLevel(level)
ctTmp := &Ciphertext{Element: Element[ring.Poly]{Value: []ring.Poly{eval.BuffQP[0].Q, eval.BuffQP[1].Q}}}
ctTmp := eval.pool.GetBuffCt(1, ringQ.Level())
defer eval.pool.RecycleBuffCt(ctTmp)
ctTmp.MetaData = ctIn.MetaData
eval.GadgetProduct(level, ctIn.Value[1], &evk.GadgetCiphertext, ctTmp)
@@ -81,8 +81,8 @@ func (eval Evaluator) AutomorphismHoisted(level int, ctIn *Ciphertext, c1DecompQ
ringQ := eval.params.RingQ().AtLevel(level)
ctTmp := &Ciphertext{}
ctTmp.Value = []ring.Poly{eval.BuffQP[0].Q, eval.BuffQP[1].Q} // GadgetProductHoisted uses the same buffers for its ciphertext QP
ctTmp := eval.pool.GetBuffCt(1, ringQ.Level())
defer eval.pool.RecycleBuffCt(ctTmp)
ctTmp.MetaData = ctIn.MetaData
eval.GadgetProductHoisted(level, c1DecompQP, &evk.EvaluationKey.GadgetCiphertext, ctTmp)
@@ -117,16 +117,21 @@ func (eval Evaluator) AutomorphismHoistedLazy(levelQ int, ctIn *Ciphertext, c1De
return fmt.Errorf("ctQP.LevelP()=%d < GaloisKey[%d].LevelP()=%d", ctQP.LevelP(), galEl, levelP)
}
ringQP := eval.params.RingQP().AtLevel(levelQ, levelP)
poolQP := eval.pool.AtLevel(levelQ, levelP)
buffQP1 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP1)
buffQP2 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP2)
ctTmp := &Element[ringqp.Poly]{}
ctTmp.Value = []ringqp.Poly{eval.BuffQP[0], eval.BuffQP[1]}
ctTmp.Value = []ringqp.Poly{*buffQP1, *buffQP2}
ctTmp.MetaData = ctIn.MetaData
if err = eval.GadgetProductHoistedLazy(levelQ, c1DecompQP, &evk.GadgetCiphertext, ctTmp); err != nil {
panic(fmt.Errorf("eval.GadgetProductHoistedLazy: %w", err))
}
ringQP := eval.params.RingQP().AtLevel(levelQ, levelP)
ringQ := ringQP.RingQ
ringP := ringQP.RingP
+7 -12
View File
@@ -3,7 +3,6 @@ package rlwe
import (
"fmt"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/utils"
)
@@ -72,13 +71,8 @@ func (eval Evaluator) ApplyEvaluationKey(ctIn *Ciphertext, evk *EvaluationKey, o
level := utils.Min(ctIn.Level(), opOut.Level())
ctTmp, err := NewCiphertextAtLevelFromPoly(level, eval.BuffCt.Value)
// Sanity check, this error should not happen unless the
// evaluator's buffer have been improperly tempered with.
if err != nil {
panic(err)
}
ctTmp := eval.pool.GetBuffCt(ctIn.Degree(), level)
defer eval.pool.RecycleBuffCt(ctTmp)
ctTmp.MetaData = ctIn.MetaData
@@ -103,9 +97,10 @@ func (eval Evaluator) ApplyEvaluationKey(ctIn *Ciphertext, evk *EvaluationKey, o
}
func (eval Evaluator) applyEvaluationKey(level int, ctIn *Ciphertext, evk *EvaluationKey, opOut *Ciphertext) {
ctTmp := &Ciphertext{}
ctTmp.Value = []ring.Poly{eval.BuffQP[0].Q, eval.BuffQP[1].Q}
ctTmp := eval.pool.GetBuffCt(1, level)
defer eval.pool.RecycleBuffCt(ctTmp)
ctTmp.MetaData = ctIn.MetaData
eval.GadgetProduct(level, ctIn.Value[1], &evk.GadgetCiphertext, ctTmp)
eval.params.RingQ().AtLevel(level).Add(ctIn.Value[0], ctTmp.Value[0], opOut.Value[0])
opOut.Value[1].CopyLvl(level, ctTmp.Value[1])
@@ -137,8 +132,8 @@ func (eval Evaluator) Relinearize(ctIn *Ciphertext, opOut *Ciphertext) (err erro
ringQ := eval.params.RingQ().AtLevel(level)
ctTmp := &Ciphertext{}
ctTmp.Value = []ring.Poly{eval.BuffQP[0].Q, eval.BuffQP[1].Q}
ctTmp := eval.pool.GetBuffCt(1, ringQ.Level())
defer eval.pool.RecycleBuffCt(ctTmp)
ctTmp.MetaData = ctIn.MetaData
eval.GadgetProduct(level, ctIn.Value[2], &rlk.GadgetCiphertext, ctTmp)
+44 -12
View File
@@ -18,8 +18,14 @@ func (eval Evaluator) GadgetProduct(levelQ int, cx ring.Poly, gadgetCt *GadgetCi
levelQ = utils.Min(levelQ, gadgetCt.LevelQ())
levelP := gadgetCt.LevelP()
poolQP := eval.pool.AtLevel(levelQ, levelP)
buffQP1 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP1)
buffQP2 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP2)
ctTmp := &Element[ringqp.Poly]{}
ctTmp.Value = []ringqp.Poly{{Q: ct.Value[0], P: eval.BuffQP[0].P}, {Q: ct.Value[1], P: eval.BuffQP[1].P}}
ctTmp.Value = []ringqp.Poly{{Q: ct.Value[0], P: (*buffQP1).P}, {Q: ct.Value[1], P: (*buffQP2).P}}
ctTmp.MetaData = ct.MetaData
if err := eval.GadgetProductLazy(levelQ, cx, gadgetCt, ctTmp); err != nil {
@@ -125,19 +131,26 @@ func (eval Evaluator) gadgetProductMultiplePLazy(levelQ int, cx ring.Poly, gadge
levelP := gadgetCt.LevelP()
ringQP := eval.params.RingQP().AtLevel(levelQ, levelP)
poolQP := eval.pool.AtLevel(levelQ, levelP)
ringQ := ringQP.RingQ
ringP := ringQP.RingP
c2QP := eval.BuffDecompQP[0]
buff := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buff)
c2QP := *buff
buffQ := poolQP.GetBuffPoly()
defer poolQP.RecycleBuffPoly(buffQ)
var cxNTT, cxInvNTT ring.Poly
if ctQP.IsNTT {
cxNTT = cx
cxInvNTT = eval.BuffInvNTT
cxInvNTT = *buffQ
ringQ.INTT(cxNTT, cxInvNTT)
} else {
cxNTT = eval.BuffInvNTT
cxNTT = *buffQ
cxInvNTT = cx
ringQ.NTT(cxInvNTT, cxNTT)
}
@@ -192,13 +205,16 @@ func (eval Evaluator) gadgetProductSinglePAndBitDecompLazy(levelQ int, cx ring.P
levelP := gadgetCt.LevelP()
ringQP := eval.params.RingQP().AtLevel(levelQ, levelP)
poolQP := eval.pool.AtLevel(levelQ, levelP)
ringQ := ringQP.RingQ
ringP := ringQP.RingP
var cxInvNTT ring.Poly
if ctQP.IsNTT {
cxInvNTT = eval.BuffInvNTT
buffQ := poolQP.GetBuffPoly()
defer poolQP.RecycleBuffPoly(buffQ)
cxInvNTT = *buffQ
ringQ.INTT(cx, cxInvNTT)
} else {
cxInvNTT = cx
@@ -211,15 +227,21 @@ func (eval Evaluator) gadgetProductSinglePAndBitDecompLazy(levelQ int, cx ring.P
mask := uint64(((1 << pw2) - 1))
cw := eval.BuffDecompQP[0].Q.Coeffs[0]
cwNTT := eval.BuffBitDecomp
buff := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buff)
cw := buff.Q.Coeffs[0]
buffBitDecomp := eval.pool.GetBuffUintArray()
defer eval.pool.RecycleBuffUintArray(buffBitDecomp)
cwNTT := *buffBitDecomp
QiOverF := eval.params.QiOverflowMargin(levelQ) >> 1
PiOverF := eval.params.PiOverflowMargin(levelP) >> 1
el := gadgetCt.Value
c2QP := eval.BuffDecompQP[0]
c2QP := buff
// Re-encryption with CRT decomposition for the Qi
var reduce int
@@ -325,10 +347,16 @@ func (eval Evaluator) gadgetProductSinglePAndBitDecompLazy(levelQ int, cx ring.P
// Result NTT domain is returned according to the NTT flag of ct.
func (eval Evaluator) GadgetProductHoisted(levelQ int, BuffQPDecompQP []ringqp.Poly, gadgetCt *GadgetCiphertext, ct *Ciphertext) {
pool := eval.pool.PoolP.AtLevel(gadgetCt.LevelP())
buffP1 := pool.GetBuffPoly()
defer pool.RecycleBuffPoly(buffP1)
buffP2 := pool.GetBuffPoly()
defer pool.RecycleBuffPoly(buffP2)
ctQP := &Element[ringqp.Poly]{}
ctQP.Value = []ringqp.Poly{
{Q: ct.Value[0], P: eval.BuffQP[0].P},
{Q: ct.Value[1], P: eval.BuffQP[1].P},
{Q: ct.Value[0], P: *buffP1},
{Q: ct.Value[1], P: *buffP2},
}
ctQP.MetaData = ct.MetaData
@@ -431,15 +459,19 @@ func (eval Evaluator) gadgetProductMultiplePLazyHoisted(levelQ int, BuffQPDecomp
func (eval Evaluator) DecomposeNTT(levelQ, levelP, nbPi int, c2 ring.Poly, c2IsNTT bool, decompQP []ringqp.Poly) {
ringQ := eval.params.RingQ().AtLevel(levelQ)
poolQ := eval.pool.AtLevel(levelQ)
var polyNTT, polyInvNTT ring.Poly
buffQ := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(buffQ)
if c2IsNTT {
polyNTT = c2
polyInvNTT = eval.BuffInvNTT
polyInvNTT = *buffQ
ringQ.INTT(polyNTT, polyInvNTT)
} else {
polyNTT = eval.BuffInvNTT
polyNTT = *buffQ
polyInvNTT = c2
ringQ.NTT(polyInvNTT, polyNTT)
}
+26 -28
View File
@@ -75,13 +75,8 @@ func (eval Evaluator) Trace(ctIn *Ciphertext, logN int, opOut *Ciphertext) (err
opOut.IsNTT = true
}
buff, err := NewCiphertextAtLevelFromPoly(level, []ring.Poly{eval.BuffQP[3].Q, eval.BuffQP[4].Q})
// Sanity check, this error should not happen unless the
// evaluator's buffer has been improperly tempered with.
if err != nil {
panic(err)
}
buff := eval.pool.GetBuffCt(1, level)
defer eval.pool.RecycleBuffCt(buff)
buff.IsNTT = true
@@ -160,19 +155,15 @@ func (eval Evaluator) PartialTracesSum(ctIn *Ciphertext, offset, n int, opOut *C
levelP := params.PCount() - 1
ringQP := params.RingQP().AtLevel(ctIn.Level(), levelP)
poolQP := eval.pool.AtLevel(ctIn.Level(), levelP)
ringQ := ringQP.RingQ
opOut.Resize(opOut.Degree(), levelQ)
*opOut.MetaData = *ctIn.MetaData
ctInNTT, err := NewCiphertextAtLevelFromPoly(levelQ, eval.BuffCt.Value[:2])
// Sanity check, this error should not happen unless the
// evaluator's buffer thave been improperly tempered with.
if err != nil {
panic(err)
}
ctInNTT := eval.pool.GetBuffCt(1, levelQ)
defer eval.pool.RecycleBuffCt(ctInNTT)
ctInNTT.MetaData = &MetaData{}
ctInNTT.IsNTT = true
@@ -192,14 +183,21 @@ func (eval Evaluator) PartialTracesSum(ctIn *Ciphertext, offset, n int, opOut *C
}
} else {
// BuffQP[0:2] are used by AutomorphismHoistedLazy
buffQP1 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP1)
buffQP2 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP2)
// Accumulator mod QP (i.e. opOut Mod QP)
accQP := &Element[ringqp.Poly]{Value: []ringqp.Poly{eval.BuffQP[2], eval.BuffQP[3]}}
accQP := &Element[ringqp.Poly]{Value: []ringqp.Poly{*buffQP1, *buffQP2}}
accQP.MetaData = ctInNTT.MetaData
// Buffer mod QP (i.e. to store the result of lazy gadget products)
cQP := &Element[ringqp.Poly]{Value: []ringqp.Poly{eval.BuffQP[4], eval.BuffQP[5]}}
buffQP3 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP3)
buffQP4 := poolQP.GetBuffPolyQP()
defer poolQP.RecycleBuffPolyQP(buffQP4)
cQP := &Element[ringqp.Poly]{Value: []ringqp.Poly{*buffQP3, *buffQP4}}
cQP.MetaData = ctInNTT.MetaData
// Buffer mod Q (i.e. to store the result of gadget products)
@@ -213,13 +211,16 @@ func (eval Evaluator) PartialTracesSum(ctIn *Ciphertext, offset, n int, opOut *C
cQ.MetaData = ctInNTT.MetaData
buffDecompQP := poolQP.GetBuffDecompQP(eval.params, levelQ, levelP)
defer eval.pool.RecycleBuffDecompQP(buffDecompQP)
state := false
copy := true
// Binary reading of the input n
for i, j := 0, n; j > 0; i, j = i+1, j>>1 {
// Starts by decomposing the input ciphertext
eval.DecomposeNTT(levelQ, levelP, levelP+1, ctInNTT.Value[1], true, eval.BuffDecompQP)
eval.DecomposeNTT(levelQ, levelP, levelP+1, ctInNTT.Value[1], true, buffDecompQP)
// If the binary reading scans a 1 (j is odd)
if j&1 == 1 {
@@ -234,12 +235,12 @@ func (eval Evaluator) PartialTracesSum(ctIn *Ciphertext, offset, n int, opOut *C
// opOutQP = opOutQP + Rotate(ctInNTT, k)
if copy {
if err = eval.AutomorphismHoistedLazy(levelQ, ctInNTT, eval.BuffDecompQP, rot, accQP); err != nil {
if err = eval.AutomorphismHoistedLazy(levelQ, ctInNTT, buffDecompQP, rot, accQP); err != nil {
return err
}
copy = false
} else {
if err = eval.AutomorphismHoistedLazy(levelQ, ctInNTT, eval.BuffDecompQP, rot, cQP); err != nil {
if err = eval.AutomorphismHoistedLazy(levelQ, ctInNTT, buffDecompQP, rot, cQP); err != nil {
return err
}
ringQP.Add(accQP.Value[0], cQP.Value[0], accQP.Value[0])
@@ -273,7 +274,7 @@ func (eval Evaluator) PartialTracesSum(ctIn *Ciphertext, offset, n int, opOut *C
rot := params.GaloisElement((1 << i) * offset)
// ctInNTT = ctInNTT + Rotate(ctInNTT, 2^i)
if err = eval.AutomorphismHoisted(levelQ, ctInNTT, eval.BuffDecompQP, rot, cQ); err != nil {
if err = eval.AutomorphismHoisted(levelQ, ctInNTT, buffDecompQP, rot, cQ); err != nil {
return err
}
ringQ.Add(ctInNTT.Value[0], cQ.Value[0], ctInNTT.Value[0])
@@ -324,11 +325,6 @@ func (eval Evaluator) InnerFunction(ctIn *Ciphertext, batchSize, n int, f func(a
opOut.Resize(opOut.Degree(), levelQ)
*opOut.MetaData = *ctIn.MetaData
P0 := params.RingQ().NewPoly()
P1 := params.RingQ().NewPoly()
P2 := params.RingQ().NewPoly()
P3 := params.RingQ().NewPoly()
ctInNTT := NewCiphertext(params, 1, levelQ)
*ctInNTT.MetaData = *ctIn.MetaData
@@ -346,7 +342,8 @@ func (eval Evaluator) InnerFunction(ctIn *Ciphertext, batchSize, n int, f func(a
} else {
// Accumulator mod Q
accQ, err := NewCiphertextAtLevelFromPoly(levelQ, []ring.Poly{P0, P1})
accQ := eval.pool.GetBuffCt(1, levelQ)
defer eval.pool.RecycleBuffCt(accQ)
*accQ.MetaData = *ctInNTT.MetaData
// Sanity check, this error should not happen unless the
@@ -356,7 +353,8 @@ func (eval Evaluator) InnerFunction(ctIn *Ciphertext, batchSize, n int, f func(a
}
// Buffer mod Q
cQ, err := NewCiphertextAtLevelFromPoly(levelQ, []ring.Poly{P2, P3})
cQ := eval.pool.GetBuffCt(1, levelQ)
defer eval.pool.RecycleBuffCt(cQ)
*cQ.MetaData = *ctInNTT.MetaData
// Sanity check, this error should not happen unless the
+31 -17
View File
@@ -13,16 +13,14 @@ import (
// as well as a memory buffer for intermediate values.
type KeyGenerator struct {
*Encryptor
bufSkIn ring.Poly
bufSkOut ringqp.Poly
pool *BufferPool
}
// NewKeyGenerator creates a new KeyGenerator, from which the secret and public keys, as well as [EvaluationKey].
func NewKeyGenerator(params ParameterProvider) *KeyGenerator {
return &KeyGenerator{
Encryptor: NewEncryptor(params, nil),
bufSkIn: params.GetRLWEParameters().RingQ().NewPoly(),
bufSkOut: params.GetRLWEParameters().RingQP().NewPoly(),
pool: NewPool(params.GetRLWEParameters().RingQP()),
}
}
@@ -113,10 +111,12 @@ func (kgen KeyGenerator) GenRelinearizationKeyNew(sk *SecretKey, evkParams ...Ev
// GenRelinearizationKey generates an [EvaluationKey] that will be used to relinearize [Ciphertexts] during multiplication.
func (kgen KeyGenerator) GenRelinearizationKey(sk *SecretKey, rlk *RelinearizationKey) {
sk2 := kgen.bufSkIn
sk2 := kgen.pool.AtLevel(rlk.LevelQ()).GetBuffPoly()
defer kgen.pool.RecycleBuffPoly(sk2)
sk2.CopyLvl(rlk.LevelQ(), sk.Value.Q)
kgen.params.RingQ().AtLevel(rlk.LevelQ()).MulCoeffsMontgomery(sk2, sk.Value.Q, sk2)
kgen.genEvaluationKey(sk2, sk.Value, &rlk.EvaluationKey)
kgen.params.RingQ().AtLevel(rlk.LevelQ()).MulCoeffsMontgomery(*sk2, sk.Value.Q, *sk2)
kgen.genEvaluationKey(*sk2, sk.Value, &rlk.EvaluationKey)
}
// GenGaloisKeyNew generates a new [GaloisKey], enabling the automorphism X^{i} -> X^{i * galEl}.
@@ -140,9 +140,12 @@ func (kgen KeyGenerator) GenGaloisKeyNew(galEl uint64, sk *SecretKey, evkParams
func (kgen KeyGenerator) GenGaloisKey(galEl uint64, sk *SecretKey, gk *GaloisKey) {
skIn := sk.Value
skOut := kgen.bufSkOut
ringQP := kgen.params.RingQP().AtLevel(gk.LevelQ(), gk.LevelP())
polyQP := kgen.pool.AtLevel(gk.LevelQ(), gk.LevelP())
skOut := polyQP.GetBuffPolyQP()
defer polyQP.RecycleBuffPolyQP(skOut)
ringQ := ringQP.RingQ
ringP := ringQP.RingP
@@ -167,7 +170,7 @@ func (kgen KeyGenerator) GenGaloisKey(galEl uint64, sk *SecretKey, gk *GaloisKey
ringP.AutomorphismNTTWithIndex(skIn.P, index, skOut.P)
}
kgen.genEvaluationKey(skIn.Q, skOut, &gk.EvaluationKey)
kgen.genEvaluationKey(skIn.Q, *skOut, &gk.EvaluationKey)
gk.GaloisElement = galEl
gk.NthRoot = ringQ.NthRoot()
@@ -213,7 +216,9 @@ func (kgen KeyGenerator) GenEvaluationKeysForRingSwapNew(skStd, skConjugateInvar
kgen.params.RingQ().AtLevel(levelQ).UnfoldConjugateInvariantToStandard(skConjugateInvariant.Value.Q, skCIMappedToStandard.Value.Q)
if kgen.params.PCount() != 0 {
ExtendBasisSmallNormAndCenterNTTMontgomery(kgen.params.RingQ(), kgen.params.RingP(), skCIMappedToStandard.Value.Q, kgen.buffQP[0].Q, skCIMappedToStandard.Value.P)
buffQ := kgen.pool.GetBuffPoly()
defer kgen.pool.RecycleBuffPoly(buffQ)
ExtendBasisSmallNormAndCenterNTTMontgomery(kgen.params.RingQ(), kgen.params.RingP(), skCIMappedToStandard.Value.Q, *buffQ, skCIMappedToStandard.Value.P)
}
levelQ, levelP, BaseTwoDecomposition, compressed := ResolveEvaluationKeyParameters(kgen.params, evkParams)
@@ -258,19 +263,25 @@ func (kgen KeyGenerator) GenEvaluationKey(skInput, skOutput *SecretKey, evk *Eva
ringQ := kgen.params.RingQ()
ringP := kgen.params.RingP()
buffSkOut := kgen.pool.GetBuffPolyQP()
defer kgen.pool.RecycleBuffPolyQP(buffSkOut)
buffSkIn := kgen.pool.GetBuffPoly()
defer kgen.pool.RecycleBuffPoly(buffSkIn)
// Maps the smaller key to the largest with Y = X^{N/n}.
ring.MapSmallDimensionToLargerDimensionNTT(skOutput.Value.Q, kgen.bufSkOut.Q)
ring.MapSmallDimensionToLargerDimensionNTT(skOutput.Value.Q, buffSkOut.Q)
// Extends the modulus P of skOutput to the one of skInput
buffQ := kgen.pool.AtLevel(0).GetBuffPoly()
defer kgen.pool.RecycleBuffPoly(buffQ)
if levelP := evk.LevelP(); levelP != -1 {
ExtendBasisSmallNormAndCenterNTTMontgomery(ringQ, ringP.AtLevel(levelP), kgen.bufSkOut.Q, kgen.buffQP[0].Q, kgen.bufSkOut.P)
ExtendBasisSmallNormAndCenterNTTMontgomery(ringQ, ringP.AtLevel(levelP), buffSkOut.Q, *buffQ, buffSkOut.P)
}
// Maps the smaller key to the largest dimension with Y = X^{N/n}.
ring.MapSmallDimensionToLargerDimensionNTT(skInput.Value.Q, kgen.bufSkIn)
ExtendBasisSmallNormAndCenterNTTMontgomery(ringQ, ringQ.AtLevel(skOutput.Value.Q.Level()), kgen.bufSkIn, kgen.buffQP[0].Q, kgen.bufSkIn)
ring.MapSmallDimensionToLargerDimensionNTT(skInput.Value.Q, *buffSkIn)
ExtendBasisSmallNormAndCenterNTTMontgomery(ringQ, ringQ.AtLevel(skOutput.Value.Q.Level()), *buffSkIn, *buffQ, *buffSkIn)
kgen.genEvaluationKey(kgen.bufSkIn, kgen.bufSkOut, evk)
kgen.genEvaluationKey(*buffSkIn, *buffSkOut, evk)
}
func (kgen KeyGenerator) genEvaluationKey(skIn ring.Poly, skOut ringqp.Poly, evk *EvaluationKey) {
@@ -291,7 +302,7 @@ func (kgen KeyGenerator) genEvaluationKey(skIn ring.Poly, skOut ringqp.Poly, evk
panic(fmt.Errorf("sampling.NewKeyedPRNG: %w", err))
}
enc = enc.WithPRNG(sampler)
enc = enc.withKeyedUniformSampling(sampler)
}
// Samples an encryption of zero for each element of the EvaluationKey.
@@ -309,7 +320,10 @@ func (kgen KeyGenerator) genEvaluationKey(skIn ring.Poly, skOut ringqp.Poly, evk
}
// Adds the plaintext (input-key) to the EvaluationKey.
if err := AddPolyTimesGadgetVectorToGadgetCiphertext(skIn, []GadgetCiphertext{evk.GadgetCiphertext}, *kgen.params.RingQP(), kgen.buffQP[0].Q); err != nil {
buffQ := kgen.pool.AtLevel(evk.GadgetCiphertext.LevelQ()).GetBuffPoly()
defer kgen.pool.RecycleBuffPoly(buffQ)
if err := AddPolyTimesGadgetVectorToGadgetCiphertext(skIn, []GadgetCiphertext{evk.GadgetCiphertext}, *kgen.params.RingQP(), *buffQ); err != nil {
// Sanity check, this error should not happen.
panic(err)
}
-8
View File
@@ -725,9 +725,6 @@ type EvaluationKeySet interface {
// GetRelinearizationKey retrieves the RelinearizationKey.
GetRelinearizationKey() (evk *RelinearizationKey, err error)
// ShallowCopy returns a thread-safe copy of the underlying object.
ShallowCopy() EvaluationKeySet
}
// MemEvaluationKeySet is a basic in-memory implementation of the [EvaluationKeySet] interface.
@@ -799,11 +796,6 @@ func (evk MemEvaluationKeySet) BinarySize() (size int) {
return
}
// ShallowCopy returns a thread-safe copy of the MemEvaluationKey object.
func (evk *MemEvaluationKeySet) ShallowCopy() EvaluationKeySet {
return evk
}
// WriteTo writes the object on an [io.Writer]. It implements the [io.WriterTo]
// interface, and will write exactly object.BinarySize() bytes on w.
//
+129
View File
@@ -0,0 +1,129 @@
package rlwe
import (
"fmt"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/ring/ringqp"
"github.com/tuneinsight/lattigo/v6/utils/structs"
)
// BufferPool represents a pool of different objects (plaintexts, ciphertexts, polys) that can be used to instantiate temporary buffers.
type BufferPool struct {
*ringqp.BufferPool
}
// NewPool returns a new pool given a RingQP, and optionally a pool to draw the backing arrays from.
func NewPool(rqp *ringqp.Ring, pools ...structs.BufferPool[*[]uint64]) *BufferPool {
// If no backing pool is given, we create one here.
switch lenPool := len(pools); lenPool {
case 0:
pools = append(pools, structs.NewSyncPoolUint64(rqp.N()))
case 1:
default:
panic(fmt.Errorf("the method takes at most 1 argument but %d were given", lenPool))
}
ringqpPool := ringqp.NewPool(rqp, pools...)
return &BufferPool{ringqpPool}
}
// AtLevel returns a new pool from which objects from polynomials at the given levels can be drawn.
// The method accepts up to two arguments:
// Zero level: the objects returned are built from polynomials at level 0.
// One level: the objects returned are built from polynomials in RingQ (resp. RingP) at the given level (resp. level 0).
// Two levels: the objects returned are built from polynomials in RingQ (resp. RingP) at levels[0] (resp. levels[1]).
func (pool BufferPool) AtLevel(levels ...int) *BufferPool {
return &BufferPool{pool.BufferPool.AtLevel(levels...)}
}
// GetBuffCt returns a ciphertext that can be used as a buffer for intermediate computations.
// After use, the ciphertext should be recycled with [BufferPool.RecycleBuffCt].
// The optional dimensions specify the degree and level of the ciphertext (default to 2, pool.GetLevel()).
func (pool *BufferPool) GetBuffCt(dimensions ...int) *Ciphertext {
degree := 2
level := pool.GetLevel()
switch nbParams := len(dimensions); nbParams {
case 0:
case 1:
degree = dimensions[0]
case 2:
degree = dimensions[0]
level = dimensions[1]
default:
panic(fmt.Errorf("getbuffct takes 2 parameters at most"))
}
poolQ := pool.AtLevel(level, 0)
polys := make([]ring.Poly, degree+1)
for i := range polys {
polys[i] = *poolQ.GetBuffPoly()
}
ct, err := NewCiphertextAtLevelFromPoly(level, polys)
// sanity check: should not happen
if err != nil {
panic(fmt.Errorf("cannot create new ciphertext: %w", err))
}
return ct
}
// RecycleBuffCt recycles a temporary ciphertext (i.e. returns its backing uint64 arrays to the pool).
// The input ciphertext must not be used after calling this method.
func (pool *BufferPool) RecycleBuffCt(ct *Ciphertext) {
for i := range ct.Value {
pool.RecycleBuffPoly(&ct.Value[i])
}
ct = nil
}
// GetBuffPt returns a plaintext that can be used as a buffer for intermediate computations.
// After use, the plaintext should be recycled with [BufferPool.RecycleBuffPt].
// The optional argument specifies the level of the returned plaintext (default to pool.GetLevel()).
func (pool *BufferPool) GetBuffPt(level ...int) *Plaintext {
lvl := pool.GetLevel()
switch nbParams := len(level); nbParams {
case 0:
case 1:
lvl = level[0]
default:
panic(fmt.Errorf("getbuffpt takes 1 parameter at most but %d were given", nbParams))
}
poly := pool.AtLevel(lvl, 0).GetBuffPoly()
pt, err := NewPlaintextAtLevelFromPoly(lvl, *poly)
// sanity check: should not happen
if err != nil {
panic(fmt.Errorf("cannot create new plaintext: %w", err))
}
return pt
}
// RecycleBuffPt recycles a temporary plaintext (i.e. returns its backing uint64 arrays to the pool).
// The input plaintext must not be used after calling this method.
func (pool *BufferPool) RecycleBuffPt(pt *Plaintext) {
pool.RecycleBuffPoly(&pt.Value)
}
// GetBuffDecompQP returns buffers of polys to be used for RNS decomposition.
// After use, the array of buffers must be recycled with [BufferPool.RecycleBuffDecompQP].
func (pool *BufferPool) GetBuffDecompQP(params Parameters, levelQ, levelP int) []ringqp.Poly {
size := params.BaseRNSDecompositionVectorSize(levelQ, levelP)
buffDecompQP := make([]ringqp.Poly, size)
for i := 0; i < size; i++ {
poly := pool.GetBuffPolyQP()
buffDecompQP[i] = *poly
}
return buffDecompQP
}
// RecycleBuffDecompQP recycles a temporary array of polys used for decomposition.
func (pool *BufferPool) RecycleBuffDecompQP(decomp []ringqp.Poly) {
for i := range decomp {
pool.RecycleBuffPolyQP(&decomp[i])
}
}
+2 -25
View File
@@ -49,24 +49,6 @@ func NewRingPackingEvaluator(evk *RingPackingEvaluationKey) *RingPackingEvaluato
}
}
// ShallowCopy creates a shallow copy of this struct in which all the read-only data-structures are
// shared with the receiver and the temporary buffers are reallocated. The receiver and the returned
// Evaluators can be used concurrently.
func (eval RingPackingEvaluator) ShallowCopy() *RingPackingEvaluator {
Evaluators := map[int]*Evaluator{}
for i := range eval.Evaluators {
Evaluators[i] = eval.Evaluators[i].ShallowCopy()
}
return &RingPackingEvaluator{
RingPackingEvaluationKey: eval.RingPackingEvaluationKey,
Evaluators: Evaluators,
XPow2NTT: eval.XPow2NTT,
XInvPow2NTT: eval.XInvPow2NTT,
}
}
// Extract takes as input a ciphertext encrypting P(X) = c[i] * X^i and returns a map of
// ciphertexts of degree eval.MinLogN(), each encrypting P(X) = c[i] * X^{0} for i in idx.
// All non-constant coefficients are zeroed and thus correctness is ensured if this method
@@ -529,13 +511,8 @@ func (eval RingPackingEvaluator) Expand(ct *Ciphertext, logGap int) (cts map[int
gap := 1 << logGap
tmp, err := NewCiphertextAtLevelFromPoly(level, []ring.Poly{evalN.BuffCt.Value[0], evalN.BuffCt.Value[1]})
// Sanity check, this error should not happen unless the
// evaluator's buffer thave been improperly tempered with.
if err != nil {
panic(err)
}
tmp := evalN.pool.GetBuffCt(1, level)
defer evalN.pool.RecycleBuffCt(tmp)
*tmp.MetaData = *ct.MetaData
+8
View File
@@ -77,6 +77,7 @@ func testRingPacking(tc *TestContext, t *testing.T) {
t.Run(testString(params, params.MaxLevelQ(), params.MaxLevelP(), 0, "Split"), func(t *testing.T) {
t.Parallel()
pt := genPlaintextNTT(params, level, 1<<40)
ct, err := enc.EncryptNew(pt)
require.NoError(t, err)
@@ -133,6 +134,8 @@ func testRingPacking(tc *TestContext, t *testing.T) {
t.Skip("eval.MaxLogN() = eval.MinLogN()")
}
t.Parallel()
paramsNHalf := *eval.Parameters[params.LogN()-1].GetRLWEParameters()
encNHalf := NewEncryptor(paramsNHalf, ski[paramsNHalf.LogN()])
@@ -177,6 +180,7 @@ func testRingPacking(tc *TestContext, t *testing.T) {
})
t.Run(testString(params, params.MaxLevelQ(), params.MaxLevelP(), 0, "Extract/Naive=False"), func(t *testing.T) {
t.Parallel()
if params.RingType() != ring.Standard {
t.Skip("Expand not supported for ring.Type = ring.ConjugateInvariant")
@@ -242,6 +246,7 @@ func testRingPacking(tc *TestContext, t *testing.T) {
})
t.Run(testString(params, params.MaxLevelQ(), params.MaxLevelP(), 0, "Extract/Naive=True"), func(t *testing.T) {
t.Parallel()
if params.RingType() != ring.Standard {
t.Skip("Expand not supported for ring.Type = ring.ConjugateInvariant")
@@ -319,6 +324,7 @@ func testRingPacking(tc *TestContext, t *testing.T) {
if params.RingType() != ring.Standard {
t.Skip("Pack not supported for ring.Type = ring.ConjugateInvariant")
}
t.Parallel()
pt := NewPlaintext(params, level)
ringQ := tc.params.RingQ().AtLevel(level)
@@ -377,10 +383,12 @@ func testRingPacking(tc *TestContext, t *testing.T) {
})
t.Run(testString(params, params.MaxLevelQ(), params.MaxLevelP(), 0, "Extract[naive=false]->Permute->Repack[naive=true]"), func(t *testing.T) {
t.Parallel()
testExtractPermuteRepack(params, level, enc, dec, eval, false, true, t)
})
t.Run(testString(params, params.MaxLevelQ(), params.MaxLevelP(), 0, "Extract[naive=true]->Permute->Repack[naive=false]"), func(t *testing.T) {
t.Parallel()
testExtractPermuteRepack(params, level, enc, dec, eval, true, false, t)
})
}
-3
View File
@@ -8,9 +8,6 @@ import (
)
type EvaluatorProvider interface {
GetBuffQP() [6]ringqp.Poly
GetBuffCt() *Ciphertext
GetBuffDecompQP() []ringqp.Poly
DecomposeNTT(level, levelP, pCount int, c1 ring.Poly, isNTT bool, BuffDecompQP []ringqp.Poly)
CheckAndGetGaloisKey(galEl uint64) (evk *GaloisKey, err error)
GadgetProductLazy(levelQ int, cx ring.Poly, gadgetCt *GadgetCiphertext, ct *Element[ringqp.Poly]) (err error)
+9
View File
@@ -143,4 +143,13 @@ func benchEvaluator(tc *TestContext, bpw2 int, b *testing.B) {
}
})
}
b.Run(testString(params, params.MaxLevelQ(), params.MaxLevelP(), bpw2, "Evaluator/BuffQPPool"), func(b *testing.B) {
poolQP := NewPool(params.RingQP())
for i := 0; i < b.N; i++ {
pol := poolQP.GetBuffPolyQP()
poolQP.RecycleBuffPolyQP(pol)
}
})
}
+26 -35
View File
@@ -41,7 +41,7 @@ func testString(params Parameters, levelQ, levelP, bpw2 int, opname string) stri
func TestRLWEConstSerialization(t *testing.T) {
// Note: changing nbIteration will change the expected value
const nbIteration = 10
const expected = "/mTt2kB+03NdOMoI1msW+glCZmrF1sxEGQkFsC6P1SA="
const expected = "1MTZP69YciXe+YjFnQbiUJIz7/d/h78atA/jaGKQhhc="
var err error
defaultParamsLiteral := testInsecure
seedKeyGen := []byte{'l', 'a', 't'}
@@ -316,7 +316,7 @@ func NewDeterministicTestContext(params Parameters, seedKeyGen, seedEnc []byte)
panic(fmt.Errorf("NewDeterministicTestContext: failed to make prngEnc %w", err))
}
kgen := NewKeyGenerator(params)
kgenEncryptor := NewTestEncryptorWithPRNG(params, nil, prngKGen)
kgenEncryptor := newTestEncryptorWithKeyedPRNG(params, nil, prngKGen)
kgen.Encryptor = kgenEncryptor
sk := kgen.GenSecretKeyNew()
@@ -325,7 +325,7 @@ func NewDeterministicTestContext(params Parameters, seedKeyGen, seedEnc []byte)
eval := NewEvaluator(params, nil)
enc := NewTestEncryptorWithPRNG(params, sk, prngEnc)
enc := newTestEncryptorWithKeyedPRNG(params, sk, prngEnc)
dec := NewDecryptor(params, sk)
@@ -608,17 +608,6 @@ func testEncryptor(tc *TestContext, level, bpw2 int, t *testing.T) {
require.GreaterOrEqual(t, math.Log2(params.NoiseFreshPK())+1, ringQ.Log2OfStandardDeviation(pt.Value))
})
t.Run(testString(params, level, params.MaxLevelP(), bpw2, "Encryptor/Encrypt/Pk/ShallowCopy"), func(t *testing.T) {
pkEnc1 := enc.WithKey(pk)
pkEnc2 := pkEnc1.ShallowCopy()
require.True(t, pkEnc1.params.Equal(&pkEnc2.params))
require.True(t, pkEnc1.encKey == pkEnc2.encKey)
require.False(t, (pkEnc1.basisextender == pkEnc2.basisextender) && (pkEnc1.basisextender != nil) && (pkEnc2.basisextender != nil))
require.False(t, pkEnc1.encryptorBuffers == pkEnc2.encryptorBuffers)
require.False(t, pkEnc1.xsSampler == pkEnc2.xsSampler)
require.False(t, pkEnc1.xeSampler == pkEnc2.xeSampler)
})
t.Run(testString(params, level, params.MaxLevelP(), bpw2, "Encryptor/Encrypt/Sk"), func(t *testing.T) {
ringQ := params.RingQ().AtLevel(level)
@@ -646,7 +635,7 @@ func testEncryptor(tc *TestContext, level, bpw2 int, t *testing.T) {
prng1, _ := sampling.NewKeyedPRNG([]byte{'a', 'b', 'c'})
prng2, _ := sampling.NewKeyedPRNG([]byte{'a', 'b', 'c'})
enc.WithPRNG(prng1).Encrypt(pt, ct)
enc.withKeyedUniformSampling(prng1).Encrypt(pt, ct)
samplerQ := ring.NewUniformSampler(prng2, ringQ)
@@ -661,18 +650,6 @@ func testEncryptor(tc *TestContext, level, bpw2 int, t *testing.T) {
require.GreaterOrEqual(t, math.Log2(params.NoiseFreshSK())+1, ringQ.Log2OfStandardDeviation(pt.Value))
})
t.Run(testString(params, level, params.MaxLevelP(), bpw2, "Encrypt/Sk/ShallowCopy"), func(t *testing.T) {
skEnc1 := NewEncryptor(params, sk)
skEnc2 := skEnc1.ShallowCopy()
require.True(t, skEnc1.params.Equal(&skEnc2.params))
require.True(t, skEnc1.encKey == skEnc2.encKey)
require.False(t, (skEnc1.basisextender == skEnc2.basisextender) && (skEnc1.basisextender != nil) && (skEnc2.basisextender != nil))
require.False(t, skEnc1.encryptorBuffers == skEnc2.encryptorBuffers)
require.False(t, skEnc1.xsSampler == skEnc2.xsSampler)
require.False(t, skEnc1.xeSampler == skEnc2.xeSampler)
})
t.Run(testString(params, level, params.MaxLevelP(), bpw2, "Encrypt/WithKey/Sk->Sk"), func(t *testing.T) {
sk2 := kgen.GenSecretKeyNew()
skEnc1 := NewEncryptor(params, sk)
@@ -681,7 +658,6 @@ func testEncryptor(tc *TestContext, level, bpw2 int, t *testing.T) {
require.True(t, skEnc1.encKey == sk)
require.True(t, skEnc2.encKey == sk2)
require.True(t, skEnc1.basisextender == skEnc2.basisextender)
require.True(t, skEnc1.encryptorBuffers == skEnc2.encryptorBuffers)
require.True(t, skEnc1.xsSampler == skEnc2.xsSampler)
require.True(t, skEnc1.xeSampler == skEnc2.xeSampler)
})
@@ -751,6 +727,11 @@ func testGadgetProduct(tc *TestContext, levelQ, bpw2 int, t *testing.T) {
t.Run(testString(params, levelQ, levelP, bpw2, "Evaluator/GadgetProductHoisted"), func(t *testing.T) {
// Setup temporary buffer for decomposition
poolQP := tc.eval.pool.AtLevel(levelQ, levelP)
buffDecompQP := poolQP.GetBuffDecompQP(params, levelQ, levelP)
defer poolQP.RecycleBuffDecompQP(buffDecompQP)
if bpw2 != 0 {
t.Skip("method is unsupported for BaseTwoDecomposition != 0")
}
@@ -773,10 +754,10 @@ func testGadgetProduct(tc *TestContext, levelQ, bpw2 int, t *testing.T) {
kgen.GenEvaluationKey(sk, skOut, evk)
//Decompose the ciphertext
eval.DecomposeNTT(levelQ, levelP, levelP+1, a, ct.IsNTT, eval.BuffDecompQP)
eval.DecomposeNTT(levelQ, levelP, levelP+1, a, ct.IsNTT, buffDecompQP)
// Gadget product: ct = [-cs1 + as0 , c]
eval.GadgetProductHoisted(levelQ, eval.BuffDecompQP, &evk.GadgetCiphertext, ct)
eval.GadgetProductHoisted(levelQ, buffDecompQP, &evk.GadgetCiphertext, ct)
// pt = as0
pt := NewDecryptor(params, skOut).DecryptNew(ct)
@@ -979,6 +960,11 @@ func testAutomorphism(tc *TestContext, level, bpw2 int, t *testing.T) {
t.Skip("method is not supported if BaseTwoDecomposition != 0")
}
// Setup temporary buffer for decomposition
poolQP := tc.eval.pool.AtLevel(level, params.MaxLevelP())
buffDecompQP := poolQP.GetBuffDecompQP(params, level, 0)
defer poolQP.RecycleBuffDecompQP(buffDecompQP)
// Generate a plaintext with values up to 2^30
pt := genPlaintext(params, level, 1<<30)
@@ -993,10 +979,10 @@ func testAutomorphism(tc *TestContext, level, bpw2 int, t *testing.T) {
evk := NewMemEvaluationKeySet(nil, kgen.GenGaloisKeyNew(galEl, sk, evkParams))
//Decompose the ciphertext
eval.DecomposeNTT(level, params.MaxLevelP(), params.MaxLevelP()+1, ct.Value[1], ct.IsNTT, eval.BuffDecompQP)
eval.DecomposeNTT(level, params.MaxLevelP(), params.MaxLevelP()+1, ct.Value[1], ct.IsNTT, buffDecompQP)
// Evaluate the automorphism
eval.WithKey(evk).AutomorphismHoisted(level, ct, eval.BuffDecompQP, galEl, ct)
eval.WithKey(evk).AutomorphismHoisted(level, ct, buffDecompQP, galEl, ct)
// Apply the same automorphism on the plaintext
ringQ := params.RingQ().AtLevel(level)
@@ -1029,6 +1015,11 @@ func testAutomorphism(tc *TestContext, level, bpw2 int, t *testing.T) {
t.Skip("method is not supported if BaseTwoDecomposition != 0")
}
// Setup temporary buffer for decomposition
poolQP := tc.eval.pool.AtLevel(level, params.MaxLevelP())
buffDecompQP := poolQP.GetBuffDecompQP(params, level, 0)
defer poolQP.RecycleBuffDecompQP(buffDecompQP)
// Generate a plaintext with values up to 2^30
pt := genPlaintext(params, level, 1<<30)
@@ -1043,12 +1034,12 @@ func testAutomorphism(tc *TestContext, level, bpw2 int, t *testing.T) {
evk := NewMemEvaluationKeySet(nil, kgen.GenGaloisKeyNew(galEl, sk, evkParams))
//Decompose the ciphertext
eval.DecomposeNTT(level, params.MaxLevelP(), params.MaxLevelP()+1, ct.Value[1], ct.IsNTT, eval.BuffDecompQP)
eval.DecomposeNTT(level, params.MaxLevelP(), params.MaxLevelP()+1, ct.Value[1], ct.IsNTT, buffDecompQP)
ctQP := NewElementExtended(params, 1, level, params.MaxLevelP())
// Evaluate the automorphism
eval.WithKey(evk).AutomorphismHoistedLazy(level, ct, eval.BuffDecompQP, galEl, ctQP)
eval.WithKey(evk).AutomorphismHoistedLazy(level, ct, buffDecompQP, galEl, ctQP)
eval.ModDown(level, params.MaxLevelP(), ctQP, ct)
@@ -1273,7 +1264,7 @@ func testMarshaller(tc *TestContext, t *testing.T) {
require.Nil(t, err)
var p Parameters
require.Nil(t, p.UnmarshalBinary(bytes))
require.Equal(t, params, p)
require.Equal(t, params.ParametersLiteral(), p.ParametersLiteral())
})
t.Run("Marshaller/MetaData", func(t *testing.T) {
+1 -1
View File
@@ -545,7 +545,7 @@ func execRequest(params bgv.Parameters, NGoRoutine int, encQuery *rlwe.Ciphertex
workers.Add(NGoRoutine)
for i := 1; i <= NGoRoutine; i++ {
go func(i int) {
evaluator := evaluator.ShallowCopy() // creates a shallow evaluator copy for this goroutine
evaluator := evaluator
tmp := bgv.NewCiphertext(params, 1, params.MaxLevel())
for task := range tasks {
task.elapsedmaskTask = runTimed(func() {
+1 -1
View File
@@ -340,7 +340,7 @@ func evalPhase(params bgv.Parameters, NGoRoutine int, encInputs []*rlwe.Cipherte
//l.Println("> Spawning", NGoRoutine, "evaluator goroutine")
for i := 1; i <= NGoRoutine; i++ {
go func(i int) {
evaluator := evaluator.ShallowCopy() // creates a shallow evaluator copy for this goroutine
evaluator := evaluator
for task := range tasks {
task.elapsedmultTask = runTimed(func() {
// 1) Multiplication of two input vectors
@@ -176,6 +176,10 @@ func main() {
if err != nil {
panic(err)
}
ciphertext2, err := encryptor.EncryptNew(plaintext)
if err != nil {
panic(err)
}
// Decrypt, print and compare with the plaintext values
fmt.Println()
@@ -188,11 +192,24 @@ func main() {
// CAUTION: the scale of the ciphertext MUST be equal (or very close) to params.DefaultScale()
// To equalize the scale, the function evaluator.SetScale(ciphertext, parameters.DefaultScale()) can be used at the expense of one level.
// If the ciphertext is is at level one or greater when given to the bootstrapper, this equalization is automatically done.
// Here we bootstrap two ciphertexts in parallel to demonstrate that evaluators are thread-safe.
resChan := make(chan *rlwe.Ciphertext, 1)
fmt.Println("Bootstrapping...")
ciphertext2, err := eval.Bootstrap(ciphertext1)
go func() {
res, err := eval.Bootstrap(ciphertext2)
if err != nil {
panic(err)
}
resChan <- res
}()
res1, err := eval.Bootstrap(ciphertext1)
if err != nil {
panic(err)
}
res2 := <-resChan
fmt.Println("Done")
//==================
@@ -202,7 +219,8 @@ func main() {
// Decrypt, print and compare with the plaintext values
fmt.Println()
fmt.Println("Precision of ciphertext vs. Bootstrap(ciphertext)")
printDebug(params, ciphertext2, valuesTest1, decryptor, encoder)
printDebug(params, res1, valuesTest1, decryptor, encoder)
printDebug(params, res2, valuesTest1, decryptor, encoder)
}
func printDebug(params ckks.Parameters, ciphertext *rlwe.Ciphertext, valuesWant []complex128, decryptor *rlwe.Decryptor, encoder *ckks.Encoder) (valuesTest []complex128) {
+1 -6
View File
@@ -759,13 +759,8 @@ func main() {
// CONCURRENCY
// ==========
//
// Lattigo does not implement low level concurrency yet.
// Currently concurrency must be done at the circuit level.
// In lattigo, a type's methods are thread-safe and can be called concurrently. E.g. one can use an evaluator's methods concurrently.
//
// By design, structs outside of the parameters are not thread safe.
// For example, one cannot use an encoder to encode concurrently on different plaintexts.
// However, all structs (for which it makes sens) have the method `ShallowCopy`, which creates
// a copy of the original struct with new internal buffers, that is safe to use concurrently.
}
-21
View File
@@ -93,27 +93,6 @@ func (ckg PublicKeyGenProtocol) GenPublicKey(roundShare PublicKeyGenShare, crp P
pubkey.Value[1].Copy(crp.Value)
}
// ShallowCopy creates a shallow copy of [PublicKeyGenProtocol] in which all the read-only data-structures are
// shared with the receiver and the temporary buffers are reallocated. The receiver and the returned
// [PublicKeyGenProtocol] can be used concurrently.
func (ckg PublicKeyGenProtocol) ShallowCopy() PublicKeyGenProtocol {
prng, err := sampling.NewPRNG()
// Sanity check, this error should not happen.
if err != nil {
panic(err)
}
sampler, err := ring.NewSampler(prng, ckg.params.RingQ(), ckg.params.Xe(), false)
// Sanity check, this error should not happen.
if err != nil {
panic(err)
}
return PublicKeyGenProtocol{ckg.params, sampler}
}
// BinarySize returns the serialized size of the object in bytes.
func (share PublicKeyGenShare) BinarySize() int {
return share.Value.BinarySize()
+6 -33
View File
@@ -16,37 +16,9 @@ import (
// EvaluationKeyGenProtocol is the structure storing the parameters for the collective EvaluationKey generation.
type EvaluationKeyGenProtocol struct {
params rlwe.Parameters
buff [2]ringqp.Poly
gaussianSamplerQ ring.Sampler
}
// ShallowCopy creates a shallow copy of [EvaluationKeyGenProtocol] in which all the read-only data-structures are
// shared with the receiver and the temporary buffers are reallocated. The receiver and the returned
// [EvaluationKeyGenProtocol] can be used concurrently.
func (evkg EvaluationKeyGenProtocol) ShallowCopy() EvaluationKeyGenProtocol {
prng, err := sampling.NewPRNG()
// Sanity check, this error should not happen.
if err != nil {
panic(err)
}
params := evkg.params
Xe, err := ring.NewSampler(prng, evkg.params.RingQ(), evkg.params.Xe(), false)
// Sanity check, this error should not happen.
if err != nil {
panic(err)
}
return EvaluationKeyGenProtocol{
params: evkg.params,
buff: [2]ringqp.Poly{params.RingQP().NewPoly(), params.RingQP().NewPoly()},
gaussianSamplerQ: Xe,
}
}
// NewEvaluationKeyGenProtocol creates a [EvaluationKeyGenProtocol] instance.
func NewEvaluationKeyGenProtocol(params rlwe.ParameterProvider) (evkg EvaluationKeyGenProtocol) {
@@ -69,7 +41,6 @@ func NewEvaluationKeyGenProtocol(params rlwe.ParameterProvider) (evkg Evaluation
return EvaluationKeyGenProtocol{
params: pRLWE,
gaussianSamplerQ: Xe,
buff: [2]ringqp.Poly{pRLWE.RingQP().NewPoly(), pRLWE.RingQP().NewPoly()},
}
}
@@ -138,12 +109,14 @@ func (evkg EvaluationKeyGenProtocol) GenShare(skIn, skOut *rlwe.SecretKey, crp E
var hasModulusP bool
buffQ := ringQ.NewPoly()
if levelP > -1 {
hasModulusP = true
ringQ.MulScalarBigint(skIn.Value.Q, ringQP.RingP.ModulusAtLevel[levelP], evkg.buff[0].Q)
ringQ.MulScalarBigint(skIn.Value.Q, ringQP.RingP.ModulusAtLevel[levelP], buffQ)
} else {
levelP = 0
evkg.buff[0].Q.CopyLvl(levelQ, skIn.Value.Q)
buffQ.CopyLvl(levelQ, skIn.Value.Q)
}
m := shareOut.Value
@@ -190,7 +163,7 @@ func (evkg EvaluationKeyGenProtocol) GenShare(skIn, skOut *rlwe.SecretKey, crp E
}
qi := ringQ.SubRings[index].Modulus
tmp0 := evkg.buff[0].Q.Coeffs[index]
tmp0 := buffQ.Coeffs[index]
tmp1 := mij.Q.Coeffs[index]
for w := 0; w < N; w++ {
@@ -203,7 +176,7 @@ func (evkg EvaluationKeyGenProtocol) GenShare(skIn, skOut *rlwe.SecretKey, crp E
}
}
ringQ.MulScalar(evkg.buff[0].Q, 1<<shareOut.BaseTwoDecomposition, evkg.buff[0].Q)
ringQ.MulScalar(buffQ, 1<<shareOut.BaseTwoDecomposition, buffQ)
}
return
+8 -15
View File
@@ -7,13 +7,11 @@ import (
"github.com/tuneinsight/lattigo/v6/core/rlwe"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/ring/ringqp"
"github.com/tuneinsight/lattigo/v6/utils/buffer"
)
// GaloisKeyGenProtocol is the structure storing the parameters for the collective GaloisKeys generation.
type GaloisKeyGenProtocol struct {
skOut ringqp.Poly
EvaluationKeyGenProtocol
}
@@ -28,16 +26,9 @@ type GaloisKeyGenCRP struct {
EvaluationKeyGenCRP
}
// ShallowCopy creates a shallow copy of [GaloisKeyGenProtocol] in which all the read-only data-structures are
// shared with the receiver and the temporary buffers are reallocated. The receiver and the returned
// [GaloisKeyGenProtocol] can be used concurrently.
func (gkg GaloisKeyGenProtocol) ShallowCopy() GaloisKeyGenProtocol {
return GaloisKeyGenProtocol{EvaluationKeyGenProtocol: gkg.EvaluationKeyGenProtocol.ShallowCopy(), skOut: gkg.params.RingQP().NewPoly()}
}
// NewGaloisKeyGenProtocol creates a [GaloisKeyGenProtocol] instance.
func NewGaloisKeyGenProtocol(params rlwe.ParameterProvider) (gkg GaloisKeyGenProtocol) {
return GaloisKeyGenProtocol{EvaluationKeyGenProtocol: NewEvaluationKeyGenProtocol(params), skOut: params.GetRLWEParameters().RingQP().NewPoly()}
return GaloisKeyGenProtocol{EvaluationKeyGenProtocol: NewEvaluationKeyGenProtocol(params)}
}
// AllocateShare allocates a party's share in the GaloisKey Generation.
@@ -59,21 +50,23 @@ func (gkg GaloisKeyGenProtocol) GenShare(sk *rlwe.SecretKey, galEl uint64, crp G
levelQ := shareOut.LevelQ()
levelP := shareOut.LevelP()
ringQ := gkg.params.RingQ().AtLevel(levelQ)
ringP := gkg.params.RingP().AtLevel(levelP)
ringQP := gkg.params.RingQP().AtLevel(levelQ, levelP)
ringQ := ringQP.RingQ
ringP := ringQP.RingP
galElInv := ring.ModExp(galEl, ringQ.NthRoot()-1, ringQ.NthRoot())
// Important
shareOut.GaloisElement = galEl
ringQ.AutomorphismNTT(sk.Value.Q, galElInv, gkg.skOut.Q)
skOut := ringQP.NewPoly()
ringQ.AutomorphismNTT(sk.Value.Q, galElInv, skOut.Q)
if levelP > -1 {
ringP.AutomorphismNTT(sk.Value.P, galElInv, gkg.skOut.P)
ringP.AutomorphismNTT(sk.Value.P, galElInv, skOut.P)
}
return gkg.EvaluationKeyGenProtocol.GenShare(sk, &rlwe.SecretKey{Value: gkg.skOut}, crp.EvaluationKeyGenCRP, &shareOut.EvaluationKeyGenShare)
return gkg.EvaluationKeyGenProtocol.GenShare(sk, &rlwe.SecretKey{Value: skOut}, crp.EvaluationKeyGenCRP, &shareOut.EvaluationKeyGenShare)
}
+14 -50
View File
@@ -17,8 +17,6 @@ type RelinearizationKeyGenProtocol struct {
gaussianSamplerQ ring.Sampler
ternarySamplerQ ring.Sampler
buf [2]ringqp.Poly
}
// RelinearizationKeyGenShare is a share in the RelinearizationKeyGen protocol.
@@ -31,42 +29,6 @@ type RelinearizationKeyGenCRP struct {
Value structs.Matrix[ringqp.Poly]
}
// ShallowCopy creates a shallow copy of [RelinearizationKeyGenProtocol] in which all the read-only data-structures are
// shared with the receiver and the temporary buffers are reallocated. The receiver and the returned
// [RelinearizationKeyGenProtocol] can be used concurrently.
func (ekg *RelinearizationKeyGenProtocol) ShallowCopy() RelinearizationKeyGenProtocol {
var err error
prng, err := sampling.NewPRNG()
// Sanity check, this error should not happen.
if err != nil {
panic(err)
}
params := ekg.params
Xe, err := ring.NewSampler(prng, ekg.params.RingQ(), ekg.params.Xe(), false)
// Sanity check, this error should not happen.
if err != nil {
panic(err)
}
Xs, err := ring.NewSampler(prng, ekg.params.RingQ(), ekg.params.Xs(), false)
// Sanity check, this error should not happen.
if err != nil {
panic(err)
}
return RelinearizationKeyGenProtocol{
params: ekg.params,
buf: [2]ringqp.Poly{params.RingQP().NewPoly(), params.RingQP().NewPoly()},
gaussianSamplerQ: Xe,
ternarySamplerQ: Xs,
}
}
// NewRelinearizationKeyGenProtocol creates a new RelinearizationKeyGen protocol struct.
func NewRelinearizationKeyGenProtocol(params rlwe.ParameterProvider) RelinearizationKeyGenProtocol {
rkg := RelinearizationKeyGenProtocol{}
@@ -94,7 +56,6 @@ func NewRelinearizationKeyGenProtocol(params rlwe.ParameterProvider) Relineariza
panic(err)
}
rkg.buf = [2]ringqp.Poly{rkg.params.RingQP().NewPoly(), rkg.params.RingQP().NewPoly()}
return rkg
}
@@ -140,15 +101,16 @@ func (ekg RelinearizationKeyGenProtocol) GenShareRoundOne(sk *rlwe.SecretKey, cr
hasModulusP := levelP > -1
buffQ := ringQ.NewPoly()
if hasModulusP {
// Computes P * sk
ringQ.MulScalarBigint(sk.Value.Q, ringQP.RingP.ModulusAtLevel[levelP], ekg.buf[0].Q)
ringQ.MulScalarBigint(sk.Value.Q, ringQP.RingP.ModulusAtLevel[levelP], buffQ)
} else {
levelP = 0
ekg.buf[0].Q.CopyLvl(levelQ, sk.Value.Q)
buffQ.CopyLvl(levelQ, sk.Value.Q)
}
ringQ.IMForm(ekg.buf[0].Q, ekg.buf[0].Q)
ringQ.IMForm(buffQ, buffQ)
// u
ekg.ternarySamplerQ.Read(ephSkOut.Value.Q)
@@ -192,7 +154,7 @@ func (ekg RelinearizationKeyGenProtocol) GenShareRoundOne(sk *rlwe.SecretKey, cr
}
qi := ringQ.SubRings[index].Modulus
skP := ekg.buf[0].Q.Coeffs[index]
skP := buffQ.Coeffs[index]
h := shareOut.Value[i][j][0].Q.Coeffs[index]
for w := 0; w < N; w++ {
@@ -217,7 +179,7 @@ func (ekg RelinearizationKeyGenProtocol) GenShareRoundOne(sk *rlwe.SecretKey, cr
}
}
ringQ.MulScalar(ekg.buf[0].Q, 1<<shareOut.BaseTwoDecomposition, ekg.buf[0].Q)
ringQ.MulScalar(buffQ, 1<<shareOut.BaseTwoDecomposition, buffQ)
}
}
@@ -238,7 +200,9 @@ func (ekg RelinearizationKeyGenProtocol) GenShareRoundTwo(ephSk, sk *rlwe.Secret
ringQP := ekg.params.RingQP().AtLevel(levelQ, levelP)
// (u_i - s_i)
ringQP.Sub(ephSk.Value, sk.Value, ekg.buf[0])
buffQP0 := ringQP.NewPoly()
buffQP1 := ekg.params.RingQP().NewPoly()
ringQP.Sub(ephSk.Value, sk.Value, buffQP0)
sampler := ekg.gaussianSamplerQ.AtLevel(levelQ)
@@ -253,18 +217,18 @@ func (ekg RelinearizationKeyGenProtocol) GenShareRoundTwo(ephSk, sk *rlwe.Secret
ringQP.MulCoeffsMontgomeryLazy(round1.Value[i][j][0], sk.Value, shareOut.Value[i][j][0])
// (AggregateShareRoundTwo samples) * sk + e_1i
sampler.Read(ekg.buf[1].Q)
sampler.Read(buffQP1.Q)
if levelP > -1 {
ringQP.ExtendBasisSmallNormAndCenter(ekg.buf[1].Q, levelP, ekg.buf[1].Q, ekg.buf[1].P)
ringQP.ExtendBasisSmallNormAndCenter(buffQP1.Q, levelP, buffQP1.Q, buffQP1.P)
}
ringQP.NTT(ekg.buf[1], ekg.buf[1])
ringQP.Add(shareOut.Value[i][j][0], ekg.buf[1], shareOut.Value[i][j][0])
ringQP.NTT(buffQP1, buffQP1)
ringQP.Add(shareOut.Value[i][j][0], buffQP1, shareOut.Value[i][j][0])
// second part
// (AggRound1Samples[0])*sk + (u_i - s_i) * (AggRound1Samples[1]) + e_1
ringQP.MulCoeffsMontgomeryThenAdd(ekg.buf[0], round1.Value[i][j][1], shareOut.Value[i][j][0])
ringQP.MulCoeffsMontgomeryThenAdd(buffQP0, round1.Value[i][j][1], shareOut.Value[i][j][0])
}
}
}
+9 -41
View File
@@ -16,8 +16,6 @@ type PublicKeySwitchProtocol struct {
params rlwe.Parameters
noise ring.DistributionParameters
buf ring.Poly
*rlwe.Encryptor
noiseSampler ring.Sampler
}
@@ -34,8 +32,6 @@ func NewPublicKeySwitchProtocol(params rlwe.ParameterProvider, noiseFlooding rin
pcks.params = *params.GetRLWEParameters()
pcks.noise = noiseFlooding
pcks.buf = pcks.params.RingQ().NewPoly()
prng, err := sampling.NewPRNG()
// Sanity check, this error should not happen.
@@ -92,18 +88,19 @@ func (pcks PublicKeySwitchProtocol) GenShare(sk *rlwe.SecretKey, pk *rlwe.Public
panic(err)
}
buffQ := pcks.params.RingQ().NewPoly()
// Add ct[1] * s and noise
if ct.IsNTT {
ringQ.MulCoeffsMontgomeryThenAdd(ct.Value[1], sk.Value.Q, shareOut.Value[0])
pcks.noiseSampler.Read(pcks.buf)
ringQ.NTT(pcks.buf, pcks.buf)
ringQ.Add(shareOut.Value[0], pcks.buf, shareOut.Value[0])
pcks.noiseSampler.Read(buffQ)
ringQ.NTT(buffQ, buffQ)
ringQ.Add(shareOut.Value[0], buffQ, shareOut.Value[0])
} else {
ringQ.NTTLazy(ct.Value[1], pcks.buf)
ringQ.MulCoeffsMontgomeryLazy(pcks.buf, sk.Value.Q, pcks.buf)
ringQ.INTT(pcks.buf, pcks.buf)
pcks.noiseSampler.ReadAndAdd(pcks.buf)
ringQ.Add(shareOut.Value[0], pcks.buf, shareOut.Value[0])
ringQ.NTTLazy(ct.Value[1], buffQ)
ringQ.MulCoeffsMontgomeryLazy(buffQ, sk.Value.Q, buffQ)
ringQ.INTT(buffQ, buffQ)
pcks.noiseSampler.ReadAndAdd(buffQ)
ringQ.Add(shareOut.Value[0], buffQ, shareOut.Value[0])
}
}
@@ -136,35 +133,6 @@ func (pcks PublicKeySwitchProtocol) KeySwitch(ctIn *rlwe.Ciphertext, combined Pu
opOut.Value[1].CopyLvl(level, combined.Value[1])
}
// ShallowCopy creates a shallow copy of [PublicKeySwitchProtocol] in which all the read-only data-structures are
// shared with the receiver and the temporary bufers are reallocated. The receiver and the returned
// [PublicKeySwitchProtocol] can be used concurrently.
func (pcks PublicKeySwitchProtocol) ShallowCopy() PublicKeySwitchProtocol {
prng, err := sampling.NewPRNG()
// Sanity check, this error should not happen.
if err != nil {
panic(err)
}
params := pcks.params
Xe, err := ring.NewSampler(prng, params.RingQ(), pcks.noise, false)
// Sanity check, this error should not happen.
if err != nil {
panic(err)
}
return PublicKeySwitchProtocol{
noiseSampler: Xe,
noise: pcks.noise,
Encryptor: pcks.Encryptor.ShallowCopy(),
params: params,
buf: params.RingQ().NewPoly(),
}
}
// BinarySize returns the serialized size of the object in bytes.
func (share PublicKeySwitchShare) BinarySize() int {
return share.Element.BinarySize()
+9 -40
View File
@@ -17,8 +17,6 @@ type KeySwitchProtocol struct {
params rlwe.Parameters
noise ring.DistributionParameters
noiseSampler ring.Sampler
buf ring.Poly
bufDelta ring.Poly
}
// KeySwitchShare is a type for the KeySwitch protocol shares.
@@ -26,35 +24,6 @@ type KeySwitchShare struct {
Value ring.Poly
}
// ShallowCopy creates a shallow copy of [KeySwitchProtocol] in which all the read-only data-structures are
// shared with the receiver and the temporary bufers are reallocated. The receiver and the returned
// [KeySwitchProtocol] can be used concurrently.
func (cks KeySwitchProtocol) ShallowCopy() KeySwitchProtocol {
prng, err := sampling.NewPRNG()
// Sanity check, this error should not happen.
if err != nil {
panic(err)
}
params := cks.params
Xe, err := ring.NewSampler(prng, cks.params.RingQ(), cks.noise, false)
// Sanity check, this error should not happen.
if err != nil {
panic(err)
}
return KeySwitchProtocol{
params: params,
noiseSampler: Xe,
buf: params.RingQ().NewPoly(),
bufDelta: params.RingQ().NewPoly(),
noise: cks.noise,
}
}
// KeySwitchCRP is a type for common reference polynomials in the KeySwitch protocol.
type KeySwitchCRP struct {
Value ring.Poly
@@ -92,8 +61,6 @@ func NewKeySwitchProtocol(params rlwe.ParameterProvider, noiseFlooding ring.Dist
panic(err)
}
cks.buf = cks.params.RingQ().NewPoly()
cks.bufDelta = cks.params.RingQ().NewPoly()
return cks, nil
}
@@ -122,19 +89,21 @@ func (cks KeySwitchProtocol) GenShare(skInput, skOutput *rlwe.SecretKey, ct *rlw
shareOut.Value.Resize(levelQ)
ringQ := cks.params.RingQ().AtLevel(levelQ)
buffDelta := ringQ.NewPoly()
buffQ := cks.params.RingQ().NewPoly()
ringQ.Sub(skInput.Value.Q, skOutput.Value.Q, cks.bufDelta)
ringQ.Sub(skInput.Value.Q, skOutput.Value.Q, buffDelta)
var c1NTT ring.Poly
if !ct.IsNTT {
ringQ.NTTLazy(ct.Value[1], cks.buf)
c1NTT = cks.buf
ringQ.NTTLazy(ct.Value[1], buffQ)
c1NTT = buffQ
} else {
c1NTT = ct.Value[1]
}
// c1NTT * (skIn - skOut)
ringQ.MulCoeffsMontgomeryLazy(c1NTT, cks.bufDelta, shareOut.Value)
ringQ.MulCoeffsMontgomeryLazy(c1NTT, buffDelta, shareOut.Value)
if !ct.IsNTT {
// InvNTT(c1NTT * (skIn - skOut)) + e
@@ -142,9 +111,9 @@ func (cks KeySwitchProtocol) GenShare(skInput, skOutput *rlwe.SecretKey, ct *rlw
cks.noiseSampler.AtLevel(levelQ).ReadAndAdd(shareOut.Value)
} else {
// c1NTT * (skIn - skOut) + e
cks.noiseSampler.AtLevel(levelQ).Read(cks.buf)
ringQ.NTT(cks.buf, cks.buf)
ringQ.Add(shareOut.Value, cks.buf, shareOut.Value)
cks.noiseSampler.AtLevel(levelQ).Read(buffQ)
ringQ.NTT(buffQ, buffQ)
ringQ.Add(shareOut.Value, buffQ, shareOut.Value)
}
}
+3 -3
View File
@@ -184,8 +184,8 @@ func testEncToShares(tc *testContext, t *testing.T) {
P[i].s2e, err = NewShareToEncProtocol(params, params.Xe())
require.NoError(t, err)
} else {
P[i].e2s = P[0].e2s.ShallowCopy()
P[i].s2e = P[0].s2e.ShallowCopy()
P[i].e2s = P[0].e2s
P[i].s2e = P[0].s2e
}
P[i].sk = tc.sk0Shards[i]
@@ -264,7 +264,7 @@ func testRefresh(tc *testContext, t *testing.T) {
p.RefreshProtocol, err = NewRefreshProtocol(tc.params, tc.params.Xe())
require.NoError(t, err)
} else {
p.RefreshProtocol = RefreshParties[0].RefreshProtocol.ShallowCopy()
p.RefreshProtocol = RefreshParties[0].RefreshProtocol
}
p.s = sk0Shards[i]
-7
View File
@@ -12,13 +12,6 @@ type RefreshProtocol struct {
MaskedTransformProtocol
}
// ShallowCopy creates a shallow copy of [RefreshProtocol] in which all the read-only data-structures are
// shared with the receiver and the temporary buffers are reallocated. The receiver and the returned
// [RefreshProtocol] can be used concurrently.
func (rfp *RefreshProtocol) ShallowCopy() RefreshProtocol {
return RefreshProtocol{rfp.MaskedTransformProtocol.ShallowCopy()}
}
// NewRefreshProtocol creates a new Refresh protocol instance.
func NewRefreshProtocol(params bgv.Parameters, noiseFlooding ring.DistributionParameters) (rfp RefreshProtocol, err error) {
rfp = RefreshProtocol{}
+17 -58
View File
@@ -20,40 +20,13 @@ type EncToShareProtocol struct {
maskSampler *ring.UniformSampler
encoder *bgv.Encoder
zero *rlwe.SecretKey
tmpPlaintextRingT ring.Poly
tmpPlaintextRingQ ring.Poly
zero *rlwe.SecretKey
}
func NewAdditiveShare(params bgv.Parameters) multiparty.AdditiveShare {
return multiparty.NewAdditiveShare(params.RingT())
}
// ShallowCopy creates a shallow copy of [EncToShareProtocol] in which all the read-only data-structures are
// shared with the receiver and the temporary buffers are reallocated. The receiver and the returned
// [EncToShareProtocol] can be used concurrently.
func (e2s EncToShareProtocol) ShallowCopy() EncToShareProtocol {
params := e2s.params
prng, err := sampling.NewPRNG()
// Sanity check, this error should not happen.
if err != nil {
panic(err)
}
return EncToShareProtocol{
KeySwitchProtocol: e2s.KeySwitchProtocol.ShallowCopy(),
params: e2s.params,
maskSampler: ring.NewUniformSampler(prng, params.RingT()),
encoder: e2s.encoder.ShallowCopy(),
zero: e2s.zero,
tmpPlaintextRingT: params.RingT().NewPoly(),
tmpPlaintextRingQ: params.RingQ().NewPoly(),
}
}
// NewEncToShareProtocol creates a new EncToShareProtocol struct from the passed bgv.Parameters.
func NewEncToShareProtocol(params bgv.Parameters, noiseFlooding ring.DistributionParameters) (EncToShareProtocol, error) {
e2s := EncToShareProtocol{}
@@ -75,8 +48,6 @@ func NewEncToShareProtocol(params bgv.Parameters, noiseFlooding ring.Distributio
e2s.maskSampler = ring.NewUniformSampler(prng, params.RingT())
e2s.zero = rlwe.NewSecretKey(params.Parameters)
e2s.tmpPlaintextRingQ = params.RingQ().NewPoly()
e2s.tmpPlaintextRingT = params.RingT().NewPoly()
return e2s, nil
}
@@ -92,10 +63,11 @@ func (e2s EncToShareProtocol) GenShare(sk *rlwe.SecretKey, ct *rlwe.Ciphertext,
level := utils.Min(ct.Level(), publicShareOut.Value.Level())
e2s.KeySwitchProtocol.GenShare(sk, e2s.zero, ct, publicShareOut)
e2s.maskSampler.Read(secretShareOut.Value)
e2s.encoder.RingT2Q(level, true, secretShareOut.Value, e2s.tmpPlaintextRingQ)
ringQ := e2s.params.RingQ().AtLevel(level)
ringQ.NTT(e2s.tmpPlaintextRingQ, e2s.tmpPlaintextRingQ)
ringQ.Sub(publicShareOut.Value, e2s.tmpPlaintextRingQ, publicShareOut.Value)
buffQ := ringQ.NewPoly()
e2s.encoder.RingT2Q(level, true, secretShareOut.Value, buffQ)
ringQ.NTT(buffQ, buffQ)
ringQ.Sub(publicShareOut.Value, buffQ, publicShareOut.Value)
}
// GetShare is the final step of the encryption-to-share protocol. It performs the masked decryption of the target ciphertext followed by a
@@ -106,13 +78,15 @@ func (e2s EncToShareProtocol) GenShare(sk *rlwe.SecretKey, ct *rlwe.Ciphertext,
func (e2s EncToShareProtocol) GetShare(secretShare *multiparty.AdditiveShare, aggregatePublicShare multiparty.KeySwitchShare, ct *rlwe.Ciphertext, secretShareOut *multiparty.AdditiveShare) {
level := utils.Min(ct.Level(), aggregatePublicShare.Value.Level())
ringQ := e2s.params.RingQ().AtLevel(level)
ringQ.Add(aggregatePublicShare.Value, ct.Value[0], e2s.tmpPlaintextRingQ)
ringQ.INTT(e2s.tmpPlaintextRingQ, e2s.tmpPlaintextRingQ)
e2s.encoder.RingQ2T(level, true, e2s.tmpPlaintextRingQ, e2s.tmpPlaintextRingT)
buffQ := ringQ.NewPoly()
buffT := e2s.params.RingT().NewPoly()
ringQ.Add(aggregatePublicShare.Value, ct.Value[0], buffQ)
ringQ.INTT(buffQ, buffQ)
e2s.encoder.RingQ2T(level, true, buffQ, buffT)
if secretShare != nil {
e2s.params.RingT().Add(secretShare.Value, e2s.tmpPlaintextRingT, secretShareOut.Value)
e2s.params.RingT().Add(secretShare.Value, buffT, secretShareOut.Value)
} else {
secretShareOut.Value.Copy(e2s.tmpPlaintextRingT)
secretShareOut.Value.Copy(buffT)
}
}
@@ -124,8 +98,7 @@ type ShareToEncProtocol struct {
encoder *bgv.Encoder
zero *rlwe.SecretKey
tmpPlaintextRingQ ring.Poly
zero *rlwe.SecretKey
}
// NewShareToEncProtocol creates a new ShareToEncProtocol struct from the passed integer parameters.
@@ -140,7 +113,6 @@ func NewShareToEncProtocol(params bgv.Parameters, noiseFlooding ring.Distributio
s2e.params = params
s2e.encoder = bgv.NewEncoder(params)
s2e.zero = rlwe.NewSecretKey(params.Parameters)
s2e.tmpPlaintextRingQ = params.RingQ().NewPoly()
return s2e, nil
}
@@ -149,20 +121,6 @@ func (s2e ShareToEncProtocol) AllocateShare(level int) (share multiparty.KeySwit
return s2e.KeySwitchProtocol.AllocateShare(level)
}
// ShallowCopy creates a shallow copy of [ShareToEncProtocol] in which all the read-only data-structures are
// shared with the receiver and the temporary buffers are reallocated. The receiver and the returned
// ShareToEncProtocol can be used concurrently.
func (s2e ShareToEncProtocol) ShallowCopy() ShareToEncProtocol {
params := s2e.params
return ShareToEncProtocol{
KeySwitchProtocol: s2e.KeySwitchProtocol.ShallowCopy(),
encoder: s2e.encoder.ShallowCopy(),
params: params,
zero: s2e.zero,
tmpPlaintextRingQ: params.RingQ().NewPoly(),
}
}
// GenShare generates a party's in the shares-to-encryption protocol given the party's secret-key share `sk`, a common
// polynomial sampled from the CRS `crp` and the party's secret share of the message.
func (s2e ShareToEncProtocol) GenShare(sk *rlwe.SecretKey, crp multiparty.KeySwitchCRP, secretShare multiparty.AdditiveShare, c0ShareOut *multiparty.KeySwitchShare) (err error) {
@@ -176,10 +134,11 @@ func (s2e ShareToEncProtocol) GenShare(sk *rlwe.SecretKey, crp multiparty.KeySwi
ct.MetaData = &rlwe.MetaData{}
ct.MetaData.IsNTT = true
s2e.KeySwitchProtocol.GenShare(s2e.zero, sk, ct, c0ShareOut)
s2e.encoder.RingT2Q(crp.Value.Level(), true, secretShare.Value, s2e.tmpPlaintextRingQ)
ringQ := s2e.params.RingQ().AtLevel(crp.Value.Level())
ringQ.NTT(s2e.tmpPlaintextRingQ, s2e.tmpPlaintextRingQ)
ringQ.Add(c0ShareOut.Value, s2e.tmpPlaintextRingQ, c0ShareOut.Value)
buffQ := ringQ.NewPoly()
s2e.encoder.RingT2Q(crp.Value.Level(), true, secretShare.Value, buffQ)
ringQ.NTT(buffQ, buffQ)
ringQ.Add(c0ShareOut.Value, buffQ, c0ShareOut.Value)
return
}
+18 -35
View File
@@ -14,25 +14,6 @@ import (
type MaskedTransformProtocol struct {
e2s EncToShareProtocol
s2e ShareToEncProtocol
tmpPt ring.Poly
tmpMask ring.Poly
tmpMaskPerm ring.Poly
}
// ShallowCopy creates a shallow copy of [MaskedTransformProtocol] in which all the read-only data-structures are
// shared with the receiver and the temporary buffers are reallocated. The receiver and the returned
// [MaskedTransformProtocol] can be used concurrently.
func (rfp MaskedTransformProtocol) ShallowCopy() MaskedTransformProtocol {
params := rfp.e2s.params
return MaskedTransformProtocol{
e2s: rfp.e2s.ShallowCopy(),
s2e: rfp.s2e.ShallowCopy(),
tmpPt: params.RingQ().NewPoly(),
tmpMask: params.RingT().NewPoly(),
tmpMaskPerm: params.RingT().NewPoly(),
}
}
// MaskedTransformFunc is a struct containing a user-defined in-place function that can be applied to masked integer plaintexts, as a part of the
@@ -67,9 +48,6 @@ func NewMaskedTransformProtocol(paramsIn, paramsOut bgv.Parameters, noiseFloodin
return
}
rfp.tmpPt = paramsOut.RingQ().NewPoly()
rfp.tmpMask = paramsIn.RingT().NewPoly()
rfp.tmpMaskPerm = paramsIn.RingT().NewPoly()
return
}
@@ -96,8 +74,9 @@ func (rfp MaskedTransformProtocol) GenShare(skIn, skOut *rlwe.SecretKey, ct *rlw
return fmt.Errorf("cannot GenShare: crs level must be equal to ShareToEncShare")
}
rfp.e2s.GenShare(skIn, ct, &multiparty.AdditiveShare{Value: rfp.tmpMask}, &shareOut.EncToShareShare)
mask := rfp.tmpMask
tmpMask := rfp.e2s.params.RingT().NewPoly()
rfp.e2s.GenShare(skIn, ct, &multiparty.AdditiveShare{Value: tmpMask}, &shareOut.EncToShareShare)
mask := tmpMask
if transform != nil {
coeffs := make([]uint64, len(mask.Coeffs[0]))
@@ -111,15 +90,16 @@ func (rfp MaskedTransformProtocol) GenShare(skIn, skOut *rlwe.SecretKey, ct *rlw
transform.Func(coeffs)
tmpMaskPerm := rfp.e2s.params.RingT().NewPoly()
if transform.Encode {
if err := rfp.s2e.encoder.EncodeRingT(coeffs, ct.Scale, rfp.tmpMaskPerm); err != nil {
if err := rfp.s2e.encoder.EncodeRingT(coeffs, ct.Scale, tmpMaskPerm); err != nil {
return fmt.Errorf("cannot GenShare: %w", err)
}
} else {
copy(rfp.tmpMaskPerm.Coeffs[0], coeffs)
copy(tmpMaskPerm.Coeffs[0], coeffs)
}
mask = rfp.tmpMaskPerm
mask = tmpMaskPerm
}
// Stores the ciphertext metadata on the public share
@@ -162,8 +142,9 @@ func (rfp MaskedTransformProtocol) Transform(ct *rlwe.Ciphertext, transform *Mas
return fmt.Errorf("cannot Transform: crs level and s2e level must be the same")
}
rfp.e2s.GetShare(nil, share.EncToShareShare, ct, &multiparty.AdditiveShare{Value: rfp.tmpMask}) // tmpMask RingT(m - sum M_i)
mask := rfp.tmpMask
tmpMask := rfp.e2s.params.RingT().NewPoly()
rfp.e2s.GetShare(nil, share.EncToShareShare, ct, &multiparty.AdditiveShare{Value: tmpMask}) // tmpMask RingT(m - sum M_i)
mask := tmpMask
if transform != nil {
coeffs := make([]uint64, len(mask.Coeffs[0]))
@@ -177,22 +158,24 @@ func (rfp MaskedTransformProtocol) Transform(ct *rlwe.Ciphertext, transform *Mas
transform.Func(coeffs)
tmpMaskPerm := rfp.e2s.params.RingT().NewPoly()
if transform.Encode {
if err := rfp.s2e.encoder.EncodeRingT(coeffs, ciphertextOut.Scale, rfp.tmpMaskPerm); err != nil {
if err := rfp.s2e.encoder.EncodeRingT(coeffs, ciphertextOut.Scale, tmpMaskPerm); err != nil {
return fmt.Errorf("cannot Transform: %w", err)
}
} else {
copy(rfp.tmpMaskPerm.Coeffs[0], coeffs)
copy(tmpMaskPerm.Coeffs[0], coeffs)
}
mask = rfp.tmpMaskPerm
mask = tmpMaskPerm
}
ciphertextOut.Resize(ciphertextOut.Degree(), maxLevel)
rfp.s2e.encoder.RingT2Q(maxLevel, true, mask, rfp.tmpPt)
rfp.s2e.params.RingQ().AtLevel(maxLevel).NTT(rfp.tmpPt, rfp.tmpPt)
rfp.s2e.params.RingQ().AtLevel(maxLevel).Add(rfp.tmpPt, share.ShareToEncShare.Value, ciphertextOut.Value[0])
tmpPt := rfp.s2e.params.RingQ().AtLevel(maxLevel).NewPoly()
rfp.s2e.encoder.RingT2Q(maxLevel, true, mask, tmpPt)
rfp.s2e.params.RingQ().AtLevel(maxLevel).NTT(tmpPt, tmpPt)
rfp.s2e.params.RingQ().AtLevel(maxLevel).Add(tmpPt, share.ShareToEncShare.Value, ciphertextOut.Value[0])
return rfp.s2e.GetEncryption(multiparty.KeySwitchShare{Value: ciphertextOut.Value[0]}, crs, ciphertextOut)
}
+1 -1
View File
@@ -435,7 +435,7 @@ func testRefreshParameterized(tc *testContext, paramsOut ckks.Parameters, skOut
t.Fail()
}
} else {
p.MaskedLinearTransformationProtocol = RefreshParties[0].MaskedLinearTransformationProtocol.ShallowCopy()
p.MaskedLinearTransformationProtocol = RefreshParties[0].MaskedLinearTransformationProtocol
}
p.sIn = tc.sk0Shards[i]
-7
View File
@@ -22,13 +22,6 @@ func NewRefreshProtocol(params ckks.Parameters, prec uint, noise ring.Distributi
return rfp, err
}
// ShallowCopy creates a shallow copy of [RefreshProtocol] in which all the read-only data-structures are
// shared with the receiver and the temporary buffers are reallocated. The receiver and the returned
// [RefreshProtocol] can be used concurrently.
func (rfp RefreshProtocol) ShallowCopy() RefreshProtocol {
return RefreshProtocol{rfp.MaskedLinearTransformationProtocol.ShallowCopy()}
}
// AllocateShare allocates the shares of the PermuteProtocol
func (rfp RefreshProtocol) AllocateShare(inputLevel, outputLevel int) multiparty.RefreshShare {
return rfp.MaskedLinearTransformationProtocol.AllocateShare(inputLevel, outputLevel)
+27 -62
View File
@@ -18,10 +18,8 @@ import (
type EncToShareProtocol struct {
multiparty.KeySwitchProtocol
params ckks.Parameters
zero *rlwe.SecretKey
maskBigint []*big.Int
buff ring.Poly
params ckks.Parameters
zero *rlwe.SecretKey
}
func NewAdditiveShare(params ckks.Parameters, logSlots int) multiparty.AdditiveShareBigint {
@@ -34,25 +32,6 @@ func NewAdditiveShare(params ckks.Parameters, logSlots int) multiparty.AdditiveS
return multiparty.NewAdditiveShareBigint(nValues)
}
// ShallowCopy creates a shallow copy of [EncToShareProtocol] in which all the read-only data-structures are
// shared with the receiver and the temporary buffers are reallocated. The receiver and the returned
// [EncToShareProtocol] can be used concurrently.
func (e2s EncToShareProtocol) ShallowCopy() EncToShareProtocol {
maskBigint := make([]*big.Int, len(e2s.maskBigint))
for i := range maskBigint {
maskBigint[i] = new(big.Int)
}
return EncToShareProtocol{
KeySwitchProtocol: e2s.KeySwitchProtocol.ShallowCopy(),
params: e2s.params,
zero: e2s.zero,
maskBigint: maskBigint,
buff: e2s.params.RingQ().NewPoly(),
}
}
// NewEncToShareProtocol creates a new EncToShareProtocol struct from the passed parameters.
func NewEncToShareProtocol(params ckks.Parameters, noise ring.DistributionParameters) (EncToShareProtocol, error) {
e2s := EncToShareProtocol{}
@@ -64,11 +43,6 @@ func NewEncToShareProtocol(params ckks.Parameters, noise ring.DistributionParame
e2s.params = params
e2s.zero = rlwe.NewSecretKey(params.Parameters)
e2s.maskBigint = make([]*big.Int, params.N())
for i := range e2s.maskBigint {
e2s.maskBigint[i] = new(big.Int)
}
e2s.buff = e2s.params.RingQ().NewPoly()
return e2s, nil
}
@@ -117,15 +91,16 @@ func (e2s EncToShareProtocol) GenShare(sk *rlwe.SecretKey, logBound uint, ct *rl
dslots *= 2
}
maskBigint := make([]*big.Int, dslots)
// Generate the mask in Z[Y] for Y = X^{N/(2*slots)}
for i := 0; i < dslots; i++ {
e2s.maskBigint[i] = bignum.RandInt(prng, bound)
sign = e2s.maskBigint[i].Cmp(boundHalf)
maskBigint[i] = bignum.RandInt(prng, bound)
sign = maskBigint[i].Cmp(boundHalf)
if sign == 1 || sign == 0 {
e2s.maskBigint[i].Sub(e2s.maskBigint[i], bound)
maskBigint[i].Sub(maskBigint[i], bound)
}
secretShareOut.Value[i].Set(e2s.maskBigint[i])
secretShareOut.Value[i].Set(maskBigint[i])
}
// Encrypt the mask
@@ -133,11 +108,12 @@ func (e2s EncToShareProtocol) GenShare(sk *rlwe.SecretKey, logBound uint, ct *rl
e2s.KeySwitchProtocol.GenShare(sk, e2s.zero, ct, publicShareOut)
// Positional -> RNS -> NTT
ringQ.SetCoefficientsBigint(secretShareOut.Value[:dslots], e2s.buff)
rlwe.NTTSparseAndMontgomery(ringQ, ct.MetaData, e2s.buff)
buffQ := ringQ.NewPoly()
ringQ.SetCoefficientsBigint(secretShareOut.Value[:dslots], buffQ)
rlwe.NTTSparseAndMontgomery(ringQ, ct.MetaData, buffQ)
// Subtracts the mask to the encryption of zero
ringQ.Sub(publicShareOut.Value, e2s.buff, publicShareOut.Value)
ringQ.Sub(publicShareOut.Value, buffQ, publicShareOut.Value)
return
}
@@ -153,11 +129,12 @@ func (e2s EncToShareProtocol) GetShare(secretShare *multiparty.AdditiveShareBigi
ringQ := e2s.params.RingQ().AtLevel(levelQ)
buffQ := ringQ.NewPoly()
// Adds the decryption share on the ciphertext and stores the result in a buff
ringQ.Add(aggregatePublicShare.Value, ct.Value[0], e2s.buff)
ringQ.Add(aggregatePublicShare.Value, ct.Value[0], buffQ)
// INTT -> RNS -> Positional
ringQ.INTT(e2s.buff, e2s.buff)
ringQ.INTT(buffQ, buffQ)
dslots := ct.Slots()
if ringQ.Type() == ring.Standard {
@@ -166,19 +143,23 @@ func (e2s EncToShareProtocol) GetShare(secretShare *multiparty.AdditiveShareBigi
gap := ringQ.N() / dslots
ringQ.PolyToBigintCentered(e2s.buff, gap, e2s.maskBigint)
maskBigint := make([]*big.Int, ringQ.N())
for i := range maskBigint {
maskBigint[i] = new(big.Int)
}
ringQ.PolyToBigintCentered(buffQ, gap, maskBigint)
// Subtracts the last mask
if secretShare != nil {
a := secretShareOut.Value
b := e2s.maskBigint
b := maskBigint
c := secretShare.Value
for i := range secretShareOut.Value[:dslots] {
a[i].Add(c[i], b[i])
}
} else {
a := secretShareOut.Value
b := e2s.maskBigint
b := maskBigint
for i := range secretShareOut.Value[:dslots] {
a[i].Set(b[i])
}
@@ -189,23 +170,8 @@ func (e2s EncToShareProtocol) GetShare(secretShare *multiparty.AdditiveShareBigi
// required by the shares-to-encryption protocol.
type ShareToEncProtocol struct {
multiparty.KeySwitchProtocol
params ckks.Parameters
tmp ring.Poly
ssBigint []*big.Int
zero *rlwe.SecretKey
}
// ShallowCopy creates a shallow copy of [ShareToEncProtocol] in which all the read-only data-structures are
// shared with the receiver and the temporary buffers are reallocated. The receiver and the returned
// [ShareToEncProtocol] can be used concurrently.
func (s2e ShareToEncProtocol) ShallowCopy() ShareToEncProtocol {
return ShareToEncProtocol{
KeySwitchProtocol: s2e.KeySwitchProtocol.ShallowCopy(),
params: s2e.params,
tmp: s2e.params.RingQ().NewPoly(),
ssBigint: make([]*big.Int, s2e.params.N()),
zero: s2e.zero,
}
params ckks.Parameters
zero *rlwe.SecretKey
}
// NewShareToEncProtocol creates a new ShareToEncProtocol struct from the passed parameters.
@@ -218,8 +184,6 @@ func NewShareToEncProtocol(params ckks.Parameters, noise ring.DistributionParame
}
s2e.params = params
s2e.tmp = s2e.params.RingQ().NewPoly()
s2e.ssBigint = make([]*big.Int, s2e.params.N())
s2e.zero = rlwe.NewSecretKey(params.Parameters)
return s2e, nil
}
@@ -251,12 +215,13 @@ func (s2e ShareToEncProtocol) GenShare(sk *rlwe.SecretKey, crs multiparty.KeySwi
dslots *= 2
}
buffQ := ringQ.NewPoly()
// Positional -> RNS -> NTT
ringQ.SetCoefficientsBigint(secretShare.Value[:dslots], s2e.tmp)
ringQ.SetCoefficientsBigint(secretShare.Value[:dslots], buffQ)
rlwe.NTTSparseAndMontgomery(ringQ, metadata, s2e.tmp)
rlwe.NTTSparseAndMontgomery(ringQ, metadata, buffQ)
ringQ.Add(c0ShareOut.Value, s2e.tmp, c0ShareOut.Value)
ringQ.Add(c0ShareOut.Value, buffQ, c0ShareOut.Value)
return
}
+9 -30
View File
@@ -23,30 +23,9 @@ type MaskedLinearTransformationProtocol struct {
defaultScale *big.Int
prec uint
mask []*big.Int
encoder *ckks.Encoder
}
// ShallowCopy creates a shallow copy of [MaskedLinearTransformationProtocol] in which all the read-only data-structures are
// shared with the receiver and the temporary buffers are reallocated. The receiver and the returned
// [MaskedLinearTransformationProtocol] can be used concurrently.
func (mltp MaskedLinearTransformationProtocol) ShallowCopy() MaskedLinearTransformationProtocol {
mask := make([]*big.Int, mltp.e2s.params.N())
for i := range mask {
mask[i] = new(big.Int)
}
return MaskedLinearTransformationProtocol{
e2s: mltp.e2s.ShallowCopy(),
s2e: mltp.s2e.ShallowCopy(),
prec: mltp.prec,
defaultScale: mltp.defaultScale,
mask: mask,
encoder: mltp.encoder.ShallowCopy(),
}
}
// WithParams creates a shallow copy of the target [MaskedLinearTransformationProtocol] but with new output parameters.
// The expected input parameters remain unchanged.
func (mltp MaskedLinearTransformationProtocol) WithParams(paramsOut ckks.Parameters) MaskedLinearTransformationProtocol {
@@ -68,11 +47,10 @@ func (mltp MaskedLinearTransformationProtocol) WithParams(paramsOut ckks.Paramet
defaultScale, _ := new(big.Float).SetPrec(mltp.prec).Set(&scale).Int(nil)
return MaskedLinearTransformationProtocol{
e2s: mltp.e2s.ShallowCopy(),
e2s: mltp.e2s,
s2e: s2e,
prec: mltp.prec,
defaultScale: defaultScale,
mask: mask,
encoder: ckks.NewEncoder(paramsOut, mltp.prec),
}
}
@@ -118,11 +96,6 @@ func NewMaskedLinearTransformationProtocol(paramsIn, paramsOut ckks.Parameters,
mltp.defaultScale, _ = new(big.Float).SetPrec(prec).Set(&scale).Int(nil)
mltp.mask = make([]*big.Int, paramsIn.N())
for i := range mltp.mask {
mltp.mask[i] = new(big.Int)
}
mltp.encoder = ckks.NewEncoder(paramsOut, prec)
return
@@ -178,7 +151,10 @@ func (mltp MaskedLinearTransformationProtocol) GenShare(skIn, skOut *rlwe.Secret
dslots *= 2
}
mask := mltp.mask[:dslots]
mask := make([]*big.Int, dslots)
for i := range mask {
mask[i] = new(big.Int)
}
// Generates the decryption share
// Returns [M_i] on mltp.tmpMask and [a*s_i -M_i + e] on EncToShareShare
@@ -251,7 +227,10 @@ func (mltp MaskedLinearTransformationProtocol) Transform(ct *rlwe.Ciphertext, tr
dslots *= 2
}
mask := mltp.mask[:dslots]
mask := make([]*big.Int, dslots)
for i := range mask {
mask[i] = new(big.Int)
}
// Returns -sum(M_i) + x (outside of the NTT domain)
mltp.e2s.GetShare(nil, share.EncToShareShare, ct, &multiparty.AdditiveShareBigint{Value: mask})
+6 -6
View File
@@ -139,7 +139,7 @@ func testPublicKeyGenProtocol(tc *testContext, levelQ, levelP, bpw2 int, t *test
if i == 0 {
ckg[i] = NewPublicKeyGenProtocol(params)
} else {
ckg[i] = ckg[0].ShallowCopy()
ckg[i] = ckg[0]
}
}
@@ -182,7 +182,7 @@ func testRelinearizationKeyGenProtocol(tc *testContext, levelQ, levelP, bpw2 int
if i == 0 {
rkg[i] = NewRelinearizationKeyGenProtocol(params)
} else {
rkg[i] = rkg[0].ShallowCopy()
rkg[i] = rkg[0]
}
}
@@ -238,7 +238,7 @@ func testEvaluationKeyGenProtocol(tc *testContext, levelQ, levelP, bpw2 int, t *
if i == 0 {
evkg[i] = NewEvaluationKeyGenProtocol(params)
} else {
evkg[i] = evkg[0].ShallowCopy()
evkg[i] = evkg[0]
}
}
@@ -293,7 +293,7 @@ func testGaloisKeyGenProtocol(tc *testContext, levelQ, levelP, bpw2 int, t *test
if i == 0 {
gkg[i] = NewGaloisKeyGenProtocol(params)
} else {
gkg[i] = gkg[0].ShallowCopy()
gkg[i] = gkg[0]
}
}
@@ -344,7 +344,7 @@ func testKeySwitchProtocol(tc *testContext, levelQ, levelP, bpw2 int, t *testing
cks[i], err = NewKeySwitchProtocol(params, ring.DiscreteGaussian{Sigma: sigmaSmudging, Bound: 6 * sigmaSmudging})
require.NoError(t, err)
} else {
cks[i] = cks[0].ShallowCopy()
cks[i] = cks[0]
}
}
@@ -420,7 +420,7 @@ func testPublicKeySwitchProtocol(tc *testContext, levelQ, levelP, bpw2 int, t *t
pcks[i], err = NewPublicKeySwitchProtocol(params, ring.DiscreteGaussian{Sigma: sigmaSmudging, Bound: 6 * sigmaSmudging})
require.NoError(t, err)
} else {
pcks[i] = pcks[0].ShallowCopy()
pcks[i] = pcks[0]
}
}
+37 -37
View File
@@ -6,6 +6,7 @@ import (
"unsafe"
"github.com/tuneinsight/lattigo/v6/utils/bignum"
"github.com/tuneinsight/lattigo/v6/utils/structs"
)
// BasisExtender stores the necessary parameters for RNS basis extension.
@@ -17,9 +18,8 @@ type BasisExtender struct {
constantsPtoQ []ModUpConstants
modDownConstantsPtoQ [][]uint64
modDownConstantsQtoP [][]uint64
buffQ Poly
buffP Poly
poolQ *BufferPool
poolP *BufferPool
}
func genmodDownConstants(ringQ, ringP *Ring) (constants [][]uint64) {
@@ -72,8 +72,16 @@ func NewBasisExtender(ringQ, ringP *Ring) (be *BasisExtender) {
be.modDownConstantsPtoQ = genmodDownConstants(ringQ, ringP)
be.modDownConstantsQtoP = genmodDownConstants(ringP, ringQ)
be.buffQ = ringQ.NewPoly()
be.buffP = ringP.NewPoly()
backingPool := structs.NewSyncPoolUint64(ringQ.N())
be.poolQ = NewPool(be.ringQ, backingPool)
backingPoolP := backingPool
// we use the same backing pool only if ringQ and ringP have the same dimension
if ringQ.N() != ringP.N() {
backingPoolP = structs.NewSyncPoolUint64(ringP.N())
}
be.poolP = NewPool(be.ringP, backingPoolP)
return
}
@@ -163,25 +171,6 @@ func GenModUpConstants(Q, P []uint64) ModUpConstants {
return ModUpConstants{qoverqiinvqi: qoverqiinvqi, qoverqimodp: qoverqimodp, vtimesqmodp: vtimesqmodp}
}
// ShallowCopy creates a shallow copy of this basis extender in which the read-only data-structures are
// shared with the receiver.
func (be *BasisExtender) ShallowCopy() *BasisExtender {
if be == nil {
return nil
}
return &BasisExtender{
ringQ: be.ringQ,
ringP: be.ringP,
constantsQtoP: be.constantsQtoP,
constantsPtoQ: be.constantsPtoQ,
modDownConstantsQtoP: be.modDownConstantsQtoP,
modDownConstantsPtoQ: be.modDownConstantsPtoQ,
buffQ: be.ringQ.NewPoly(),
buffP: be.ringP.NewPoly(),
}
}
// ModUpQtoP extends the RNS basis of a polynomial from Q to QP.
// Given a polynomial with coefficients in basis {Q0,Q1....Qlevel},
// it extends its basis from {Q0,Q1....Qlevel} to {Q0,Q1....Qlevel,P0,P1...Pj}
@@ -189,12 +178,13 @@ func (be *BasisExtender) ModUpQtoP(levelQ, levelP int, polQ, polP Poly) {
ringQ := be.ringQ.AtLevel(levelQ)
ringP := be.ringP.AtLevel(levelP)
buffQ := be.buffQ
buffQ := be.poolQ.GetBuffPoly()
defer be.poolQ.RecycleBuffPoly(buffQ)
QHalf := bignum.NewInt(ringQ.ModulusAtLevel[levelQ])
QHalf.Rsh(QHalf, 1)
ringQ.AddScalarBigint(polQ, QHalf, buffQ)
ringQ.AddScalarBigint(polQ, QHalf, *buffQ)
ModUpExact(buffQ.Coeffs[:levelQ+1], polP.Coeffs[:levelP+1], be.ringQ, be.ringP, be.constantsQtoP[levelQ])
ringP.SubScalarBigint(polP, QHalf, polP)
}
@@ -206,12 +196,14 @@ func (be *BasisExtender) ModUpPtoQ(levelP, levelQ int, polP, polQ Poly) {
ringQ := be.ringQ.AtLevel(levelQ)
ringP := be.ringP.AtLevel(levelP)
buffP := be.buffP
poolP := be.poolP.AtLevel(levelP)
buffP := poolP.AtLevel(levelP).GetBuffPoly()
defer poolP.RecycleBuffPoly(buffP)
PHalf := bignum.NewInt(ringP.ModulusAtLevel[levelP])
PHalf.Rsh(PHalf, 1)
ringP.AddScalarBigint(polP, PHalf, buffP)
ringP.AddScalarBigint(polP, PHalf, *buffP)
ModUpExact(buffP.Coeffs[:levelP+1], polQ.Coeffs[:levelQ+1], be.ringP, be.ringQ, be.constantsPtoQ[levelP])
ringQ.SubScalarBigint(polQ, PHalf, polQ)
}
@@ -223,10 +215,12 @@ func (be *BasisExtender) ModUpPtoQ(levelP, levelQ int, polP, polQ Poly) {
func (be *BasisExtender) ModDownQPtoQ(levelQ, levelP int, p1Q, p1P, p2Q Poly) {
ringQ := be.ringQ.AtLevel(levelQ)
poolQ := be.poolQ.AtLevel(levelQ)
modDownConstants := be.modDownConstantsPtoQ[levelP]
buffQ := be.buffQ
buffQ := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(buffQ)
be.ModUpPtoQ(levelP, levelQ, p1P, buffQ)
be.ModUpPtoQ(levelP, levelQ, p1P, *buffQ)
for i, s := range ringQ.SubRings[:levelQ+1] {
s.SubThenMulScalarMontgomeryTwoModulus(buffQ.Coeffs[i], p1Q.Coeffs[i], s.Modulus-modDownConstants[i], p2Q.Coeffs[i])
@@ -241,14 +235,18 @@ func (be *BasisExtender) ModDownQPtoQ(levelQ, levelP int, p1Q, p1P, p2Q Poly) {
func (be *BasisExtender) ModDownQPtoQNTT(levelQ, levelP int, p1Q, p1P, p2Q Poly) {
ringQ := be.ringQ.AtLevel(levelQ)
poolQ := be.poolQ.AtLevel(levelQ)
ringP := be.ringP.AtLevel(levelP)
poolP := be.poolP.AtLevel(levelP)
modDownConstants := be.modDownConstantsPtoQ[levelP]
buffP := be.buffP
buffQ := be.buffQ
buffP := poolP.GetBuffPoly()
defer poolP.RecycleBuffPoly(buffP)
buffQ := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(buffQ)
ringP.INTTLazy(p1P, buffP)
be.ModUpPtoQ(levelP, levelQ, buffP, buffQ)
ringQ.NTTLazy(buffQ, buffQ)
ringP.INTTLazy(p1P, *buffP)
be.ModUpPtoQ(levelP, levelQ, *buffP, *buffQ)
ringQ.NTTLazy(*buffQ, *buffQ)
// Finally, for each level of p1 (and the buffer since they now share the same basis) we compute p2 = (P^-1) * (p1 - buff) mod Q
for i, s := range ringQ.SubRings[:levelQ+1] {
@@ -264,10 +262,12 @@ func (be *BasisExtender) ModDownQPtoQNTT(levelQ, levelP int, p1Q, p1P, p2Q Poly)
func (be *BasisExtender) ModDownQPtoP(levelQ, levelP int, p1Q, p1P, p2P Poly) {
ringP := be.ringP.AtLevel(levelP)
poolP := be.poolP.AtLevel(levelP)
modDownConstants := be.modDownConstantsQtoP[levelQ]
buffP := be.buffP
buffP := poolP.GetBuffPoly()
defer poolP.RecycleBuffPoly(buffP)
be.ModUpQtoP(levelQ, levelP, p1Q, buffP)
be.ModUpQtoP(levelQ, levelP, p1Q, *buffP)
// Finally, for each level of p1 (and buff since they now share the same basis) we compute p2 = (P^-1) * (p1 - buff) mod Q
for i, s := range ringP.SubRings[:levelP+1] {
+19
View File
@@ -14,6 +14,25 @@ type Poly struct {
Coeffs structs.Matrix[uint64]
}
// NewPolyFromUintPool returns a new [Poly], built from backing []uint64 arrays obtained from a pool.
// After use, the [Poly] should be recycled using the [RecyclePolyInUintPool] method.
func NewPolyFromUintPool(pool structs.BufferPool[*[]uint64], level int) (pol *Poly) {
coeffs := make([][]uint64, level+1)
for i := range coeffs {
coeffs[i] = *pool.Get()
}
return &Poly{Coeffs: coeffs}
}
// RecyclePolyInUintPool takes a reference to a [Poly] and recycles its backing []uint64 arrays
// (i.e. they are returned to a pool). The input [Poly] must not be used after calling this method.
func RecyclePolyInUintPool(pool structs.BufferPool[*[]uint64], pol *Poly) {
for i := range pol.Coeffs {
pool.Put(&pol.Coeffs[i])
}
pol = nil
}
// NewPoly creates a new polynomial with N coefficients set to zero and Level+1 moduli.
func NewPoly(N, Level int) (pol Poly) {
Coeffs := make([][]uint64, Level+1)
+61
View File
@@ -0,0 +1,61 @@
package ring
import (
"fmt"
"github.com/tuneinsight/lattigo/v6/utils/structs"
)
// BufferPool represents a pool of polys that can be used (concurrently) to instantiate temporary polynomials.
type BufferPool struct {
level int
bufferPool structs.BufferPool[*[]uint64]
}
// NewPool returns a new pool given a ring, and optionally a pool to draw the backing arrays from.
func NewPool(ring *Ring, pools ...structs.BufferPool[*[]uint64]) *BufferPool {
newPool := &BufferPool{level: ring.level}
switch lenPool := len(pools); lenPool {
case 0:
newPool.bufferPool = structs.NewSyncPoolUint64(ring.N())
case 1:
newPool.bufferPool = pools[0]
default:
panic(fmt.Errorf("the method takes at most 1 argument but %d were given", lenPool))
}
return newPool
}
// GetLevel returns the level of the polynomials obtained from the pool.
func (p BufferPool) GetLevel() int {
return p.level
}
// AtLevel returns a new pool from which polynomials at the given level can be drawn.
func (p BufferPool) AtLevel(level int) *BufferPool {
return &BufferPool{level, p.bufferPool}
}
// GetBuffUintArray returns a new []uint64 slice obtained from a pool.
// After use, the slice should be recycled using the [BufferPool.RecycleBuffUintArray] method.
func (p BufferPool) GetBuffUintArray() *[]uint64 {
return p.bufferPool.Get()
}
// RecycleBuffUintArray takes a reference to a []uint64 slice and puts it back in the pool.
func (p BufferPool) RecycleBuffUintArray(arr *[]uint64) {
p.bufferPool.Put(arr)
}
// GetBuffPoly returns a new [Poly], built from backing []uint64 arrays obtained from a pool.
// After use, the [Poly] should be recycled using the [BufferPool.RecycleBuffPoly] method.
func (p BufferPool) GetBuffPoly() *Poly {
return NewPolyFromUintPool(p.bufferPool, p.level)
}
// RecycleBuffPoly takes a reference to a [Poly] and recycles its backing []uint64 arrays
// (i.e. they are returned to a pool). The input [Poly] must not be used after calling this method.
func (p BufferPool) RecycleBuffPoly(pol *Poly) {
RecyclePolyInUintPool(p.bufferPool, pol)
}
+3
View File
@@ -78,6 +78,7 @@ type Ring struct {
RescaleConstants [][]uint64
level int
pool *BufferPool
}
// ConjugateInvariantRing returns the conjugate invariant ring of the receiver ring.
@@ -195,6 +196,7 @@ func (r Ring) AtLevel(level int) *Ring {
ModulusAtLevel: r.ModulusAtLevel,
RescaleConstants: r.RescaleConstants,
level: level,
pool: r.pool,
}
}
@@ -243,6 +245,7 @@ func (r Ring) BRedConstants() (BRC [][2]uint64) {
// NewRing creates a new RNS Ring with degree N and coefficient moduli Moduli with Standard NTT. N must be a power of two larger than 8. Moduli should be
// a non-empty []uint64 with distinct prime elements. All moduli must also be equal to 1 modulo 2*N.
// A pool implementing BufferPool[*[]uint64] will be stored in the returned Ring and will be used to efficiently instantiate large objects.
// An error is returned with a nil *Ring in the case of non NTT-enabling parameters.
func NewRing(N int, Moduli []uint64) (r *Ring, err error) {
return NewRingWithCustomNTT(N, Moduli, NewNumberTheoreticTransformerStandard, 2*N)
+4 -1
View File
@@ -163,6 +163,9 @@ func testNewRing(t *testing.T) {
require.NotNil(t, r)
require.NoError(t, err)
r, err = NewRing(16, []uint64{97}) // Passing NTT-enabling coeff modulus
require.NotNil(t, r)
require.NoError(t, err)
})
}
@@ -346,7 +349,7 @@ func testMarshalBinary(tc *testParams, t *testing.T) {
t.Fatal(err)
}
require.Equal(t, ringQTest, tc.ringQ)
require.Equal(t, ringQTest.parametersLiteral(), tc.ringQ.parametersLiteral())
})
t.Run(testString("MarshalBinary/Poly", tc.ringQ), func(t *testing.T) {
+91
View File
@@ -0,0 +1,91 @@
package ringqp
import (
"fmt"
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/utils/structs"
)
// BufferPool represents a pool of polys that can be used (concurrently) to instantiate temporary polynomials in RingQP.
type BufferPool struct {
*ring.BufferPool
PoolP *ring.BufferPool
}
// NewPool returns a new pool given a RingQP, and optionally a pool to draw the backing arrays from.
func NewPool(ringqp *Ring, pools ...structs.BufferPool[*[]uint64]) *BufferPool {
// If no backing pool is given, we create one here.
switch lenPool := len(pools); lenPool {
case 0:
pools = append(pools, structs.NewSyncPoolUint64(ringqp.N()))
case 1:
default:
panic(fmt.Errorf("the method takes at most 1 argument but %d were given", lenPool))
}
var poolQ, poolP *ring.BufferPool
if ringqp.RingQ != nil {
poolQ = ring.NewPool(ringqp.RingQ, pools...)
}
if ringqp.RingP != nil {
poolP = ring.NewPool(ringqp.RingP, pools...)
}
return &BufferPool{poolQ, poolP}
}
// AtLevel returns a new pool from which polynomials at the given levels can be drawn.
// The method accepts up to two arguments:
// Zero level: the polyomials are returned at level 0.
// One level: the polynomials in RingQ (resp. RingP) are returned at the given level (resp. level 0).
// Two levels: the polynomials in RingQ (resp. RingP) are returned at levels[0] (resp. levels[1]).
func (p BufferPool) AtLevel(levels ...int) *BufferPool {
var levelQ, levelP int
switch nbParams := len(levels); nbParams {
case 0:
case 1:
levelQ = levels[0]
case 2:
levelQ = levels[0]
levelP = levels[1]
default:
panic(fmt.Errorf("atlevel takes 2 parameters at most"))
}
var poolQ, poolP *ring.BufferPool
if p.BufferPool != nil {
poolQ = p.BufferPool.AtLevel(levelQ)
}
if p.PoolP != nil {
poolP = p.PoolP.AtLevel(levelP)
}
return &BufferPool{poolQ, poolP}
}
// GetBuffPolyQP returns a new [Poly], built from backing []uint64 arrays obtained from a pool.
// After use, the [Poly] should be recycled using the [BufferPool.RecycleBuffPolyQP] method.
func (p BufferPool) GetBuffPolyQP() *Poly {
var Q, P ring.Poly
if p.BufferPool != nil {
buffQ := p.GetBuffPoly()
Q = *buffQ
}
if p.PoolP != nil {
buffP := p.PoolP.GetBuffPoly()
P = *buffP
}
return &Poly{Q, P}
}
// RecycleBuffPolyQP takes a reference to a [Poly] and recycles its backing []uint64 arrays
// (i.e. they are returned to a pool). The input [Poly] must not be used after calling this method.
func (p BufferPool) RecycleBuffPolyQP(poly *Poly) {
if p.BufferPool != nil {
p.RecycleBuffPoly(&poly.Q)
}
if p.PoolP != nil {
p.PoolP.RecycleBuffPoly(&poly.P)
}
}
+1 -8
View File
@@ -11,6 +11,7 @@ type UniformSampler struct {
}
// NewUniformSampler instantiates a new UniformSampler from a given PRNG.
// WARNING: If the PRNG is deterministic/keyed (of type [sampling.KeyedPRNG]), *concurrent* calls to the sampler will not necessarily result in a deterministic output.
func NewUniformSampler(prng sampling.PRNG, r Ring) (s UniformSampler) {
if r.RingQ != nil {
s.samplerQ = ring.NewUniformSampler(prng, r.RingQ)
@@ -67,11 +68,3 @@ func (s UniformSampler) ReadNew() (p Poly) {
return Poly{Q: Q, P: P}
}
func (s UniformSampler) WithPRNG(prng sampling.PRNG) UniformSampler {
sp := UniformSampler{samplerQ: s.samplerQ.WithPRNG(prng)}
if s.samplerP != nil {
sp.samplerP = s.samplerP.WithPRNG(prng)
}
return sp
}
+2 -11
View File
@@ -61,6 +61,8 @@ type Ternary struct {
// i.e., with coefficients uniformly distributed in the given ring.
type Uniform struct{}
// NewSampler returns a new sampler that follows the distribution given by DistributionParameters.
// WARNING: If the PRNG is deterministic/keyed (of type [sampling.KeyedPRNG]), *concurrent* calls to the sampler will not necessarily result in a deterministic output.
func NewSampler(prng sampling.PRNG, baseRing *Ring, X DistributionParameters, montgomery bool) (Sampler, error) {
switch X := X.(type) {
case DiscreteGaussian:
@@ -79,17 +81,6 @@ type baseSampler struct {
baseRing *Ring
}
type randomBuffer struct {
randomBufferN []byte
ptr int
}
func newRandomBuffer() *randomBuffer {
return &randomBuffer{
randomBufferN: make([]byte, 1024),
}
}
// AtLevel returns an instance of the target base sampler that operates at the target level.
// This instance is not thread safe and cannot be used concurrently to the base instance.
func (b baseSampler) AtLevel(level int) *baseSampler {
+21 -21
View File
@@ -16,7 +16,6 @@ const (
// GaussianSampler keeps the state of a truncated Gaussian polynomial sampler.
type GaussianSampler struct {
*baseSampler
*randomBuffer
xe DiscreteGaussian
montgomery bool
}
@@ -24,11 +23,11 @@ type GaussianSampler struct {
// NewGaussianSampler creates a new instance of GaussianSampler from a PRNG, a ring definition and the truncated
// Gaussian distribution parameters. Sigma is the desired standard deviation and bound is the maximum coefficient norm in absolute
// value.
// WARNING: If the PRNG is deterministic/keyed (of type [sampling.KeyedPRNG]), *concurrent* calls to the sampler will not necessarily result in a deterministic output.
func NewGaussianSampler(prng sampling.PRNG, baseRing *Ring, X DiscreteGaussian, montgomery bool) (g *GaussianSampler) {
g = new(GaussianSampler)
g.baseSampler = &baseSampler{}
g.prng = prng
g.randomBuffer = newRandomBuffer()
g.baseRing = baseRing
g.xe = X
g.montgomery = montgomery
@@ -39,10 +38,9 @@ func NewGaussianSampler(prng sampling.PRNG, baseRing *Ring, X DiscreteGaussian,
// This instance is not thread safe and cannot be used concurrently to the base instance.
func (g *GaussianSampler) AtLevel(level int) Sampler {
return &GaussianSampler{
baseSampler: g.baseSampler.AtLevel(level),
randomBuffer: g.randomBuffer,
xe: g.xe,
montgomery: g.montgomery,
baseSampler: g.baseSampler.AtLevel(level),
xe: g.xe,
montgomery: g.montgomery,
}
}
@@ -74,9 +72,12 @@ func (g *GaussianSampler) read(pol Poly, f func(a, b, c uint64) uint64) {
r := g.baseRing
var randomBufferN [1024]byte
var ptr int
level := r.level
if _, err := g.prng.Read(g.randomBufferN); err != nil {
if _, err := g.prng.Read(randomBufferN[:]); err != nil {
// Sanity check, this error should not happen.
panic(err)
}
@@ -120,7 +121,7 @@ func (g *GaussianSampler) read(pol Poly, f func(a, b, c uint64) uint64) {
for {
// Sample norm with sigma = 1 and sign
norm, sign = g.normFloat64()
norm, sign = g.normFloat64(randomBufferN[:], &ptr)
// Sets normFlo = norm * sigma with precision 53 bits
// and 0.5 for rounding discretization
@@ -159,7 +160,7 @@ func (g *GaussianSampler) read(pol Poly, f func(a, b, c uint64) uint64) {
for i := 0; i < N; i++ {
for {
norm, sign = g.normFloat64()
norm, sign = g.normFloat64(randomBufferN[:], &ptr)
if v := norm * sigma; v <= bound {
coeffInt = uint64(v + 0.5) // rounding
@@ -188,34 +189,33 @@ func (g *GaussianSampler) read(pol Poly, f func(a, b, c uint64) uint64) {
//
// Algorithm adapted from https://golang.org/src/math/rand/normal.go
// to use a secure PRNG instead of math/rand.
func (g *GaussianSampler) normFloat64() (float64, uint64) {
func (g *GaussianSampler) normFloat64(buff []byte, ptr *int) (float64, uint64) {
ptr := g.ptr
buff := g.randomBufferN
currPtr := *ptr
prng := g.prng
buffLen := len(buff)
read := func() {
if ptr == buffLen {
if currPtr == buffLen {
if _, err := prng.Read(buff); err != nil {
// Sanity check, this error should not happen.
panic(err)
}
ptr = 0
currPtr = 0
}
}
randU32 := func() (x uint32) {
read()
x = binary.LittleEndian.Uint32(buff[ptr : ptr+4])
ptr += 8 // Avoids buffer misalignment
x = binary.LittleEndian.Uint32(buff[currPtr : currPtr+4])
currPtr += 8 // Avoids buffer misalignment
return
}
randF64 := func() (x float64) {
read()
x = float64(binary.LittleEndian.Uint64(buff[ptr:ptr+8])&0x1fffffffffffff) / float64(0x1fffffffffffff)
ptr += 8
x = float64(binary.LittleEndian.Uint64(buff[currPtr:currPtr+8])&0x1fffffffffffff) / float64(0x1fffffffffffff)
currPtr += 8
return
}
@@ -234,7 +234,7 @@ func (g *GaussianSampler) normFloat64() (float64, uint64) {
// 1 (>99%)
/* #nosec G115 -- j cannot be negative */
if uint32(j) < kn[i] {
g.ptr = ptr
*ptr = currPtr
return x, sign
}
@@ -252,13 +252,13 @@ func (g *GaussianSampler) normFloat64() (float64, uint64) {
}
}
g.ptr = ptr
*ptr = currPtr
return x + rn, sign
}
// 3
if fn[i]+float32(randF64())*(fn[i-1]-fn[i]) < float32(math.Exp(-0.5*x*x)) {
g.ptr = ptr
*ptr = currPtr
return x, sign
}
}
+1
View File
@@ -22,6 +22,7 @@ type TernarySampler struct {
// NewTernarySampler creates a new instance of TernarySampler from a PRNG, the ring definition and the distribution
// parameters (see type Ternary). If "montgomery" is set to true, polynomials read from this sampler are in Montgomery form.
// WARNING: If the PRNG is deterministic/keyed (of type [sampling.KeyedPRNG]), *concurrent* calls to the sampler will not necessarily result in a deterministic output.
func NewTernarySampler(prng sampling.PRNG, baseRing *Ring, X Ternary, montgomery bool) (ts *TernarySampler, err error) {
ts = new(TernarySampler)
ts.baseSampler = &baseSampler{}
+8 -25
View File
@@ -9,16 +9,15 @@ import (
// UniformSampler wraps a util.PRNG and represents the state of a sampler of uniform polynomials.
type UniformSampler struct {
*baseSampler
*randomBuffer
}
// NewUniformSampler creates a new instance of UniformSampler from a PRNG and ring definition.
// WARNING: If the PRNG is deterministic/keyed (of type [sampling.KeyedPRNG]), *concurrent* calls to the sampler will not necessarily result in a deterministic output.
func NewUniformSampler(prng sampling.PRNG, baseRing *Ring) (u *UniformSampler) {
u = new(UniformSampler)
u.baseSampler = &baseSampler{}
u.baseRing = baseRing
u.prng = prng
u.randomBuffer = newRandomBuffer()
return
}
@@ -26,8 +25,7 @@ func NewUniformSampler(prng sampling.PRNG, baseRing *Ring) (u *UniformSampler) {
// The returned sampler cannot be used concurrently to the original sampler.
func (u *UniformSampler) AtLevel(level int) Sampler {
return &UniformSampler{
baseSampler: u.baseSampler.AtLevel(level),
randomBuffer: u.randomBuffer,
baseSampler: u.baseSampler.AtLevel(level),
}
}
@@ -48,22 +46,18 @@ func (u *UniformSampler) read(pol Poly, f func(a, b, c uint64) uint64) {
level := u.baseRing.Level()
var randomUint, mask, qi uint64
var buffer [1024]byte
prng := u.prng
N := u.baseRing.N()
byteArrayLength := len(u.randomBufferN)
byteArrayLength := len(buffer)
var ptr int
if ptr = u.ptr; ptr == 0 || ptr == byteArrayLength {
if _, err := prng.Read(u.randomBufferN); err != nil {
// Sanity check, this error should not happen.
panic(err)
}
ptr = 0 // for the case where ptr == byteArrayLength
if _, err := prng.Read(buffer[:]); err != nil {
// Sanity check, this error should not happen.
panic(err)
}
buffer := u.randomBufferN
for j := 0; j < level+1; j++ {
qi = u.baseRing.SubRings[j].Modulus
@@ -81,7 +75,7 @@ func (u *UniformSampler) read(pol Poly, f func(a, b, c uint64) uint64) {
// Refills the buff if it runs empty
if ptr == byteArrayLength {
if _, err := u.prng.Read(buffer); err != nil {
if _, err := u.prng.Read(buffer[:]); err != nil {
// Sanity check, this error should not happen.
panic(err)
}
@@ -102,7 +96,6 @@ func (u *UniformSampler) read(pol Poly, f func(a, b, c uint64) uint64) {
}
}
u.ptr = ptr
}
// ReadNew generates a new polynomial with coefficients following a uniform distribution over [0, Qi-1].
@@ -113,16 +106,6 @@ func (u *UniformSampler) ReadNew() (pol Poly) {
return
}
func (u *UniformSampler) WithPRNG(prng sampling.PRNG) *UniformSampler {
return &UniformSampler{
baseSampler: &baseSampler{
prng: prng,
baseRing: u.baseRing,
},
randomBuffer: newRandomBuffer(),
}
}
// RandUniform samples a uniform randomInt variable in the range [0, mask] until randomInt is in the range [0, v-1].
// mask needs to be of the form 2^n -1.
func RandUniform(prng sampling.PRNG, v uint64, mask uint64) (randomInt uint64) {
+231
View File
@@ -35,6 +35,7 @@ func BenchmarkBGV(b *testing.B) {
for _, testSet := range []func(tc *TestContext, b *testing.B){
benchEncoder,
benchEvaluator,
benchEvaluatorParallel,
} {
testSet(tc, b)
runtime.GC()
@@ -102,6 +103,236 @@ func benchEncoder(tc *TestContext, b *testing.B) {
})
}
func benchEvaluatorParallel(tc *TestContext, b *testing.B) {
params := tc.Params
lvl := params.MaxLevel()
eval := tc.Evl
plaintext := NewPlaintext(params, lvl)
plaintext.Value = rlwe.NewCiphertextRandom(tc.Prng, params.Parameters, 0, plaintext.Level()).Value[0]
ciphertext1 := rlwe.NewCiphertextRandom(tc.Prng, params.Parameters, 1, lvl)
ciphertext2 := rlwe.NewCiphertextRandom(tc.Prng, params.Parameters, 1, lvl)
scalar := params.PlaintextModulus() >> 1
*ciphertext1.MetaData = *plaintext.MetaData
*ciphertext2.MetaData = *plaintext.MetaData
vector := plaintext.Value.Coeffs[0][:params.MaxSlots()]
b.Run(name("EvaluatorParallel/Add/Scalar", tc, lvl), func(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
receiver := NewCiphertext(params, 1, ciphertext1.Level())
for pb.Next() {
if err := eval.Add(ciphertext1, scalar, receiver); err != nil {
b.Log(err)
b.Fail()
}
}
})
})
b.Run(name("EvaluatorParallel/Add/Vector", tc, lvl), func(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
receiver := NewCiphertext(params, 1, ciphertext1.Level())
for pb.Next() {
if err := eval.Add(ciphertext1, vector, receiver); err != nil {
b.Log(err)
b.Fail()
}
}
})
})
b.Run(name("EvaluatorParallel/Add/Plaintext", tc, lvl), func(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
receiver := NewCiphertext(params, 1, ciphertext1.Level())
for pb.Next() {
if err := eval.Add(ciphertext1, plaintext, receiver); err != nil {
b.Log(err)
b.Fail()
}
}
})
})
b.Run(name("EvaluatorParallel/Add/Ciphertext", tc, lvl), func(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
receiver := NewCiphertext(params, 1, ciphertext1.Level())
for pb.Next() {
if err := eval.Add(ciphertext1, ciphertext2, receiver); err != nil {
b.Log(err)
b.Fail()
}
}
})
})
b.Run(name("EvaluatorParallel/Mul/Scalar", tc, lvl), func(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
receiver := NewCiphertext(params, 1, ciphertext1.Level())
for pb.Next() {
if err := eval.Mul(ciphertext1, scalar, receiver); err != nil {
b.Log(err)
b.Fail()
}
}
})
})
b.Run(name("EvaluatorParallel/Mul/Vector", tc, lvl), func(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
receiver := NewCiphertext(params, 1, ciphertext1.Level())
for pb.Next() {
if err := eval.Mul(ciphertext1, vector, receiver); err != nil {
b.Log(err)
b.Fail()
}
}
})
})
b.Run(name("EvaluatorParallel/Mul/Plaintext", tc, lvl), func(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
receiver := NewCiphertext(params, 1, ciphertext1.Level())
for pb.Next() {
if err := eval.Mul(ciphertext1, plaintext, receiver); err != nil {
b.Log(err)
b.Fail()
}
}
})
})
b.Run(name("EvaluatorParallel/Mul/Ciphertext", tc, lvl), func(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
receiver := NewCiphertext(params, 2, ciphertext1.Level())
for pb.Next() {
if err := eval.Mul(ciphertext1, ciphertext2, receiver); err != nil {
b.Log(err)
b.Fail()
}
}
})
})
b.Run(name("EvaluatorParallel/MulRelin/Ciphertext", tc, lvl), func(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
receiver := NewCiphertext(params, 1, ciphertext1.Level())
for pb.Next() {
if err := eval.MulRelin(ciphertext1, ciphertext2, receiver); err != nil {
b.Log(err)
b.Fail()
}
}
})
})
b.Run(name("EvaluatorParallel/MulThenAdd/Scalar", tc, lvl), func(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
receiver := NewCiphertext(params, 1, ciphertext1.Level())
for pb.Next() {
if err := eval.MulThenAdd(ciphertext1, scalar, receiver); err != nil {
b.Log(err)
b.Fail()
}
}
})
})
b.Run(name("EvaluatorParallel/MulThenAdd/Vector", tc, lvl), func(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
receiver := NewCiphertext(params, 1, ciphertext1.Level())
for pb.Next() {
if err := eval.MulThenAdd(ciphertext1, vector, receiver); err != nil {
b.Log(err)
b.Fail()
}
}
})
})
b.Run(name("EvaluatorParallel/MulThenAdd/Plaintext", tc, lvl), func(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
receiver := NewCiphertext(params, 1, ciphertext1.Level())
for pb.Next() {
if err := eval.MulThenAdd(ciphertext1, plaintext, receiver); err != nil {
b.Log(err)
b.Fail()
}
}
})
})
b.Run(name("EvaluatorParallel/MulThenAdd/Ciphertext", tc, lvl), func(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
receiver := NewCiphertext(params, 2, ciphertext1.Level())
for pb.Next() {
if err := eval.MulThenAdd(ciphertext1, ciphertext2, receiver); err != nil {
b.Log(err)
b.Fail()
}
}
})
})
b.Run(name("EvaluatorParallel/MulRelinThenAdd/Ciphertext", tc, lvl), func(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
receiver := NewCiphertext(params, 1, ciphertext1.Level())
for pb.Next() {
if err := eval.MulRelinThenAdd(ciphertext1, ciphertext2, receiver); err != nil {
b.Log(err)
b.Fail()
}
}
})
})
b.Run(name("EvaluatorParallel/Rescale", tc, lvl), func(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
receiver := NewCiphertext(params, 1, ciphertext1.Level()-1)
for pb.Next() {
if err := eval.Rescale(ciphertext1, receiver); err != nil {
b.Log(err)
b.Fail()
}
}
})
})
b.Run(name("EvaluatorParallel/Rotate", tc, lvl), func(b *testing.B) {
b.ResetTimer()
gk := tc.Kgen.GenGaloisKeyNew(5, tc.Sk)
evk := rlwe.NewMemEvaluationKeySet(nil, gk)
eval := eval.WithKey(evk)
b.RunParallel(func(pb *testing.PB) {
receiver := NewCiphertext(params, 1, ciphertext1.Level())
b.ResetTimer()
for pb.Next() {
if err := eval.RotateColumns(ciphertext1, 1, receiver); err != nil {
b.Log(err)
b.Fail()
}
}
})
})
}
func benchEvaluator(tc *TestContext, b *testing.B) {
params := tc.Params
+78
View File
@@ -137,14 +137,18 @@ func testEncoder(tc *TestContext, t *testing.T) {
testLevel := [2]int{0, tc.Params.MaxLevel()}
for _, lvl := range testLevel {
lvl := lvl
t.Run(name("Encoder/Uint/IsBatched=true", tc, lvl), func(t *testing.T) {
t.Parallel()
values, plaintext, _ := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.DefaultScale())
VerifyTestVectors(tc.Params, tc.Ecd, tc.Dec, plaintext, values, t)
})
}
for _, lvl := range testLevel {
lvl := lvl
t.Run(name("Encoder/Int/IsBatched=true", tc, lvl), func(t *testing.T) {
t.Parallel()
T := tc.Params.PlaintextModulus()
THalf := T >> 1
@@ -168,7 +172,9 @@ func testEncoder(tc *TestContext, t *testing.T) {
}
for _, lvl := range testLevel {
lvl := lvl
t.Run(name("Encoder/Uint/IsBatched=false", tc, lvl), func(t *testing.T) {
t.Parallel()
T := tc.Params.PlaintextModulus()
poly := tc.Sampler.ReadNew()
coeffs := make([]uint64, poly.N())
@@ -186,7 +192,9 @@ func testEncoder(tc *TestContext, t *testing.T) {
}
for _, lvl := range testLevel {
lvl := lvl
t.Run(name("Encoder/Int/IsBatched=false", tc, lvl), func(t *testing.T) {
t.Parallel()
T := tc.Params.PlaintextModulus()
THalf := T >> 1
@@ -215,7 +223,9 @@ func testEvaluatorBvg(tc *TestContext, t *testing.T) {
testLevel := [2]int{0, tc.Params.MaxLevel()}
for _, lvl := range testLevel {
lvl := lvl
t.Run(name("Evaluator/Add/Ct/Ct/New", tc, lvl), func(t *testing.T) {
t.Parallel()
values0, _, ciphertext0 := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.NewScale(3))
values1, _, ciphertext1 := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.NewScale(7))
@@ -233,7 +243,9 @@ func testEvaluatorBvg(tc *TestContext, t *testing.T) {
}
for _, lvl := range testLevel {
lvl := lvl
t.Run(name("Evaluator/Add/Ct/Ct/Inplace", tc, lvl), func(t *testing.T) {
t.Parallel()
values0, _, ciphertext0 := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.NewScale(3))
values1, _, ciphertext1 := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.NewScale(7))
@@ -250,7 +262,9 @@ func testEvaluatorBvg(tc *TestContext, t *testing.T) {
}
for _, lvl := range testLevel {
lvl := lvl
t.Run(name("Evaluator/Add/Ct/Pt/Inplace", tc, lvl), func(t *testing.T) {
t.Parallel()
values0, _, ciphertext0 := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.NewScale(3))
values1, plaintext, _ := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.NewScale(7))
@@ -267,7 +281,9 @@ func testEvaluatorBvg(tc *TestContext, t *testing.T) {
}
for _, lvl := range testLevel {
lvl := lvl
t.Run(name("Evaluator/Add/Ct/Scalar/Inplace", tc, lvl), func(t *testing.T) {
t.Parallel()
values, _, ciphertext := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.DefaultScale())
scalar := tc.Params.PlaintextModulus() >> 1
@@ -282,7 +298,9 @@ func testEvaluatorBvg(tc *TestContext, t *testing.T) {
}
for _, lvl := range testLevel {
lvl := lvl
t.Run(name("Evaluator/Add/Ct/Vector/Inplace", tc, lvl), func(t *testing.T) {
t.Parallel()
values, _, ciphertext := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.DefaultScale())
p := ring.Poly{Coeffs: [][]uint64{values}}
@@ -295,7 +313,9 @@ func testEvaluatorBvg(tc *TestContext, t *testing.T) {
}
for _, lvl := range testLevel {
lvl := lvl
t.Run(name("Evaluator/Sub/Ct/Ct/New", tc, lvl), func(t *testing.T) {
t.Parallel()
values0, _, ciphertext0 := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.NewScale(3))
values1, _, ciphertext1 := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.NewScale(7))
@@ -313,7 +333,9 @@ func testEvaluatorBvg(tc *TestContext, t *testing.T) {
}
for _, lvl := range testLevel {
lvl := lvl
t.Run(name("Evaluator/Sub/Ct/Ct/Inplace", tc, lvl), func(t *testing.T) {
t.Parallel()
values0, _, ciphertext0 := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.NewScale(3))
values1, _, ciphertext1 := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.NewScale(7))
@@ -330,7 +352,9 @@ func testEvaluatorBvg(tc *TestContext, t *testing.T) {
}
for _, lvl := range testLevel {
lvl := lvl
t.Run(name("Evaluator/Sub/Ct/Pt/Inplace", tc, lvl), func(t *testing.T) {
t.Parallel()
values0, _, ciphertext0 := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.NewScale(3))
values1, plaintext, _ := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.NewScale(7))
@@ -347,7 +371,9 @@ func testEvaluatorBvg(tc *TestContext, t *testing.T) {
}
for _, lvl := range testLevel {
lvl := lvl
t.Run(name("Evaluator/Sub/Ct/Scalar/Inplace", tc, lvl), func(t *testing.T) {
t.Parallel()
values, _, ciphertext := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.DefaultScale())
scalar := tc.Params.PlaintextModulus() >> 1
@@ -362,7 +388,9 @@ func testEvaluatorBvg(tc *TestContext, t *testing.T) {
}
for _, lvl := range testLevel {
lvl := lvl
t.Run(name("Evaluator/Sub/Ct/Vector/Inplace", tc, lvl), func(t *testing.T) {
t.Parallel()
values, _, ciphertext := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.DefaultScale())
p := ring.Poly{Coeffs: [][]uint64{values}}
@@ -375,7 +403,9 @@ func testEvaluatorBvg(tc *TestContext, t *testing.T) {
}
for _, lvl := range testLevel {
lvl := lvl
t.Run(name("Evaluator/Mul/Ct/Ct/Inplace", tc, lvl), func(t *testing.T) {
t.Parallel()
if lvl == 0 {
t.Skip("Skipping: Level = 0")
}
@@ -396,7 +426,9 @@ func testEvaluatorBvg(tc *TestContext, t *testing.T) {
}
for _, lvl := range testLevel {
lvl := lvl
t.Run(name("Evaluator/Mul/Ct/Pt/Inplace", tc, lvl), func(t *testing.T) {
t.Parallel()
if lvl == 0 {
t.Skip("Skipping: Level = 0")
}
@@ -417,7 +449,9 @@ func testEvaluatorBvg(tc *TestContext, t *testing.T) {
}
for _, lvl := range testLevel {
lvl := lvl
t.Run(name("Evaluator/Mul/Ct/Scalar/Inplace", tc, lvl), func(t *testing.T) {
t.Parallel()
if lvl == 0 {
t.Skip("Skipping: Level = 0")
}
@@ -436,7 +470,9 @@ func testEvaluatorBvg(tc *TestContext, t *testing.T) {
}
for _, lvl := range testLevel {
lvl := lvl
t.Run(name("Evaluator/Mul/Ct/Vector/Inplace", tc, lvl), func(t *testing.T) {
t.Parallel()
if lvl == 0 {
t.Skip("Skipping: Level = 0")
@@ -454,7 +490,9 @@ func testEvaluatorBvg(tc *TestContext, t *testing.T) {
}
for _, lvl := range testLevel {
lvl := lvl
t.Run(name("Evaluator/Square/Ct/Ct/Inplace", tc, lvl), func(t *testing.T) {
t.Parallel()
if lvl == 0 {
t.Skip("Skipping: Level = 0")
}
@@ -471,7 +509,9 @@ func testEvaluatorBvg(tc *TestContext, t *testing.T) {
}
for _, lvl := range testLevel {
lvl := lvl
t.Run(name("Evaluator/MulRelin/Ct/Ct/Inplace", tc, lvl), func(t *testing.T) {
t.Parallel()
if lvl == 0 {
t.Skip("Skipping: Level = 0")
}
@@ -496,7 +536,9 @@ func testEvaluatorBvg(tc *TestContext, t *testing.T) {
}
for _, lvl := range testLevel {
lvl := lvl
t.Run(name("Evaluator/MulThenAdd/Ct/Ct/Inplace", tc, lvl), func(t *testing.T) {
t.Parallel()
if lvl == 0 {
t.Skip("Skipping: Level = 0")
}
@@ -520,7 +562,9 @@ func testEvaluatorBvg(tc *TestContext, t *testing.T) {
}
for _, lvl := range testLevel {
lvl := lvl
t.Run(name("Evaluator/MulThenAdd/Ct/Pt/Inplace", tc, lvl), func(t *testing.T) {
t.Parallel()
if lvl == 0 {
t.Skip("Skipping: Level = 0")
}
@@ -544,7 +588,9 @@ func testEvaluatorBvg(tc *TestContext, t *testing.T) {
}
for _, lvl := range testLevel {
lvl := lvl
t.Run(name("Evaluator/MulThenAdd/Ct/Scalar/Inplace", tc, lvl), func(t *testing.T) {
t.Parallel()
if lvl == 0 {
t.Skip("Skipping: Level = 0")
}
@@ -567,7 +613,9 @@ func testEvaluatorBvg(tc *TestContext, t *testing.T) {
}
for _, lvl := range testLevel {
lvl := lvl
t.Run(name("Evaluator/MulThenAdd/Ct/Vector/Inplace", tc, lvl), func(t *testing.T) {
t.Parallel()
if lvl == 0 {
t.Skip("Skipping: Level = 0")
}
@@ -593,7 +641,9 @@ func testEvaluatorBvg(tc *TestContext, t *testing.T) {
}
for _, lvl := range testLevel {
lvl := lvl
t.Run(name("Evaluator/MulRelinThenAdd/Ct/Ct/Inplace", tc, lvl), func(t *testing.T) {
t.Parallel()
if lvl == 0 {
t.Skip("Skipping: Level = 0")
}
@@ -617,7 +667,9 @@ func testEvaluatorBvg(tc *TestContext, t *testing.T) {
}
for _, lvl := range testLevel[:] {
lvl := lvl
t.Run(name("Evaluator/Rescale", tc, lvl), func(t *testing.T) {
t.Parallel()
ringT := tc.Params.RingT()
@@ -695,10 +747,14 @@ func testEvaluatorBvg(tc *TestContext, t *testing.T) {
}
for _, i := range []int{0, 1, 2} {
i := i
// n*batchSize = N, N/2, N/8
for _, offset := range []int{0, 1, 3} {
offset := offset
for _, lvl := range testLevel {
lvl := lvl
t.Run(name("Evaluator/InnerSum/", tc, lvl), func(t *testing.T) {
t.Parallel()
if lvl == 0 {
t.Skip("Skipping: Level = 0")
}
@@ -728,9 +784,13 @@ func testEvaluatorBvg(tc *TestContext, t *testing.T) {
// Test RotateAndAdd with n*batchSize dividing and not dividing #slots
for _, n := range []int{tc.Params.MaxSlots() >> 3, 7} {
n := n
for _, batchSize := range []int{8, 3} {
batchSize := batchSize
for _, lvl := range testLevel {
lvl := lvl
t.Run(name("Evaluator/RotateAndAdd/", tc, lvl), func(t *testing.T) {
t.Parallel()
if lvl == 0 {
t.Skip("Skipping: Level = 0")
}
@@ -763,7 +823,9 @@ func testEvaluatorBfv(tc *TestContext, t *testing.T) {
t.Run("Evaluator", func(t *testing.T) {
for _, lvl := range testLevels {
lvl := lvl
t.Run(name("Mul/Ct/Ct/Inplace", tc, lvl), func(t *testing.T) {
t.Parallel()
if lvl == 0 {
t.Skip("Skipping: Level = 0")
}
@@ -784,7 +846,9 @@ func testEvaluatorBfv(tc *TestContext, t *testing.T) {
}
for _, lvl := range testLevels {
lvl := lvl
t.Run(name("Mul/Ct/Pt/Inplace", tc, lvl), func(t *testing.T) {
t.Parallel()
if lvl == 0 {
t.Skip("Level = 0")
}
@@ -805,7 +869,9 @@ func testEvaluatorBfv(tc *TestContext, t *testing.T) {
}
for _, lvl := range testLevels {
lvl := lvl
t.Run(name("Mul/Ct/Scalar/Inplace", tc, lvl), func(t *testing.T) {
t.Parallel()
if lvl == 0 {
t.Skip("Level = 0")
}
@@ -824,7 +890,9 @@ func testEvaluatorBfv(tc *TestContext, t *testing.T) {
}
for _, lvl := range testLevels {
lvl := lvl
t.Run(name("Square/Ct/Ct/Inplace", tc, lvl), func(t *testing.T) {
t.Parallel()
if lvl == 0 {
t.Skip("Level = 0")
}
@@ -841,7 +909,9 @@ func testEvaluatorBfv(tc *TestContext, t *testing.T) {
}
for _, lvl := range testLevels {
lvl := lvl
t.Run(name("MulRelin/Ct/Ct/Inplace", tc, lvl), func(t *testing.T) {
t.Parallel()
if lvl == 0 {
t.Skip("Level = 0")
}
@@ -866,7 +936,9 @@ func testEvaluatorBfv(tc *TestContext, t *testing.T) {
}
for _, lvl := range testLevels {
lvl := lvl
t.Run(name("MulThenAdd/Ct/Ct/Inplace", tc, lvl), func(t *testing.T) {
t.Parallel()
if lvl == 0 {
t.Skip("Level = 0")
}
@@ -890,7 +962,9 @@ func testEvaluatorBfv(tc *TestContext, t *testing.T) {
}
for _, lvl := range testLevels {
lvl := lvl
t.Run(name("MulThenAdd/Ct/Pt/Inplace", tc, lvl), func(t *testing.T) {
t.Parallel()
if lvl == 0 {
t.Skip("Level = 0")
@@ -915,7 +989,9 @@ func testEvaluatorBfv(tc *TestContext, t *testing.T) {
}
for _, lvl := range testLevels {
lvl := lvl
t.Run(name("MulThenAdd/Ct/Scalar/Inplace", tc, lvl), func(t *testing.T) {
t.Parallel()
if lvl == 0 {
t.Skip("Level = 0")
}
@@ -938,7 +1014,9 @@ func testEvaluatorBfv(tc *TestContext, t *testing.T) {
}
for _, lvl := range testLevels {
lvl := lvl
t.Run(name("MulRelinThenAdd/Ct/Ct/Inplace", tc, lvl), func(t *testing.T) {
t.Parallel()
if lvl == 0 {
t.Skip("Level = 0")
}
+77 -65
View File
@@ -8,6 +8,7 @@ import (
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/ring/ringqp"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/tuneinsight/lattigo/v6/utils/structs"
)
type Integer interface {
@@ -30,14 +31,13 @@ type Encoder struct {
indexMatrix []uint64
bufQ ring.Poly
bufT ring.Poly
// bufB is allocated in the case when the degree of RingT is smaller
// BuffBigIntPool is allocated in the case when the degree of RingT is smaller
// than the degree of RingQ (gap > 1), hence a more involved conversion
// between the two structures is necessary. The size of bufB is then
// MaxSlots() elements.
bufB []*big.Int
// between the two structures is necessary.
// The size of an object returned from the pool MaxSlots() elements.
BuffBigIntPool structs.BufferPool[*[]*big.Int]
poolQ *ring.BufferPool
poolT *ring.BufferPool
paramsQP []ring.ModUpConstants
qHalf []*big.Int
@@ -70,28 +70,40 @@ func NewEncoder(parameters Parameters) *Encoder {
tInvModQ[i] = new(big.Int).ModInverse(TBig, ringQ.ModulusAtLevel[i])
}
var bufB []*big.Int
var buffBigIntPool structs.BufferPool[*[]*big.Int]
if parameters.LogMaxDimensions().Cols < parameters.LogN()-1 {
slots := parameters.MaxSlots()
bufB = make([]*big.Int, slots)
for i := 0; i < slots; i++ {
bufB[i] = new(big.Int)
}
buffBigIntPool = structs.NewSyncPool(func() *[]*big.Int {
buff := make([]*big.Int, slots)
for i := 0; i < slots; i++ {
buff[i] = new(big.Int)
}
return &buff
})
}
// create pools for polys in ringQ and ringT
backingPool := structs.NewSyncPoolUint64(ringQ.N())
poolQ := ring.NewPool(ringQ, backingPool)
backingPoolT := backingPool
// we use the same backing pool only if ringQ and ringT have the same dimension
if ringQ.N() != ringT.N() {
backingPoolT = structs.NewSyncPoolUint64(ringT.N())
}
poolT := ring.NewPool(ringT, backingPoolT)
return &Encoder{
parameters: parameters,
indexMatrix: permuteMatrix(parameters.LogMaxSlots()),
bufQ: ringQ.NewPoly(),
bufT: ringT.NewPoly(),
bufB: bufB,
paramsQP: paramsQP,
qHalf: qHalf,
tInvModQ: tInvModQ,
parameters: parameters,
indexMatrix: permuteMatrix(parameters.LogMaxSlots()),
BuffBigIntPool: buffBigIntPool,
paramsQP: paramsQP,
qHalf: qHalf,
tInvModQ: tInvModQ,
poolQ: poolQ,
poolT: poolT,
}
}
@@ -138,7 +150,9 @@ func (ecd Encoder) Encode(values interface{}, pt *rlwe.Plaintext) (err error) {
T := ringT.SubRings[0].Modulus
BRC := ringT.SubRings[0].BRedConstant
ptT := ecd.bufT.Coeffs[0]
buffT := ecd.poolT.GetBuffPoly()
defer ecd.poolT.RecycleBuffPoly(buffT)
ptT := buffT.Coeffs[0]
var valLen int
switch values := values.(type) {
@@ -172,8 +186,8 @@ func (ecd Encoder) Encode(values interface{}, pt *rlwe.Plaintext) (err error) {
ptT[i] = 0
}
ringT.MulScalar(ecd.bufT, pt.Scale.Uint64(), ecd.bufT)
ecd.RingT2Q(pt.Level(), true, ecd.bufT, pt.Value)
ringT.MulScalar(*buffT, pt.Scale.Uint64(), *buffT)
ecd.RingT2Q(pt.Level(), true, *buffT, pt.Value)
if pt.IsNTT {
ecd.parameters.RingQ().AtLevel(pt.Level()).NTT(pt.Value, pt.Value)
@@ -251,7 +265,9 @@ func (ecd Encoder) EncodeRingT(values IntegerSlice, scale rlwe.Scale, pT ring.Po
// Accepted polyOut.(type) are a ringqp.Poly and *ring.Poly
func (ecd Encoder) EmbedScale(values IntegerSlice, scaleUp bool, metadata *rlwe.MetaData, polyOut interface{}) (err error) {
pT := ecd.bufT
buffT := ecd.poolT.GetBuffPoly()
defer ecd.poolT.RecycleBuffPoly(buffT)
pT := *buffT
if err = ecd.EncodeRingT(values, metadata.Scale, pT); err != nil {
return
@@ -322,10 +338,13 @@ func (ecd Encoder) Embed(values interface{}, metadata *rlwe.MetaData, polyOut in
// DecodeRingT decodes a polynomial pT with coefficients modulo the plaintext modulu PlaintextModulus on an InterSlice at the given scale.
func (ecd Encoder) DecodeRingT(pT ring.Poly, scale rlwe.Scale, values IntegerSlice) (err error) {
ringT := ecd.parameters.RingT()
ringT.MulScalar(pT, ring.ModExp(scale.Uint64(), ringT.SubRings[0].Modulus-2, ringT.SubRings[0].Modulus), ecd.bufT)
ringT.NTT(ecd.bufT, ecd.bufT)
buffT := ecd.poolT.GetBuffPoly()
defer ecd.poolT.RecycleBuffPoly(buffT)
tmp := ecd.bufT.Coeffs[0]
ringT.MulScalar(pT, ring.ModExp(scale.Uint64(), ringT.SubRings[0].Modulus-2, ringT.SubRings[0].Modulus), *buffT)
ringT.NTT(*buffT, *buffT)
tmp := buffT.Coeffs[0]
switch values := values.(type) {
case []uint64:
@@ -391,12 +410,16 @@ func (ecd Encoder) RingT2Q(level int, scaleUp bool, pT, pQ ring.Poly) {
func (ecd Encoder) RingQ2T(level int, scaleDown bool, pQ, pT ring.Poly) {
ringQ := ecd.parameters.RingQ().AtLevel(level)
poolQ := ecd.poolQ.AtLevel(level)
ringT := ecd.parameters.RingT()
var poly ring.Poly
buffQ := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(buffQ)
if scaleDown {
ringQ.MulScalar(pQ, ecd.parameters.PlaintextModulus(), ecd.bufQ)
poly = ecd.bufQ
ringQ.MulScalar(pQ, ecd.parameters.PlaintextModulus(), *buffQ)
poly = *buffQ
} else {
poly = pQ
}
@@ -406,25 +429,28 @@ func (ecd Encoder) RingQ2T(level int, scaleDown bool, pQ, pT ring.Poly) {
if level > 0 {
if gap == 1 {
ringQ.AddScalarBigint(poly, ecd.qHalf[level], ecd.bufQ)
ring.ModUpExact(ecd.bufQ.Coeffs[:level+1], pT.Coeffs, ringQ, ringT, ecd.paramsQP[level])
ringQ.AddScalarBigint(poly, ecd.qHalf[level], *buffQ)
ring.ModUpExact(buffQ.Coeffs[:level+1], pT.Coeffs, ringQ, ringT, ecd.paramsQP[level])
ringT.SubScalarBigint(pT, ecd.qHalf[level], pT)
} else {
ringQ.PolyToBigintCentered(poly, gap, ecd.bufB)
ringT.SetCoefficientsBigint(ecd.bufB, pT)
buffB := ecd.BuffBigIntPool.Get()
ringQ.PolyToBigintCentered(poly, gap, *buffB)
ringT.SetCoefficientsBigint(*buffB, pT)
// buffB only used in this block, can be put back in the pool:
ecd.BuffBigIntPool.Put(buffB)
}
} else {
if gap == 1 {
ringQ.AddScalar(poly, ringQ.SubRings[0].Modulus>>1, ecd.bufQ)
ringT.Reduce(ecd.bufQ, pT)
ringQ.AddScalar(poly, ringQ.SubRings[0].Modulus>>1, *buffQ)
ringT.Reduce(*buffQ, pT)
} else {
n := pT.N()
pQCoeffs := poly.Coeffs[0]
bufQCoeffs := ecd.bufQ.Coeffs[0]
bufQCoeffs := buffQ.Coeffs[0]
for i := 0; i < n; i++ {
bufQCoeffs[i] = pQCoeffs[i*gap]
@@ -441,27 +467,32 @@ func (ecd Encoder) RingQ2T(level int, scaleDown bool, pQ, pT ring.Poly) {
// Decode decodes a plaintext on an IntegerSlice mod PlaintextModulus of size at most N, where N is the smallest value satisfying PlaintextModulus = 1 mod 2N.
func (ecd Encoder) Decode(pt *rlwe.Plaintext, values interface{}) (err error) {
bufT := ecd.bufT
buffT := ecd.poolT.GetBuffPoly()
defer ecd.poolT.RecycleBuffPoly(buffT)
if pt.IsNTT {
ecd.parameters.RingQ().AtLevel(pt.Level()).INTT(pt.Value, ecd.bufQ)
ecd.RingQ2T(pt.Level(), true, ecd.bufQ, bufT)
ringQ := ecd.parameters.RingQ().AtLevel(pt.Level())
poolQ := ecd.poolQ.AtLevel(pt.Level())
buffQ := poolQ.GetBuffPoly()
ringQ.INTT(pt.Value, *buffQ)
ecd.RingQ2T(pt.Level(), true, *buffQ, *buffT)
poolQ.RecycleBuffPoly(buffQ)
} else {
ecd.RingQ2T(pt.Level(), true, pt.Value, bufT)
ecd.RingQ2T(pt.Level(), true, pt.Value, *buffT)
}
if pt.IsBatched {
return ecd.DecodeRingT(ecd.bufT, pt.Scale, values)
return ecd.DecodeRingT(*buffT, pt.Scale, values)
} else {
ringT := ecd.parameters.RingT()
ringT.MulScalar(bufT, ring.ModExp(pt.Scale.Uint64(), ringT.SubRings[0].Modulus-2, ringT.SubRings[0].Modulus), bufT)
ringT.MulScalar(*buffT, ring.ModExp(pt.Scale.Uint64(), ringT.SubRings[0].Modulus-2, ringT.SubRings[0].Modulus), *buffT)
switch values := values.(type) {
case []uint64:
copy(values, ecd.bufT.Coeffs[0])
copy(values, buffT.Coeffs[0])
case []int64:
ptT := bufT.Coeffs[0]
ptT := buffT.Coeffs[0]
N := ecd.parameters.RingT().N()
/* #nosec G115 -- PlaintextModulus <= 61 bits */
@@ -485,22 +516,3 @@ func (ecd Encoder) Decode(pt *rlwe.Plaintext, values interface{}) (err error) {
return
}
}
// ShallowCopy returns a lightweight copy of the target object
// that can be used concurrently with the original object.
func (ecd Encoder) ShallowCopy() (e *Encoder) {
e = &Encoder{
parameters: ecd.parameters,
indexMatrix: ecd.indexMatrix,
bufQ: ecd.parameters.RingQ().NewPoly(),
bufT: ecd.parameters.RingT().NewPoly(),
paramsQP: ecd.paramsQP,
qHalf: ecd.qHalf,
tInvModQ: ecd.tInvModQ,
}
for i := 0; ecd.parameters.LogMaxDimensions().Cols < ecd.parameters.LogN()-1 && i < ecd.parameters.MaxSlots(); i++ {
e.bufB = append(e.bufB, new(big.Int))
}
return
}
+135 -183
View File
@@ -9,15 +9,14 @@ import (
"github.com/tuneinsight/lattigo/v6/ring"
"github.com/tuneinsight/lattigo/v6/ring/ringqp"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/tuneinsight/lattigo/v6/utils/structs"
)
// Evaluator is a struct that holds the necessary elements to perform the homomorphic operations between ciphertexts and/or plaintexts.
// It also holds a memory buffer used to store intermediate computations.
// The [Evaluator.ScaleInvariant] flag needs to be set in order to use a BFV-style
// version of the evaluator.
type Evaluator struct {
*evaluatorBase
*evaluatorBuffers
*rlwe.Evaluator
*Encoder
@@ -25,6 +24,8 @@ type Evaluator struct {
// scale-invariant multiplications (transforming the BGV evaluator into
// BFV evaluator).
ScaleInvariant bool
pool *rlwe.BufferPool
poolQMul *ring.BufferPool
}
type evaluatorBase struct {
@@ -34,15 +35,6 @@ type evaluatorBase struct {
basisExtenderQ1toQ2 *ring.BasisExtender
}
func (eval evaluatorBase) ShallowCopy() *evaluatorBase {
return &evaluatorBase{
tMontgomery: eval.tMontgomery,
levelQMul: eval.levelQMul,
pHalf: eval.pHalf,
basisExtenderQ1toQ2: eval.basisExtenderQ1toQ2.ShallowCopy(),
}
}
func newEvaluatorPrecomp(parameters Parameters) *evaluatorBase {
ringQ := parameters.RingQ()
ringQMul := parameters.RingQMul()
@@ -77,45 +69,6 @@ func newEvaluatorPrecomp(parameters Parameters) *evaluatorBase {
}
}
type evaluatorBuffers struct {
buffQ [3]ring.Poly
buffQMul [9]ring.Poly
}
// BuffQ returns a pointer to the internal memory buffer buffQ.
func (eval Evaluator) BuffQ() [3]ring.Poly {
return eval.buffQ
}
func newEvaluatorBuffer(params Parameters) *evaluatorBuffers {
ringQ := params.RingQ()
buffQ := [3]ring.Poly{
ringQ.NewPoly(),
ringQ.NewPoly(),
ringQ.NewPoly(),
}
ringQMul := params.RingQMul()
buffQMul := [9]ring.Poly{
ringQMul.NewPoly(),
ringQMul.NewPoly(),
ringQMul.NewPoly(),
ringQMul.NewPoly(),
ringQMul.NewPoly(),
ringQMul.NewPoly(),
ringQMul.NewPoly(),
ringQMul.NewPoly(),
ringQMul.NewPoly(),
}
return &evaluatorBuffers{
buffQ: buffQ,
buffQMul: buffQMul,
}
}
// NewEvaluator creates a new [Evaluator], that can be used to do homomorphic
// operations on ciphertexts and/or plaintexts. It stores a memory buffer
// and ciphertexts that will be used for intermediate values.
@@ -125,11 +78,15 @@ func newEvaluatorBuffer(params Parameters) *evaluatorBuffers {
func NewEvaluator(parameters Parameters, evk rlwe.EvaluationKeySet, scaleInvariant ...bool) *Evaluator {
ev := new(Evaluator)
ev.evaluatorBase = newEvaluatorPrecomp(parameters)
ev.evaluatorBuffers = newEvaluatorBuffer(parameters)
ev.Evaluator = rlwe.NewEvaluator(parameters.Parameters, evk)
ev.Encoder = NewEncoder(parameters)
ev.ScaleInvariant = len(scaleInvariant) > 0 && scaleInvariant[0]
uintPool := structs.NewSyncPoolUint64(parameters.N())
ev.pool = rlwe.NewPool(parameters.RingQP(), uintPool)
ev.poolQMul = ring.NewPool(parameters.ringQMul, uintPool)
return ev
}
@@ -138,26 +95,14 @@ func (eval Evaluator) GetParameters() *Parameters {
return &eval.Encoder.parameters
}
// ShallowCopy creates a shallow copy of this [Evaluator] in which the read-only data-structures are
// shared with the receiver.
func (eval Evaluator) ShallowCopy() *Evaluator {
return &Evaluator{
evaluatorBase: eval.evaluatorBase.ShallowCopy(),
Evaluator: eval.Evaluator.ShallowCopy(),
evaluatorBuffers: newEvaluatorBuffer(*eval.GetParameters()),
Encoder: eval.Encoder.ShallowCopy(),
ScaleInvariant: eval.ScaleInvariant,
}
}
// WithKey creates a shallow copy of this [Evaluator] in which the read-only data-structures are
// shared with the receiver but the evaluation key is set to the provided [rlwe.EvaluationKeySet].
func (eval Evaluator) WithKey(evk rlwe.EvaluationKeySet) *Evaluator {
return &Evaluator{
evaluatorBase: eval.evaluatorBase,
Evaluator: eval.Evaluator.WithKey(evk),
evaluatorBuffers: eval.evaluatorBuffers,
Encoder: eval.Encoder,
evaluatorBase: eval.evaluatorBase,
Evaluator: eval.Evaluator.WithKey(evk),
Encoder: eval.Encoder,
pool: eval.pool,
}
}
@@ -242,14 +187,8 @@ func (eval Evaluator) Add(op0 *rlwe.Ciphertext, op1 rlwe.Operand, opOut *rlwe.Ci
opOut.Resize(op0.Degree(), level)
// Instantiates new plaintext from buffer
pt, err := rlwe.NewPlaintextAtLevelFromPoly(level, eval.buffQ[0])
// This error should not happen, unless the evaluator's buffer were
// improperly tempered with. If it does happen, there is no way to
// recover from it.
if err != nil {
panic(err)
}
pt := eval.pool.GetBuffPt(level)
defer eval.pool.RecycleBuffPt(pt)
pt.MetaData = op0.MetaData // Sets the metadata, notably matches scalses
@@ -383,14 +322,8 @@ func (eval Evaluator) Sub(op0 *rlwe.Ciphertext, op1 rlwe.Operand, opOut *rlwe.Ci
opOut.Resize(op0.Degree(), level)
// Instantiates new plaintext from buffer
pt, err := rlwe.NewPlaintextAtLevelFromPoly(level, eval.buffQ[0])
// This error should not happen, unless the evaluator's buffer were
// improperly tempered with. If it does happen, there is no way to
// recover from it.
if err != nil {
panic(err)
}
pt := eval.pool.GetBuffPt(level)
defer eval.pool.RecycleBuffPt(pt)
pt.MetaData = op0.MetaData // Sets the metadata, notably matches scales
@@ -516,14 +449,8 @@ func (eval Evaluator) Mul(op0 *rlwe.Ciphertext, op1 rlwe.Operand, opOut *rlwe.Ci
}
// Instantiates new plaintext from buffer
pt, err := rlwe.NewPlaintextAtLevelFromPoly(level, eval.buffQ[0])
// This error should not happen, unless the evaluator's buffer were
// improperly tempered with. If it does happen, there is no way to
// recover from it.
if err != nil {
panic(err)
}
pt := eval.pool.GetBuffPt(level)
defer eval.pool.RecycleBuffPt(pt)
pt.MetaData = op0.MetaData.CopyNew() // Sets the metadata, notably matches scales
pt.Scale = rlwe.NewScale(1)
@@ -669,14 +596,18 @@ func (eval Evaluator) tensorStandard(op0 *rlwe.Ciphertext, op1 *rlwe.Element[rin
opOut.Scale = op0.Scale.Mul(op1.Scale)
ringQ := eval.parameters.RingQ().AtLevel(level)
poolQ := eval.pool.AtLevel(level)
var c00, c01, c0, c1, c2 ring.Poly
var c00, c01 *ring.Poly
var c0, c1, c2 ring.Poly
// Case Ciphertext (x) Ciphertext
if op0.Degree() == 1 && op1.Degree() == 1 {
c00 = eval.buffQ[0]
c01 = eval.buffQ[1]
c00 = poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(c00)
c01 = poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(c01)
c0 = opOut.Value[0]
c1 = opOut.Value[1]
@@ -686,7 +617,9 @@ func (eval Evaluator) tensorStandard(op0 *rlwe.Ciphertext, op1 *rlwe.Element[rin
c2 = opOut.Value[2]
} else {
opOut.Resize(1, opOut.Level())
c2 = eval.buffQ[2]
buffq := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(buffq)
c2 = *buffq
}
// Avoid overwriting if the second input is the output
@@ -698,20 +631,20 @@ func (eval Evaluator) tensorStandard(op0 *rlwe.Ciphertext, op1 *rlwe.Element[rin
}
// Multiply by T * 2^{64} * 2^{64} -> result multipled by T and switched in the Montgomery domain
ringQ.MulRNSScalarMontgomery(tmp0.Value[0], eval.tMontgomery, c00)
ringQ.MulRNSScalarMontgomery(tmp0.Value[1], eval.tMontgomery, c01)
ringQ.MulRNSScalarMontgomery(tmp0.Value[0], eval.tMontgomery, *c00)
ringQ.MulRNSScalarMontgomery(tmp0.Value[1], eval.tMontgomery, *c01)
if op0.El() == op1.El() { // squaring case
ringQ.MulCoeffsMontgomery(c00, tmp1.Value[0], c0) // c0 = c[0]*c[0]
ringQ.MulCoeffsMontgomery(c01, tmp1.Value[1], c2) // c2 = c[1]*c[1]
ringQ.MulCoeffsMontgomery(c00, tmp1.Value[1], c1) // c1 = 2*c[0]*c[1]
ringQ.MulCoeffsMontgomery(*c00, tmp1.Value[0], c0) // c0 = c[0]*c[0]
ringQ.MulCoeffsMontgomery(*c01, tmp1.Value[1], c2) // c2 = c[1]*c[1]
ringQ.MulCoeffsMontgomery(*c00, tmp1.Value[1], c1) // c1 = 2*c[0]*c[1]
ringQ.Add(c1, c1, c1)
} else { // regular case
ringQ.MulCoeffsMontgomery(c00, tmp1.Value[0], c0) // c0 = c0[0]*c0[0]
ringQ.MulCoeffsMontgomery(c01, tmp1.Value[1], c2) // c2 = c0[1]*c1[1]
ringQ.MulCoeffsMontgomery(c00, tmp1.Value[1], c1)
ringQ.MulCoeffsMontgomeryThenAdd(c01, tmp1.Value[0], c1) // c1 = c0[0]*c1[1] + c0[1]*c1[0]
ringQ.MulCoeffsMontgomery(*c00, tmp1.Value[0], c0) // c0 = c0[0]*c0[0]
ringQ.MulCoeffsMontgomery(*c01, tmp1.Value[1], c2) // c2 = c0[1]*c1[1]
ringQ.MulCoeffsMontgomery(*c00, tmp1.Value[1], c1)
ringQ.MulCoeffsMontgomeryThenAdd(*c01, tmp1.Value[0], c1) // c1 = c0[0]*c1[1] + c0[1]*c1[0]
}
if relin {
@@ -722,8 +655,8 @@ func (eval Evaluator) tensorStandard(op0 *rlwe.Ciphertext, op1 *rlwe.Element[rin
return fmt.Errorf("cannot Tensor: cannot Relinearize: %w", err)
}
tmpCt := &rlwe.Ciphertext{}
tmpCt.Value = []ring.Poly{eval.BuffQP[1].Q, eval.BuffQP[2].Q}
tmpCt := eval.pool.GetBuffCt(1, ringQ.Level())
defer eval.pool.RecycleBuffCt(tmpCt)
tmpCt.MetaData = &rlwe.MetaData{}
tmpCt.IsNTT = true
@@ -738,12 +671,13 @@ func (eval Evaluator) tensorStandard(op0 *rlwe.Ciphertext, op1 *rlwe.Element[rin
opOut.Resize(op0.Degree(), level)
c00 := eval.buffQ[0]
c00 = poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(c00)
// Multiply by T * 2^{64} * 2^{64} -> result multipled by T and switched in the Montgomery domain
ringQ.MulRNSScalarMontgomery(op1.El().Value[0], eval.tMontgomery, c00)
ringQ.MulRNSScalarMontgomery(op1.El().Value[0], eval.tMontgomery, *c00)
for i := range opOut.Value {
ringQ.MulCoeffsMontgomery(op0.Value[i], c00, opOut.Value[i])
ringQ.MulCoeffsMontgomery(op0.Value[i], *c00, opOut.Value[i])
}
}
@@ -801,14 +735,9 @@ func (eval Evaluator) MulScaleInvariant(op0 *rlwe.Ciphertext, op1 rlwe.Operand,
opOut.Resize(op0.Degree(), level)
// Instantiates new plaintext from buffer
pt, err := rlwe.NewPlaintextAtLevelFromPoly(level, eval.buffQ[0])
pt := eval.pool.GetBuffPt(level)
defer eval.pool.RecycleBuffPt(pt)
// This error should not happen, unless the evaluator's buffer were
// improperly tempered with. If it does happen, there is no way to
// recover from it.
if err != nil {
panic(err)
}
pt.MetaData = op0.MetaData.CopyNew() // Sets the metadata, notably matches scales
pt.Scale = rlwe.NewScale(1)
@@ -909,14 +838,8 @@ func (eval Evaluator) MulRelinScaleInvariant(op0 *rlwe.Ciphertext, op1 rlwe.Oper
opOut.Resize(op0.Degree(), level)
// Instantiates new plaintext from buffer
pt, err := rlwe.NewPlaintextAtLevelFromPoly(level, eval.buffQ[0])
// This error should not happen, unless the evaluator's buffer were
// improperly tempered with. If it does happen, there is no way to
// recover from it.
if err != nil {
panic(err)
}
pt := eval.pool.GetBuffPt(level)
defer eval.pool.RecycleBuffPt(pt)
pt.MetaData = op0.MetaData.CopyNew() // Sets the metadata, notably matches scales
pt.Scale = rlwe.NewScale(1)
@@ -977,6 +900,7 @@ func (eval Evaluator) tensorScaleInvariant(ct0 *rlwe.Ciphertext, ct1 *rlwe.Eleme
level := opOut.Level()
levelQMul := eval.levelQMul[level]
poolQMul := eval.poolQMul.AtLevel(levelQMul)
// Avoid overwriting if the second input is the output
var tmp0Q0, tmp1Q0 *rlwe.Element[ring.Poly]
@@ -986,8 +910,18 @@ func (eval Evaluator) tensorScaleInvariant(ct0 *rlwe.Ciphertext, ct1 *rlwe.Eleme
tmp0Q0, tmp1Q0 = ct0.El(), ct1
}
tmp0Q1 := &rlwe.Element[ring.Poly]{Value: eval.buffQMul[0:3]}
tmp1Q1 := &rlwe.Element[ring.Poly]{Value: eval.buffQMul[3:5]}
buffQ0 := poolQMul.GetBuffPoly()
defer poolQMul.RecycleBuffPoly(buffQ0)
buffQ1 := poolQMul.GetBuffPoly()
defer poolQMul.RecycleBuffPoly(buffQ1)
buffQ2 := poolQMul.GetBuffPoly()
defer poolQMul.RecycleBuffPoly(buffQ2)
buffQ3 := poolQMul.GetBuffPoly()
defer poolQMul.RecycleBuffPoly(buffQ3)
buffQ4 := poolQMul.GetBuffPoly()
defer poolQMul.RecycleBuffPoly(buffQ4)
tmp0Q1 := &rlwe.Element[ring.Poly]{Value: []ring.Poly{*buffQ0, *buffQ1, *buffQ2}}
tmp1Q1 := &rlwe.Element[ring.Poly]{Value: []ring.Poly{*buffQ3, *buffQ4}}
tmp2Q1 := tmp0Q1
eval.modUpAndNTT(level, levelQMul, tmp0Q0, tmp0Q1)
@@ -1002,7 +936,10 @@ func (eval Evaluator) tensorScaleInvariant(ct0 *rlwe.Ciphertext, ct1 *rlwe.Eleme
c2 = opOut.Value[2]
} else {
opOut.Resize(1, opOut.Level())
c2 = eval.buffQ[2]
poolQ := eval.poolQ.AtLevel(level)
buffQ := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(buffQ)
c2 = *buffQ
}
tmp2Q0 := &rlwe.Element[ring.Poly]{Value: []ring.Poly{opOut.Value[0], opOut.Value[1], c2}}
@@ -1021,15 +958,16 @@ func (eval Evaluator) tensorScaleInvariant(ct0 *rlwe.Ciphertext, ct1 *rlwe.Eleme
return fmt.Errorf("cannot TensorInvariant: %w", err)
}
tmpCt := &rlwe.Ciphertext{}
tmpCt.Value = []ring.Poly{eval.BuffQP[1].Q, eval.BuffQP[2].Q}
ringQ := eval.parameters.RingQ().AtLevel(level)
tmpCt := eval.pool.GetBuffCt(1, ringQ.Level())
defer eval.pool.RecycleBuffCt(tmpCt)
tmpCt.MetaData = &rlwe.MetaData{}
tmpCt.IsNTT = true
eval.GadgetProduct(level, c2, &rlk.GadgetCiphertext, tmpCt)
ringQ := eval.parameters.RingQ().AtLevel(level)
ringQ.Add(opOut.Value[0], tmpCt.Value[0], opOut.Value[0])
ringQ.Add(opOut.Value[1], tmpCt.Value[1], opOut.Value[1])
}
@@ -1052,9 +990,12 @@ func MulScaleInvariant(params Parameters, a, b rlwe.Scale, level int) (c rlwe.Sc
func (eval Evaluator) modUpAndNTT(level, levelQMul int, ctQ0, ctQ1 *rlwe.Element[ring.Poly]) {
ringQ, ringQMul := eval.parameters.RingQ().AtLevel(level), eval.parameters.RingQMul().AtLevel(levelQMul)
poolQ := eval.poolQ.AtLevel(level)
buffQ := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(buffQ)
for i := range ctQ0.Value {
ringQ.INTT(ctQ0.Value[i], eval.buffQ[0])
eval.basisExtenderQ1toQ2.ModUpQtoP(level, levelQMul, eval.buffQ[0], ctQ1.Value[i])
ringQ.INTT(ctQ0.Value[i], *buffQ)
eval.basisExtenderQ1toQ2.ModUpQtoP(level, levelQMul, *buffQ, ctQ1.Value[i])
ringQMul.NTTLazy(ctQ1.Value[i], ctQ1.Value[i])
}
}
@@ -1062,42 +1003,47 @@ func (eval Evaluator) modUpAndNTT(level, levelQMul int, ctQ0, ctQ1 *rlwe.Element
func (eval Evaluator) tensorLowDeg(level, levelQMul int, ct0Q0, ct1Q0, ct2Q0, ct0Q1, ct1Q1, ct2Q1 *rlwe.Element[ring.Poly]) {
ringQ, ringQMul := eval.parameters.RingQ().AtLevel(level), eval.parameters.RingQMul().AtLevel(levelQMul)
poolQ, poolQMul := eval.poolQ.AtLevel(level), eval.poolQMul.AtLevel(levelQMul)
c00 := eval.buffQ[0]
c01 := eval.buffQ[1]
c00 := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(c00)
c01 := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(c01)
ringQ.MForm(ct0Q0.Value[0], c00)
ringQ.MForm(ct0Q0.Value[1], c01)
ringQ.MForm(ct0Q0.Value[0], *c00)
ringQ.MForm(ct0Q0.Value[1], *c01)
c00M := eval.buffQMul[5]
c01M := eval.buffQMul[6]
c00M := poolQMul.GetBuffPoly()
defer poolQMul.RecycleBuffPoly(c00M)
c01M := poolQMul.GetBuffPoly()
defer poolQMul.RecycleBuffPoly(c01M)
ringQMul.MForm(ct0Q1.Value[0], c00M)
ringQMul.MForm(ct0Q1.Value[1], c01M)
ringQMul.MForm(ct0Q1.Value[0], *c00M)
ringQMul.MForm(ct0Q1.Value[1], *c01M)
// Squaring case
if ct0Q0 == ct1Q0 {
ringQ.MulCoeffsMontgomery(c00, ct0Q0.Value[0], ct2Q0.Value[0]) // c0 = c0[0]*c0[0]
ringQ.MulCoeffsMontgomery(c01, ct0Q0.Value[1], ct2Q0.Value[2]) // c2 = c0[1]*c0[1]
ringQ.MulCoeffsMontgomery(c00, ct0Q0.Value[1], ct2Q0.Value[1]) // c1 = 2*c0[0]*c0[1]
ringQ.MulCoeffsMontgomery(*c00, ct0Q0.Value[0], ct2Q0.Value[0]) // c0 = c0[0]*c0[0]
ringQ.MulCoeffsMontgomery(*c01, ct0Q0.Value[1], ct2Q0.Value[2]) // c2 = c0[1]*c0[1]
ringQ.MulCoeffsMontgomery(*c00, ct0Q0.Value[1], ct2Q0.Value[1]) // c1 = 2*c0[0]*c0[1]
ringQ.AddLazy(ct2Q0.Value[1], ct2Q0.Value[1], ct2Q0.Value[1])
ringQMul.MulCoeffsMontgomery(c00M, ct0Q1.Value[0], ct2Q1.Value[0])
ringQMul.MulCoeffsMontgomery(c01M, ct0Q1.Value[1], ct2Q1.Value[2])
ringQMul.MulCoeffsMontgomery(c00M, ct0Q1.Value[1], ct2Q1.Value[1])
ringQMul.MulCoeffsMontgomery(*c00M, ct0Q1.Value[0], ct2Q1.Value[0])
ringQMul.MulCoeffsMontgomery(*c01M, ct0Q1.Value[1], ct2Q1.Value[2])
ringQMul.MulCoeffsMontgomery(*c00M, ct0Q1.Value[1], ct2Q1.Value[1])
ringQMul.AddLazy(ct2Q1.Value[1], ct2Q1.Value[1], ct2Q1.Value[1])
// Normal case
} else {
ringQ.MulCoeffsMontgomery(c00, ct1Q0.Value[0], ct2Q0.Value[0]) // c0 = c0[0]*c1[0]
ringQ.MulCoeffsMontgomery(c01, ct1Q0.Value[1], ct2Q0.Value[2]) // c2 = c0[1]*c1[1]
ringQ.MulCoeffsMontgomery(c00, ct1Q0.Value[1], ct2Q0.Value[1]) // c1 = c0[0]*c1[1] + c0[1]*c1[0]
ringQ.MulCoeffsMontgomeryThenAddLazy(c01, ct1Q0.Value[0], ct2Q0.Value[1])
ringQ.MulCoeffsMontgomery(*c00, ct1Q0.Value[0], ct2Q0.Value[0]) // c0 = c0[0]*c1[0]
ringQ.MulCoeffsMontgomery(*c01, ct1Q0.Value[1], ct2Q0.Value[2]) // c2 = c0[1]*c1[1]
ringQ.MulCoeffsMontgomery(*c00, ct1Q0.Value[1], ct2Q0.Value[1]) // c1 = c0[0]*c1[1] + c0[1]*c1[0]
ringQ.MulCoeffsMontgomeryThenAddLazy(*c01, ct1Q0.Value[0], ct2Q0.Value[1])
ringQMul.MulCoeffsMontgomery(c00M, ct1Q1.Value[0], ct2Q1.Value[0])
ringQMul.MulCoeffsMontgomery(c01M, ct1Q1.Value[1], ct2Q1.Value[2])
ringQMul.MulCoeffsMontgomery(c00M, ct1Q1.Value[1], ct2Q1.Value[1])
ringQMul.MulCoeffsMontgomeryThenAddLazy(c01M, ct1Q1.Value[0], ct2Q1.Value[1])
ringQMul.MulCoeffsMontgomery(*c00M, ct1Q1.Value[0], ct2Q1.Value[0])
ringQMul.MulCoeffsMontgomery(*c01M, ct1Q1.Value[1], ct2Q1.Value[2])
ringQMul.MulCoeffsMontgomery(*c00M, ct1Q1.Value[1], ct2Q1.Value[1])
ringQMul.MulCoeffsMontgomeryThenAddLazy(*c01M, ct1Q1.Value[0], ct2Q1.Value[1])
}
}
@@ -1210,14 +1156,9 @@ func (eval Evaluator) MulThenAdd(op0 *rlwe.Ciphertext, op1 rlwe.Operand, opOut *
opOut.Resize(op0.Degree(), opOut.Level())
// Instantiates new plaintext from buffer
pt, err := rlwe.NewPlaintextAtLevelFromPoly(level, eval.buffQ[0])
pt := eval.pool.GetBuffPt(level)
defer eval.pool.RecycleBuffPt(pt)
// This error should not happen, unless the evaluator's buffer were
// improperly tempered with. If it does happen, there is no way to
// recover from it.
if err != nil {
panic(err)
}
pt.MetaData = op0.MetaData.CopyNew() // Sets the metadata, notably matches scales
// op1 *= (op1.Scale / opOut.Scale)
@@ -1291,15 +1232,19 @@ func (eval Evaluator) mulRelinThenAdd(op0 *rlwe.Ciphertext, op1 *rlwe.Element[ri
level := opOut.Level()
ringQ := eval.parameters.RingQ().AtLevel(level)
poolQ := eval.poolQ.AtLevel(level)
sT := eval.parameters.RingT().SubRings[0]
var c00, c01, c0, c1, c2 ring.Poly
var c0, c1, c2 ring.Poly
var c00, c01 *ring.Poly
// Case Ciphertext (x) Ciphertext
if op0.Degree() == 1 && op1.Degree() == 1 {
c00 = eval.buffQ[0]
c01 = eval.buffQ[1]
c00 = poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(c00)
c01 = poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(c01)
c0 = opOut.Value[0]
c1 = opOut.Value[1]
@@ -1309,7 +1254,9 @@ func (eval Evaluator) mulRelinThenAdd(op0 *rlwe.Ciphertext, op1 *rlwe.Element[ri
c2 = opOut.Value[2]
} else {
opOut.Resize(utils.Max(1, opOut.Degree()), level)
c2 = eval.buffQ[2]
buffQ := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(buffQ)
c2 = *buffQ
}
tmp0, tmp1 := op0.El(), op1.El()
@@ -1329,18 +1276,18 @@ func (eval Evaluator) mulRelinThenAdd(op0 *rlwe.Ciphertext, op1 *rlwe.Element[ri
}
// Multiply by T * 2^{64} * 2^{64} -> result multipled by T and switched in the Montgomery domain
ringQ.MulRNSScalarMontgomery(tmp0.Value[0], eval.tMontgomery, c00)
ringQ.MulRNSScalarMontgomery(tmp0.Value[1], eval.tMontgomery, c01)
ringQ.MulRNSScalarMontgomery(tmp0.Value[0], eval.tMontgomery, *c00)
ringQ.MulRNSScalarMontgomery(tmp0.Value[1], eval.tMontgomery, *c01)
// Scales the input to the output scale
if r0 != 1 {
ringQ.MulScalar(c00, r0, c00)
ringQ.MulScalar(c01, r0, c01)
ringQ.MulScalar(*c00, r0, *c00)
ringQ.MulScalar(*c01, r0, *c01)
}
ringQ.MulCoeffsMontgomeryThenAdd(c00, tmp1.Value[0], c0) // c0 += c[0]*c[0]
ringQ.MulCoeffsMontgomeryThenAdd(c00, tmp1.Value[1], c1) // c1 += c[0]*c[1]
ringQ.MulCoeffsMontgomeryThenAdd(c01, tmp1.Value[0], c1) // c1 += c[1]*c[0]
ringQ.MulCoeffsMontgomeryThenAdd(*c00, tmp1.Value[0], c0) // c0 += c[0]*c[0]
ringQ.MulCoeffsMontgomeryThenAdd(*c00, tmp1.Value[1], c1) // c1 += c[0]*c[1]
ringQ.MulCoeffsMontgomeryThenAdd(*c01, tmp1.Value[0], c1) // c1 += c[1]*c[0]
if relin {
@@ -1350,10 +1297,10 @@ func (eval Evaluator) mulRelinThenAdd(op0 *rlwe.Ciphertext, op1 *rlwe.Element[ri
return fmt.Errorf("cannot Relinearize: %w", err)
}
ringQ.MulCoeffsMontgomery(c01, tmp1.Value[1], c2) // c2 += c[1]*c[1]
ringQ.MulCoeffsMontgomery(*c01, tmp1.Value[1], c2) // c2 += c[1]*c[1]
tmpCt := &rlwe.Ciphertext{}
tmpCt.Value = []ring.Poly{eval.BuffQP[1].Q, eval.BuffQP[2].Q}
tmpCt := eval.pool.GetBuffCt(1, level)
defer eval.pool.RecycleBuffCt(tmpCt)
tmpCt.MetaData = &rlwe.MetaData{}
tmpCt.IsNTT = true
@@ -1363,7 +1310,7 @@ func (eval Evaluator) mulRelinThenAdd(op0 *rlwe.Ciphertext, op1 *rlwe.Element[ri
ringQ.Add(opOut.Value[1], tmpCt.Value[1], opOut.Value[1])
} else {
ringQ.MulCoeffsMontgomeryThenAdd(c01, tmp1.Value[1], c2) // c2 += c[1]*c[1]
ringQ.MulCoeffsMontgomeryThenAdd(*c01, tmp1.Value[1], c2) // c2 += c[1]*c[1]
}
// Case Plaintext (x) Ciphertext or Ciphertext (x) Plaintext
@@ -1371,10 +1318,11 @@ func (eval Evaluator) mulRelinThenAdd(op0 *rlwe.Ciphertext, op1 *rlwe.Element[ri
opOut.Resize(utils.Max(op0.Degree(), opOut.Degree()), level)
c00 := eval.buffQ[0]
c00 := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(c00)
// Multiply by T * 2^{64} * 2^{64} -> result multipled by T and switched in the Montgomery domain
ringQ.MulRNSScalarMontgomery(op1.El().Value[0], eval.tMontgomery, c00)
ringQ.MulRNSScalarMontgomery(op1.El().Value[0], eval.tMontgomery, *c00)
// If op0.Scale * op1.Scale != opOut.Scale then
// updates op1.Scale and opOut.Scale
@@ -1391,11 +1339,11 @@ func (eval Evaluator) mulRelinThenAdd(op0 *rlwe.Ciphertext, op1 *rlwe.Element[ri
}
if r0 != 1 {
ringQ.MulScalar(c00, r0, c00)
ringQ.MulScalar(*c00, r0, *c00)
}
for i := range op0.Value {
ringQ.MulCoeffsMontgomeryThenAdd(op0.Value[i], c00, opOut.Value[i])
ringQ.MulCoeffsMontgomeryThenAdd(op0.Value[i], *c00, opOut.Value[i])
}
}
@@ -1432,9 +1380,12 @@ func (eval Evaluator) Rescale(op0, opOut *rlwe.Ciphertext) (err error) {
level := op0.Level()
ringQ := eval.parameters.RingQ().AtLevel(level)
poolQ := eval.poolQ.AtLevel(level)
buffQ := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(buffQ)
for i := range opOut.Value {
ringQ.DivRoundByLastModulusNTT(op0.Value[i], eval.buffQ[0], opOut.Value[i])
ringQ.DivRoundByLastModulusNTT(op0.Value[i], *buffQ, opOut.Value[i])
}
opOut.Resize(opOut.Degree(), level-1)
@@ -1548,7 +1499,8 @@ func (eval Evaluator) InnerSum(ctIn *rlwe.Ciphertext, batchSize, n int, opOut *r
return
}
ctTmp := &rlwe.Ciphertext{Element: rlwe.Element[ring.Poly]{Value: []ring.Poly{eval.BuffQP[2].Q, eval.BuffQP[3].Q}}}
ctTmp := eval.pool.GetBuffCt(1, opOut.Level())
defer eval.pool.RecycleBuffCt(ctTmp)
ctTmp.MetaData = opOut.MetaData
if err = eval.RotateRows(opOut, ctTmp); err != nil {
return
+3 -1
View File
@@ -103,6 +103,7 @@ func NewParameters(rlweParams rlwe.Parameters, t uint64) (p Parameters, err erro
if err != nil {
return Parameters{}, err
}
if ringQMul, err = ring.NewRing(rlweParams.N(), primes); err != nil {
return Parameters{}, err
}
@@ -118,7 +119,8 @@ func NewParameters(rlweParams rlwe.Parameters, t uint64) (p Parameters, err erro
var ringT *ring.Ring
/* #nosec G115 -- library requires 64-bit system -> int = int64 */
if ringT, err = ring.NewRing(utils.Min(rlweParams.N(), int(order>>1)), []uint64{t}); err != nil {
dimRingT := utils.Min(rlweParams.N(), int(order>>1))
if ringT, err = ring.NewRing(dimRingT, []uint64{t}); err != nil {
return Parameters{}, fmt.Errorf("provided plaintext modulus t is invalid: %w", err)
}
+24 -9
View File
@@ -15,6 +15,7 @@ type DomainSwitcher struct {
stdToci, ciToStd *rlwe.EvaluationKey
automorphismIndex []uint64
poolQ *ring.BufferPool
}
// NewDomainSwitcher instantiate a new [DomainSwitcher] type. It may be instantiated from parameters from either RingType.
@@ -40,6 +41,7 @@ func NewDomainSwitcher(params Parameters, comlexToRealEvk, realToComplexEvk *rlw
if s.automorphismIndex, err = ring.AutomorphismNTTIndex(s.stdRingQ.N(), s.stdRingQ.NthRoot(), s.stdRingQ.NthRoot()-1); err != nil {
panic(err)
}
s.poolQ = ring.NewPool(s.stdRingQ)
return s, nil
}
@@ -72,15 +74,21 @@ func (switcher DomainSwitcher) ComplexToReal(eval *Evaluator, ctIn, opOut *rlwe.
return fmt.Errorf("cannot ComplexToReal: no realToComplexEvk provided to this DomainSwitcher")
}
poolQ := switcher.poolQ.AtLevel(level)
buffQ1 := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(buffQ1)
buffQ2 := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(buffQ2)
ctTmp := &rlwe.Ciphertext{}
ctTmp.Value = []ring.Poly{evalRLWE.BuffQP[1].Q, evalRLWE.BuffQP[2].Q}
ctTmp.Value = []ring.Poly{*buffQ1, *buffQ2}
ctTmp.MetaData = ctIn.MetaData
evalRLWE.GadgetProduct(level, ctIn.Value[1], &switcher.stdToci.GadgetCiphertext, ctTmp)
switcher.stdRingQ.AtLevel(level).Add(evalRLWE.BuffQP[1].Q, ctIn.Value[0], evalRLWE.BuffQP[1].Q)
switcher.stdRingQ.AtLevel(level).Add(*buffQ1, ctIn.Value[0], *buffQ1)
switcher.conjugateRingQ.AtLevel(level).FoldStandardToConjugateInvariant(evalRLWE.BuffQP[1].Q, switcher.automorphismIndex, opOut.Value[0])
switcher.conjugateRingQ.AtLevel(level).FoldStandardToConjugateInvariant(evalRLWE.BuffQP[2].Q, switcher.automorphismIndex, opOut.Value[1])
switcher.conjugateRingQ.AtLevel(level).FoldStandardToConjugateInvariant(*buffQ1, switcher.automorphismIndex, opOut.Value[0])
switcher.conjugateRingQ.AtLevel(level).FoldStandardToConjugateInvariant(*buffQ2, switcher.automorphismIndex, opOut.Value[1])
*opOut.MetaData = *ctIn.MetaData
opOut.Scale = ctIn.Scale.Mul(rlwe.NewScale(2))
return
@@ -113,17 +121,24 @@ func (switcher DomainSwitcher) RealToComplex(eval *Evaluator, ctIn, opOut *rlwe.
return fmt.Errorf("cannot RealToComplex: no realToComplexEvk provided to this DomainSwitcher")
}
switcher.stdRingQ.AtLevel(level).UnfoldConjugateInvariantToStandard(ctIn.Value[0], opOut.Value[0])
switcher.stdRingQ.AtLevel(level).UnfoldConjugateInvariantToStandard(ctIn.Value[1], opOut.Value[1])
stdRingQ := switcher.stdRingQ.AtLevel(level)
stdRingQ.UnfoldConjugateInvariantToStandard(ctIn.Value[0], opOut.Value[0])
stdRingQ.UnfoldConjugateInvariantToStandard(ctIn.Value[1], opOut.Value[1])
poolQ := switcher.poolQ.AtLevel(level)
buffQ1 := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(buffQ1)
buffQ2 := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(buffQ2)
ctTmp := &rlwe.Ciphertext{}
ctTmp.Value = []ring.Poly{evalRLWE.BuffQP[1].Q, evalRLWE.BuffQP[2].Q}
ctTmp.Value = []ring.Poly{*buffQ1, *buffQ2}
ctTmp.MetaData = ctIn.MetaData
// Switches the RCKswitcher key [X+X^-1] to a CKswitcher key [X]
evalRLWE.GadgetProduct(level, opOut.Value[1], &switcher.ciToStd.GadgetCiphertext, ctTmp)
switcher.stdRingQ.AtLevel(level).Add(opOut.Value[0], evalRLWE.BuffQP[1].Q, opOut.Value[0])
opOut.Value[1].CopyLvl(level, evalRLWE.BuffQP[2].Q)
stdRingQ.Add(opOut.Value[0], *buffQ1, opOut.Value[0])
opOut.Value[1].CopyLvl(level, *buffQ2)
*opOut.MetaData = *ctIn.MetaData
return
}
+233
View File
@@ -39,6 +39,7 @@ func BenchmarkCKKS(b *testing.B) {
for _, testSet := range []func(tc *TestContext, b *testing.B){
benchEncoder,
benchEvaluator,
benchEvaluatorParallel,
} {
testSet(tc, b)
runtime.GC()
@@ -91,6 +92,238 @@ func benchEncoder(tc *TestContext, b *testing.B) {
})
}
func benchEvaluatorParallel(tc *TestContext, b *testing.B) {
params := tc.Params
plaintext := NewPlaintext(params, params.MaxLevel())
plaintext.Value = rlwe.NewCiphertextRandom(tc.Prng, params.Parameters, 0, plaintext.Level()).Value[0]
vector := make([]float64, params.MaxSlots())
for i := range vector {
vector[i] = 1
}
ciphertext1 := rlwe.NewCiphertextRandom(tc.Prng, params.Parameters, 1, params.MaxLevel())
ciphertext2 := rlwe.NewCiphertextRandom(tc.Prng, params.Parameters, 1, params.MaxLevel())
*ciphertext1.MetaData = *plaintext.MetaData
*ciphertext2.MetaData = *plaintext.MetaData
eval := tc.Evl.WithKey(rlwe.NewMemEvaluationKeySet(tc.Kgen.GenRelinearizationKeyNew(tc.Sk)))
b.Run(name("EvaluatorParallel/Add/Scalar", tc), func(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
receiver := NewCiphertext(params, 1, ciphertext1.Level())
for pb.Next() {
if err := eval.Add(ciphertext1, 3.1415-1.4142i, receiver); err != nil {
b.Log(err)
b.Fail()
}
}
})
})
b.Run(name("EvaluatorParallel/Add/Vector", tc), func(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
receiver := NewCiphertext(params, 1, ciphertext1.Level())
for pb.Next() {
if err := eval.Add(ciphertext1, vector, receiver); err != nil {
b.Log(err)
b.Fail()
}
}
})
})
b.Run(name("EvaluatorParallel/Add/Plaintext", tc), func(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
receiver := NewCiphertext(params, 1, ciphertext1.Level())
for pb.Next() {
if err := eval.Add(ciphertext1, plaintext, receiver); err != nil {
b.Log(err)
b.Fail()
}
}
})
})
b.Run(name("EvaluatorParallel/Add/Ciphertext", tc), func(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
receiver := NewCiphertext(params, 1, ciphertext1.Level())
for pb.Next() {
if err := eval.Add(ciphertext1, ciphertext2, receiver); err != nil {
b.Log(err)
b.Fail()
}
}
})
})
b.Run(name("EvaluatorParallel/Mul/Scalar", tc), func(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
receiver := NewCiphertext(params, 1, ciphertext1.Level())
for pb.Next() {
if err := eval.Mul(ciphertext1, 3.1415-1.4142i, receiver); err != nil {
b.Log(err)
b.Fail()
}
}
})
})
b.Run(name("EvaluatorParallel/Mul/Vector", tc), func(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
receiver := NewCiphertext(params, 1, ciphertext1.Level())
for pb.Next() {
if err := eval.Mul(ciphertext1, vector, receiver); err != nil {
b.Log(err)
b.Fail()
}
}
})
})
b.Run(name("EvaluatorParallel/Mul/Plaintext", tc), func(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
receiver := NewCiphertext(params, 1, ciphertext1.Level())
for pb.Next() {
if err := eval.Mul(ciphertext1, plaintext, receiver); err != nil {
b.Log(err)
b.Fail()
}
}
})
})
b.Run(name("EvaluatorParallel/Mul/Ciphertext", tc), func(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
receiver := NewCiphertext(params, 2, ciphertext1.Level())
for pb.Next() {
if err := eval.Mul(ciphertext1, ciphertext2, receiver); err != nil {
b.Log(err)
b.Fail()
}
}
})
})
b.Run(name("EvaluatorParallel/MulRelin/Ciphertext", tc), func(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
receiver := NewCiphertext(params, 1, ciphertext1.Level())
for pb.Next() {
if err := eval.MulRelin(ciphertext1, ciphertext2, receiver); err != nil {
b.Log(err)
b.Fail()
}
}
})
})
b.Run(name("EvaluatorParallel/MulThenAdd/Scalar", tc), func(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
receiver := NewCiphertext(params, 1, ciphertext1.Level())
for pb.Next() {
if err := eval.MulThenAdd(ciphertext1, 3.1415-1.4142i, receiver); err != nil {
b.Log(err)
b.Fail()
}
}
})
})
b.Run(name("EvaluatorParallel/MulThenAdd/Vector", tc), func(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
receiver := NewCiphertext(params, 1, ciphertext1.Level())
for pb.Next() {
if err := eval.MulThenAdd(ciphertext1, vector, receiver); err != nil {
b.Log(err)
b.Fail()
}
}
})
})
b.Run(name("EvaluatorParallel/MulThenAdd/Plaintext", tc), func(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
receiver := NewCiphertext(params, 1, ciphertext1.Level())
for pb.Next() {
if err := eval.MulThenAdd(ciphertext1, plaintext, receiver); err != nil {
b.Log(err)
b.Fail()
}
}
})
})
b.Run(name("EvaluatorParallel/MulThenAdd/Ciphertext", tc), func(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
receiver := NewCiphertext(params, 2, ciphertext1.Level())
for pb.Next() {
if err := eval.MulThenAdd(ciphertext1, ciphertext2, receiver); err != nil {
b.Log(err)
b.Fail()
}
}
})
})
b.Run(name("EvaluatorParallel/MulRelinThenAdd/Ciphertext", tc), func(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
receiver := NewCiphertext(params, 1, ciphertext1.Level())
for pb.Next() {
if err := eval.MulRelinThenAdd(ciphertext1, ciphertext2, receiver); err != nil {
b.Log(err)
b.Fail()
}
}
})
})
b.Run(name("EvaluatorParallel/Rescale", tc), func(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
receiver := NewCiphertext(params, 1, ciphertext1.Level()-1)
for pb.Next() {
if err := eval.Rescale(ciphertext1, receiver); err != nil {
b.Log(err)
b.Fail()
}
}
})
})
b.Run(name("EvaluatorParallel/Rotate", tc), func(b *testing.B) {
b.ResetTimer()
gk := tc.Kgen.GenGaloisKeyNew(5, tc.Sk)
evk := rlwe.NewMemEvaluationKeySet(nil, gk)
eval := eval.WithKey(evk)
b.RunParallel(func(pb *testing.PB) {
receiver := NewCiphertext(params, 1, ciphertext1.Level())
b.ResetTimer()
for pb.Next() {
if err := eval.Rotate(ciphertext1, 1, receiver); err != nil {
b.Log(err)
b.Fail()
}
}
})
})
}
func benchEvaluator(tc *TestContext, b *testing.B) {
params := tc.Params
+29
View File
@@ -149,6 +149,7 @@ func testParameters(tc *TestContext, t *testing.T) {
func testEncoder(tc *TestContext, t *testing.T) {
t.Run(name("Encoder/IsBatched=true", tc), func(t *testing.T) {
t.Parallel()
values, plaintext, _ := tc.NewTestVector(-1-1i, 1+1i)
@@ -158,6 +159,7 @@ func testEncoder(tc *TestContext, t *testing.T) {
logprec := float64(tc.Params.LogDefaultScale()) / 2
t.Run(name("Encoder/IsBatched=true/DecodePublic/[]float64", tc), func(t *testing.T) {
t.Parallel()
values, plaintext, _ := tc.NewTestVector(-1-1i, 1+1i)
@@ -176,6 +178,7 @@ func testEncoder(tc *TestContext, t *testing.T) {
})
t.Run(name("Encoder/IsBatched=true/DecodePublic/[]complex128", tc), func(t *testing.T) {
t.Parallel()
if tc.Params.RingType() == ring.ConjugateInvariant {
t.Skip()
@@ -202,6 +205,7 @@ func testEncoder(tc *TestContext, t *testing.T) {
})
t.Run(name("Encoder/IsBatched=true/DecodePublic/[]big.Float", tc), func(t *testing.T) {
t.Parallel()
values, plaintext, _ := tc.NewTestVector(-1-1i, 1+1i)
have := make([]*big.Float, len(values))
require.NoError(t, tc.Ecd.DecodePublic(plaintext, have, logprec))
@@ -216,6 +220,7 @@ func testEncoder(tc *TestContext, t *testing.T) {
})
t.Run(name("Encoder/IsBatched=true/DecodePublic/[]bignum.Complex", tc), func(t *testing.T) {
t.Parallel()
if tc.Params.RingType() == ring.ConjugateInvariant {
t.Skip()
}
@@ -237,6 +242,7 @@ func testEncoder(tc *TestContext, t *testing.T) {
})
t.Run(name("Encoder/IsBatched=false", tc), func(t *testing.T) {
t.Parallel()
slots := tc.Params.N()
@@ -302,6 +308,7 @@ func testEncoder(tc *TestContext, t *testing.T) {
func testEvaluatorAdd(tc *TestContext, t *testing.T) {
t.Run(name("Evaluator/AddNew/Ct", tc), func(t *testing.T) {
t.Parallel()
values1, _, ciphertext1 := tc.NewTestVector(-1-1i, 1+1i)
values2, _, ciphertext2 := tc.NewTestVector(-1-1i, 1+1i)
@@ -317,6 +324,7 @@ func testEvaluatorAdd(tc *TestContext, t *testing.T) {
})
t.Run(name("Evaluator/Add/Ct", tc), func(t *testing.T) {
t.Parallel()
values1, _, ciphertext1 := tc.NewTestVector(-1-1i, 1+1i)
values2, _, ciphertext2 := tc.NewTestVector(-1-1i, 1+1i)
@@ -331,6 +339,7 @@ func testEvaluatorAdd(tc *TestContext, t *testing.T) {
})
t.Run(name("Evaluator/Add/Pt", tc), func(t *testing.T) {
t.Parallel()
values1, _, ciphertext1 := tc.NewTestVector(-1-1i, 1+1i)
values2, plaintext2, _ := tc.NewTestVector(-1-1i, 1+1i)
@@ -345,6 +354,7 @@ func testEvaluatorAdd(tc *TestContext, t *testing.T) {
})
t.Run(name("Evaluator/Add/Scalar", tc), func(t *testing.T) {
t.Parallel()
values, _, ciphertext := tc.NewTestVector(-1-1i, 1+1i)
@@ -360,6 +370,7 @@ func testEvaluatorAdd(tc *TestContext, t *testing.T) {
})
t.Run(name("Evaluator/Add/Vector", tc), func(t *testing.T) {
t.Parallel()
values1, _, ciphertext := tc.NewTestVector(-1-1i, 1+1i)
values2, _, _ := tc.NewTestVector(-1-1i, 1+1i)
@@ -377,6 +388,7 @@ func testEvaluatorAdd(tc *TestContext, t *testing.T) {
func testEvaluatorSub(tc *TestContext, t *testing.T) {
t.Run(name("Evaluator/SubNew/Ct", tc), func(t *testing.T) {
t.Parallel()
values1, _, ciphertext1 := tc.NewTestVector(-1-1i, 1+1i)
values2, _, ciphertext2 := tc.NewTestVector(-1-1i, 1+1i)
@@ -392,6 +404,7 @@ func testEvaluatorSub(tc *TestContext, t *testing.T) {
})
t.Run(name("Evaluator/Sub/Ct", tc), func(t *testing.T) {
t.Parallel()
values1, _, ciphertext1 := tc.NewTestVector(-1-1i, 1+1i)
values2, _, ciphertext2 := tc.NewTestVector(-1-1i, 1+1i)
@@ -406,6 +419,7 @@ func testEvaluatorSub(tc *TestContext, t *testing.T) {
})
t.Run(name("Evaluator/Sub/Pt", tc), func(t *testing.T) {
t.Parallel()
values1, _, ciphertext1 := tc.NewTestVector(-1-1i, 1+1i)
values2, plaintext2, ciphertext2 := tc.NewTestVector(-1-1i, 1+1i)
@@ -422,6 +436,7 @@ func testEvaluatorSub(tc *TestContext, t *testing.T) {
})
t.Run(name("Evaluator/Sub/Scalar", tc), func(t *testing.T) {
t.Parallel()
values, _, ciphertext := tc.NewTestVector(-1-1i, 1+1i)
@@ -437,6 +452,7 @@ func testEvaluatorSub(tc *TestContext, t *testing.T) {
})
t.Run(name("Evaluator/Sub/Vector", tc), func(t *testing.T) {
t.Parallel()
values1, _, ciphertext := tc.NewTestVector(-1-1i, 1+1i)
values2, _, _ := tc.NewTestVector(-1-1i, 1+1i)
@@ -454,6 +470,7 @@ func testEvaluatorSub(tc *TestContext, t *testing.T) {
func testEvaluatorRescale(tc *TestContext, t *testing.T) {
t.Run(name("Evaluator/RescaleTo/Single", tc), func(t *testing.T) {
t.Parallel()
if tc.Params.MaxLevel() < 2 {
t.Skip("skipping test for params max level < 2")
@@ -475,6 +492,7 @@ func testEvaluatorRescale(tc *TestContext, t *testing.T) {
})
t.Run(name("Evaluator/RescaleTo/Many", tc), func(t *testing.T) {
t.Parallel()
if tc.Params.MaxLevel() < 2 {
t.Skip("skipping test for params max level < 2")
@@ -504,6 +522,7 @@ func testEvaluatorRescale(tc *TestContext, t *testing.T) {
func testEvaluatorMul(tc *TestContext, t *testing.T) {
t.Run(name("Evaluator/MulNew/Ct/Pt", tc), func(t *testing.T) {
t.Parallel()
values1, plaintext1, ciphertext1 := tc.NewTestVector(-1-1i, 1+1i)
@@ -520,6 +539,7 @@ func testEvaluatorMul(tc *TestContext, t *testing.T) {
})
t.Run(name("Evaluator/Mul/Ct/Scalar", tc), func(t *testing.T) {
t.Parallel()
values, _, ciphertext := tc.NewTestVector(-1-1i, 1+1i)
@@ -537,6 +557,7 @@ func testEvaluatorMul(tc *TestContext, t *testing.T) {
})
t.Run(name("Evaluator/Mul/Ct/Vector", tc), func(t *testing.T) {
t.Parallel()
values1, _, ciphertext := tc.NewTestVector(-1-1i, 1+1i)
values2, _, _ := tc.NewTestVector(-1-1i, 1+1i)
@@ -553,6 +574,7 @@ func testEvaluatorMul(tc *TestContext, t *testing.T) {
})
t.Run(name("Evaluator/Mul/Ct/Pt", tc), func(t *testing.T) {
t.Parallel()
values1, plaintext1, ciphertext1 := tc.NewTestVector(-1-1i, 1+1i)
@@ -568,6 +590,7 @@ func testEvaluatorMul(tc *TestContext, t *testing.T) {
})
t.Run(name("Evaluator/Mul/Ct/Ct/Degree0", tc), func(t *testing.T) {
t.Parallel()
values1, plaintext1, _ := tc.NewTestVector(-1-1i, 1+1i)
values2, _, ciphertext2 := tc.NewTestVector(-1-1i, 1+1i)
@@ -588,6 +611,7 @@ func testEvaluatorMul(tc *TestContext, t *testing.T) {
})
t.Run(name("Evaluator/MulRelin/Ct/Ct", tc), func(t *testing.T) {
t.Parallel()
// op0 <- op0 * op1
values1, _, ciphertext1 := tc.NewTestVector(-1-1i, 1+1i)
@@ -632,6 +656,7 @@ func testEvaluatorMul(tc *TestContext, t *testing.T) {
func testEvaluatorMulThenAdd(tc *TestContext, t *testing.T) {
t.Run(name("Evaluator/MulThenAdd/Scalar", tc), func(t *testing.T) {
t.Parallel()
values1, _, ciphertext1 := tc.NewTestVector(-1-1i, 1+1i)
values2, _, ciphertext2 := tc.NewTestVector(-1-1i, 1+1i)
@@ -655,6 +680,7 @@ func testEvaluatorMulThenAdd(tc *TestContext, t *testing.T) {
})
t.Run(name("Evaluator/MulThenAdd/Vector", tc), func(t *testing.T) {
t.Parallel()
values1, _, ciphertext1 := tc.NewTestVector(-1-1i, 1+1i)
values2, _, ciphertext2 := tc.NewTestVector(-1-1i, 1+1i)
@@ -678,6 +704,7 @@ func testEvaluatorMulThenAdd(tc *TestContext, t *testing.T) {
})
t.Run(name("Evaluator/MulThenAdd/Pt", tc), func(t *testing.T) {
t.Parallel()
values1, plaintext1, ciphertext1 := tc.NewTestVector(-1, 1)
values2, _, ciphertext2 := tc.NewTestVector(-1, 1)
@@ -701,6 +728,7 @@ func testEvaluatorMulThenAdd(tc *TestContext, t *testing.T) {
})
t.Run(name("Evaluator/MulRelinThenAdd/Ct", tc), func(t *testing.T) {
t.Parallel()
// opOut = opOut + op1 * op0
values1, _, ciphertext1 := tc.NewTestVector(-1-1i, 1+1i)
@@ -747,6 +775,7 @@ func testEvaluatorMulThenAdd(tc *TestContext, t *testing.T) {
func testBridge(tc *TestContext, t *testing.T) {
t.Run(name("Bridge", tc), func(t *testing.T) {
t.Parallel()
if tc.Params.RingType() != ring.ConjugateInvariant {
t.Skip("only tested for params.RingType() == ring.ConjugateInvariant")
+67 -68
View File
@@ -11,6 +11,7 @@ import (
"github.com/tuneinsight/lattigo/v6/ring/ringqp"
"github.com/tuneinsight/lattigo/v6/utils"
"github.com/tuneinsight/lattigo/v6/utils/bignum"
"github.com/tuneinsight/lattigo/v6/utils/structs"
)
type Float interface {
@@ -23,6 +24,10 @@ type Float interface {
type FloatSlice interface {
}
// Complex is an empty interface whose goal is to
// indicate that the expected input should be a complex number.
type Complex any
// GaloisGen is an integer of order N/2 modulo M and that spans Z_M with the integer -1.
// The j-th ring automorphism takes the root zeta to zeta^(5j).
const GaloisGen uint64 = ring.GaloisGen
@@ -55,14 +60,15 @@ type Encoder struct {
prec uint
bigintCoeffs []*big.Int
qHalf *big.Int
buff ring.Poly
m int
rotGroup []int
m int
rotGroup []int
roots interface{}
buffCmplx interface{}
roots interface{}
// Pools used to recycle large objects.
BuffBigIntPool structs.BufferPool[*[]*big.Int]
BuffComplexPool structs.BufferPool[Complex]
poolQ *ring.BufferPool
}
// NewEncoder creates a new [Encoder] from the target parameters.
@@ -90,32 +96,41 @@ func NewEncoder(parameters Parameters, precision ...uint) (ecd *Encoder) {
}
ecd = &Encoder{
prec: prec,
parameters: parameters,
bigintCoeffs: make([]*big.Int, m>>1),
qHalf: bignum.NewInt(0),
buff: parameters.RingQ().NewPoly(),
m: m,
rotGroup: rotGroup,
prec: prec,
parameters: parameters,
m: m,
rotGroup: rotGroup,
}
ecd.BuffBigIntPool = structs.NewSyncPool(func() *[]*big.Int {
buff := make([]*big.Int, m>>1)
return &buff
})
if prec <= 53 {
ecd.roots = GetRootsComplex128(ecd.m)
ecd.buffCmplx = make([]complex128, ecd.m>>2)
ecd.BuffComplexPool = structs.NewSyncPool(func() Complex {
buff := make([]complex128, ecd.m>>2)
return &buff
})
} else {
tmp := make([]*bignum.Complex, ecd.m>>2)
for i := 0; i < ecd.m>>2; i++ {
tmp[i] = &bignum.Complex{bignum.NewFloat(0, prec), bignum.NewFloat(0, prec)}
}
ecd.roots = GetRootsBigComplex(ecd.m, prec)
ecd.buffCmplx = tmp
ecd.BuffComplexPool = structs.NewSyncPool(func() Complex {
buff := make([]*bignum.Complex, ecd.m>>2)
for i := 0; i < ecd.m>>2; i++ {
buff[i] = &bignum.Complex{bignum.NewFloat(0, prec), bignum.NewFloat(0, prec)}
}
return &buff
})
}
ecd.poolQ = ring.NewPool(parameters.RingQ())
return
}
@@ -210,7 +225,9 @@ func (ecd Encoder) embedDouble(values FloatSlice, metadata *rlwe.MetaData, polyO
slots := 1 << metadata.LogDimensions.Cols
var lenValues int
buffCmplx := ecd.buffCmplx.([]complex128)
buffRef := ecd.BuffComplexPool.Get().(*[]complex128)
defer ecd.BuffComplexPool.Put(buffRef)
buffCmplx := *buffRef
switch values := values.(type) {
@@ -331,7 +348,9 @@ func (ecd Encoder) embedArbitrary(values FloatSlice, metadata *rlwe.MetaData, po
slots := 1 << metadata.LogDimensions.Cols
var lenValues int
buffCmplx := ecd.buffCmplx.([]*bignum.Complex)
buffRef := ecd.BuffComplexPool.Get().(*[]*bignum.Complex)
defer ecd.BuffComplexPool.Put(buffRef)
buffCmplx := *buffRef
switch values := values.(type) {
@@ -460,7 +479,9 @@ func (ecd Encoder) plaintextToComplex(level int, scale rlwe.Scale, logSlots int,
if level == 0 {
return polyToComplexNoCRT(p.Coeffs[0], values, scale, logSlots, isreal, ecd.parameters.RingQ().AtLevel(level))
}
return polyToComplexCRT(p, ecd.bigintCoeffs, values, scale, logSlots, isreal, ecd.parameters.RingQ().AtLevel(level))
bigintCoeffs := ecd.BuffBigIntPool.Get()
defer ecd.BuffBigIntPool.Put(bigintCoeffs)
return polyToComplexCRT(p, *bigintCoeffs, values, scale, logSlots, isreal, ecd.parameters.RingQ().AtLevel(level))
}
// plaintextToFloat maps a CRT polynomial to a real valued [FloatSlice].
@@ -482,10 +503,12 @@ func (ecd Encoder) decodePublic(pt *rlwe.Plaintext, values FloatSlice, logprec f
return fmt.Errorf("cannot Decode: ensure that %d <= logSlots (%d) <= %d", 0, logSlots, maxLogCols)
}
buff := ecd.poolQ.GetBuffPoly()
defer ecd.poolQ.RecycleBuffPoly(buff)
if pt.IsNTT {
ecd.parameters.RingQ().AtLevel(pt.Level()).INTT(pt.Value, ecd.buff)
ecd.parameters.RingQ().AtLevel(pt.Level()).INTT(pt.Value, *buff)
} else {
ecd.buff.CopyLvl(pt.Level(), pt.Value)
buff.CopyLvl(pt.Level(), pt.Value)
}
switch values.(type) {
@@ -498,9 +521,11 @@ func (ecd Encoder) decodePublic(pt *rlwe.Plaintext, values FloatSlice, logprec f
if ecd.prec <= 53 {
buffCmplx := ecd.buffCmplx.([]complex128)
buffRef := ecd.BuffComplexPool.Get().(*[]complex128)
defer ecd.BuffComplexPool.Put(buffRef)
buffCmplx := *buffRef
if err = ecd.plaintextToComplex(pt.Level(), pt.Scale, logSlots, ecd.buff, buffCmplx); err != nil {
if err = ecd.plaintextToComplex(pt.Level(), pt.Scale, logSlots, *buff, buffCmplx); err != nil {
return
}
@@ -575,9 +600,11 @@ func (ecd Encoder) decodePublic(pt *rlwe.Plaintext, values FloatSlice, logprec f
}
} else {
buffCmplx := ecd.buffCmplx.([]*bignum.Complex)
buffRef := ecd.BuffComplexPool.Get().(*[]*bignum.Complex)
defer ecd.BuffComplexPool.Put(buffRef)
buffCmplx := *buffRef
if err = ecd.plaintextToComplex(pt.Level(), pt.Scale, logSlots, ecd.buff, buffCmplx[:slots]); err != nil {
if err = ecd.plaintextToComplex(pt.Level(), pt.Scale, logSlots, *buff, buffCmplx[:slots]); err != nil {
return
}
@@ -728,7 +755,7 @@ func (ecd Encoder) decodePublic(pt *rlwe.Plaintext, values FloatSlice, logprec f
}
} else {
return ecd.plaintextToFloat(pt.Level(), pt.Scale, logSlots, ecd.buff, values)
return ecd.plaintextToFloat(pt.Level(), pt.Scale, logSlots, *buff, values)
}
return
@@ -1017,21 +1044,24 @@ func (ecd *Encoder) polyToFloatCRT(p ring.Poly, values FloatSlice, scale rlwe.Sc
slots = utils.Min(len(p.Coeffs[0]), len(values))
}
bigintCoeffs := ecd.bigintCoeffs
buffRef := ecd.BuffBigIntPool.Get()
defer ecd.BuffBigIntPool.Put(buffRef)
bigintCoeffs := *buffRef
ecd.parameters.RingQ().PolyToBigint(ecd.buff, 1, bigintCoeffs)
ecd.parameters.RingQ().PolyToBigint(p, 1, bigintCoeffs)
Q := r.ModulusAtLevel[r.Level()]
ecd.qHalf.Set(Q)
ecd.qHalf.Rsh(ecd.qHalf, 1)
qHalf := new(big.Int)
qHalf.Set(Q)
qHalf.Rsh(qHalf, 1)
var sign int
for i := 0; i < slots; i++ {
// Centers the value around the current modulus
bigintCoeffs[i].Mod(bigintCoeffs[i], Q)
sign = bigintCoeffs[i].Cmp(ecd.qHalf)
sign = bigintCoeffs[i].Cmp(qHalf)
if sign == 1 || sign == 0 {
bigintCoeffs[i].Sub(bigintCoeffs[i], Q)
}
@@ -1183,34 +1213,3 @@ func (ecd *Encoder) polyToFloatNoCRT(coeffs []uint64, values FloatSlice, scale r
return
}
// ShallowCopy returns a lightweight copy of the target object
// that can be used concurrently with the original object.
func (ecd Encoder) ShallowCopy() *Encoder {
var buffCmplx interface{}
if prec := ecd.prec; prec <= 53 {
buffCmplx = make([]complex128, ecd.m>>1)
} else {
tmp := make([]*bignum.Complex, ecd.m>>2)
for i := 0; i < ecd.m>>2; i++ {
tmp[i] = &bignum.Complex{bignum.NewFloat(0, prec), bignum.NewFloat(0, prec)}
}
buffCmplx = tmp
}
return &Encoder{
prec: ecd.prec,
parameters: ecd.parameters,
bigintCoeffs: make([]*big.Int, len(ecd.bigintCoeffs)),
qHalf: new(big.Int),
buff: *ecd.buff.CopyNew(),
m: ecd.m,
rotGroup: ecd.rotGroup,
roots: ecd.roots,
buffCmplx: buffCmplx,
}
}
+73 -83
View File
@@ -15,18 +15,19 @@ import (
// It also holds a memory buffer used to store intermediate computations.
type Evaluator struct {
*Encoder
*evaluatorBuffers
*rlwe.Evaluator
pool *rlwe.BufferPool
}
// NewEvaluator creates a new [Evaluator], that can be used to do homomorphic
// operations on the Ciphertexts and/or Plaintexts. It stores a memory buffer
// and Ciphertexts that will be used for intermediate values.
func NewEvaluator(parameters Parameters, evk rlwe.EvaluationKeySet) *Evaluator {
return &Evaluator{
Encoder: NewEncoder(parameters),
evaluatorBuffers: newEvaluatorBuffers(parameters),
Evaluator: rlwe.NewEvaluator(parameters.Parameters, evk),
Encoder: NewEncoder(parameters),
Evaluator: rlwe.NewEvaluator(parameters.Parameters, evk),
pool: rlwe.NewPool(parameters.RingQP()),
}
}
@@ -40,22 +41,6 @@ func (eval Evaluator) GetRLWEParameters() *rlwe.Parameters {
return &eval.Encoder.parameters.Parameters
}
type evaluatorBuffers struct {
buffQ [3]ring.Poly // Memory buffer in order: for MForm(c0), MForm(c1), c2
}
// BuffQ returns a pointer to the internal memory buffer buffQ.
func (eval Evaluator) BuffQ() [3]ring.Poly {
return eval.buffQ
}
func newEvaluatorBuffers(parameters Parameters) *evaluatorBuffers {
buff := new(evaluatorBuffers)
ringQ := parameters.RingQ()
buff.buffQ = [3]ring.Poly{ringQ.NewPoly(), ringQ.NewPoly(), ringQ.NewPoly()}
return buff
}
// Add adds op1 to op0 and returns the result in opOut.
// The following types are accepted for op1:
// - [rlwe.ElementInterface][[ring.Poly]]
@@ -110,13 +95,8 @@ func (eval Evaluator) Add(op0 *rlwe.Ciphertext, op1 rlwe.Operand, opOut *rlwe.Ci
opOut.Resize(op0.Degree(), level)
// Instantiates new plaintext from buffer
pt, err := rlwe.NewPlaintextAtLevelFromPoly(level, eval.buffQ[0])
// Sanity check, this error should not happen unless the evaluator's buffers
// were improperly tempered with.
if err != nil {
panic(err)
}
pt := eval.pool.GetBuffPt(level)
defer eval.pool.RecycleBuffPt(pt)
pt.MetaData = op0.MetaData // Sets the metadata, notably matches scales
@@ -206,13 +186,8 @@ func (eval Evaluator) Sub(op0 *rlwe.Ciphertext, op1 rlwe.Operand, opOut *rlwe.Ci
opOut.Resize(op0.Degree(), level)
// Instantiates new plaintext from buffer
pt, err := rlwe.NewPlaintextAtLevelFromPoly(level, eval.buffQ[0])
// Sanity check, this error should not happen unless the evaluator's buffers
// were improperly tempered with.
if err != nil {
panic(err)
}
pt := eval.pool.GetBuffPt(level)
defer eval.pool.RecycleBuffPt(pt)
pt.MetaData = op0.MetaData
@@ -257,6 +232,8 @@ func (eval Evaluator) evaluateInPlace(level int, c0 *rlwe.Ciphertext, c1 *rlwe.E
var err error
buffCt := eval.pool.GetBuffCt(maxDegree, level)
defer eval.pool.RecycleBuffCt(buffCt)
// Checks whether or not the receiver element is the same as one of the input elements
// and acts accordingly to avoid unnecessary element creation or element overwriting,
// and scales properly the element before the evaluation.
@@ -270,7 +247,7 @@ func (eval Evaluator) evaluateInPlace(level int, c0 *rlwe.Ciphertext, c1 *rlwe.E
if ratioInt.Cmp(new(big.Int).SetUint64(0)) == 1 {
tmp1, err = rlwe.NewCiphertextAtLevelFromPoly(level, eval.BuffCt.Value[:c1.Degree()+1])
tmp1, err = rlwe.NewCiphertextAtLevelFromPoly(level, buffCt.Value[:c1.Degree()+1])
// Sanity check, this error should not happen unless the evaluator's buffers
// were improperly tempered with.
@@ -333,7 +310,7 @@ func (eval Evaluator) evaluateInPlace(level int, c0 *rlwe.Ciphertext, c1 *rlwe.E
if ratioInt.Cmp(new(big.Int).SetUint64(0)) == 1 {
// Will avoid resizing on the output
tmp0, err = rlwe.NewCiphertextAtLevelFromPoly(level, eval.BuffCt.Value[:c0.Degree()+1])
tmp0, err = rlwe.NewCiphertextAtLevelFromPoly(level, buffCt.Value[:c0.Degree()+1])
// Sanity check, this error should not happen unless the evaluator's buffers
// were improperly tempered with.
@@ -363,7 +340,7 @@ func (eval Evaluator) evaluateInPlace(level int, c0 *rlwe.Ciphertext, c1 *rlwe.E
if ratioInt.Cmp(new(big.Int).SetUint64(0)) == 1 {
// Will avoid resizing on the output
tmp1, err = rlwe.NewCiphertextAtLevelFromPoly(level, eval.BuffCt.Value[:c1.Degree()+1])
tmp1, err = rlwe.NewCiphertextAtLevelFromPoly(level, buffCt.Value[:c1.Degree()+1])
// Sanity check, this error should not happen unless the evaluator's buffers
// were improperly tempered with.
@@ -387,7 +364,7 @@ func (eval Evaluator) evaluateInPlace(level int, c0 *rlwe.Ciphertext, c1 *rlwe.E
if ratioInt.Cmp(new(big.Int).SetUint64(0)) == 1 {
tmp0, err = rlwe.NewCiphertextAtLevelFromPoly(level, eval.BuffCt.Value[:c0.Degree()+1])
tmp0, err = rlwe.NewCiphertextAtLevelFromPoly(level, buffCt.Value[:c0.Degree()+1])
// Sanity check, this error should not happen unless the evaluator's buffers
// were improperly tempered with.
@@ -518,13 +495,16 @@ func (eval Evaluator) Rescale(op0, opOut *rlwe.Ciphertext) (err error) {
*opOut.MetaData = *op0.MetaData
ringQ := params.RingQ().AtLevel(op0.Level())
poolQ := eval.pool.AtLevel(op0.Level())
for i := 0; i < nbRescales; i++ {
opOut.Scale = opOut.Scale.Div(rlwe.NewScale(ringQ.SubRings[op0.Level()-i].Modulus))
}
buffQ := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(buffQ)
for i := range opOut.Value {
ringQ.DivRoundByLastModulusManyNTT(nbRescales, op0.Value[i], eval.buffQ[0], opOut.Value[i])
ringQ.DivRoundByLastModulusManyNTT(nbRescales, op0.Value[i], *buffQ, opOut.Value[i])
}
if op0 == opOut {
@@ -565,6 +545,7 @@ func (eval Evaluator) RescaleTo(op0 *rlwe.Ciphertext, minScale rlwe.Scale, opOut
newLevel := op0.Level()
ringQ := eval.GetParameters().RingQ().AtLevel(op0.Level())
poolQ := eval.pool.AtLevel(op0.Level())
// Divides the scale by each moduli of the modulus chain as long as the scale isn't smaller than minScale/2
// or until the output Level() would be zero
@@ -588,8 +569,10 @@ func (eval Evaluator) RescaleTo(op0 *rlwe.Ciphertext, minScale rlwe.Scale, opOut
}
if nbRescales > 0 {
buffQ := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(buffQ)
for i := range opOut.Value {
ringQ.DivRoundByLastModulusManyNTT(nbRescales, op0.Value[i], eval.buffQ[0], opOut.Value[i])
ringQ.DivRoundByLastModulusManyNTT(nbRescales, op0.Value[i], *buffQ, opOut.Value[i])
}
opOut.Resize(opOut.Degree(), newLevel)
} else {
@@ -695,13 +678,8 @@ func (eval Evaluator) Mul(op0 *rlwe.Ciphertext, op1 rlwe.Operand, opOut *rlwe.Ci
ringQ := eval.GetParameters().RingQ().AtLevel(level)
// Instantiates new plaintext from buffer
pt, err := rlwe.NewPlaintextAtLevelFromPoly(level, eval.buffQ[0])
// Sanity check, this error should not happen unless the evaluator's buffers
// were improperly tempered with.
if err != nil {
panic(err)
}
pt := eval.pool.GetBuffPt(level)
defer eval.pool.RecycleBuffPt(pt)
*pt.MetaData = *op0.MetaData
pt.Scale = rlwe.NewScale(ringQ.SubRings[level].Modulus)
@@ -795,9 +773,14 @@ func (eval Evaluator) mulRelin(op0 *rlwe.Ciphertext, op1 *rlwe.Element[ring.Poly
if op0.Degree() == 1 && op1.Degree() == 1 {
ringQ := eval.GetParameters().RingQ().AtLevel(level)
poolQ := eval.pool.AtLevel(level)
c00 = eval.buffQ[0]
c01 = eval.buffQ[1]
buffQ0 := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(buffQ0)
buffQ1 := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(buffQ1)
c00 = *buffQ0
c01 = *buffQ1
c0 = opOut.Value[0]
c1 = opOut.Value[1]
@@ -806,8 +789,11 @@ func (eval Evaluator) mulRelin(op0 *rlwe.Ciphertext, op1 *rlwe.Element[ring.Poly
opOut.El().Resize(2, level)
c2 = opOut.Value[2]
} else {
buffQ2 := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(buffQ2)
opOut.El().Resize(1, level)
c2 = eval.buffQ[2]
c2 = *buffQ2
}
// Avoid overwriting if the second input is the output
@@ -842,8 +828,8 @@ func (eval Evaluator) mulRelin(op0 *rlwe.Ciphertext, op1 *rlwe.Element[ring.Poly
return fmt.Errorf("cannot MulRelin: Relinearize: %w", err)
}
tmpCt := &rlwe.Ciphertext{}
tmpCt.Value = []ring.Poly{eval.BuffQP[1].Q, eval.BuffQP[2].Q}
tmpCt := eval.pool.GetBuffCt(1, level)
defer eval.pool.RecycleBuffCt(tmpCt)
tmpCt.MetaData = &rlwe.MetaData{}
tmpCt.IsNTT = true
@@ -856,16 +842,21 @@ func (eval Evaluator) mulRelin(op0 *rlwe.Ciphertext, op1 *rlwe.Element[ring.Poly
} else {
ringQ := eval.GetParameters().RingQ().AtLevel(level)
poolQ := eval.pool.AtLevel(level)
var c0 ring.Poly
var c1 []ring.Poly
buffQ := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(buffQ)
if op0.Degree() == 0 {
c0 = eval.buffQ[0]
c0 = *buffQ
ringQ.MForm(op0.Value[0], c0)
c1 = op1.El().Value
} else {
c0 = eval.buffQ[0]
c0 = *buffQ
ringQ.MForm(op1.El().Value[0], c0)
c1 = op0.Value
}
@@ -1019,13 +1010,8 @@ func (eval Evaluator) MulThenAdd(op0 *rlwe.Ciphertext, op1 rlwe.Operand, opOut *
}
// Instantiates new plaintext from buffer
pt, err := rlwe.NewPlaintextAtLevelFromPoly(level, eval.buffQ[0])
// Sanity check, this error should not happen unless the evaluator's buffers
// were improperly tempered with.
if err != nil {
panic(err)
}
pt := eval.pool.GetBuffPt(level)
defer eval.pool.RecycleBuffPt(pt)
pt.MetaData = op0.MetaData.CopyNew()
pt.Scale = scaleRLWE
@@ -1110,14 +1096,19 @@ func (eval Evaluator) mulRelinThenAdd(op0 *rlwe.Ciphertext, op1 *rlwe.Element[ri
}
ringQ := eval.GetParameters().RingQ().AtLevel(level)
poolQ := eval.pool.AtLevel(level)
var c00, c01, c0, c1, c2 ring.Poly
// Case Ciphertext (x) Ciphertext
if op0.Degree() == 1 && op1.Degree() == 1 {
c00 = eval.buffQ[0]
c01 = eval.buffQ[1]
buffQ0 := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(buffQ0)
buffQ1 := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(buffQ1)
c00 = *buffQ0
c01 = *buffQ1
c0 = opOut.Value[0]
c1 = opOut.Value[1]
@@ -1127,7 +1118,9 @@ func (eval Evaluator) mulRelinThenAdd(op0 *rlwe.Ciphertext, op1 *rlwe.Element[ri
c2 = opOut.Value[2]
} else {
opOut.Resize(utils.Max(1, opOut.Degree()), level)
c2 = eval.buffQ[2]
buffQ2 := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(buffQ2)
c2 = *buffQ2
}
tmp0, tmp1 := op0.El(), op1.El()
@@ -1149,8 +1142,8 @@ func (eval Evaluator) mulRelinThenAdd(op0 *rlwe.Ciphertext, op1 *rlwe.Element[ri
ringQ.MulCoeffsMontgomery(c01, tmp1.Value[1], c2) // c2 += c[1]*c[1]
tmpCt := &rlwe.Ciphertext{}
tmpCt.Value = []ring.Poly{eval.BuffQP[1].Q, eval.BuffQP[2].Q}
tmpCt := eval.pool.GetBuffCt(1, level)
defer eval.pool.RecycleBuffCt(tmpCt)
tmpCt.MetaData = &rlwe.MetaData{}
tmpCt.IsNTT = true
@@ -1166,7 +1159,9 @@ func (eval Evaluator) mulRelinThenAdd(op0 *rlwe.Ciphertext, op1 *rlwe.Element[ri
opOut.Resize(utils.Max(op0.Degree(), opOut.Degree()), level)
c00 := eval.buffQ[0]
buffQ0 := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(buffQ0)
c00 := *buffQ0
ringQ.MForm(op1.El().Value[0], c00)
for i := range op0.Value {
@@ -1244,9 +1239,15 @@ func (eval Evaluator) RotateHoistedNew(ctIn *rlwe.Ciphertext, rotations []int) (
// It is much faster than sequential calls to [Evaluator.Rotate].
func (eval Evaluator) RotateHoisted(ctIn *rlwe.Ciphertext, rotations []int, opOut map[int]*rlwe.Ciphertext) (err error) {
levelQ := ctIn.Level()
eval.DecomposeNTT(levelQ, eval.GetParameters().MaxLevelP(), eval.GetParameters().PCount(), ctIn.Value[1], ctIn.IsNTT, eval.BuffDecompQP)
poolQP := eval.pool.AtLevel(levelQ, eval.GetParameters().MaxLevelP())
buffDecompQP := poolQP.GetBuffDecompQP(*eval.GetRLWEParameters(), levelQ, eval.GetParameters().MaxLevelP())
defer poolQP.RecycleBuffDecompQP(buffDecompQP)
eval.DecomposeNTT(levelQ, eval.GetParameters().MaxLevelP(), eval.GetParameters().PCount(), ctIn.Value[1], ctIn.IsNTT, buffDecompQP)
for _, i := range rotations {
if err = eval.AutomorphismHoisted(levelQ, ctIn, eval.BuffDecompQP, eval.GetParameters().GaloisElement(i), opOut[i]); err != nil {
if err = eval.AutomorphismHoisted(levelQ, ctIn, buffDecompQP, eval.GetParameters().GaloisElement(i), opOut[i]); err != nil {
return fmt.Errorf("cannot RotateHoisted: %w", err)
}
}
@@ -1316,23 +1317,12 @@ func (eval Evaluator) RotateAndAdd(ctIn *rlwe.Ciphertext, batchSize, n int, opOu
return
}
// ShallowCopy creates a shallow copy of this evaluator in which all the read-only data-structures are
// shared with the receiver and the temporary buffers are reallocated. The receiver and the returned
// Evaluators can be used concurrently.
func (eval Evaluator) ShallowCopy() *Evaluator {
return &Evaluator{
Encoder: eval.Encoder.ShallowCopy(),
Evaluator: eval.Evaluator.ShallowCopy(),
evaluatorBuffers: newEvaluatorBuffers(*eval.GetParameters()),
}
}
// WithKey creates a shallow copy of the receiver Evaluator for which the new EvaluationKey is evaluationKey
// and where the temporary buffers are shared. The receiver and the returned Evaluators cannot be used concurrently.
func (eval Evaluator) WithKey(evk rlwe.EvaluationKeySet) *Evaluator {
return &Evaluator{
Evaluator: eval.Evaluator.WithKey(evk),
Encoder: eval.Encoder,
evaluatorBuffers: eval.evaluatorBuffers,
Evaluator: eval.Evaluator.WithKey(evk),
Encoder: eval.Encoder,
pool: eval.pool,
}
}
+28 -18
View File
@@ -2,29 +2,46 @@ package sampling
import (
"crypto/rand"
"fmt"
"io"
"sync"
"golang.org/x/crypto/blake2b"
)
// PRNG is an interface for secure (keyed) deterministic generation of random bytes
// PRNG is an interface for secure generation of random bytes
type PRNG interface {
io.Reader
}
// KeyedPRNG is a structure storing the parameters used to securely and deterministically generate shared
type ThreadSafePRNG struct {
}
// NewPRNG returns a new PRNG that is thread-safe
func NewPRNG() (*ThreadSafePRNG, error) {
return &ThreadSafePRNG{}, nil
}
// Read reads bytes from the KeyedPRNG on sum.
func (prng *ThreadSafePRNG) Read(sum []byte) (n int, err error) {
return rand.Read(sum)
}
// KeyedPRNG is a structure storing the parameters used to securely and *deterministically* generate shared
// sequences of random bytes among different parties using the hash function blake2b. Backward sequence
// security (given the digest i, compute the digest i-1) is ensured by default, however forward sequence
// security (given the digest i, compute the digest i+1) is only ensured if the KeyedPRNG is keyed.
// WARNING: If KeyedPRNG is called concurrently by multiple threads, the resulting sequences will be independent and no error will be triggered. However, the result will not be deterministic and therefore, in most cases, it does not make sense to use KeyedPRNG in a concurrent setting.
// NOTE: For a PRNG securely seeded with a private key use [ThreadSafePRNG].
type KeyedPRNG struct {
key []byte
xof blake2b.XOF
mutex sync.Mutex
key []byte
xof blake2b.XOF
}
// NewKeyedPRNG creates a new instance of KeyedPRNG.
// Accepts an optional key, else set key=nil which is treated as key=[]byte{}
// WARNING: A PRNG INITIALISED WITH key=nil IS INSECURE!
// WARNING: KeyedPRNG can be called by multiple threads BUT the generated sequences will not be deterministic.
func NewKeyedPRNG(key []byte) (*KeyedPRNG, error) {
var err error
prng := new(KeyedPRNG)
@@ -32,19 +49,6 @@ func NewKeyedPRNG(key []byte) (*KeyedPRNG, error) {
return prng, err
}
// NewPRNG creates KeyedPRNG keyed from rand.Read for instances were no key should be provided by the user
func NewPRNG() (*KeyedPRNG, error) {
var err error
prng := new(KeyedPRNG)
key := make([]byte, 64)
if _, err := rand.Read(key); err != nil {
return nil, fmt.Errorf("crypto rand error: %w", err)
}
prng.key = key
prng.xof, err = blake2b.NewXOF(blake2b.OutputLengthUnknown, key)
return prng, err
}
// Key returns a copy of the key used to seed the PRNG.
// This value can be used with `NewKeyedPRNG` to instantiate
// a new PRNG that will produce the same stream of bytes.
@@ -55,11 +59,17 @@ func (prng *KeyedPRNG) Key() (key []byte) {
}
// Read reads bytes from the KeyedPRNG on sum.
// WARNING: Read() should NOT be called concurrently by multiple threads. If that occurs, the generated sequence will not be deterministic.
func (prng *KeyedPRNG) Read(sum []byte) (n int, err error) {
prng.mutex.Lock()
defer prng.mutex.Unlock()
return prng.xof.Read(sum)
}
// Reset resets the PRNG to its initial state.
// WARNING: KeyedPRNG's methods should not be called concurrently. If that occurs, the generated sequence will not be deterministic.
func (prng *KeyedPRNG) Reset() {
prng.mutex.Lock()
defer prng.mutex.Unlock()
prng.xof.Reset()
}
+43
View File
@@ -0,0 +1,43 @@
package structs
import "sync"
// BufferPool is an interface for all pools of buffers.
type BufferPool[T any] interface {
Get() T
Put(T)
}
// SyncPool is a wrapper around [sync.Pool] (it avoids doing type conversion after Get()).
type SyncPool[T any] struct {
pool *sync.Pool
}
// NewSyncPool creates a new SyncPool.
// The input function f is the function that is used to create new objects if none is available in the pool.
func NewSyncPool[T any](f func() T) *SyncPool[T] {
pool := &sync.Pool{
New: func() any {
return f()
},
}
return &SyncPool[T]{pool: pool}
}
func NewSyncPoolUint64(N int) *SyncPool[*[]uint64] {
return NewSyncPool(func() *[]uint64 {
buff := make([]uint64, N)
return &buff
})
}
// Get returns a new object of type T from the pool.
func (spool *SyncPool[T]) Get() T {
return spool.pool.Get().(T)
}
// Put returns the buff to the pool.
func (spool *SyncPool[T]) Put(buff T) {
spool.pool.Put(buff)
}