mirror of
https://github.com/tuneinsight/lattigo.git
synced 2025-09-13 03:27:14 +00:00
collapse bfv
This commit is contained in:
@@ -34,7 +34,7 @@ func TestLinearTransformation(t *testing.T) {
|
||||
for _, plaintextModulus := range testPlaintextModulus[:] {
|
||||
p.PlaintextModulus = plaintextModulus
|
||||
|
||||
tc := bgv.NewTestContext(p)
|
||||
tc := bgv.NewTestContext(p, false)
|
||||
|
||||
for _, testSet := range []func(tc *bgv.TestContext, t *testing.T){
|
||||
run,
|
||||
|
||||
@@ -5,8 +5,6 @@ import (
|
||||
|
||||
"github.com/tuneinsight/lattigo/v5/circuits/common/polynomial"
|
||||
"github.com/tuneinsight/lattigo/v5/core/rlwe"
|
||||
"github.com/tuneinsight/lattigo/v5/schemes"
|
||||
"github.com/tuneinsight/lattigo/v5/schemes/bfv"
|
||||
"github.com/tuneinsight/lattigo/v5/schemes/bgv"
|
||||
)
|
||||
|
||||
@@ -16,38 +14,16 @@ type Evaluator struct {
|
||||
InvariantTensoring bool
|
||||
}
|
||||
|
||||
// NewEvaluator instantiates a new PolynomialEvaluator from a [schemes.Evaluator].
|
||||
// The default [bgv.Evaluator] is compliant to the [schemes.Evaluator] interface.
|
||||
// InvariantTensoring is a boolean that specifies if the evaluator performed the invariant tensoring (BFV-style) or
|
||||
// the regular tensoring (BGV-style).
|
||||
func NewEvaluator(params bgv.Parameters, eval schemes.Evaluator, InvariantTensoring bool) *Evaluator {
|
||||
|
||||
var evalForPoly schemes.Evaluator
|
||||
|
||||
switch eval := eval.(type) {
|
||||
case *bgv.Evaluator:
|
||||
if InvariantTensoring {
|
||||
evalForPoly = scaleInvariantEvaluator{eval}
|
||||
} else {
|
||||
evalForPoly = eval
|
||||
}
|
||||
case *bfv.Evaluator:
|
||||
if InvariantTensoring {
|
||||
evalForPoly = eval
|
||||
} else {
|
||||
evalForPoly = eval.Evaluator
|
||||
}
|
||||
default:
|
||||
evalForPoly = eval
|
||||
}
|
||||
// NewEvaluator instantiates a new PolynomialEvaluator from a [bgv.Evaluator].
|
||||
func NewEvaluator(params bgv.Parameters, eval *bgv.Evaluator) *Evaluator {
|
||||
|
||||
return &Evaluator{
|
||||
Parameters: params,
|
||||
Evaluator: polynomial.Evaluator[uint64]{
|
||||
Evaluator: evalForPoly,
|
||||
Evaluator: eval,
|
||||
CoefficientGetter: CoefficientGetter{values: make([]uint64, params.MaxSlots())},
|
||||
},
|
||||
InvariantTensoring: InvariantTensoring,
|
||||
InvariantTensoring: eval.ScaleInvariant,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ func TestPolynomialEvaluator(t *testing.T) {
|
||||
for _, plaintextModulus := range testPlaintextModulus[:] {
|
||||
p.PlaintextModulus = plaintextModulus
|
||||
|
||||
tc := bgv.NewTestContext(p)
|
||||
tc := bgv.NewTestContext(p, false)
|
||||
|
||||
for _, testSet := range []func(tc *bgv.TestContext, t *testing.T){
|
||||
run,
|
||||
@@ -65,7 +65,7 @@ func run(tc *bgv.TestContext, t *testing.T) {
|
||||
|
||||
t.Run("Standard"+tc.String(), func(t *testing.T) {
|
||||
|
||||
polyEval := NewEvaluator(tc.Params, tc.Evl, false)
|
||||
polyEval := NewEvaluator(tc.Params, tc.Evl)
|
||||
|
||||
res, err := polyEval.Evaluate(ciphertext, poly, tc.Params.DefaultScale())
|
||||
require.NoError(t, err)
|
||||
@@ -76,8 +76,9 @@ func run(tc *bgv.TestContext, t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Invariant"+tc.String(), func(t *testing.T) {
|
||||
tc.Evl.ScaleInvariant = true
|
||||
|
||||
polyEval := NewEvaluator(tc.Params, tc.Evl, true)
|
||||
polyEval := NewEvaluator(tc.Params, tc.Evl)
|
||||
|
||||
res, err := polyEval.Evaluate(ciphertext, poly, tc.Params.DefaultScale())
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
|
||||
"github.com/tuneinsight/lattigo/v5/circuits/common/polynomial"
|
||||
"github.com/tuneinsight/lattigo/v5/core/rlwe"
|
||||
"github.com/tuneinsight/lattigo/v5/schemes"
|
||||
"github.com/tuneinsight/lattigo/v5/schemes/ckks"
|
||||
"github.com/tuneinsight/lattigo/v5/utils/bignum"
|
||||
)
|
||||
@@ -19,7 +18,7 @@ type Evaluator struct {
|
||||
|
||||
// NewEvaluator instantiates a new [Evaluator] from a [ckks.Evaluator].
|
||||
// This method is allocation free.
|
||||
func NewEvaluator(params ckks.Parameters, eval schemes.Evaluator) *Evaluator {
|
||||
func NewEvaluator(params ckks.Parameters, eval *ckks.Evaluator) *Evaluator {
|
||||
return &Evaluator{
|
||||
Parameters: params,
|
||||
Evaluator: polynomial.Evaluator[*bignum.Complex]{
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 130 KiB |
@@ -6,7 +6,7 @@ The BFV package provides an RNS-accelerated implementation of the Fan-Vercautere
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The proposed implementation is built as a wrapper over the `bgv` package, which implements a unified variant of the BFV and BGV schemes. The only practical difference with the standard BFV is that the plaintext modulus must be coprime with the ciphertext modulus. This is both required for correctness ($T^{-1}\mod Q$ must be defined) and for security reasons (if $T|Q$ then the BGV scheme is not IND-CPA secure anymore).
|
||||
The proposed implementation is built as a wrapper over the `bgv` package, which implements a unified variant of the BFV and BGV schemes. The only practical difference with the standard BFV is that the plaintext modulus must be coprime with the ciphertext modulus. This is both required for correctness ($T^{-1}\mod Q$ must be defined) and for security reasons (if $T|Q$ then the BGV scheme is not IND-CPA secure anymore). To instantiate the BFV cryptosystem, generate a new BGV evaluator by with the optional scale-invariant parameter set to `true`.
|
||||
|
||||
For additional information, see the [`README.md`](../bgv/README.md) in the `bgv` package.
|
||||
|
||||
|
||||
@@ -1,180 +1,4 @@
|
||||
// Package bfv provides an RNS-accelerated implementation of the Fan-Vercauteren version of Brakerski's (BFV) scale-invariant homomorphic encryption scheme.
|
||||
// The BFV scheme enables SIMD modular arithmetic over encrypted vectors or integers.
|
||||
// The BFV scheme enables SIMD modular arithmetic over encrypted vectors or integers. See `README.md` for more information
|
||||
// on how to instantiate a BFV evaluator.
|
||||
package bfv
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/tuneinsight/lattigo/v5/core/rlwe"
|
||||
"github.com/tuneinsight/lattigo/v5/ring"
|
||||
"github.com/tuneinsight/lattigo/v5/schemes/bgv"
|
||||
)
|
||||
|
||||
// NewPlaintext allocates a new [rlwe.Plaintext] from the BFV parameters, at the
|
||||
// specified level. If the level argument is not provided, the plaintext is
|
||||
// initialized at level params.MaxLevelQ().
|
||||
//
|
||||
// The plaintext is initialized with its metadata so that it can be passed to a,
|
||||
// [bfv.Encoder]. Before doing so, the user can update the MetaData field to set
|
||||
// a specific scaling factor,
|
||||
// plaintext dimensions (if applicable) or encoding domain.
|
||||
func NewPlaintext(params Parameters, level ...int) (pt *rlwe.Plaintext) {
|
||||
pt = rlwe.NewPlaintext(params, level...)
|
||||
pt.IsBatched = true
|
||||
pt.Scale = params.DefaultScale()
|
||||
pt.LogDimensions = params.LogMaxDimensions()
|
||||
return
|
||||
}
|
||||
|
||||
// NewCiphertext allocates a new [rlwe.Ciphertext] from the BFV parameters,
|
||||
// at the specified level and ciphertext degree. If the level argument is not
|
||||
// provided, the ciphertext is initialized at level params.MaxLevelQ().
|
||||
//
|
||||
// To create a ciphertext for encrypting a new message, the ciphertext should be
|
||||
// at degree 1.
|
||||
func NewCiphertext(params Parameters, degree int, level ...int) (ct *rlwe.Ciphertext) {
|
||||
ct = rlwe.NewCiphertext(params, degree, level...)
|
||||
ct.IsBatched = true
|
||||
ct.Scale = params.DefaultScale()
|
||||
ct.LogDimensions = params.LogMaxDimensions()
|
||||
return
|
||||
}
|
||||
|
||||
// NewEncryptor instantiates a new [rlwe.Encryptor] from the given BFV parameters and
|
||||
// encryption key. This key can be either a *[rlwe.SecretKey] or a *[rlwe.PublicKey].
|
||||
func NewEncryptor(params Parameters, key rlwe.EncryptionKey) *rlwe.Encryptor {
|
||||
return rlwe.NewEncryptor(params, key)
|
||||
}
|
||||
|
||||
// NewDecryptor instantiates a new [rlwe.Decryptor] from the given BFV parameters and
|
||||
// secret decryption key.
|
||||
func NewDecryptor(params Parameters, key *rlwe.SecretKey) *rlwe.Decryptor {
|
||||
return rlwe.NewDecryptor(params, key)
|
||||
}
|
||||
|
||||
// NewKeyGenerator instantiates a new [rlwe.KeyGenerator] from the given
|
||||
// BFV parameters.
|
||||
func NewKeyGenerator(params Parameters) *rlwe.KeyGenerator {
|
||||
return rlwe.NewKeyGenerator(params)
|
||||
}
|
||||
|
||||
// Encoder is a structure that stores the parameters to encode values on a plaintext in a SIMD (Single-Instruction Multiple-Data) fashion.
|
||||
type Encoder struct {
|
||||
*bgv.Encoder
|
||||
}
|
||||
|
||||
// NewEncoder creates a new [Encoder] from the provided parameters.
|
||||
func NewEncoder(params Parameters) *Encoder {
|
||||
return &Encoder{bgv.NewEncoder(params.Parameters)}
|
||||
}
|
||||
|
||||
// ShallowCopy creates a shallow copy of this [Encoder] in which the read-only data-structures are
|
||||
// shared with the receiver.
|
||||
func (e Encoder) ShallowCopy() *Encoder {
|
||||
return &Encoder{Encoder: e.Encoder.ShallowCopy()}
|
||||
}
|
||||
|
||||
// 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.
|
||||
type Evaluator struct {
|
||||
*bgv.Evaluator
|
||||
}
|
||||
|
||||
// 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.
|
||||
func NewEvaluator(params Parameters, evk rlwe.EvaluationKeySet) *Evaluator {
|
||||
return &Evaluator{bgv.NewEvaluator(params.Parameters, evk)}
|
||||
}
|
||||
|
||||
// WithKey creates a shallow copy of this [Evaluator] in which the read-only data-structures are
|
||||
// shared with the receiver but for which the evaluation key is set to the provided [rlwe.EvaluationKeySet].
|
||||
func (eval Evaluator) WithKey(evk rlwe.EvaluationKeySet) *Evaluator {
|
||||
return &Evaluator{eval.Evaluator.WithKey(evk)}
|
||||
}
|
||||
|
||||
// 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{eval.Evaluator.ShallowCopy()}
|
||||
}
|
||||
|
||||
// Mul multiplies op0 with op1 without relinearization and returns the result in opOut.
|
||||
// inputs:
|
||||
// - op0: an *[rlwe.Ciphertext]
|
||||
// - op1:
|
||||
// - [rlwe.ElementInterface][ring.Poly]
|
||||
// - *big.Int, uint64, int64, int
|
||||
// - []uint64 or []int64 (of size at most N where N is the smallest integer satisfying PlaintextModulus = 1 mod 2N)
|
||||
// - opOut: an *[rlwe.Ciphertext]
|
||||
//
|
||||
// The procedure will return an error if either op0 or op1 are have a degree higher than 1.
|
||||
// The procedure will return an error if opOut.Degree != op0.Degree + op1.Degree.
|
||||
func (eval Evaluator) Mul(op0 *rlwe.Ciphertext, op1 rlwe.Operand, opOut *rlwe.Ciphertext) (err error) {
|
||||
switch op1 := op1.(type) {
|
||||
case rlwe.ElementInterface[ring.Poly], []uint64:
|
||||
return eval.Evaluator.MulScaleInvariant(op0, op1, opOut)
|
||||
case uint64, int64, int:
|
||||
return eval.Evaluator.Mul(op0, op1, op0)
|
||||
default:
|
||||
return fmt.Errorf("invalid op1.(Type), expected rlwe.ElementInterface[ring.Poly], []uint64 or uint64, int64, int, but got %T", op1)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MulNew multiplies op0 with op1 without relinearization and returns the result in a new opOut.
|
||||
// inputs:
|
||||
// - op0: an *[rlwe.Ciphertext]
|
||||
// - op1:
|
||||
// - [rlwe.ElementInterface][[ring.Poly]]
|
||||
// - *big.Int, uint64, int64, int
|
||||
// - []uint64 or []int64 (of size at most N where N is the smallest integer satisfying PlaintextModulus = 1 mod 2N)
|
||||
// - opOut: an *[rlwe.Ciphertext]
|
||||
//
|
||||
// The procedure will return an error if either op0.Degree or op1.Degree > 1.
|
||||
func (eval Evaluator) MulNew(op0 *rlwe.Ciphertext, op1 rlwe.Operand) (opOut *rlwe.Ciphertext, err error) {
|
||||
switch op1 := op1.(type) {
|
||||
case rlwe.ElementInterface[ring.Poly], []uint64:
|
||||
return eval.Evaluator.MulScaleInvariantNew(op0, op1)
|
||||
case uint64, int64, int:
|
||||
return eval.Evaluator.MulNew(op0, op1)
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid op1.(Type), expected rlwe.ElementInterface[ring.Poly], []uint64 or uint64, int64, int, but got %T", op1)
|
||||
}
|
||||
}
|
||||
|
||||
// MulRelinNew multiplies op0 with op1 with relinearization and returns the result in a new opOut.
|
||||
// inputs:
|
||||
// - op0: an *[rlwe.Ciphertext]
|
||||
// - op1:
|
||||
// - [rlwe.ElementInterface][[ring.Poly]]
|
||||
// - *big.Int, uint64, int64, int
|
||||
// - []uint64 or []int64 (of size at most N where N is the smallest integer satisfying PlaintextModulus = 1 mod 2N)
|
||||
// - opOut: an *[rlwe.Ciphertext]
|
||||
//
|
||||
// The procedure will return an error if either op0.Degree or op1.Degree > 1.
|
||||
// The procedure will return an error if the evaluator was not created with an relinearization key.
|
||||
func (eval Evaluator) MulRelinNew(op0 *rlwe.Ciphertext, op1 rlwe.Operand) (opOut *rlwe.Ciphertext, err error) {
|
||||
return eval.Evaluator.MulRelinScaleInvariantNew(op0, op1)
|
||||
}
|
||||
|
||||
// MulRelin multiplies op0 with op1 with relinearization and returns the result in opOut.
|
||||
// inputs:
|
||||
// - op0: an *[rlwe.Ciphertext]
|
||||
// - op1:
|
||||
// - [rlwe.ElementInterface][[ring.Poly]]
|
||||
// - *big.Int, uint64, int64, int
|
||||
// - []uint64 or []int64 (of size at most N where N is the smallest integer satisfying PlaintextModulus = 1 mod 2N)
|
||||
// - opOut: an *[rlwe.Ciphertext]
|
||||
//
|
||||
// The procedure will return an error if either op0.Degree or op1.Degree > 1.
|
||||
// The procedure will return an error if opOut.Degree != op0.Degree + op1.Degree.
|
||||
// The procedure will return an error if the evaluator was not created with an relinearization key.
|
||||
func (eval Evaluator) MulRelin(op0 *rlwe.Ciphertext, op1 rlwe.Operand, opOut *rlwe.Ciphertext) (err error) {
|
||||
return eval.Evaluator.MulRelinScaleInvariant(op0, op1, opOut)
|
||||
}
|
||||
|
||||
// Rescale does nothing when instantiated with the BFV scheme.
|
||||
func (eval Evaluator) Rescale(op0, op1 *rlwe.Ciphertext) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,285 +0,0 @@
|
||||
package bfv
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/tuneinsight/lattigo/v5/core/rlwe"
|
||||
)
|
||||
|
||||
func BenchmarkBFV(b *testing.B) {
|
||||
var err error
|
||||
|
||||
var testParams []ParametersLiteral
|
||||
switch {
|
||||
case *flagParamString != "": // the custom test suite reads the parameters from the -params flag
|
||||
testParams = append(testParams, ParametersLiteral{})
|
||||
if err = json.Unmarshal([]byte(*flagParamString), &testParams[0]); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
default:
|
||||
testParams = []ParametersLiteral{
|
||||
{
|
||||
LogN: 14,
|
||||
LogQ: []int{50, 40, 40, 40, 40, 40, 40, 40},
|
||||
LogP: []int{60},
|
||||
PlaintextModulus: 0x10001,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
for _, paramsLiteral := range testParams {
|
||||
tc := NewTestContext(paramsLiteral)
|
||||
|
||||
for _, testSet := range []func(tc *TestContext, b *testing.B){
|
||||
benchEncoder,
|
||||
benchEvaluator,
|
||||
} {
|
||||
testSet(tc, b)
|
||||
runtime.GC()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func benchEncoder(tc *TestContext, b *testing.B) {
|
||||
|
||||
params := tc.Params
|
||||
|
||||
poly := tc.Sampler.ReadNew()
|
||||
params.RingT().Reduce(poly, poly)
|
||||
coeffsUint64 := poly.Coeffs[0]
|
||||
coeffsInt64 := make([]int64, len(coeffsUint64))
|
||||
for i := range coeffsUint64 {
|
||||
coeffsInt64[i] = int64(coeffsUint64[i])
|
||||
}
|
||||
|
||||
encoder := tc.Ecd
|
||||
|
||||
level := params.MaxLevel()
|
||||
plaintext := NewPlaintext(params, level)
|
||||
|
||||
b.Run(name("Encoder/Encode/Uint", tc, level), func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := encoder.Encode(coeffsUint64, plaintext); err != nil {
|
||||
b.Log(err)
|
||||
b.Fail()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
b.Run(name("Encoder/Encode/Int", tc, level), func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := encoder.Encode(coeffsInt64, plaintext); err != nil {
|
||||
b.Log(err)
|
||||
b.Fail()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
b.Run(name("Encoder/Decode/Uint", tc, level), func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := encoder.Decode(plaintext, coeffsUint64); err != nil {
|
||||
b.Log(err)
|
||||
b.Fail()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
b.Run(name("Encoder/Decode/Int", tc, level), func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := encoder.Decode(plaintext, coeffsInt64); err != nil {
|
||||
b.Log(err)
|
||||
b.Fail()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func benchEvaluator(tc *TestContext, b *testing.B) {
|
||||
|
||||
params := tc.Params
|
||||
eval := tc.Evl
|
||||
|
||||
level := params.MaxLevel()
|
||||
|
||||
plaintext := NewPlaintext(params, level)
|
||||
plaintext.Value = rlwe.NewCiphertextRandom(tc.Prng, params.Parameters, 0, plaintext.Level()).Value[0]
|
||||
|
||||
ciphertext1 := rlwe.NewCiphertextRandom(tc.Prng, params.Parameters, 1, level)
|
||||
ciphertext2 := rlwe.NewCiphertextRandom(tc.Prng, params.Parameters, 1, level)
|
||||
scalar := params.PlaintextModulus() >> 1
|
||||
|
||||
*ciphertext1.MetaData = *plaintext.MetaData
|
||||
*ciphertext2.MetaData = *plaintext.MetaData
|
||||
|
||||
vector := plaintext.Value.Coeffs[0][:params.MaxSlots()]
|
||||
|
||||
b.Run(name("Evaluator/Add/Scalar", tc, level), func(b *testing.B) {
|
||||
receiver := NewCiphertext(params, 1, ciphertext1.Level())
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := eval.Add(ciphertext1, scalar, receiver); err != nil {
|
||||
b.Log(err)
|
||||
b.Fail()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
b.Run(name("Evaluator/Add/Vector", tc, level), func(b *testing.B) {
|
||||
receiver := NewCiphertext(params, 1, ciphertext1.Level())
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := eval.Add(ciphertext1, vector, receiver); err != nil {
|
||||
b.Log(err)
|
||||
b.Fail()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
b.Run(name("Evaluator/Add/Plaintext", tc, level), func(b *testing.B) {
|
||||
receiver := NewCiphertext(params, 1, ciphertext1.Level())
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := eval.Add(ciphertext1, plaintext, receiver); err != nil {
|
||||
b.Log(err)
|
||||
b.Fail()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
b.Run(name("Evaluator/Add/Ciphertext", tc, level), func(b *testing.B) {
|
||||
receiver := NewCiphertext(params, 1, ciphertext1.Level())
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := eval.Add(ciphertext1, ciphertext2, receiver); err != nil {
|
||||
b.Log(err)
|
||||
b.Fail()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
b.Run(name("Evaluator/Mul/Scalar", tc, level), func(b *testing.B) {
|
||||
receiver := NewCiphertext(params, 1, ciphertext1.Level())
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := eval.Mul(ciphertext1, scalar, receiver); err != nil {
|
||||
b.Log(err)
|
||||
b.Fail()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
b.Run(name("Evaluator/Mul/Plaintext", tc, level), func(b *testing.B) {
|
||||
receiver := NewCiphertext(params, 1, ciphertext1.Level())
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := eval.Mul(ciphertext1, plaintext, receiver); err != nil {
|
||||
b.Log(err)
|
||||
b.Fail()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
b.Run(name("Evaluator/Mul/Vector", tc, level), func(b *testing.B) {
|
||||
receiver := NewCiphertext(params, 1, ciphertext1.Level())
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := eval.Mul(ciphertext1, vector, receiver); err != nil {
|
||||
b.Log(err)
|
||||
b.Fail()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
b.Run(name("Evaluator/Mul/Ciphertext", tc, level), func(b *testing.B) {
|
||||
receiver := NewCiphertext(params, 2, ciphertext1.Level())
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := eval.Mul(ciphertext1, ciphertext2, receiver); err != nil {
|
||||
b.Log(err)
|
||||
b.Fail()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
b.Run(name("Evaluator/MulRelin/Ciphertext", tc, level), func(b *testing.B) {
|
||||
receiver := NewCiphertext(params, 1, ciphertext1.Level())
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := eval.MulRelin(ciphertext1, ciphertext2, receiver); err != nil {
|
||||
b.Log(err)
|
||||
b.Fail()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
b.Run(name("Evaluator/MulThenAdd/Scalar", tc, level), func(b *testing.B) {
|
||||
receiver := NewCiphertext(params, 1, ciphertext1.Level())
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := eval.MulThenAdd(ciphertext1, scalar, receiver); err != nil {
|
||||
b.Log(err)
|
||||
b.Fail()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
b.Run(name("Evaluator/MulThenAdd/Vector", tc, level), func(b *testing.B) {
|
||||
receiver := NewCiphertext(params, 1, ciphertext1.Level())
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := eval.MulThenAdd(ciphertext1, vector, receiver); err != nil {
|
||||
b.Log(err)
|
||||
b.Fail()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
b.Run(name("Evaluator/MulThenAdd/Plaintext", tc, level), func(b *testing.B) {
|
||||
receiver := NewCiphertext(params, 1, ciphertext1.Level())
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := eval.MulThenAdd(ciphertext1, plaintext, receiver); err != nil {
|
||||
b.Log(err)
|
||||
b.Fail()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
b.Run(name("Evaluator/MulThenAdd/Ciphertext", tc, level), func(b *testing.B) {
|
||||
receiver := NewCiphertext(params, 1, ciphertext1.Level())
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := eval.MulThenAdd(ciphertext1, plaintext, receiver); err != nil {
|
||||
b.Log(err)
|
||||
b.Fail()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
b.Run(name("Evaluator/MulRelinThenAdd/Ciphertext", tc, level), func(b *testing.B) {
|
||||
receiver := NewCiphertext(params, 2, ciphertext1.Level())
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := eval.MulRelinThenAdd(ciphertext1, ciphertext2, receiver); err != nil {
|
||||
b.Log(err)
|
||||
b.Fail()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
b.Run(name("Evaluator/Rotate", tc, level), func(b *testing.B) {
|
||||
gk := tc.Kgen.GenGaloisKeyNew(5, tc.Sk)
|
||||
evk := rlwe.NewMemEvaluationKeySet(nil, gk)
|
||||
eval := eval.WithKey(evk)
|
||||
receiver := NewCiphertext(params, 1, ciphertext2.Level())
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := eval.RotateColumns(ciphertext2, 1, receiver); err != nil {
|
||||
b.Log(err)
|
||||
b.Fail()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,456 +0,0 @@
|
||||
package bfv
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/tuneinsight/lattigo/v5/core/rlwe"
|
||||
"github.com/tuneinsight/lattigo/v5/ring"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var flagPrintNoise = flag.Bool("print-noise", false, "print the residual noise")
|
||||
var flagParamString = flag.String("params", "", "specify the test cryptographic parameters as a JSON string. Overrides -short.")
|
||||
|
||||
func name(op string, tc *TestContext, lvl int) string {
|
||||
return fmt.Sprintf("%s/%s/lvl=%d", op, tc, lvl)
|
||||
}
|
||||
|
||||
func TestBFV(t *testing.T) {
|
||||
var err error
|
||||
|
||||
paramsLiterals := TestParams
|
||||
|
||||
if *flagParamString != "" {
|
||||
var jsonParams ParametersLiteral
|
||||
if err = json.Unmarshal([]byte(*flagParamString), &jsonParams); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
paramsLiterals = []ParametersLiteral{jsonParams} // the custom test suite reads the parameters from the -params flag
|
||||
}
|
||||
|
||||
for _, p := range paramsLiterals[:] {
|
||||
|
||||
for _, plaintextModulus := range TestPlaintextModulus[:] {
|
||||
|
||||
p.PlaintextModulus = plaintextModulus
|
||||
|
||||
tc := NewTestContext(p)
|
||||
|
||||
for _, testSet := range []func(tc *TestContext, t *testing.T){
|
||||
testParameters,
|
||||
testEncoder,
|
||||
testEvaluator,
|
||||
} {
|
||||
testSet(tc, t)
|
||||
runtime.GC()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testParameters(tc *TestContext, t *testing.T) {
|
||||
t.Run(name("Parameters/Marshaller/Binary", tc, 0), func(t *testing.T) {
|
||||
bytes, err := tc.Params.MarshalBinary()
|
||||
require.Nil(t, err)
|
||||
var p Parameters
|
||||
require.Nil(t, p.UnmarshalBinary(bytes))
|
||||
require.True(t, tc.Params.Equal(&p))
|
||||
})
|
||||
|
||||
t.Run(name("Parameters/Marshaller/JSON", tc, 0), func(t *testing.T) {
|
||||
// checks that parameters can be marshalled without error
|
||||
data, err := json.Marshal(tc.Params)
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, data)
|
||||
|
||||
// checks that the Parameters can be unmarshalled without error
|
||||
var paramsRec Parameters
|
||||
err = json.Unmarshal(data, ¶msRec)
|
||||
require.Nil(t, err)
|
||||
require.True(t, tc.Params.Equal(¶msRec))
|
||||
|
||||
// checks that the Parameters can be unmarshalled with log-moduli definition without error
|
||||
dataWithLogModuli := []byte(fmt.Sprintf(`{"LogN":%d,"LogQ":[50,50],"LogP":[60], "PlaintextModulus":65537}`, tc.Params.LogN()))
|
||||
var paramsWithLogModuli Parameters
|
||||
err = json.Unmarshal(dataWithLogModuli, ¶msWithLogModuli)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, 2, paramsWithLogModuli.QCount())
|
||||
require.Equal(t, 1, paramsWithLogModuli.PCount())
|
||||
require.Equal(t, rlwe.DefaultXe, paramsWithLogModuli.Xe()) // Omitting Xe should result in Default being used
|
||||
require.Equal(t, rlwe.DefaultXs, paramsWithLogModuli.Xs()) // Omitting Xe should result in Default being used
|
||||
|
||||
// checks that one can provide custom parameters for the secret-key and error distributions
|
||||
dataWithCustomSecrets := []byte(fmt.Sprintf(`{"LogN":%d,"LogQ":[50,50],"LogP":[60], "PlaintextModulus":65537, "Xs": {"Type": "Ternary", "H": 192}, "Xe": {"Type": "DiscreteGaussian", "Sigma": 6.6, "Bound": 39.6}}`, tc.Params.LogN()))
|
||||
var paramsWithCustomSecrets Parameters
|
||||
err = json.Unmarshal(dataWithCustomSecrets, ¶msWithCustomSecrets)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, ring.DiscreteGaussian{Sigma: 6.6, Bound: 39.6}, paramsWithCustomSecrets.Xe())
|
||||
require.Equal(t, ring.Ternary{H: 192}, paramsWithCustomSecrets.Xs())
|
||||
})
|
||||
}
|
||||
|
||||
func testEncoder(tc *TestContext, t *testing.T) {
|
||||
testLevels := []int{0, tc.Params.MaxLevel()}
|
||||
|
||||
for _, lvl := range testLevels {
|
||||
t.Run(name("Encoder/Uint", tc, lvl), func(t *testing.T) {
|
||||
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 testLevels {
|
||||
t.Run(name("Encoder/Int", tc, lvl), func(t *testing.T) {
|
||||
|
||||
T := tc.Params.PlaintextModulus()
|
||||
THalf := T >> 1
|
||||
coeffs := tc.Sampler.ReadNew()
|
||||
coeffsInt := make([]int64, len(coeffs.Coeffs[0]))
|
||||
for i, c := range coeffs.Coeffs[0] {
|
||||
c %= T
|
||||
if c >= THalf {
|
||||
coeffsInt[i] = -int64(T - c)
|
||||
} else {
|
||||
coeffsInt[i] = int64(c)
|
||||
}
|
||||
}
|
||||
|
||||
plaintext := NewPlaintext(tc.Params, lvl)
|
||||
tc.Ecd.Encode(coeffsInt, plaintext)
|
||||
have := make([]int64, tc.Params.MaxSlots())
|
||||
tc.Ecd.Decode(plaintext, have)
|
||||
require.True(t, slices.Equal(coeffsInt, have))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testEvaluator(tc *TestContext, t *testing.T) {
|
||||
testLevels := []int{0, tc.Params.MaxLevel()}
|
||||
|
||||
t.Run("Evaluator", func(t *testing.T) {
|
||||
|
||||
for _, lvl := range testLevels {
|
||||
t.Run(name("Add/Ct/Ct/New", tc, lvl), func(t *testing.T) {
|
||||
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))
|
||||
|
||||
require.True(t, ciphertext0.Scale.Cmp(ciphertext1.Scale) != 0)
|
||||
|
||||
p0 := ring.Poly{Coeffs: [][]uint64{values0}}
|
||||
p1 := ring.Poly{Coeffs: [][]uint64{values1}}
|
||||
|
||||
ciphertext2, err := tc.Evl.AddNew(ciphertext0, ciphertext1)
|
||||
require.NoError(t, err)
|
||||
tc.Params.RingT().Add(p0, p1, p0)
|
||||
|
||||
VerifyTestVectors(tc.Params, tc.Ecd, tc.Dec, ciphertext2, p0.Coeffs[0], t)
|
||||
})
|
||||
}
|
||||
|
||||
for _, lvl := range testLevels {
|
||||
t.Run(name("Add/Ct/Ct/Inplace", tc, lvl), func(t *testing.T) {
|
||||
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))
|
||||
|
||||
require.True(t, ciphertext0.Scale.Cmp(ciphertext1.Scale) != 0)
|
||||
|
||||
p0 := ring.Poly{Coeffs: [][]uint64{values0}}
|
||||
p1 := ring.Poly{Coeffs: [][]uint64{values1}}
|
||||
|
||||
require.NoError(t, tc.Evl.Add(ciphertext0, ciphertext1, ciphertext0))
|
||||
tc.Params.RingT().Add(p0, p1, p0)
|
||||
|
||||
VerifyTestVectors(tc.Params, tc.Ecd, tc.Dec, ciphertext0, p0.Coeffs[0], t)
|
||||
})
|
||||
}
|
||||
|
||||
for _, lvl := range testLevels {
|
||||
t.Run(name("Add/Ct/Pt/Inplace", tc, lvl), func(t *testing.T) {
|
||||
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))
|
||||
|
||||
require.True(t, ciphertext0.Scale.Cmp(plaintext.Scale) != 0)
|
||||
|
||||
p0 := ring.Poly{Coeffs: [][]uint64{values0}}
|
||||
p1 := ring.Poly{Coeffs: [][]uint64{values1}}
|
||||
|
||||
require.NoError(t, tc.Evl.Add(ciphertext0, plaintext, ciphertext0))
|
||||
tc.Params.RingT().Add(p0, p1, p0)
|
||||
|
||||
VerifyTestVectors(tc.Params, tc.Ecd, tc.Dec, ciphertext0, p0.Coeffs[0], t)
|
||||
})
|
||||
}
|
||||
|
||||
for _, lvl := range testLevels {
|
||||
t.Run(name("Add/Ct/Scalar/Inplace", tc, lvl), func(t *testing.T) {
|
||||
values, _, ciphertext := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.DefaultScale())
|
||||
|
||||
scalar := tc.Params.PlaintextModulus() >> 1
|
||||
|
||||
p := ring.Poly{Coeffs: [][]uint64{values}}
|
||||
|
||||
require.NoError(t, tc.Evl.Add(ciphertext, scalar, ciphertext))
|
||||
tc.Params.RingT().AddScalar(p, scalar, p)
|
||||
|
||||
VerifyTestVectors(tc.Params, tc.Ecd, tc.Dec, ciphertext, p.Coeffs[0], t)
|
||||
})
|
||||
}
|
||||
|
||||
for _, lvl := range testLevels {
|
||||
t.Run(name("Sub/Ct/Ct/New", tc, lvl), func(t *testing.T) {
|
||||
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))
|
||||
|
||||
require.True(t, ciphertext0.Scale.Cmp(ciphertext1.Scale) != 0)
|
||||
|
||||
p0 := ring.Poly{Coeffs: [][]uint64{values0}}
|
||||
p1 := ring.Poly{Coeffs: [][]uint64{values1}}
|
||||
|
||||
ciphertext0, err := tc.Evl.SubNew(ciphertext0, ciphertext1)
|
||||
require.NoError(t, err)
|
||||
tc.Params.RingT().Sub(p0, p1, p0)
|
||||
|
||||
VerifyTestVectors(tc.Params, tc.Ecd, tc.Dec, ciphertext0, p0.Coeffs[0], t)
|
||||
})
|
||||
}
|
||||
|
||||
for _, lvl := range testLevels {
|
||||
t.Run(name("Sub/Ct/Ct/Inplace", tc, lvl), func(t *testing.T) {
|
||||
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))
|
||||
|
||||
require.True(t, ciphertext0.Scale.Cmp(ciphertext1.Scale) != 0)
|
||||
|
||||
p0 := ring.Poly{Coeffs: [][]uint64{values0}}
|
||||
p1 := ring.Poly{Coeffs: [][]uint64{values1}}
|
||||
|
||||
require.NoError(t, tc.Evl.Sub(ciphertext0, ciphertext1, ciphertext0))
|
||||
tc.Params.RingT().Sub(p0, p1, p0)
|
||||
|
||||
VerifyTestVectors(tc.Params, tc.Ecd, tc.Dec, ciphertext0, p0.Coeffs[0], t)
|
||||
})
|
||||
}
|
||||
|
||||
for _, lvl := range testLevels {
|
||||
t.Run(name("Sub/Ct/Pt/Inplace", tc, lvl), func(t *testing.T) {
|
||||
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))
|
||||
|
||||
require.True(t, ciphertext0.Scale.Cmp(plaintext.Scale) != 0)
|
||||
|
||||
p0 := ring.Poly{Coeffs: [][]uint64{values0}}
|
||||
p1 := ring.Poly{Coeffs: [][]uint64{values1}}
|
||||
|
||||
require.NoError(t, tc.Evl.Sub(ciphertext0, plaintext, ciphertext0))
|
||||
tc.Params.RingT().Sub(p0, p1, p0)
|
||||
|
||||
VerifyTestVectors(tc.Params, tc.Ecd, tc.Dec, ciphertext0, p0.Coeffs[0], t)
|
||||
})
|
||||
}
|
||||
|
||||
for _, lvl := range testLevels {
|
||||
t.Run(name("Mul/Ct/Ct/Inplace", tc, lvl), func(t *testing.T) {
|
||||
if lvl == 0 {
|
||||
t.Skip("Skipping: Level = 0")
|
||||
}
|
||||
|
||||
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))
|
||||
|
||||
require.True(t, ciphertext0.Scale.Cmp(ciphertext1.Scale) != 0)
|
||||
|
||||
p0 := ring.Poly{Coeffs: [][]uint64{values0}}
|
||||
p1 := ring.Poly{Coeffs: [][]uint64{values1}}
|
||||
|
||||
require.NoError(t, tc.Evl.Mul(ciphertext0, ciphertext1, ciphertext0))
|
||||
tc.Params.RingT().MulCoeffsBarrett(p0, p1, p0)
|
||||
|
||||
VerifyTestVectors(tc.Params, tc.Ecd, tc.Dec, ciphertext0, p0.Coeffs[0], t)
|
||||
})
|
||||
}
|
||||
|
||||
for _, lvl := range testLevels {
|
||||
t.Run(name("Mul/Ct/Pt/Inplace", tc, lvl), func(t *testing.T) {
|
||||
if lvl == 0 {
|
||||
t.Skip("Level = 0")
|
||||
}
|
||||
|
||||
values0, _, ciphertext := 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))
|
||||
|
||||
require.True(t, ciphertext.Scale.Cmp(plaintext.Scale) != 0)
|
||||
|
||||
p0 := ring.Poly{Coeffs: [][]uint64{values0}}
|
||||
p1 := ring.Poly{Coeffs: [][]uint64{values1}}
|
||||
|
||||
require.NoError(t, tc.Evl.Mul(ciphertext, plaintext, ciphertext))
|
||||
tc.Params.RingT().MulCoeffsBarrett(p0, p1, p0)
|
||||
|
||||
VerifyTestVectors(tc.Params, tc.Ecd, tc.Dec, ciphertext, p0.Coeffs[0], t)
|
||||
})
|
||||
}
|
||||
|
||||
for _, lvl := range testLevels {
|
||||
t.Run(name("Mul/Ct/Scalar/Inplace", tc, lvl), func(t *testing.T) {
|
||||
if lvl == 0 {
|
||||
t.Skip("Level = 0")
|
||||
}
|
||||
|
||||
values, _, ciphertext := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.DefaultScale())
|
||||
|
||||
scalar := tc.Params.PlaintextModulus() >> 1
|
||||
|
||||
p := ring.Poly{Coeffs: [][]uint64{values}}
|
||||
|
||||
require.NoError(t, tc.Evl.Mul(ciphertext, scalar, ciphertext))
|
||||
tc.Params.RingT().MulScalar(p, scalar, p)
|
||||
|
||||
VerifyTestVectors(tc.Params, tc.Ecd, tc.Dec, ciphertext, p.Coeffs[0], t)
|
||||
})
|
||||
}
|
||||
|
||||
for _, lvl := range testLevels {
|
||||
t.Run(name("Square/Ct/Ct/Inplace", tc, lvl), func(t *testing.T) {
|
||||
if lvl == 0 {
|
||||
t.Skip("Level = 0")
|
||||
}
|
||||
|
||||
values, _, ciphertext := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.DefaultScale())
|
||||
|
||||
p := ring.Poly{Coeffs: [][]uint64{values}}
|
||||
|
||||
require.NoError(t, tc.Evl.Mul(ciphertext, ciphertext, ciphertext))
|
||||
tc.Params.RingT().MulCoeffsBarrett(p, p, p)
|
||||
|
||||
VerifyTestVectors(tc.Params, tc.Ecd, tc.Dec, ciphertext, p.Coeffs[0], t)
|
||||
})
|
||||
}
|
||||
|
||||
for _, lvl := range testLevels {
|
||||
t.Run(name("MulRelin/Ct/Ct/Inplace", tc, lvl), func(t *testing.T) {
|
||||
if lvl == 0 {
|
||||
t.Skip("Level = 0")
|
||||
}
|
||||
|
||||
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))
|
||||
|
||||
p0 := ring.Poly{Coeffs: [][]uint64{values0}}
|
||||
p1 := ring.Poly{Coeffs: [][]uint64{values1}}
|
||||
|
||||
tc.Params.RingT().MulCoeffsBarrett(p0, p1, p0)
|
||||
|
||||
require.True(t, ciphertext0.Scale.Cmp(ciphertext1.Scale) != 0)
|
||||
|
||||
receiver := NewCiphertext(tc.Params, 1, lvl)
|
||||
|
||||
require.NoError(t, tc.Evl.MulRelin(ciphertext0, ciphertext1, receiver))
|
||||
require.NoError(t, tc.Evl.Rescale(receiver, receiver))
|
||||
|
||||
VerifyTestVectors(tc.Params, tc.Ecd, tc.Dec, receiver, p0.Coeffs[0], t)
|
||||
})
|
||||
}
|
||||
|
||||
for _, lvl := range testLevels {
|
||||
t.Run(name("MulThenAdd/Ct/Ct/Inplace", tc, lvl), func(t *testing.T) {
|
||||
if lvl == 0 {
|
||||
t.Skip("Level = 0")
|
||||
}
|
||||
|
||||
values0, _, ciphertext0 := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.DefaultScale())
|
||||
values1, _, ciphertext1 := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.NewScale(2))
|
||||
values2, _, ciphertext2 := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.NewScale(7))
|
||||
|
||||
p0 := ring.Poly{Coeffs: [][]uint64{values0}}
|
||||
p1 := ring.Poly{Coeffs: [][]uint64{values1}}
|
||||
p2 := ring.Poly{Coeffs: [][]uint64{values2}}
|
||||
|
||||
require.True(t, ciphertext0.Scale.Cmp(ciphertext1.Scale) != 0)
|
||||
require.True(t, ciphertext0.Scale.Cmp(ciphertext2.Scale) != 0)
|
||||
|
||||
require.NoError(t, tc.Evl.MulThenAdd(ciphertext0, ciphertext1, ciphertext2))
|
||||
tc.Params.RingT().MulCoeffsBarrettThenAdd(p0, p1, p2)
|
||||
|
||||
VerifyTestVectors(tc.Params, tc.Ecd, tc.Dec, ciphertext2, p2.Coeffs[0], t)
|
||||
})
|
||||
}
|
||||
|
||||
for _, lvl := range testLevels {
|
||||
t.Run(name("MulThenAdd/Ct/Pt/Inplace", tc, lvl), func(t *testing.T) {
|
||||
|
||||
if lvl == 0 {
|
||||
t.Skip("Level = 0")
|
||||
}
|
||||
|
||||
values0, _, ciphertext0 := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.DefaultScale())
|
||||
values1, plaintext1, _ := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.NewScale(2))
|
||||
values2, _, ciphertext2 := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.NewScale(7))
|
||||
|
||||
p0 := ring.Poly{Coeffs: [][]uint64{values0}}
|
||||
p1 := ring.Poly{Coeffs: [][]uint64{values1}}
|
||||
p2 := ring.Poly{Coeffs: [][]uint64{values2}}
|
||||
|
||||
require.True(t, ciphertext0.Scale.Cmp(plaintext1.Scale) != 0)
|
||||
require.True(t, ciphertext0.Scale.Cmp(ciphertext2.Scale) != 0)
|
||||
|
||||
require.NoError(t, tc.Evl.MulThenAdd(ciphertext0, plaintext1, ciphertext2))
|
||||
tc.Params.RingT().MulCoeffsBarrettThenAdd(p0, p1, p2)
|
||||
|
||||
VerifyTestVectors(tc.Params, tc.Ecd, tc.Dec, ciphertext2, p2.Coeffs[0], t)
|
||||
})
|
||||
}
|
||||
|
||||
for _, lvl := range testLevels {
|
||||
t.Run(name("MulThenAdd/Ct/Scalar/Inplace", tc, lvl), func(t *testing.T) {
|
||||
if lvl == 0 {
|
||||
t.Skip("Level = 0")
|
||||
}
|
||||
|
||||
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))
|
||||
|
||||
p0 := ring.Poly{Coeffs: [][]uint64{values0}}
|
||||
p1 := ring.Poly{Coeffs: [][]uint64{values1}}
|
||||
|
||||
require.True(t, ciphertext0.Scale.Cmp(ciphertext1.Scale) != 0)
|
||||
|
||||
scalar := tc.Params.PlaintextModulus() >> 1
|
||||
|
||||
require.NoError(t, tc.Evl.MulThenAdd(ciphertext0, scalar, ciphertext1))
|
||||
tc.Params.RingT().MulScalarThenAdd(p0, scalar, p1)
|
||||
|
||||
VerifyTestVectors(tc.Params, tc.Ecd, tc.Dec, ciphertext1, p1.Coeffs[0], t)
|
||||
})
|
||||
}
|
||||
|
||||
for _, lvl := range testLevels {
|
||||
t.Run(name("MulRelinThenAdd/Ct/Ct/Inplace", tc, lvl), func(t *testing.T) {
|
||||
if lvl == 0 {
|
||||
t.Skip("Level = 0")
|
||||
}
|
||||
|
||||
values0, _, ciphertext0 := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.DefaultScale())
|
||||
values1, _, ciphertext1 := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.NewScale(2))
|
||||
values2, _, ciphertext2 := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.NewScale(7))
|
||||
|
||||
require.True(t, ciphertext0.Scale.Cmp(ciphertext1.Scale) != 0)
|
||||
require.True(t, ciphertext0.Scale.Cmp(ciphertext2.Scale) != 0)
|
||||
|
||||
p0 := ring.Poly{Coeffs: [][]uint64{values0}}
|
||||
p1 := ring.Poly{Coeffs: [][]uint64{values1}}
|
||||
p2 := ring.Poly{Coeffs: [][]uint64{values2}}
|
||||
|
||||
require.NoError(t, tc.Evl.MulRelinThenAdd(ciphertext0, ciphertext1, ciphertext2))
|
||||
tc.Params.RingT().MulCoeffsBarrettThenAdd(p0, p1, p2)
|
||||
|
||||
VerifyTestVectors(tc.Params, tc.Ecd, tc.Dec, ciphertext2, p2.Coeffs[0], t)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package bfv
|
||||
|
||||
var (
|
||||
// ExampleParameters128BitLogN14LogQP438 is an example parameters set with logN=14, logQP=438
|
||||
// and a 16-bit plaintext modulus, offering 128-bit of security.
|
||||
ExampleParameters128BitLogN14LogQP438 = ParametersLiteral{
|
||||
LogN: 14,
|
||||
Q: []uint64{0x100000000060001, 0x80000000068001, 0x80000000080001,
|
||||
0x3fffffffef8001, 0x40000000120001, 0x3fffffffeb8001}, // 56 + 55 + 55 + 54 + 54 + 54 bits
|
||||
P: []uint64{0x80000000130001, 0x7fffffffe90001}, // 55 + 55 bits
|
||||
PlaintextModulus: 0x10001,
|
||||
}
|
||||
)
|
||||
@@ -1,107 +0,0 @@
|
||||
package bfv
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/tuneinsight/lattigo/v5/core/rlwe"
|
||||
"github.com/tuneinsight/lattigo/v5/ring"
|
||||
"github.com/tuneinsight/lattigo/v5/schemes/bgv"
|
||||
)
|
||||
|
||||
// NewParameters instantiate a set of BFV parameters from the generic RLWE parameters and a plaintext modulus t.
|
||||
// User must ensure that t = 1 mod 2n for 4 < n <= N, where N is the ring degree.
|
||||
// It returns the empty parameters [Parameters]{} and a non-nil error if the specified parameters are invalid.
|
||||
func NewParameters(rlweParams rlwe.Parameters, t uint64) (p Parameters, err error) {
|
||||
var pbgv bgv.Parameters
|
||||
pbgv, err = bgv.NewParameters(rlweParams, t)
|
||||
return Parameters{pbgv}, err
|
||||
}
|
||||
|
||||
// NewParametersFromLiteral instantiate a set of BFV parameters from a [ParametersLiteral] specification.
|
||||
// It returns the empty parameters [Parameters]{} and a non-nil error if the specified parameters are invalid.
|
||||
//
|
||||
// See [rlwe.NewParametersFromLiteral] for default values of the optional fields.
|
||||
func NewParametersFromLiteral(pl ParametersLiteral) (p Parameters, err error) {
|
||||
var pbgv bgv.Parameters
|
||||
pbgv, err = bgv.NewParametersFromLiteral(bgv.ParametersLiteral(pl))
|
||||
return Parameters{pbgv}, err
|
||||
}
|
||||
|
||||
// ParametersLiteral is a literal representation of BFV parameters. It has public
|
||||
// fields and is used to express unchecked user-defined parameters literally into
|
||||
// Go programs. The [NewParametersFromLiteral] function is used to generate the actual
|
||||
// checked parameters from the literal representation.
|
||||
//
|
||||
// Users must set the polynomial degree (LogN) and the coefficient modulus, by either setting
|
||||
// the Q and P fields to the desired moduli chain, or by setting the LogQ and LogP fields to
|
||||
// the desired moduli sizes. Users must also specify the coefficient modulus in plaintext-space
|
||||
// (T).
|
||||
//
|
||||
// Optionally, users may specify the error variance (Sigma) and secrets' density (H). If left
|
||||
// unset, standard default values for these field are substituted at parameter creation (see
|
||||
// [NewParametersFromLiteral]).
|
||||
type ParametersLiteral bgv.ParametersLiteral
|
||||
|
||||
// GetRLWEParametersLiteral returns the [rlwe.ParametersLiteral] from the target [bfv.ParametersLiteral].
|
||||
func (p ParametersLiteral) GetRLWEParametersLiteral() rlwe.ParametersLiteral {
|
||||
return bgv.ParametersLiteral(p).GetRLWEParametersLiteral()
|
||||
}
|
||||
|
||||
// Parameters represents a parameter set for the BFV cryptosystem. Its fields are private and
|
||||
// immutable. See [ParametersLiteral] for user-specified parameters.
|
||||
type Parameters struct {
|
||||
bgv.Parameters
|
||||
}
|
||||
|
||||
// Equal compares two sets of parameters for equality.
|
||||
func (p Parameters) Equal(other *Parameters) bool {
|
||||
return p.Parameters.Equal(&other.Parameters)
|
||||
}
|
||||
|
||||
// UnmarshalBinary decodes a []byte into a parameter set struct.
|
||||
func (p *Parameters) UnmarshalBinary(data []byte) (err error) {
|
||||
return p.Parameters.UnmarshalJSON(data)
|
||||
}
|
||||
|
||||
// UnmarshalJSON reads a JSON representation of a parameter set into the receiver Parameter. See Unmarshal from the [encoding/json] package.
|
||||
func (p *Parameters) UnmarshalJSON(data []byte) (err error) {
|
||||
return p.Parameters.UnmarshalJSON(data)
|
||||
}
|
||||
|
||||
func (p *ParametersLiteral) UnmarshalJSON(b []byte) (err error) {
|
||||
var pl struct {
|
||||
LogN int
|
||||
LogNthRoot int
|
||||
Q []uint64
|
||||
P []uint64
|
||||
LogQ []int
|
||||
LogP []int
|
||||
Xe map[string]interface{}
|
||||
Xs map[string]interface{}
|
||||
RingType ring.Type
|
||||
PlaintextModulus uint64
|
||||
}
|
||||
|
||||
err = json.Unmarshal(b, &pl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.LogN = pl.LogN
|
||||
p.LogNthRoot = pl.LogNthRoot
|
||||
p.Q, p.P, p.LogQ, p.LogP = pl.Q, pl.P, pl.LogQ, pl.LogP
|
||||
if pl.Xs != nil {
|
||||
p.Xs, err = ring.ParametersFromMap(pl.Xs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if pl.Xe != nil {
|
||||
p.Xe, err = ring.ParametersFromMap(pl.Xe)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
p.PlaintextModulus = pl.PlaintextModulus
|
||||
return err
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
package bfv
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tuneinsight/lattigo/v5/core/rlwe"
|
||||
"github.com/tuneinsight/lattigo/v5/ring"
|
||||
"github.com/tuneinsight/lattigo/v5/utils/sampling"
|
||||
)
|
||||
|
||||
type TestContext struct {
|
||||
Params Parameters
|
||||
Ecd *Encoder
|
||||
|
||||
Prng sampling.PRNG
|
||||
Sampler *ring.UniformSampler
|
||||
|
||||
Kgen *rlwe.KeyGenerator
|
||||
Sk *rlwe.SecretKey
|
||||
Pk *rlwe.PublicKey
|
||||
|
||||
Enc *rlwe.Encryptor
|
||||
Dec *rlwe.Decryptor
|
||||
|
||||
Evl *Evaluator
|
||||
}
|
||||
|
||||
func NewTestContext(params ParametersLiteral) *TestContext {
|
||||
tc := new(TestContext)
|
||||
|
||||
var err error
|
||||
|
||||
tc.Params, err = NewParametersFromLiteral(params)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tc.Ecd = NewEncoder(tc.Params)
|
||||
|
||||
tc.Prng, err = sampling.NewPRNG()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tc.Sampler = ring.NewUniformSampler(tc.Prng, tc.Params.RingT())
|
||||
|
||||
tc.Kgen = rlwe.NewKeyGenerator(tc.Params)
|
||||
tc.Sk, tc.Pk = tc.Kgen.GenKeyPairNew()
|
||||
|
||||
tc.Enc = rlwe.NewEncryptor(tc.Params, tc.Pk)
|
||||
tc.Dec = rlwe.NewDecryptor(tc.Params, tc.Sk)
|
||||
|
||||
tc.Evl = NewEvaluator(tc.Params, rlwe.NewMemEvaluationKeySet(tc.Kgen.GenRelinearizationKeyNew(tc.Sk)))
|
||||
|
||||
return tc
|
||||
}
|
||||
|
||||
func (tc TestContext) String() string {
|
||||
return fmt.Sprintf("LogN=%d/logQ=%d/logP=%d/LogSlots=%dx%d/logT=%d/Qi=%d/Pi=%d",
|
||||
tc.Params.LogN(),
|
||||
int(math.Round(tc.Params.LogQ())),
|
||||
int(math.Round(tc.Params.LogP())),
|
||||
tc.Params.LogMaxDimensions().Rows,
|
||||
tc.Params.LogMaxDimensions().Cols,
|
||||
int(math.Round(tc.Params.LogT())),
|
||||
tc.Params.QCount(),
|
||||
tc.Params.PCount())
|
||||
}
|
||||
|
||||
func VerifyTestVectors(params Parameters, encoder *Encoder, decryptor *rlwe.Decryptor, have interface{}, want []uint64, t *testing.T) {
|
||||
values := make([]uint64, params.MaxSlots())
|
||||
|
||||
switch have := have.(type) {
|
||||
case *rlwe.Plaintext:
|
||||
require.NoError(t, encoder.Decode(have, values))
|
||||
case *rlwe.Ciphertext:
|
||||
require.NoError(t, encoder.Decode(decryptor.DecryptNew(have), values))
|
||||
default:
|
||||
t.Error("invalid unsupported test object type")
|
||||
}
|
||||
|
||||
require.True(t, slices.Equal(values, want))
|
||||
}
|
||||
|
||||
func NewTestVector(params Parameters, encoder *Encoder, encryptor *rlwe.Encryptor, level int, scale rlwe.Scale) (values []uint64, pt *rlwe.Plaintext, ct *rlwe.Ciphertext) {
|
||||
values = make([]uint64, params.MaxSlots())
|
||||
for i := range values {
|
||||
values[i] = sampling.RandUint64() % params.PlaintextModulus()
|
||||
}
|
||||
|
||||
pt = NewPlaintext(params, level)
|
||||
pt.Scale = scale
|
||||
if err := encoder.Encode(values, pt); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if encryptor != nil {
|
||||
var err error
|
||||
ct, err = encryptor.EncryptNew(pt)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
// testInsecure are insecure parameters used for the sole purpose of fast testing.
|
||||
TestInsecure = ParametersLiteral{
|
||||
LogN: 10,
|
||||
Q: []uint64{0x3fffffa8001, 0x1000090001, 0x10000c8001, 0x10000f0001, 0xffff00001},
|
||||
P: []uint64{0x7fffffd8001},
|
||||
}
|
||||
|
||||
TestPlaintextModulus = []uint64{0x101, 0xffc001}
|
||||
|
||||
TestParams = []ParametersLiteral{TestInsecure}
|
||||
)
|
||||
@@ -30,7 +30,7 @@ func BenchmarkBGV(b *testing.B) {
|
||||
}
|
||||
|
||||
for _, paramsLiteral := range testParams {
|
||||
tc := NewTestContext(paramsLiteral)
|
||||
tc := NewTestContext(paramsLiteral, false)
|
||||
|
||||
for _, testSet := range []func(tc *TestContext, b *testing.B){
|
||||
benchEncoder,
|
||||
|
||||
@@ -41,12 +41,44 @@ func TestBGV(t *testing.T) {
|
||||
|
||||
p.PlaintextModulus = plaintextModulus
|
||||
|
||||
tc := NewTestContext(p)
|
||||
tc := NewTestContext(p, false)
|
||||
|
||||
for _, testSet := range []func(tc *TestContext, t *testing.T){
|
||||
testParameters,
|
||||
testEncoder,
|
||||
testEvaluator,
|
||||
testEvaluatorBvg,
|
||||
} {
|
||||
testSet(tc, t)
|
||||
runtime.GC()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBFV(t *testing.T) {
|
||||
|
||||
var err error
|
||||
|
||||
paramsLiterals := testParams
|
||||
|
||||
if *flagParamString != "" {
|
||||
var jsonParams ParametersLiteral
|
||||
if err = json.Unmarshal([]byte(*flagParamString), &jsonParams); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
paramsLiterals = []ParametersLiteral{jsonParams} // the custom test suite reads the parameters from the -params flag
|
||||
}
|
||||
|
||||
for _, p := range paramsLiterals[:] {
|
||||
|
||||
for _, plaintextModulus := range testPlaintextModulus[:] {
|
||||
|
||||
p.PlaintextModulus = plaintextModulus
|
||||
|
||||
tc := NewTestContext(p, true)
|
||||
|
||||
for _, testSet := range []func(tc *TestContext, t *testing.T){
|
||||
testEvaluatorBfv,
|
||||
} {
|
||||
testSet(tc, t)
|
||||
runtime.GC()
|
||||
@@ -176,7 +208,7 @@ func testEncoder(tc *TestContext, t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func testEvaluator(tc *TestContext, t *testing.T) {
|
||||
func testEvaluatorBvg(tc *TestContext, t *testing.T) {
|
||||
testLevel := [2]int{0, tc.Params.MaxLevel()}
|
||||
|
||||
for _, lvl := range testLevel {
|
||||
@@ -633,6 +665,212 @@ func testEvaluator(tc *TestContext, t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func testEvaluatorBfv(tc *TestContext, t *testing.T) {
|
||||
testLevels := []int{0, tc.Params.MaxLevel()}
|
||||
|
||||
t.Run("Evaluator", func(t *testing.T) {
|
||||
|
||||
for _, lvl := range testLevels {
|
||||
t.Run(name("Mul/Ct/Ct/Inplace", tc, lvl), func(t *testing.T) {
|
||||
if lvl == 0 {
|
||||
t.Skip("Skipping: Level = 0")
|
||||
}
|
||||
|
||||
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))
|
||||
|
||||
require.True(t, ciphertext0.Scale.Cmp(ciphertext1.Scale) != 0)
|
||||
|
||||
p0 := ring.Poly{Coeffs: [][]uint64{values0}}
|
||||
p1 := ring.Poly{Coeffs: [][]uint64{values1}}
|
||||
|
||||
require.NoError(t, tc.Evl.Mul(ciphertext0, ciphertext1, ciphertext0))
|
||||
tc.Params.RingT().MulCoeffsBarrett(p0, p1, p0)
|
||||
|
||||
VerifyTestVectors(tc.Params, tc.Ecd, tc.Dec, ciphertext0, p0.Coeffs[0], t)
|
||||
})
|
||||
}
|
||||
|
||||
for _, lvl := range testLevels {
|
||||
t.Run(name("Mul/Ct/Pt/Inplace", tc, lvl), func(t *testing.T) {
|
||||
if lvl == 0 {
|
||||
t.Skip("Level = 0")
|
||||
}
|
||||
|
||||
values0, _, ciphertext := 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))
|
||||
|
||||
require.True(t, ciphertext.Scale.Cmp(plaintext.Scale) != 0)
|
||||
|
||||
p0 := ring.Poly{Coeffs: [][]uint64{values0}}
|
||||
p1 := ring.Poly{Coeffs: [][]uint64{values1}}
|
||||
|
||||
require.NoError(t, tc.Evl.Mul(ciphertext, plaintext, ciphertext))
|
||||
tc.Params.RingT().MulCoeffsBarrett(p0, p1, p0)
|
||||
|
||||
VerifyTestVectors(tc.Params, tc.Ecd, tc.Dec, ciphertext, p0.Coeffs[0], t)
|
||||
})
|
||||
}
|
||||
|
||||
for _, lvl := range testLevels {
|
||||
t.Run(name("Mul/Ct/Scalar/Inplace", tc, lvl), func(t *testing.T) {
|
||||
if lvl == 0 {
|
||||
t.Skip("Level = 0")
|
||||
}
|
||||
|
||||
values, _, ciphertext := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.DefaultScale())
|
||||
|
||||
scalar := tc.Params.PlaintextModulus() >> 1
|
||||
|
||||
p := ring.Poly{Coeffs: [][]uint64{values}}
|
||||
|
||||
require.NoError(t, tc.Evl.Mul(ciphertext, scalar, ciphertext))
|
||||
tc.Params.RingT().MulScalar(p, scalar, p)
|
||||
|
||||
VerifyTestVectors(tc.Params, tc.Ecd, tc.Dec, ciphertext, p.Coeffs[0], t)
|
||||
})
|
||||
}
|
||||
|
||||
for _, lvl := range testLevels {
|
||||
t.Run(name("Square/Ct/Ct/Inplace", tc, lvl), func(t *testing.T) {
|
||||
if lvl == 0 {
|
||||
t.Skip("Level = 0")
|
||||
}
|
||||
|
||||
values, _, ciphertext := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.DefaultScale())
|
||||
|
||||
p := ring.Poly{Coeffs: [][]uint64{values}}
|
||||
|
||||
require.NoError(t, tc.Evl.Mul(ciphertext, ciphertext, ciphertext))
|
||||
tc.Params.RingT().MulCoeffsBarrett(p, p, p)
|
||||
|
||||
VerifyTestVectors(tc.Params, tc.Ecd, tc.Dec, ciphertext, p.Coeffs[0], t)
|
||||
})
|
||||
}
|
||||
|
||||
for _, lvl := range testLevels {
|
||||
t.Run(name("MulRelin/Ct/Ct/Inplace", tc, lvl), func(t *testing.T) {
|
||||
if lvl == 0 {
|
||||
t.Skip("Level = 0")
|
||||
}
|
||||
|
||||
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))
|
||||
|
||||
p0 := ring.Poly{Coeffs: [][]uint64{values0}}
|
||||
p1 := ring.Poly{Coeffs: [][]uint64{values1}}
|
||||
|
||||
tc.Params.RingT().MulCoeffsBarrett(p0, p1, p0)
|
||||
|
||||
require.True(t, ciphertext0.Scale.Cmp(ciphertext1.Scale) != 0)
|
||||
|
||||
receiver := NewCiphertext(tc.Params, 1, lvl)
|
||||
|
||||
require.NoError(t, tc.Evl.MulRelin(ciphertext0, ciphertext1, receiver))
|
||||
require.NoError(t, tc.Evl.Rescale(receiver, receiver))
|
||||
|
||||
VerifyTestVectors(tc.Params, tc.Ecd, tc.Dec, receiver, p0.Coeffs[0], t)
|
||||
})
|
||||
}
|
||||
|
||||
for _, lvl := range testLevels {
|
||||
t.Run(name("MulThenAdd/Ct/Ct/Inplace", tc, lvl), func(t *testing.T) {
|
||||
if lvl == 0 {
|
||||
t.Skip("Level = 0")
|
||||
}
|
||||
|
||||
values0, _, ciphertext0 := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.DefaultScale())
|
||||
values1, _, ciphertext1 := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.NewScale(2))
|
||||
values2, _, ciphertext2 := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.NewScale(7))
|
||||
|
||||
p0 := ring.Poly{Coeffs: [][]uint64{values0}}
|
||||
p1 := ring.Poly{Coeffs: [][]uint64{values1}}
|
||||
p2 := ring.Poly{Coeffs: [][]uint64{values2}}
|
||||
|
||||
require.True(t, ciphertext0.Scale.Cmp(ciphertext1.Scale) != 0)
|
||||
require.True(t, ciphertext0.Scale.Cmp(ciphertext2.Scale) != 0)
|
||||
|
||||
require.NoError(t, tc.Evl.MulThenAdd(ciphertext0, ciphertext1, ciphertext2))
|
||||
tc.Params.RingT().MulCoeffsBarrettThenAdd(p0, p1, p2)
|
||||
|
||||
VerifyTestVectors(tc.Params, tc.Ecd, tc.Dec, ciphertext2, p2.Coeffs[0], t)
|
||||
})
|
||||
}
|
||||
|
||||
for _, lvl := range testLevels {
|
||||
t.Run(name("MulThenAdd/Ct/Pt/Inplace", tc, lvl), func(t *testing.T) {
|
||||
|
||||
if lvl == 0 {
|
||||
t.Skip("Level = 0")
|
||||
}
|
||||
|
||||
values0, _, ciphertext0 := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.DefaultScale())
|
||||
values1, plaintext1, _ := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.NewScale(2))
|
||||
values2, _, ciphertext2 := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.NewScale(7))
|
||||
|
||||
p0 := ring.Poly{Coeffs: [][]uint64{values0}}
|
||||
p1 := ring.Poly{Coeffs: [][]uint64{values1}}
|
||||
p2 := ring.Poly{Coeffs: [][]uint64{values2}}
|
||||
|
||||
require.True(t, ciphertext0.Scale.Cmp(plaintext1.Scale) != 0)
|
||||
require.True(t, ciphertext0.Scale.Cmp(ciphertext2.Scale) != 0)
|
||||
|
||||
require.NoError(t, tc.Evl.MulThenAdd(ciphertext0, plaintext1, ciphertext2))
|
||||
tc.Params.RingT().MulCoeffsBarrettThenAdd(p0, p1, p2)
|
||||
|
||||
VerifyTestVectors(tc.Params, tc.Ecd, tc.Dec, ciphertext2, p2.Coeffs[0], t)
|
||||
})
|
||||
}
|
||||
|
||||
for _, lvl := range testLevels {
|
||||
t.Run(name("MulThenAdd/Ct/Scalar/Inplace", tc, lvl), func(t *testing.T) {
|
||||
if lvl == 0 {
|
||||
t.Skip("Level = 0")
|
||||
}
|
||||
|
||||
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))
|
||||
|
||||
p0 := ring.Poly{Coeffs: [][]uint64{values0}}
|
||||
p1 := ring.Poly{Coeffs: [][]uint64{values1}}
|
||||
|
||||
require.True(t, ciphertext0.Scale.Cmp(ciphertext1.Scale) != 0)
|
||||
|
||||
scalar := tc.Params.PlaintextModulus() >> 1
|
||||
|
||||
require.NoError(t, tc.Evl.MulThenAdd(ciphertext0, scalar, ciphertext1))
|
||||
tc.Params.RingT().MulScalarThenAdd(p0, scalar, p1)
|
||||
|
||||
VerifyTestVectors(tc.Params, tc.Ecd, tc.Dec, ciphertext1, p1.Coeffs[0], t)
|
||||
})
|
||||
}
|
||||
|
||||
for _, lvl := range testLevels {
|
||||
t.Run(name("MulRelinThenAdd/Ct/Ct/Inplace", tc, lvl), func(t *testing.T) {
|
||||
if lvl == 0 {
|
||||
t.Skip("Level = 0")
|
||||
}
|
||||
|
||||
values0, _, ciphertext0 := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.DefaultScale())
|
||||
values1, _, ciphertext1 := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.NewScale(2))
|
||||
values2, _, ciphertext2 := NewTestVector(tc.Params, tc.Ecd, tc.Enc, lvl, tc.Params.NewScale(7))
|
||||
|
||||
require.True(t, ciphertext0.Scale.Cmp(ciphertext1.Scale) != 0)
|
||||
require.True(t, ciphertext0.Scale.Cmp(ciphertext2.Scale) != 0)
|
||||
|
||||
p0 := ring.Poly{Coeffs: [][]uint64{values0}}
|
||||
p1 := ring.Poly{Coeffs: [][]uint64{values1}}
|
||||
p2 := ring.Poly{Coeffs: [][]uint64{values2}}
|
||||
|
||||
require.NoError(t, tc.Evl.MulRelinThenAdd(ciphertext0, ciphertext1, ciphertext2))
|
||||
tc.Params.RingT().MulCoeffsBarrettThenAdd(p0, p1, p2)
|
||||
|
||||
VerifyTestVectors(tc.Params, tc.Ecd, tc.Dec, ciphertext2, p2.Coeffs[0], t)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var (
|
||||
// testInsecure are insecure parameters used for the sole purpose of fast testing.
|
||||
testInsecure = ParametersLiteral{
|
||||
|
||||
@@ -13,11 +13,18 @@ import (
|
||||
|
||||
// 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
|
||||
|
||||
// ScaleInvariant is a flag indicating whether the evaluator executes
|
||||
// scale-invariant multiplications (transforming the BGV evaluator into
|
||||
// BFV evaluator).
|
||||
ScaleInvariant bool
|
||||
}
|
||||
|
||||
type evaluatorBase struct {
|
||||
@@ -112,12 +119,16 @@ func newEvaluatorBuffer(params Parameters) *evaluatorBuffers {
|
||||
// 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.
|
||||
func NewEvaluator(parameters Parameters, evk rlwe.EvaluationKeySet) *Evaluator {
|
||||
// The evaluator can optionally be initialized as scale-invariant, which
|
||||
// transforms it into a BFV evaluator. See `schemes/bfv/README.md` for more
|
||||
// information.
|
||||
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]
|
||||
|
||||
return ev
|
||||
}
|
||||
@@ -444,6 +455,13 @@ func (eval Evaluator) DropLevel(op0 *rlwe.Ciphertext, levels int) {
|
||||
// - the scale of opOut will be updated to op0.Scale * op1.Scale
|
||||
func (eval Evaluator) Mul(op0 *rlwe.Ciphertext, op1 rlwe.Operand, opOut *rlwe.Ciphertext) (err error) {
|
||||
|
||||
if eval.ScaleInvariant {
|
||||
switch op1 := op1.(type) {
|
||||
case rlwe.ElementInterface[ring.Poly], []uint64, []int64:
|
||||
return eval.MulScaleInvariant(op0, op1, opOut)
|
||||
}
|
||||
}
|
||||
|
||||
switch op1 := op1.(type) {
|
||||
case rlwe.ElementInterface[ring.Poly]:
|
||||
|
||||
@@ -540,6 +558,14 @@ func (eval Evaluator) Mul(op0 *rlwe.Ciphertext, op1 rlwe.Operand, opOut *rlwe.Ci
|
||||
// - the level of opOut will be to min(op0.Level(), op1.Level())
|
||||
// - the scale of opOut will be to op0.Scale * op1.Scale
|
||||
func (eval Evaluator) MulNew(op0 *rlwe.Ciphertext, op1 rlwe.Operand) (opOut *rlwe.Ciphertext, err error) {
|
||||
|
||||
if eval.ScaleInvariant {
|
||||
switch op1 := op1.(type) {
|
||||
case rlwe.ElementInterface[ring.Poly], []uint64, []int64:
|
||||
return eval.MulScaleInvariantNew(op0, op1)
|
||||
}
|
||||
}
|
||||
|
||||
switch op1 := op1.(type) {
|
||||
case rlwe.ElementInterface[ring.Poly]:
|
||||
opOut = NewCiphertext(eval.parameters, op0.Degree()+op1.Degree(), utils.Min(op0.Level(), op1.Level()))
|
||||
@@ -569,6 +595,11 @@ func (eval Evaluator) MulNew(op0 *rlwe.Ciphertext, op1 rlwe.Operand) (opOut *rlw
|
||||
// - the level of opOut will be updated to min(op0.Level(), op1.Level())
|
||||
// - the scale of opOut will be updated to op0.Scale * op1.Scale
|
||||
func (eval Evaluator) MulRelin(op0 *rlwe.Ciphertext, op1 rlwe.Operand, opOut *rlwe.Ciphertext) (err error) {
|
||||
|
||||
if eval.ScaleInvariant {
|
||||
return eval.MulRelinScaleInvariant(op0, op1, opOut)
|
||||
}
|
||||
|
||||
switch op1 := op1.(type) {
|
||||
case rlwe.ElementInterface[ring.Poly]:
|
||||
|
||||
@@ -609,6 +640,11 @@ func (eval Evaluator) MulRelin(op0 *rlwe.Ciphertext, op1 rlwe.Operand, opOut *rl
|
||||
// - the level of opOut will be to min(op0.Level(), op1.Level())
|
||||
// - the scale of opOut will be to op0.Scale * op1.Scale
|
||||
func (eval Evaluator) MulRelinNew(op0 *rlwe.Ciphertext, op1 rlwe.Operand) (opOut *rlwe.Ciphertext, err error) {
|
||||
|
||||
if eval.ScaleInvariant {
|
||||
return eval.MulRelinScaleInvariantNew(op0, op1)
|
||||
}
|
||||
|
||||
switch op1 := op1.(type) {
|
||||
case rlwe.ElementInterface[ring.Poly]:
|
||||
opOut = NewCiphertext(eval.parameters, 1, utils.Min(op0.Level(), op1.Level()))
|
||||
@@ -1370,6 +1406,10 @@ func (eval Evaluator) mulRelinThenAdd(op0 *rlwe.Ciphertext, op1 *rlwe.Element[ri
|
||||
// the rescaling operation.
|
||||
func (eval Evaluator) Rescale(op0, opOut *rlwe.Ciphertext) (err error) {
|
||||
|
||||
if eval.ScaleInvariant {
|
||||
return nil
|
||||
}
|
||||
|
||||
if op0.MetaData == nil || opOut.MetaData == nil {
|
||||
return fmt.Errorf("cannot Rescale: op0.MetaData or opOut.MetaData is nil")
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ type TestContext struct {
|
||||
Evl *Evaluator
|
||||
}
|
||||
|
||||
func NewTestContext(params ParametersLiteral) *TestContext {
|
||||
func NewTestContext(params ParametersLiteral, scaleInvariant bool) *TestContext {
|
||||
tc := new(TestContext)
|
||||
|
||||
var err error
|
||||
@@ -53,7 +53,7 @@ func NewTestContext(params ParametersLiteral) *TestContext {
|
||||
tc.Enc = rlwe.NewEncryptor(tc.Params, tc.Pk)
|
||||
tc.Dec = rlwe.NewDecryptor(tc.Params, tc.Sk)
|
||||
|
||||
tc.Evl = NewEvaluator(tc.Params, rlwe.NewMemEvaluationKeySet(tc.Kgen.GenRelinearizationKeyNew(tc.Sk)))
|
||||
tc.Evl = NewEvaluator(tc.Params, rlwe.NewMemEvaluationKeySet(tc.Kgen.GenRelinearizationKeyNew(tc.Sk)), scaleInvariant)
|
||||
|
||||
return tc
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user