mirror of
https://github.com/luxfi/lattice.git
synced 2026-09-24 00:25:44 +00:00
305 lines
9.3 KiB
Go
305 lines
9.3 KiB
Go
package rlwe
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/luxfi/lattice/v5/ring"
|
|
"github.com/luxfi/lattice/v5/ring/ringqp"
|
|
"github.com/luxfi/lattice/v5/utils"
|
|
)
|
|
|
|
// Evaluator is a struct that holds the necessary elements to execute general homomorphic
|
|
// operation on RLWE ciphertexts, such as automorphisms, key-switching and relinearization.
|
|
type Evaluator struct {
|
|
params Parameters
|
|
EvaluationKeySet
|
|
*EvaluatorBuffers
|
|
|
|
automorphismIndex map[uint64][]uint64
|
|
|
|
BasisExtender *ring.BasisExtender
|
|
Decomposer *ring.Decomposer
|
|
}
|
|
|
|
type EvaluatorBuffers struct {
|
|
BuffCt *Ciphertext
|
|
// BuffQP[0-1]: Key-Switch output Key-Switch on the fly decomp(c2)
|
|
// BuffQP[2-5]: Available
|
|
BuffQP [6]ringqp.Poly
|
|
BuffInvNTT ring.Poly
|
|
BuffDecompQP []ringqp.Poly // Memory Buff for the basis extension in hoisting
|
|
BuffBitDecomp []uint64
|
|
}
|
|
|
|
func NewEvaluatorBuffers(params Parameters) *EvaluatorBuffers {
|
|
|
|
buff := new(EvaluatorBuffers)
|
|
BaseRNSDecompositionVectorSize := params.BaseRNSDecompositionVectorSize(params.MaxLevelQ(), 0)
|
|
ringQP := params.RingQP()
|
|
|
|
buff.BuffCt = NewCiphertext(params, 2, params.MaxLevel())
|
|
|
|
buff.BuffQP = [6]ringqp.Poly{
|
|
ringQP.NewPoly(),
|
|
ringQP.NewPoly(),
|
|
ringQP.NewPoly(),
|
|
ringQP.NewPoly(),
|
|
ringQP.NewPoly(),
|
|
ringQP.NewPoly(),
|
|
}
|
|
|
|
buff.BuffInvNTT = params.RingQ().NewPoly()
|
|
|
|
buff.BuffDecompQP = make([]ringqp.Poly, BaseRNSDecompositionVectorSize)
|
|
for i := 0; i < BaseRNSDecompositionVectorSize; i++ {
|
|
buff.BuffDecompQP[i] = ringQP.NewPoly()
|
|
}
|
|
|
|
buff.BuffBitDecomp = make([]uint64, params.RingQ().N())
|
|
|
|
return buff
|
|
}
|
|
|
|
// NewEvaluator creates a new Evaluator.
|
|
func NewEvaluator(params ParameterProvider, evk EvaluationKeySet) (eval *Evaluator) {
|
|
eval = new(Evaluator)
|
|
p := params.GetRLWEParameters()
|
|
eval.params = *p
|
|
eval.EvaluatorBuffers = NewEvaluatorBuffers(eval.params)
|
|
|
|
if p.RingP() != nil {
|
|
eval.BasisExtender = ring.NewBasisExtender(p.RingQ(), p.RingP())
|
|
}
|
|
|
|
eval.Decomposer = ring.NewDecomposer(p.RingQ(), p.RingP())
|
|
|
|
eval.EvaluationKeySet = evk
|
|
|
|
var AutomorphismIndex map[uint64][]uint64
|
|
|
|
if !utils.IsNil(evk) {
|
|
if galEls := evk.GetGaloisKeysList(); len(galEls) != 0 {
|
|
AutomorphismIndex = make(map[uint64][]uint64)
|
|
|
|
N := p.N()
|
|
NthRoot := p.RingQ().NthRoot()
|
|
|
|
var err error
|
|
for _, galEl := range galEls {
|
|
if AutomorphismIndex[galEl], err = ring.AutomorphismNTTIndex(N, NthRoot, galEl); err != nil {
|
|
// Sanity check, this error should not happen.
|
|
panic(err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
eval.automorphismIndex = AutomorphismIndex
|
|
|
|
return
|
|
}
|
|
|
|
func (eval *Evaluator) GetRLWEParameters() *Parameters {
|
|
return &eval.params
|
|
}
|
|
|
|
// CheckAndGetGaloisKey returns an error if the GaloisKey for the given Galois element is missing or the EvaluationKey interface is nil.
|
|
func (eval Evaluator) CheckAndGetGaloisKey(galEl uint64) (evk *GaloisKey, err error) {
|
|
if eval.EvaluationKeySet != nil {
|
|
if evk, err = eval.GetGaloisKey(galEl); err != nil {
|
|
return nil, fmt.Errorf("%w: key for galEl %d = 5^{%d} key is missing", err, galEl, eval.params.SolveDiscreteLogGaloisElement(galEl))
|
|
}
|
|
} else {
|
|
return nil, fmt.Errorf("evaluation key interface is nil")
|
|
}
|
|
|
|
if eval.automorphismIndex == nil {
|
|
eval.automorphismIndex = map[uint64][]uint64{}
|
|
}
|
|
|
|
if _, ok := eval.automorphismIndex[galEl]; !ok {
|
|
if eval.automorphismIndex[galEl], err = ring.AutomorphismNTTIndex(eval.params.N(), eval.params.RingQ().NthRoot(), galEl); err != nil {
|
|
// Sanity check, this error should not happen.
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// CheckAndGetRelinearizationKey returns an error if the RelinearizationKey is missing or the EvaluationKey interface is nil.
|
|
func (eval Evaluator) CheckAndGetRelinearizationKey() (evk *RelinearizationKey, err error) {
|
|
if eval.EvaluationKeySet != nil {
|
|
if evk, err = eval.GetRelinearizationKey(); err != nil {
|
|
return nil, fmt.Errorf("%w: relineariztion key is missing", err)
|
|
}
|
|
} else {
|
|
return nil, fmt.Errorf("evaluation key interface is nil")
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// InitOutputBinaryOp initializes the output Element opOut for receiving the result of a binary operation over
|
|
// op0 and op1. The method also performs the following checks:
|
|
//
|
|
// 1. Inputs are not nil
|
|
// 2. MetaData are not nil
|
|
// 3. op0.Degree() + op1.Degree() != 0 (i.e at least one Element is a ciphertext)
|
|
// 4. op0.IsNTT == op1.IsNTT == DefaultNTTFlag
|
|
// 5. op0.IsBatched == op1.IsBatched
|
|
//
|
|
// The opOut metadata are initilized as:
|
|
// IsNTT <- DefaultNTTFlag
|
|
// IsBatched <- op0.IsBatched
|
|
// LogDimensions <- max(op0.LogDimensions, op1.LogDimensions)
|
|
//
|
|
// The method returns max(op0.Degree(), op1.Degree(), opOut.Degree()) and min(op0.Level(), op1.Level(), opOut.Level())
|
|
func (eval Evaluator) InitOutputBinaryOp(op0, op1 *Element[ring.Poly], opInTotalMaxDegree int, opOut *Element[ring.Poly]) (degree, level int, err error) {
|
|
|
|
if op0 == nil || op1 == nil || opOut == nil {
|
|
return 0, 0, fmt.Errorf("op0, op1 and opOut cannot be nil")
|
|
}
|
|
|
|
if op0.MetaData == nil || op1.MetaData == nil || opOut.MetaData == nil {
|
|
return 0, 0, fmt.Errorf("op0, op1 and opOut MetaData cannot be nil")
|
|
}
|
|
|
|
degree = utils.Max(op0.Degree(), op1.Degree())
|
|
degree = utils.Max(degree, opOut.Degree())
|
|
level = utils.Min(op0.Level(), op1.Level())
|
|
level = utils.Min(level, opOut.Level())
|
|
|
|
totDegree := op0.Degree() + op1.Degree()
|
|
|
|
if totDegree == 0 {
|
|
return 0, 0, fmt.Errorf("op0 and op1 cannot be both plaintexts")
|
|
}
|
|
|
|
if totDegree > opInTotalMaxDegree {
|
|
return 0, 0, fmt.Errorf("op0 and op1 total degree cannot exceed %d but is %d", opInTotalMaxDegree, totDegree)
|
|
}
|
|
|
|
if op0.El().IsNTT != op1.El().IsNTT || op0.El().IsNTT != eval.params.NTTFlag() {
|
|
return 0, 0, fmt.Errorf("op0.El().IsNTT or op1.El().IsNTT != %t", eval.params.NTTFlag())
|
|
} else {
|
|
opOut.El().IsNTT = op0.El().IsNTT
|
|
}
|
|
|
|
if op0.El().IsBatched != op1.El().IsBatched {
|
|
return 0, 0, fmt.Errorf("op1.El().IsBatched != opOut.El().IsBatched")
|
|
} else {
|
|
opOut.El().IsBatched = op0.El().IsBatched
|
|
}
|
|
|
|
opOut.El().LogDimensions.Rows = utils.Max(op0.El().LogDimensions.Rows, op1.El().LogDimensions.Rows)
|
|
opOut.El().LogDimensions.Cols = utils.Max(op0.El().LogDimensions.Cols, op1.El().LogDimensions.Cols)
|
|
|
|
return
|
|
}
|
|
|
|
// InitOutputUnaryOp initializes the output Element opOut for receiving the result of a unary operation over
|
|
// op0. The method also performs the following checks:
|
|
//
|
|
// 1. Input and output are not nil
|
|
// 2. Inoutp and output Metadata are not nil
|
|
// 2. op0.IsNTT == DefaultNTTFlag
|
|
//
|
|
// The method will also update the metadata of opOut:
|
|
//
|
|
// IsNTT <- NTTFlag
|
|
// IsBatched <- op0.IsBatched
|
|
// LogDimensions <- op0.LogDimensions
|
|
//
|
|
// The method returns max(op0.Degree(), opOut.Degree()) and min(op0.Level(), opOut.Level()).
|
|
func (eval Evaluator) InitOutputUnaryOp(op0, opOut *Element[ring.Poly]) (degree, level int, err error) {
|
|
|
|
if op0 == nil || opOut == nil {
|
|
return 0, 0, fmt.Errorf("op0 and opOut cannot be nil")
|
|
}
|
|
|
|
if op0.MetaData == nil || opOut.MetaData == nil {
|
|
return 0, 0, fmt.Errorf("op0 and opOut MetaData cannot be nil")
|
|
}
|
|
|
|
if op0.El().IsNTT != eval.params.NTTFlag() {
|
|
return 0, 0, fmt.Errorf("op0.IsNTT() != %t", eval.params.NTTFlag())
|
|
} else {
|
|
opOut.El().IsNTT = op0.El().IsNTT
|
|
}
|
|
|
|
opOut.El().IsBatched = op0.El().IsBatched
|
|
opOut.El().LogDimensions = op0.El().LogDimensions
|
|
|
|
return utils.Max(op0.Degree(), opOut.Degree()), utils.Min(op0.Level(), opOut.Level()), nil
|
|
}
|
|
|
|
// ShallowCopy creates a shallow copy of this Evaluator in which all the read-only data-structures are
|
|
// shared with the receiver and the temporary buffers are reallocated. The receiver and the returned
|
|
// Evaluators can be used concurrently.
|
|
func (eval Evaluator) ShallowCopy() *Evaluator {
|
|
return &Evaluator{
|
|
params: eval.params,
|
|
Decomposer: eval.Decomposer,
|
|
BasisExtender: eval.BasisExtender.ShallowCopy(),
|
|
EvaluatorBuffers: NewEvaluatorBuffers(eval.params),
|
|
EvaluationKeySet: eval.EvaluationKeySet,
|
|
automorphismIndex: eval.automorphismIndex,
|
|
}
|
|
}
|
|
|
|
// WithKey creates a shallow copy of the receiver Evaluator for which the new EvaluationKey is evaluationKey
|
|
// and where the temporary buffers are shared. The receiver and the returned Evaluators cannot be used concurrently.
|
|
func (eval Evaluator) WithKey(evk EvaluationKeySet) *Evaluator {
|
|
|
|
var AutomorphismIndex map[uint64][]uint64
|
|
|
|
if galEls := evk.GetGaloisKeysList(); len(galEls) != 0 {
|
|
AutomorphismIndex = make(map[uint64][]uint64)
|
|
|
|
N := eval.params.N()
|
|
NthRoot := eval.params.RingQ().NthRoot()
|
|
|
|
var err error
|
|
for _, galEl := range galEls {
|
|
if AutomorphismIndex[galEl], err = ring.AutomorphismNTTIndex(N, NthRoot, galEl); err != nil {
|
|
// Sanity check, this error should not happen.
|
|
panic(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
return &Evaluator{
|
|
params: eval.params,
|
|
EvaluatorBuffers: eval.EvaluatorBuffers,
|
|
Decomposer: eval.Decomposer,
|
|
BasisExtender: eval.BasisExtender,
|
|
EvaluationKeySet: evk,
|
|
automorphismIndex: AutomorphismIndex,
|
|
}
|
|
}
|
|
|
|
func (eval Evaluator) AutomorphismIndex(galEl uint64) []uint64 {
|
|
return eval.automorphismIndex[galEl]
|
|
}
|
|
|
|
func (eval Evaluator) GetEvaluatorBuffer() *EvaluatorBuffers {
|
|
return eval.EvaluatorBuffers
|
|
}
|
|
|
|
func (eval Evaluator) GetBuffQP() [6]ringqp.Poly {
|
|
return eval.BuffQP
|
|
}
|
|
|
|
func (eval Evaluator) GetBuffCt() *Ciphertext {
|
|
return eval.BuffCt
|
|
}
|
|
|
|
func (eval Evaluator) GetBuffDecompQP() []ringqp.Poly {
|
|
return eval.BuffDecompQP
|
|
}
|
|
|
|
func (eval Evaluator) ModDownQPtoQNTT(levelQ, levelP int, p1Q, p1P, p2Q ring.Poly) {
|
|
eval.BasisExtender.ModDownQPtoQNTT(levelQ, levelP, p1Q, p1P, p2Q)
|
|
}
|