collapse bfv

This commit is contained in:
Andrea Caforio
2024-08-04 17:16:40 +02:00
parent a9f43f03e0
commit 6b66bca68f
16 changed files with 300 additions and 1202 deletions

View File

@@ -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,

View File

@@ -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,
}
}

View File

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

View File

@@ -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

View File

@@ -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.

View File

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

View File

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

View File

@@ -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, &paramsRec)
require.Nil(t, err)
require.True(t, tc.Params.Equal(&paramsRec))
// 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, &paramsWithLogModuli)
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, &paramsWithCustomSecrets)
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)
})
}
})
}

View File

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

View File

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

View File

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

View File

@@ -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,

View File

@@ -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{

View File

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

View File

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