mirror of
https://github.com/luxfi/lattice.git
synced 2026-09-24 00:25:44 +00:00
Merge branch 'main' into Security_note_update
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
//
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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
@@ -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] {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user