mirror of
https://github.com/luxfi/lattice.git
synced 2026-09-24 00:25:44 +00:00
333 lines
12 KiB
Go
333 lines
12 KiB
Go
package rgsw
|
|
|
|
import (
|
|
"github.com/luxfi/lattice/v5/core/rlwe"
|
|
"github.com/luxfi/lattice/v5/ring"
|
|
"github.com/luxfi/lattice/v5/ring/ringqp"
|
|
)
|
|
|
|
// Evaluator is a type for evaluating homomorphic operations involving RGSW ciphertexts.
|
|
// It currently supports the external product between a RLWE and a RGSW ciphertext (see
|
|
// Evaluator.ExternalProduct).
|
|
type Evaluator struct {
|
|
rlwe.Evaluator
|
|
}
|
|
|
|
// NewEvaluator creates a new Evaluator type supporting RGSW operations in addition
|
|
// to rlwe.Evaluator operations.
|
|
func NewEvaluator(params rlwe.ParameterProvider, evk rlwe.EvaluationKeySet) *Evaluator {
|
|
return &Evaluator{*rlwe.NewEvaluator(params, evk)}
|
|
}
|
|
|
|
// 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{*eval.Evaluator.ShallowCopy()}
|
|
}
|
|
|
|
// 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 rlwe.EvaluationKeySet) *Evaluator {
|
|
return &Evaluator{*eval.Evaluator.WithKey(evk)}
|
|
}
|
|
|
|
// ExternalProduct computes RLWE x RGSW -> RLWE
|
|
//
|
|
// RLWE : (-as + m + e, a)
|
|
// x
|
|
// RGSW : [(-as + P*w*m1 + e, a), (-bs + e, b + P*w*m1)]
|
|
// =
|
|
// RLWE : (<RLWE, RGSW[0]>, <RLWE, RGSW[1]>)
|
|
func (eval Evaluator) ExternalProduct(op0 *rlwe.Ciphertext, op1 *Ciphertext, opOut *rlwe.Ciphertext) {
|
|
|
|
levelQ, levelP := op1.LevelQ(), op1.LevelP()
|
|
|
|
var c0QP, c1QP ringqp.Poly
|
|
if op0 == opOut {
|
|
c0QP, c1QP = eval.BuffQP[1], eval.BuffQP[2]
|
|
} else {
|
|
c0QP, c1QP = ringqp.Poly{Q: opOut.Value[0], P: eval.BuffQP[1].P}, ringqp.Poly{Q: opOut.Value[1], P: eval.BuffQP[2].P}
|
|
}
|
|
|
|
if levelP < 1 {
|
|
|
|
params := eval.GetRLWEParameters()
|
|
|
|
// If log(Q) * (Q-1)**2 < 2^{64}-1
|
|
if ringQ := params.RingQ(); levelQ == 0 && levelP == -1 && (ringQ.SubRings[0].Modulus>>29) == 0 {
|
|
eval.externalProduct32Bit(op0, op1, c0QP.Q, c1QP.Q)
|
|
ringQ.AtLevel(0).IMForm(c0QP.Q, opOut.Value[0])
|
|
ringQ.AtLevel(0).IMForm(c1QP.Q, opOut.Value[1])
|
|
} else {
|
|
|
|
eval.externalProductInPlaceSinglePAndBitDecomp(op0, op1, c0QP, c1QP)
|
|
|
|
if levelP == 0 {
|
|
eval.BasisExtender.ModDownQPtoQNTT(levelQ, levelP, c0QP.Q, c0QP.P, opOut.Value[0])
|
|
eval.BasisExtender.ModDownQPtoQNTT(levelQ, levelP, c1QP.Q, c1QP.P, opOut.Value[1])
|
|
} else {
|
|
opOut.Value[0].CopyLvl(levelQ, c0QP.Q)
|
|
opOut.Value[1].CopyLvl(levelQ, c1QP.Q)
|
|
}
|
|
}
|
|
} else {
|
|
eval.externalProductInPlaceMultipleP(levelQ, levelP, op0, op1, eval.BuffQP[1].Q, eval.BuffQP[1].P, eval.BuffQP[2].Q, eval.BuffQP[2].P)
|
|
eval.BasisExtender.ModDownQPtoQNTT(levelQ, levelP, c0QP.Q, c0QP.P, opOut.Value[0])
|
|
eval.BasisExtender.ModDownQPtoQNTT(levelQ, levelP, c1QP.Q, c1QP.P, opOut.Value[1])
|
|
|
|
}
|
|
}
|
|
|
|
func (eval Evaluator) externalProduct32Bit(ct0 *rlwe.Ciphertext, rgsw *Ciphertext, c0, c1 ring.Poly) {
|
|
|
|
// rgsw = [(-as + P*w*m1 + e, a), (-bs + e, b + P*w*m1)]
|
|
// ct = [-cs + m0 + e, c]
|
|
// opOut = [<ct, rgsw[0]>, <ct, rgsw[1]>] = [ct[0] * rgsw[0][0] + ct[1] * rgsw[0][1], ct[0] * rgsw[1][0] + ct[1] * rgsw[1][1]]
|
|
params := eval.GetRLWEParameters()
|
|
ringQ := params.RingQ().AtLevel(0)
|
|
subRing := ringQ.SubRings[0]
|
|
pw2 := rgsw.Value[0].BaseTwoDecomposition
|
|
mask := uint64(((1 << pw2) - 1))
|
|
|
|
cw := eval.BuffQP[0].Q.Coeffs[0]
|
|
cwNTT := eval.BuffBitDecomp
|
|
|
|
acc0 := c0.Coeffs[0]
|
|
acc1 := c1.Coeffs[0]
|
|
|
|
// (a, b) + (c0 * rgsw[0][0], c0 * rgsw[0][1])
|
|
// (a, b) + (c1 * rgsw[1][0], c1 * rgsw[1][1])
|
|
for i, el := range rgsw.Value {
|
|
ringQ.INTT(ct0.Value[i], eval.BuffInvNTT)
|
|
for j := range el.Value[0] {
|
|
// TODO: center values if mask = 0
|
|
ring.MaskVec(eval.BuffInvNTT.Coeffs[0], j*pw2, mask, cw)
|
|
if j == 0 && i == 0 {
|
|
subRing.NTTLazy(cw, cwNTT)
|
|
subRing.MulCoeffsLazy(el.Value[0][j][0].Q.Coeffs[0], cwNTT, acc0)
|
|
subRing.MulCoeffsLazy(el.Value[0][j][1].Q.Coeffs[0], cwNTT, acc1)
|
|
} else {
|
|
subRing.NTTLazy(cw, cwNTT)
|
|
subRing.MulCoeffsLazyThenAddLazy(el.Value[0][j][0].Q.Coeffs[0], cwNTT, acc0)
|
|
subRing.MulCoeffsLazyThenAddLazy(el.Value[0][j][1].Q.Coeffs[0], cwNTT, acc1)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (eval Evaluator) externalProductInPlaceSinglePAndBitDecomp(ct0 *rlwe.Ciphertext, rgsw *Ciphertext, c0QP, c1QP ringqp.Poly) {
|
|
|
|
// rgsw = [(-as + P*w*m1 + e, a), (-bs + e, b + P*w*m1)]
|
|
// ct = [-cs + m0 + e, c]
|
|
// opOut = [<ct, rgsw[0]>, <ct, rgsw[1]>] = [ct[0] * rgsw[0][0] + ct[1] * rgsw[0][1], ct[0] * rgsw[1][0] + ct[1] * rgsw[1][1]]
|
|
levelQ := rgsw.LevelQ()
|
|
levelP := rgsw.LevelP()
|
|
|
|
params := eval.GetRLWEParameters()
|
|
ringQP := params.RingQP().AtLevel(levelQ, levelP)
|
|
|
|
ringQ := ringQP.RingQ
|
|
ringP := ringQP.RingP
|
|
|
|
pw2 := rgsw.Value[0].BaseTwoDecomposition
|
|
mask := uint64(((1 << pw2) - 1))
|
|
if mask == 0 {
|
|
mask = 0xFFFFFFFFFFFFFFFF
|
|
}
|
|
|
|
BaseRNSDecompositionVectorSize := rgsw.Value[0].BaseRNSDecompositionVectorSize()
|
|
BaseTwoDecompositionVectorSize := rgsw.Value[0].BaseTwoDecompositionVectorSize()
|
|
|
|
// (a, b) + (c0 * rgsw[k][0], c0 * rgsw[k][1])
|
|
for k, el := range rgsw.Value {
|
|
ringQ.INTT(ct0.Value[k], eval.BuffInvNTT)
|
|
cw := eval.BuffQP[0].Q.Coeffs[0]
|
|
cwNTT := eval.BuffBitDecomp
|
|
for i := 0; i < BaseRNSDecompositionVectorSize; i++ {
|
|
for j := 0; j < BaseTwoDecompositionVectorSize[i]; j++ {
|
|
// TODO: center values if mask == 0
|
|
ring.MaskVec(eval.BuffInvNTT.Coeffs[i], j*pw2, mask, cw)
|
|
if k == 0 && i == 0 && j == 0 {
|
|
|
|
for u, s := range ringQ.SubRings[:levelQ+1] {
|
|
s.NTTLazy(cw, cwNTT)
|
|
s.MulCoeffsMontgomery(el.Value[i][j][0].Q.Coeffs[u], cwNTT, c0QP.Q.Coeffs[u])
|
|
s.MulCoeffsMontgomery(el.Value[i][j][1].Q.Coeffs[u], cwNTT, c1QP.Q.Coeffs[u])
|
|
}
|
|
|
|
if ringP != nil {
|
|
for u, s := range ringP.SubRings[:levelP+1] {
|
|
s.NTTLazy(cw, cwNTT)
|
|
s.MulCoeffsMontgomery(el.Value[i][j][0].P.Coeffs[u], cwNTT, c0QP.P.Coeffs[u])
|
|
s.MulCoeffsMontgomery(el.Value[i][j][1].P.Coeffs[u], cwNTT, c1QP.P.Coeffs[u])
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
for u, s := range ringQ.SubRings[:levelQ+1] {
|
|
s.NTTLazy(cw, cwNTT)
|
|
s.MulCoeffsMontgomeryThenAdd(el.Value[i][j][0].Q.Coeffs[u], cwNTT, c0QP.Q.Coeffs[u])
|
|
s.MulCoeffsMontgomeryThenAdd(el.Value[i][j][1].Q.Coeffs[u], cwNTT, c1QP.Q.Coeffs[u])
|
|
}
|
|
|
|
if ringP != nil {
|
|
for u, s := range ringP.SubRings[:levelP+1] {
|
|
s.NTTLazy(cw, cwNTT)
|
|
s.MulCoeffsMontgomeryThenAdd(el.Value[i][j][0].P.Coeffs[u], cwNTT, c0QP.P.Coeffs[u])
|
|
s.MulCoeffsMontgomeryThenAdd(el.Value[i][j][1].P.Coeffs[u], cwNTT, c1QP.P.Coeffs[u])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (eval Evaluator) externalProductInPlaceMultipleP(levelQ, levelP int, ct0 *rlwe.Ciphertext, rgsw *Ciphertext, c0OutQ, c0OutP, c1OutQ, c1OutP ring.Poly) {
|
|
var reduce int
|
|
|
|
params := eval.GetRLWEParameters()
|
|
ringQP := params.RingQP().AtLevel(levelQ, levelP)
|
|
ringQ := ringQP.RingQ
|
|
ringP := ringQP.RingP
|
|
|
|
c2QP := eval.BuffQP[0]
|
|
|
|
c0QP := ringqp.Poly{Q: c0OutQ, P: c0OutP}
|
|
c1QP := ringqp.Poly{Q: c1OutQ, P: c1OutP}
|
|
|
|
BaseRNSDecompositionVectorSize := params.BaseRNSDecompositionVectorSize(levelQ, levelP)
|
|
|
|
QiOverF := params.QiOverflowMargin(levelQ) >> 1
|
|
PiOverF := params.PiOverflowMargin(levelP) >> 1
|
|
|
|
var c2NTT, c2InvNTT ring.Poly
|
|
|
|
for k, el := range rgsw.Value {
|
|
|
|
if ct0.IsNTT {
|
|
c2NTT = ct0.Value[k]
|
|
c2InvNTT = eval.BuffInvNTT
|
|
ringQ.INTT(c2NTT, c2InvNTT)
|
|
} else {
|
|
c2NTT = eval.BuffInvNTT
|
|
c2InvNTT = ct0.Value[k]
|
|
ringQ.NTT(c2InvNTT, c2NTT)
|
|
}
|
|
|
|
// (a, b) + (c0 * rgsw[0][0], c0 * rgsw[0][1])
|
|
for i := 0; i < BaseRNSDecompositionVectorSize; i++ {
|
|
|
|
eval.DecomposeSingleNTT(levelQ, levelP, levelP+1, i, c2NTT, c2InvNTT, c2QP.Q, c2QP.P)
|
|
|
|
if k == 0 && i == 0 {
|
|
ringQP.MulCoeffsMontgomeryLazy(el.Value[i][0][0], c2QP, c0QP)
|
|
ringQP.MulCoeffsMontgomeryLazy(el.Value[i][0][1], c2QP, c1QP)
|
|
} else {
|
|
ringQP.MulCoeffsMontgomeryLazyThenAddLazy(el.Value[i][0][0], c2QP, c0QP)
|
|
ringQP.MulCoeffsMontgomeryLazyThenAddLazy(el.Value[i][0][1], c2QP, c1QP)
|
|
}
|
|
|
|
if reduce%QiOverF == QiOverF-1 {
|
|
ringQ.Reduce(c0QP.Q, c0QP.Q)
|
|
ringQ.Reduce(c1QP.Q, c1QP.Q)
|
|
}
|
|
|
|
if reduce%PiOverF == PiOverF-1 {
|
|
ringP.Reduce(c0QP.P, c0QP.P)
|
|
ringP.Reduce(c1QP.P, c1QP.P)
|
|
}
|
|
|
|
reduce++
|
|
}
|
|
}
|
|
|
|
if reduce%QiOverF != 0 {
|
|
ringQ.Reduce(c0QP.Q, c0QP.Q)
|
|
ringQ.Reduce(c1QP.Q, c1QP.Q)
|
|
}
|
|
|
|
if reduce%PiOverF != 0 {
|
|
ringP.Reduce(c0QP.P, c0QP.P)
|
|
ringP.Reduce(c1QP.P, c1QP.P)
|
|
}
|
|
}
|
|
|
|
// AddLazy adds op to opOut, without modular reduction.
|
|
func AddLazy(op interface{}, ringQP ringqp.Ring, opOut *Ciphertext) {
|
|
switch el := op.(type) {
|
|
case *Plaintext:
|
|
|
|
nQ := ringQP.LevelQ() + 1
|
|
nP := ringQP.LevelP() + 1
|
|
|
|
if nP == 0 {
|
|
nP = 1
|
|
}
|
|
|
|
s := ringQP.RingQ.SubRings[0] // Doesn't matter which one since we add without modular reduction
|
|
|
|
for i := range opOut.Value[0].Value {
|
|
for j := range opOut.Value[0].Value[i] {
|
|
start, end := i*nP, (i+1)*nP
|
|
if end > nQ {
|
|
end = nQ
|
|
}
|
|
for k := start; k < end; k++ {
|
|
s.AddLazy(opOut.Value[0].Value[i][j][0].Q.Coeffs[k], el.Value[j].Coeffs[k], opOut.Value[0].Value[i][j][0].Q.Coeffs[k])
|
|
s.AddLazy(opOut.Value[1].Value[i][j][1].Q.Coeffs[k], el.Value[j].Coeffs[k], opOut.Value[1].Value[i][j][1].Q.Coeffs[k])
|
|
}
|
|
}
|
|
}
|
|
case *Ciphertext:
|
|
for i := range el.Value[0].Value {
|
|
for j := range el.Value[0].Value[i] {
|
|
ringQP.AddLazy(opOut.Value[0].Value[i][j][0], el.Value[0].Value[i][j][0], opOut.Value[0].Value[i][j][0])
|
|
ringQP.AddLazy(opOut.Value[0].Value[i][j][1], el.Value[0].Value[i][j][1], opOut.Value[0].Value[i][j][1])
|
|
ringQP.AddLazy(opOut.Value[1].Value[i][j][0], el.Value[1].Value[i][j][0], opOut.Value[1].Value[i][j][0])
|
|
ringQP.AddLazy(opOut.Value[1].Value[i][j][1], el.Value[1].Value[i][j][1], opOut.Value[1].Value[i][j][1])
|
|
}
|
|
}
|
|
default:
|
|
panic("cannot AddLazy: unsuported op.(type), must be either *rgsw.Plaintext or *rgsw.Ciphertext")
|
|
}
|
|
}
|
|
|
|
// Reduce applies the modular reduction on ctIn and returns the result on opOut.
|
|
func Reduce(ctIn *Ciphertext, ringQP ringqp.Ring, opOut *Ciphertext) {
|
|
for i := range ctIn.Value[0].Value {
|
|
for j := range ctIn.Value[0].Value[i] {
|
|
ringQP.Reduce(ctIn.Value[0].Value[i][j][0], opOut.Value[0].Value[i][j][0])
|
|
ringQP.Reduce(ctIn.Value[0].Value[i][j][1], opOut.Value[0].Value[i][j][1])
|
|
ringQP.Reduce(ctIn.Value[1].Value[i][j][0], opOut.Value[1].Value[i][j][0])
|
|
ringQP.Reduce(ctIn.Value[1].Value[i][j][1], opOut.Value[1].Value[i][j][1])
|
|
}
|
|
}
|
|
}
|
|
|
|
// MulByXPowAlphaMinusOneLazy multiplies opOut by (X^alpha - 1) and returns the result on opOut.
|
|
func MulByXPowAlphaMinusOneLazy(ctIn *Ciphertext, powXMinusOne ringqp.Poly, ringQP ringqp.Ring, opOut *Ciphertext) {
|
|
for i := range ctIn.Value[0].Value {
|
|
for j := range ctIn.Value[0].Value[i] {
|
|
ringQP.MulCoeffsMontgomeryLazy(ctIn.Value[0].Value[i][j][0], powXMinusOne, opOut.Value[0].Value[i][j][0])
|
|
ringQP.MulCoeffsMontgomeryLazy(ctIn.Value[0].Value[i][j][1], powXMinusOne, opOut.Value[0].Value[i][j][1])
|
|
ringQP.MulCoeffsMontgomeryLazy(ctIn.Value[1].Value[i][j][0], powXMinusOne, opOut.Value[1].Value[i][j][0])
|
|
ringQP.MulCoeffsMontgomeryLazy(ctIn.Value[1].Value[i][j][1], powXMinusOne, opOut.Value[1].Value[i][j][1])
|
|
}
|
|
}
|
|
}
|
|
|
|
// MulByXPowAlphaMinusOneThenAddLazy multiplies opOut by (X^alpha - 1) and adds the result on opOut.
|
|
func MulByXPowAlphaMinusOneThenAddLazy(ctIn *Ciphertext, powXMinusOne ringqp.Poly, ringQP ringqp.Ring, opOut *Ciphertext) {
|
|
for i := range ctIn.Value[0].Value {
|
|
for j := range ctIn.Value[0].Value[i] {
|
|
ringQP.MulCoeffsMontgomeryLazyThenAddLazy(ctIn.Value[0].Value[i][j][0], powXMinusOne, opOut.Value[0].Value[i][j][0])
|
|
ringQP.MulCoeffsMontgomeryLazyThenAddLazy(ctIn.Value[0].Value[i][j][1], powXMinusOne, opOut.Value[0].Value[i][j][1])
|
|
ringQP.MulCoeffsMontgomeryLazyThenAddLazy(ctIn.Value[1].Value[i][j][0], powXMinusOne, opOut.Value[1].Value[i][j][0])
|
|
ringQP.MulCoeffsMontgomeryLazyThenAddLazy(ctIn.Value[1].Value[i][j][1], powXMinusOne, opOut.Value[1].Value[i][j][1])
|
|
}
|
|
}
|
|
}
|