Files
lattice/core/rlwe/evaluator_automorphism.go
T

155 lines
4.9 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"
)
// Automorphism computes phi(ct), where phi is the map X -> X^galEl. The method requires
// that the corresponding RotationKey has been added to the Evaluator. The method will
// return an error if either ctIn or opOut degree is not equal to 1.
func (eval Evaluator) Automorphism(ctIn *Ciphertext, galEl uint64, opOut *Ciphertext) (err error) {
if ctIn.Degree() != 1 || opOut.Degree() != 1 {
return fmt.Errorf("cannot apply Automorphism: input and output Ciphertext must be of degree 1")
}
if galEl == 1 {
if opOut != ctIn {
opOut.Copy(ctIn)
}
return
}
var evk *GaloisKey
if evk, err = eval.CheckAndGetGaloisKey(galEl); err != nil {
return fmt.Errorf("cannot apply Automorphism: %w", err)
}
level := utils.Min(ctIn.Level(), opOut.Level())
opOut.Resize(opOut.Degree(), level)
ringQ := eval.params.RingQ().AtLevel(level)
ctTmp := &Ciphertext{Element: Element[ring.Poly]{Value: []ring.Poly{eval.BuffQP[0].Q, eval.BuffQP[1].Q}}}
ctTmp.MetaData = ctIn.MetaData
eval.GadgetProduct(level, ctIn.Value[1], &evk.GadgetCiphertext, ctTmp)
ringQ.Add(ctTmp.Value[0], ctIn.Value[0], ctTmp.Value[0])
if ctIn.IsNTT {
ringQ.AutomorphismNTTWithIndex(ctTmp.Value[0], eval.automorphismIndex[galEl], opOut.Value[0])
ringQ.AutomorphismNTTWithIndex(ctTmp.Value[1], eval.automorphismIndex[galEl], opOut.Value[1])
} else {
ringQ.Automorphism(ctTmp.Value[0], galEl, opOut.Value[0])
ringQ.Automorphism(ctTmp.Value[1], galEl, opOut.Value[1])
}
*opOut.MetaData = *ctIn.MetaData
return
}
// AutomorphismHoisted is similar to Automorphism, except that it takes as input ctIn and c1DecompQP, where c1DecompQP is the RNS
// decomposition of its element of degree 1. This decomposition can be obtained with DecomposeNTT.
// The method requires that the corresponding RotationKey has been added to the Evaluator.
// The method will return an error if either ctIn or opOut degree is not equal to 1.
func (eval Evaluator) AutomorphismHoisted(level int, ctIn *Ciphertext, c1DecompQP []ringqp.Poly, galEl uint64, opOut *Ciphertext) (err error) {
if ctIn.Degree() != 1 || opOut.Degree() != 1 {
return fmt.Errorf("cannot apply AutomorphismHoisted: input and output Ciphertext must be of degree 1")
}
if galEl == 1 {
if ctIn != opOut {
opOut.Copy(ctIn)
}
return
}
var evk *GaloisKey
if evk, err = eval.CheckAndGetGaloisKey(galEl); err != nil {
return fmt.Errorf("cannot apply AutomorphismHoisted: %w", err)
}
opOut.Resize(opOut.Degree(), level)
ringQ := eval.params.RingQ().AtLevel(level)
ctTmp := &Ciphertext{}
ctTmp.Value = []ring.Poly{eval.BuffQP[0].Q, eval.BuffQP[1].Q} // GadgetProductHoisted uses the same buffers for its ciphertext QP
ctTmp.MetaData = ctIn.MetaData
eval.GadgetProductHoisted(level, c1DecompQP, &evk.EvaluationKey.GadgetCiphertext, ctTmp)
ringQ.Add(ctTmp.Value[0], ctIn.Value[0], ctTmp.Value[0])
if ctIn.IsNTT {
ringQ.AutomorphismNTTWithIndex(ctTmp.Value[0], eval.automorphismIndex[galEl], opOut.Value[0])
ringQ.AutomorphismNTTWithIndex(ctTmp.Value[1], eval.automorphismIndex[galEl], opOut.Value[1])
} else {
ringQ.Automorphism(ctTmp.Value[0], galEl, opOut.Value[0])
ringQ.Automorphism(ctTmp.Value[1], galEl, opOut.Value[1])
}
*opOut.MetaData = *ctIn.MetaData
return
}
// AutomorphismHoistedLazy is similar to AutomorphismHoisted, except that it returns a ciphertext modulo QP and scaled by P.
// The method requires that the corresponding RotationKey has been added to the Evaluator.
// Result NTT domain is returned according to the NTT flag of ctQP.
func (eval Evaluator) AutomorphismHoistedLazy(levelQ int, ctIn *Ciphertext, c1DecompQP []ringqp.Poly, galEl uint64, ctQP *Element[ringqp.Poly]) (err error) {
var evk *GaloisKey
if evk, err = eval.CheckAndGetGaloisKey(galEl); err != nil {
return fmt.Errorf("cannot apply AutomorphismHoistedLazy: %w", err)
}
levelP := evk.LevelP()
ctTmp := &Element[ringqp.Poly]{}
ctTmp.Value = []ringqp.Poly{eval.BuffQP[0], eval.BuffQP[1]}
ctTmp.MetaData = ctIn.MetaData
eval.GadgetProductHoistedLazy(levelQ, c1DecompQP, &evk.GadgetCiphertext, ctTmp)
ringQP := eval.params.RingQP().AtLevel(levelQ, levelP)
ringQ := ringQP.RingQ
ringP := ringQP.RingP
index := eval.automorphismIndex[galEl]
if ctQP.IsNTT {
ringQP.AutomorphismNTTWithIndex(ctTmp.Value[1], index, ctQP.Value[1])
if levelP > -1 {
ringQ.MulScalarBigint(ctIn.Value[0], ringP.ModulusAtLevel[levelP], ctTmp.Value[1].Q)
}
ringQ.Add(ctTmp.Value[0].Q, ctTmp.Value[1].Q, ctTmp.Value[0].Q)
ringQP.AutomorphismNTTWithIndex(ctTmp.Value[0], index, ctQP.Value[0])
} else {
ringQP.Automorphism(ctTmp.Value[1], galEl, ctQP.Value[1])
if levelP > -1 {
ringQ.MulScalarBigint(ctIn.Value[0], ringP.ModulusAtLevel[levelP], ctTmp.Value[1].Q)
}
ringQ.Add(ctTmp.Value[0].Q, ctTmp.Value[1].Q, ctTmp.Value[0].Q)
ringQP.Automorphism(ctTmp.Value[0], galEl, ctQP.Value[0])
}
return
}