mirror of
https://github.com/luxfi/lattice.git
synced 2026-09-24 00:25:44 +00:00
- Update module path to github.com/luxfi/lattice/v7 - Add GPU acceleration with Metal (macOS) and CUDA backends - Enhance threshold FHE operations - Revert to vanilla Apache 2.0 license (remove tfhe) - Update Go version to 1.25.5 - Merge upstream lattigo improvements
236 lines
7.3 KiB
Go
236 lines
7.3 KiB
Go
package rlwe
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/luxfi/lattice/v7/ring"
|
|
"github.com/luxfi/lattice/v7/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
|
|
|
|
automorphismIndex map[uint64][]uint64
|
|
|
|
BasisExtender *ring.BasisExtender
|
|
Decomposer *ring.Decomposer
|
|
pool *BufferPool
|
|
}
|
|
|
|
// NewEvaluator creates a new [Evaluator].
|
|
func NewEvaluator(params ParameterProvider, evk EvaluationKeySet) (eval *Evaluator) {
|
|
eval = new(Evaluator)
|
|
p := params.GetRLWEParameters()
|
|
eval.params = *p
|
|
|
|
eval.pool = NewPool(p.RingQP())
|
|
|
|
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
|
|
// 3. 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
|
|
}
|
|
|
|
// 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,
|
|
Decomposer: eval.Decomposer,
|
|
BasisExtender: eval.BasisExtender,
|
|
EvaluationKeySet: evk,
|
|
automorphismIndex: AutomorphismIndex,
|
|
pool: eval.pool,
|
|
}
|
|
}
|
|
|
|
func (eval Evaluator) AutomorphismIndex(galEl uint64) []uint64 {
|
|
return eval.automorphismIndex[galEl]
|
|
}
|
|
|
|
func (eval Evaluator) ModDownQPtoQNTT(levelQ, levelP int, p1Q, p1P, p2Q ring.Poly) {
|
|
eval.BasisExtender.ModDownQPtoQNTT(levelQ, levelP, p1Q, p1P, p2Q)
|
|
}
|