mirror of
https://github.com/luxfi/lattice.git
synced 2026-09-24 00:25:44 +00:00
277 lines
12 KiB
Go
277 lines
12 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"
|
|
)
|
|
|
|
// KeyGenerator is a structure that stores the elements required to create new keys,
|
|
// as well as a memory buffer for intermediate values.
|
|
type KeyGenerator struct {
|
|
*Encryptor
|
|
}
|
|
|
|
// NewKeyGenerator creates a new KeyGenerator, from which the secret and public keys, as well as EvaluationKeys.
|
|
func NewKeyGenerator(params ParameterProvider) *KeyGenerator {
|
|
return &KeyGenerator{
|
|
Encryptor: NewEncryptor(params, nil),
|
|
}
|
|
}
|
|
|
|
// GenSecretKeyNew generates a new SecretKey.
|
|
// Distribution is set according to `rlwe.Parameters.HammingWeight()`.
|
|
func (kgen KeyGenerator) GenSecretKeyNew() (sk *SecretKey) {
|
|
sk = NewSecretKey(kgen.params)
|
|
kgen.GenSecretKey(sk)
|
|
return
|
|
}
|
|
|
|
// GenSecretKey generates a SecretKey.
|
|
// Distribution is set according to `rlwe.Parameters.HammingWeight()`.
|
|
func (kgen KeyGenerator) GenSecretKey(sk *SecretKey) {
|
|
kgen.genSecretKeyFromSampler(kgen.xsSampler, sk)
|
|
}
|
|
|
|
// GenSecretKeyWithHammingWeightNew generates a new SecretKey with exactly hw non-zero coefficients.
|
|
func (kgen *KeyGenerator) GenSecretKeyWithHammingWeightNew(hw int) (sk *SecretKey) {
|
|
sk = NewSecretKey(kgen.params)
|
|
kgen.GenSecretKeyWithHammingWeight(hw, sk)
|
|
return
|
|
}
|
|
|
|
// GenSecretKeyWithHammingWeight generates a SecretKey with exactly hw non-zero coefficients.
|
|
func (kgen KeyGenerator) GenSecretKeyWithHammingWeight(hw int, sk *SecretKey) {
|
|
Xs, err := ring.NewSampler(kgen.prng, kgen.params.RingQ(), ring.Ternary{H: hw}, false)
|
|
// Sanity check, this error should not happen.
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
kgen.genSecretKeyFromSampler(Xs, sk)
|
|
}
|
|
|
|
func (kgen KeyGenerator) genSecretKeyFromSampler(sampler ring.Sampler, sk *SecretKey) {
|
|
|
|
ringQP := kgen.params.RingQP().AtLevel(sk.LevelQ(), sk.LevelP())
|
|
|
|
sampler.AtLevel(sk.LevelQ()).Read(sk.Value.Q)
|
|
|
|
if levelP := sk.LevelP(); levelP > -1 {
|
|
ringQP.ExtendBasisSmallNormAndCenter(sk.Value.Q, levelP, sk.Value.Q, sk.Value.P)
|
|
}
|
|
|
|
ringQP.NTT(sk.Value, sk.Value)
|
|
ringQP.MForm(sk.Value, sk.Value)
|
|
}
|
|
|
|
// GenPublicKeyNew generates a new public key from the provided SecretKey.
|
|
func (kgen KeyGenerator) GenPublicKeyNew(sk *SecretKey) (pk *PublicKey) {
|
|
pk = NewPublicKey(kgen.params)
|
|
kgen.GenPublicKey(sk, pk)
|
|
return
|
|
}
|
|
|
|
// GenPublicKey generates a public key from the provided SecretKey.
|
|
func (kgen KeyGenerator) GenPublicKey(sk *SecretKey, pk *PublicKey) {
|
|
if err := kgen.WithKey(sk).EncryptZero(Element[ringqp.Poly]{
|
|
MetaData: &MetaData{CiphertextMetaData: CiphertextMetaData{IsNTT: true, IsMontgomery: true}},
|
|
Value: []ringqp.Poly(pk.Value),
|
|
}); err != nil {
|
|
// Sanity check, this error should not happen.
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
// GenKeyPairNew generates a new SecretKey and a corresponding public key.
|
|
// Distribution is of the SecretKey set according to `rlwe.Parameters.HammingWeight()`.
|
|
func (kgen KeyGenerator) GenKeyPairNew() (sk *SecretKey, pk *PublicKey) {
|
|
sk = kgen.GenSecretKeyNew()
|
|
pk = kgen.GenPublicKeyNew(sk)
|
|
return
|
|
}
|
|
|
|
// GenRelinearizationKeyNew generates a new EvaluationKey that will be used to relinearize Ciphertexts during multiplication.
|
|
func (kgen KeyGenerator) GenRelinearizationKeyNew(sk *SecretKey, evkParams ...EvaluationKeyParameters) (rlk *RelinearizationKey) {
|
|
levelQ, levelP, BaseTwoDecomposition := ResolveEvaluationKeyParameters(kgen.params, evkParams)
|
|
rlk = &RelinearizationKey{EvaluationKey: EvaluationKey{GadgetCiphertext: *NewGadgetCiphertext(kgen.params, 1, levelQ, levelP, BaseTwoDecomposition)}}
|
|
kgen.GenRelinearizationKey(sk, rlk)
|
|
return
|
|
}
|
|
|
|
// GenRelinearizationKey generates an EvaluationKey that will be used to relinearize Ciphertexts during multiplication.
|
|
func (kgen KeyGenerator) GenRelinearizationKey(sk *SecretKey, rlk *RelinearizationKey) {
|
|
kgen.buffQP.Q.CopyLvl(rlk.LevelQ(), sk.Value.Q)
|
|
kgen.params.RingQ().AtLevel(rlk.LevelQ()).MulCoeffsMontgomery(kgen.buffQP.Q, sk.Value.Q, kgen.buffQP.Q)
|
|
kgen.genEvaluationKey(kgen.buffQP.Q, sk.Value, &rlk.EvaluationKey)
|
|
}
|
|
|
|
// GenGaloisKeyNew generates a new GaloisKey, enabling the automorphism X^{i} -> X^{i * galEl}.
|
|
func (kgen KeyGenerator) GenGaloisKeyNew(galEl uint64, sk *SecretKey, evkParams ...EvaluationKeyParameters) (gk *GaloisKey) {
|
|
levelQ, levelP, BaseTwoDecomposition := ResolveEvaluationKeyParameters(kgen.params, evkParams)
|
|
gk = &GaloisKey{
|
|
EvaluationKey: EvaluationKey{GadgetCiphertext: *NewGadgetCiphertext(kgen.params, 1, levelQ, levelP, BaseTwoDecomposition)},
|
|
NthRoot: kgen.params.GetRLWEParameters().RingQ().NthRoot(),
|
|
}
|
|
kgen.GenGaloisKey(galEl, sk, gk)
|
|
return
|
|
}
|
|
|
|
// GenGaloisKey generates a GaloisKey, enabling the automorphism X^{i} -> X^{i * galEl}.
|
|
func (kgen KeyGenerator) GenGaloisKey(galEl uint64, sk *SecretKey, gk *GaloisKey) {
|
|
|
|
skIn := sk.Value
|
|
skOut := kgen.buffQP
|
|
|
|
ringQP := kgen.params.RingQP().AtLevel(gk.LevelQ(), gk.LevelP())
|
|
|
|
ringQ := ringQP.RingQ
|
|
ringP := ringQP.RingP
|
|
|
|
// We encrypt [-a * pi_{k^-1}(sk) + sk, a]
|
|
// This enables to first apply the gadget product, re-encrypting
|
|
// a ciphetext from sk to pi_{k^-1}(sk) and then we apply pi_{k}
|
|
// on the ciphertext.
|
|
galElInv := kgen.params.ModInvGaloisElement(galEl)
|
|
|
|
index, err := ring.AutomorphismNTTIndex(ringQ.N(), ringQ.NthRoot(), galElInv)
|
|
|
|
// Sanity check, this error should not happen unless the
|
|
// evaluator's buffer thave been improperly tempered with.
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
ringQ.AutomorphismNTTWithIndex(skIn.Q, index, skOut.Q)
|
|
|
|
if ringP != nil {
|
|
ringP.AutomorphismNTTWithIndex(skIn.P, index, skOut.P)
|
|
}
|
|
|
|
kgen.genEvaluationKey(skIn.Q, skOut, &gk.EvaluationKey)
|
|
|
|
gk.GaloisElement = galEl
|
|
gk.NthRoot = ringQ.NthRoot()
|
|
}
|
|
|
|
// GenGaloisKeys generates the GaloisKey objects for all galois elements in galEls, and stores
|
|
// the resulting key for galois element i in gks[i].
|
|
// The galEls and gks parameters must have the same length.
|
|
func (kgen KeyGenerator) GenGaloisKeys(galEls []uint64, sk *SecretKey, gks []*GaloisKey) {
|
|
|
|
// Sanity check
|
|
if len(galEls) != len(gks) {
|
|
panic(fmt.Errorf("galEls and gks must have the same length"))
|
|
}
|
|
|
|
for i, galEl := range galEls {
|
|
if gks[i] == nil {
|
|
gks[i] = kgen.GenGaloisKeyNew(galEl, sk)
|
|
} else {
|
|
kgen.GenGaloisKey(galEl, sk, gks[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
// GenGaloisKeysNew generates the GaloisKey objects for all galois elements in galEls, and
|
|
// returns the resulting keys in a newly allocated []*GaloisKey.
|
|
func (kgen KeyGenerator) GenGaloisKeysNew(galEls []uint64, sk *SecretKey, evkParams ...EvaluationKeyParameters) (gks []*GaloisKey) {
|
|
levelQ, levelP, BaseTwoDecomposition := ResolveEvaluationKeyParameters(kgen.params, evkParams)
|
|
gks = make([]*GaloisKey, len(galEls))
|
|
for i, galEl := range galEls {
|
|
gks[i] = newGaloisKey(kgen.params, levelQ, levelP, BaseTwoDecomposition)
|
|
kgen.GenGaloisKey(galEl, sk, gks[i])
|
|
}
|
|
return
|
|
}
|
|
|
|
// GenEvaluationKeysForRingSwapNew generates the necessary EvaluationKeys to switch from a standard ring to to a conjugate invariant ring and vice-versa.
|
|
func (kgen KeyGenerator) GenEvaluationKeysForRingSwapNew(skStd, skConjugateInvariant *SecretKey, evkParams ...EvaluationKeyParameters) (stdToci, ciToStd *EvaluationKey) {
|
|
|
|
levelQ := utils.Min(skStd.Value.Q.Level(), skConjugateInvariant.Value.Q.Level())
|
|
|
|
skCIMappedToStandard := &SecretKey{Value: kgen.params.RingQP().AtLevel(levelQ, kgen.params.MaxLevelP()).NewPoly()}
|
|
kgen.params.RingQ().AtLevel(levelQ).UnfoldConjugateInvariantToStandard(skConjugateInvariant.Value.Q, skCIMappedToStandard.Value.Q)
|
|
|
|
if kgen.params.PCount() != 0 {
|
|
ExtendBasisSmallNormAndCenterNTTMontgomery(kgen.params.RingQ(), kgen.params.RingP(), skCIMappedToStandard.Value.Q, kgen.buffQ[1], skCIMappedToStandard.Value.P)
|
|
}
|
|
|
|
levelQ, levelP, BaseTwoDecomposition := ResolveEvaluationKeyParameters(kgen.params, evkParams)
|
|
|
|
stdToci = newEvaluationKey(kgen.params, levelQ, levelP, BaseTwoDecomposition)
|
|
kgen.GenEvaluationKey(skStd, skCIMappedToStandard, stdToci)
|
|
|
|
ciToStd = newEvaluationKey(kgen.params, levelQ, levelP, BaseTwoDecomposition)
|
|
kgen.GenEvaluationKey(skCIMappedToStandard, skStd, ciToStd)
|
|
|
|
return
|
|
}
|
|
|
|
// GenEvaluationKeyNew generates a new EvaluationKey, that will re-encrypt a Ciphertext encrypted under the input key into the output key.
|
|
// If the ringDegree(skOutput) > ringDegree(skInput), generates [-a*SkOut + w*P*skIn_{Y^{N/n}} + e, a] in X^{N}.
|
|
// If the ringDegree(skOutput) < ringDegree(skInput), generates [-a*skOut_{Y^{N/n}} + w*P*skIn + e_{N}, a_{N}] in X^{N}.
|
|
// Else generates [-a*skOut + w*P*skIn + e, a] in X^{N}.
|
|
// The output EvaluationKey is always given in max(N, n) and in the moduli of the output EvaluationKey.
|
|
// When re-encrypting a Ciphertext from Y^{N/n} to X^{N}, the Ciphertext must first be mapped to X^{N}
|
|
// using SwitchCiphertextRingDegreeNTT(ctSmallDim, nil, ctLargeDim).
|
|
// When re-encrypting a Ciphertext from X^{N} to Y^{N/n}, the output of the re-encryption is in still X^{N} and
|
|
// must be mapped Y^{N/n} using SwitchCiphertextRingDegreeNTT(ctLargeDim, ringQLargeDim, ctSmallDim).
|
|
func (kgen KeyGenerator) GenEvaluationKeyNew(skInput, skOutput *SecretKey, evkParams ...EvaluationKeyParameters) (evk *EvaluationKey) {
|
|
levelQ, levelP, BaseTwoDecomposition := ResolveEvaluationKeyParameters(kgen.params, evkParams)
|
|
evk = newEvaluationKey(kgen.params, levelQ, levelP, BaseTwoDecomposition)
|
|
kgen.GenEvaluationKey(skInput, skOutput, evk)
|
|
return
|
|
}
|
|
|
|
// GenEvaluationKey generates an EvaluationKey, that will re-encrypt a Ciphertext encrypted under the input key into the output key.
|
|
// If the ringDegree(skOutput) > ringDegree(skInput), generates [-a*SkOut + w*P*skIn_{Y^{N/n}} + e, a] in X^{N}.
|
|
// If the ringDegree(skOutput) < ringDegree(skInput), generates [-a*skOut_{Y^{N/n}} + w*P*skIn + e_{N}, a_{N}] in X^{N}.
|
|
// Else generates [-a*skOut + w*P*skIn + e, a] in X^{N}.
|
|
// The output EvaluationKey is always given in max(N, n) and in the moduli of the output EvaluationKey.
|
|
// When re-encrypting a Ciphertext from Y^{N/n} to X^{N}, the Ciphertext must first be mapped to X^{N}
|
|
// using SwitchCiphertextRingDegreeNTT(ctSmallDim, nil, ctLargeDim).
|
|
// When re-encrypting a Ciphertext from X^{N} to Y^{N/n}, the output of the re-encryption is in still X^{N} and
|
|
// must be mapped Y^{N/n} using SwitchCiphertextRingDegreeNTT(ctLargeDim, ringQLargeDim, ctSmallDim).
|
|
func (kgen KeyGenerator) GenEvaluationKey(skInput, skOutput *SecretKey, evk *EvaluationKey) {
|
|
|
|
ringQ := kgen.params.RingQ()
|
|
ringP := kgen.params.RingP()
|
|
|
|
// Maps the smaller key to the largest with Y = X^{N/n}.
|
|
ring.MapSmallDimensionToLargerDimensionNTT(skOutput.Value.Q, kgen.buffQP.Q)
|
|
|
|
// Extends the modulus P of skOutput to the one of skInput
|
|
if levelP := evk.LevelP(); levelP != -1 {
|
|
ExtendBasisSmallNormAndCenterNTTMontgomery(ringQ, ringP.AtLevel(levelP), kgen.buffQP.Q, kgen.buffQ[0], kgen.buffQP.P)
|
|
}
|
|
|
|
// Maps the smaller key to the largest dimension with Y = X^{N/n}.
|
|
ring.MapSmallDimensionToLargerDimensionNTT(skInput.Value.Q, kgen.buffQ[0])
|
|
ExtendBasisSmallNormAndCenterNTTMontgomery(ringQ, ringQ.AtLevel(skOutput.Value.Q.Level()), kgen.buffQ[0], kgen.buffQ[1], kgen.buffQ[0])
|
|
|
|
kgen.genEvaluationKey(kgen.buffQ[0], kgen.buffQP, evk)
|
|
}
|
|
|
|
func (kgen KeyGenerator) genEvaluationKey(skIn ring.Poly, skOut ringqp.Poly, evk *EvaluationKey) {
|
|
|
|
enc := kgen.WithKey(&SecretKey{Value: skOut})
|
|
|
|
// Samples an encryption of zero for each element of the EvaluationKey.
|
|
for i := 0; i < len(evk.Value); i++ {
|
|
for j := 0; j < len(evk.Value[i]); j++ {
|
|
if err := enc.EncryptZero(Element[ringqp.Poly]{MetaData: &MetaData{CiphertextMetaData: CiphertextMetaData{IsNTT: true, IsMontgomery: true}}, Value: []ringqp.Poly(evk.Value[i][j])}); err != nil {
|
|
// Sanity check, this error should not happen.
|
|
panic(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Adds the plaintext (input-key) to the EvaluationKey.
|
|
if err := AddPolyTimesGadgetVectorToGadgetCiphertext(skIn, []GadgetCiphertext{evk.GadgetCiphertext}, *kgen.params.RingQP(), kgen.buffQ[0]); err != nil {
|
|
// Sanity check, this error should not happen.
|
|
panic(err)
|
|
}
|
|
}
|