Files
zeekay eaf864890e feat: release v7.0.0 with GPU acceleration and module update
- 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
2025-12-30 15:37:37 -08:00

145 lines
5.8 KiB
Go

package ckks
import (
"fmt"
"github.com/luxfi/lattice/v7/core/rlwe"
"github.com/luxfi/lattice/v7/ring"
"github.com/luxfi/lattice/v7/utils"
)
// DomainSwitcher is a type for switching between the standard CKKS domain (which encrypts vectors of complex numbers)
// and the conjugate invariant variant of CKKS (which encrypts vectors of real numbers).
type DomainSwitcher struct {
stdRingQ, conjugateRingQ *ring.Ring
stdToci, ciToStd *rlwe.EvaluationKey
automorphismIndex []uint64
poolQ *ring.BufferPool
}
// NewDomainSwitcher instantiate a new [DomainSwitcher] type. It may be instantiated from parameters from either RingType.
// The method returns an error if the parameters cannot support the switching (e.g., the NTTs are undefined for
// either of the two ring types).
// The comlexToRealEvk and comlexToRealEvk EvaluationKeys can be generated using [rlwe.KeyGenerator.GenEvaluationKeysForRingSwap].
func NewDomainSwitcher(params Parameters, comlexToRealEvk, realToComplexEvk *rlwe.EvaluationKey) (DomainSwitcher, error) {
s := DomainSwitcher{
stdToci: comlexToRealEvk,
ciToStd: realToComplexEvk,
}
var err error
if s.stdRingQ, err = params.RingQ().StandardRing(); err != nil {
return DomainSwitcher{}, fmt.Errorf("cannot NewDomainSwitcher because the standard NTT is undefined for params: %s", err)
}
if s.conjugateRingQ, err = params.RingQ().ConjugateInvariantRing(); err != nil {
return DomainSwitcher{}, fmt.Errorf("cannot NewDomainSwitcher because the standard NTT is undefined for params: %s", err)
}
// Sanity check, this error should not happen unless the
// algorithm has been modified to provide invalid inputs.
if s.automorphismIndex, err = ring.AutomorphismNTTIndex(s.stdRingQ.N(), s.stdRingQ.NthRoot(), s.stdRingQ.NthRoot()-1); err != nil {
panic(err)
}
s.poolQ = ring.NewPool(s.stdRingQ)
return s, nil
}
// ComplexToReal switches the provided ciphertext ctIn from the standard domain to the conjugate
// invariant domain and writes the result into opOut.
// Given ctInCKKS = enc(real(m) + imag(m)) in Z[X](X^N + 1), returns opOutCI = enc(real(m))
// in Z[X+X^-1]/(X^N + 1) in compressed form (N/2 coefficients).
// The scale of the output ciphertext is twice the scale of the input one.
// Requires the ring degree of opOut to be half the ring degree of ctIn.
// The security is changed from Z[X]/(X^N+1) to Z[X]/(X^N/2+1).
// The method will return an error if the DomainSwitcher was not initialized with a the appropriate EvaluationKeys.
func (switcher DomainSwitcher) ComplexToReal(eval *Evaluator, ctIn, opOut *rlwe.Ciphertext) (err error) {
evalRLWE := eval.Evaluator
if evalRLWE.GetRLWEParameters().RingType() != ring.Standard {
return fmt.Errorf("cannot ComplexToReal: provided evaluator is not instantiated with RingType ring.Standard")
}
level := utils.Min(ctIn.Level(), opOut.Level())
if ctIn.Value[0].N() != 2*opOut.Value[0].N() {
return fmt.Errorf("cannot ComplexToReal: ctIn ring degree must be twice opOut ring degree")
}
opOut.Resize(1, level)
if switcher.stdToci == nil {
return fmt.Errorf("cannot ComplexToReal: no realToComplexEvk provided to this DomainSwitcher")
}
poolQ := switcher.poolQ.AtLevel(level)
buffQ1 := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(buffQ1)
buffQ2 := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(buffQ2)
ctTmp := &rlwe.Ciphertext{}
ctTmp.Value = []ring.Poly{*buffQ1, *buffQ2}
ctTmp.MetaData = ctIn.MetaData
evalRLWE.GadgetProduct(level, ctIn.Value[1], &switcher.stdToci.GadgetCiphertext, ctTmp)
switcher.stdRingQ.AtLevel(level).Add(*buffQ1, ctIn.Value[0], *buffQ1)
switcher.conjugateRingQ.AtLevel(level).FoldStandardToConjugateInvariant(*buffQ1, switcher.automorphismIndex, opOut.Value[0])
switcher.conjugateRingQ.AtLevel(level).FoldStandardToConjugateInvariant(*buffQ2, switcher.automorphismIndex, opOut.Value[1])
*opOut.MetaData = *ctIn.MetaData
opOut.Scale = ctIn.Scale.Mul(rlwe.NewScale(2))
return
}
// RealToComplex switches the provided ciphertext ctIn from the conjugate invariant domain to the
// standard domain and writes the result into opOut.
// Given ctInCI = enc(real(m)) in Z[X+X^-1]/(X^2N+1) in compressed form (N coefficients), returns
// opOutCKKS = enc(real(m) + imag(0)) in Z[X]/(X^2N+1).
// Requires the ring degree of opOut to be twice the ring degree of ctIn.
// The security is changed from Z[X]/(X^N+1) to Z[X]/(X^2N+1).
// The method will return an error if the [DomainSwitcher] was not initialized with a the appropriate EvaluationKeys.
func (switcher DomainSwitcher) RealToComplex(eval *Evaluator, ctIn, opOut *rlwe.Ciphertext) (err error) {
evalRLWE := eval.Evaluator
if evalRLWE.GetRLWEParameters().RingType() != ring.Standard {
return fmt.Errorf("cannot RealToComplex: provided evaluator is not instantiated with RingType ring.Standard")
}
level := utils.Min(ctIn.Level(), opOut.Level())
if 2*ctIn.Value[0].N() != opOut.Value[0].N() {
return fmt.Errorf("cannot RealToComplex: opOut ring degree must be twice ctIn ring degree")
}
opOut.Resize(1, level)
if switcher.ciToStd == nil {
return fmt.Errorf("cannot RealToComplex: no realToComplexEvk provided to this DomainSwitcher")
}
stdRingQ := switcher.stdRingQ.AtLevel(level)
stdRingQ.UnfoldConjugateInvariantToStandard(ctIn.Value[0], opOut.Value[0])
stdRingQ.UnfoldConjugateInvariantToStandard(ctIn.Value[1], opOut.Value[1])
poolQ := switcher.poolQ.AtLevel(level)
buffQ1 := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(buffQ1)
buffQ2 := poolQ.GetBuffPoly()
defer poolQ.RecycleBuffPoly(buffQ2)
ctTmp := &rlwe.Ciphertext{}
ctTmp.Value = []ring.Poly{*buffQ1, *buffQ2}
ctTmp.MetaData = ctIn.MetaData
// Switches the RCKswitcher key [X+X^-1] to a CKswitcher key [X]
evalRLWE.GadgetProduct(level, opOut.Value[1], &switcher.ciToStd.GadgetCiphertext, ctTmp)
stdRingQ.Add(opOut.Value[0], *buffQ1, opOut.Value[0])
opOut.Value[1].CopyLvl(level, *buffQ2)
*opOut.MetaData = *ctIn.MetaData
return
}