repackage blind rotations

This commit is contained in:
Andrea Caforio
2024-07-04 13:54:42 +02:00
parent d331bb4d78
commit bf44a70609
9 changed files with 764 additions and 24 deletions

View File

@@ -0,0 +1,3 @@
## References
1. Efficient FHEW Bootstrapping with Small Evaluation Keys, and Applications to Threshold Homomorphic Encryption (<https://eprint.iacr.org/2022/198>)

View File

@@ -0,0 +1,38 @@
package blind_rotation
import (
"github.com/tuneinsight/lattigo/v5/core/rlwe"
"github.com/tuneinsight/lattigo/v5/ring"
)
// InitTestPolynomial takes a function g, and creates a test polynomial polynomial for the function in the interval [a, b].
// Inputs to the blind rotation evaluation are assumed to have been normalized with the change of basis (2*x - a - b)/(b-a).
// Interval [a, b] should take into account the "drift" of the value x, caused by the change of modulus from Q to 2N.
func InitTestPolynomial(g func(x float64) (y float64), scale rlwe.Scale, ringQ *ring.Ring, a, b float64) (F ring.Poly) {
F = ringQ.NewPoly()
Q := ringQ.ModuliChain()[:ringQ.Level()+1]
sf64 := scale.Float64()
N := ringQ.N()
// Discretization interval
interval := 2.0 / float64(N)
for j, qi := range Q {
// Interval [-1, 0] of g(x)
for i := 0; i < (N>>1)+1; i++ {
F.Coeffs[j][i] = scaleUp(g(normalizeInv(-interval*float64(i), a, b)), sf64, qi)
}
// Interval ]0, 1[ of g(x)
for i := (N >> 1) + 1; i < N; i++ {
F.Coeffs[j][i] = scaleUp(-g(normalizeInv(interval*float64(N-i), a, b)), sf64, qi)
}
}
ringQ.NTT(F, F)
return
}

View File

@@ -0,0 +1,71 @@
package blind_rotation
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/tuneinsight/lattigo/v5/core/rlwe"
"github.com/tuneinsight/lattigo/v5/utils"
"github.com/tuneinsight/lattigo/v5/utils/sampling"
)
func BenchmarkHEBin(b *testing.B) {
b.Run("BlindRotateCore/LogN=(9, 10)/LogQ=(13.6,26.99)/Gadget=2^7", func(b *testing.B) {
// RLWE parameters of the BlindRotation
// N=1024, Q=0x7fff801 -> 131 bit secure
paramsBR, err := rlwe.NewParametersFromLiteral(rlwe.ParametersLiteral{
LogN: 10,
Q: []uint64{0x7fff801},
NTTFlag: NTTFlag,
})
require.NoError(b, err)
// RLWE parameters of the samples
// N=512, Q=0x3001 -> 135 bit secure
paramsLWE, err := rlwe.NewParametersFromLiteral(rlwe.ParametersLiteral{
LogN: 9,
Q: []uint64{0x3001},
NTTFlag: NTTFlag,
})
require.NoError(b, err)
evkParams := rlwe.EvaluationKeyParameters{BaseTwoDecomposition: utils.Pointy(7)}
// RLWE secret for the samples
skLWE := rlwe.NewKeyGenerator(paramsLWE).GenSecretKeyNew()
// Secret of the RGSW ciphertexts encrypting the bits of skLWE
skBR := rlwe.NewKeyGenerator(paramsBR).GenSecretKeyNew()
// Collection of RGSW ciphertexts encrypting the bits of skLWE under skBR
BRK := GenEvaluationKeyNew(paramsBR, skBR, paramsLWE, skLWE, evkParams)
// Random LWE mask mod 2N with odd coefficients
a := make([]uint64, paramsLWE.N())
mask := uint64(2*paramsLWE.N() - 1)
for i := range a {
ai := sampling.RandUint64() & mask
if ai&1 == 0 && ai != 0 {
ai ^= 1
}
a[i] = ai
}
acc := rlwe.NewCiphertext(paramsBR, 1, paramsBR.MaxLevel())
// Evaluator for the Blind Rotation evaluation
eval := NewEvaluator(paramsBR, paramsLWE)
b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := eval.BlindRotateCore(a, acc, BRK); err != nil {
panic(err)
}
}
})
}

View File

@@ -0,0 +1,168 @@
package blind_rotation
import (
"fmt"
"math"
"runtime"
"testing"
"github.com/stretchr/testify/require"
"github.com/tuneinsight/lattigo/v5/core/rlwe"
"github.com/tuneinsight/lattigo/v5/ring"
"github.com/tuneinsight/lattigo/v5/utils"
)
func testString(params rlwe.Parameters, opname string) string {
return fmt.Sprintf("%slogN=%d/logQ=%f/logP=%f/#Qi=%d/#Pi=%d",
opname,
params.LogN(),
params.LogQ(),
params.LogP(),
params.QCount(),
params.PCount())
}
// TestBlindRotation tests the BlindRotation evaluation.
func TestBlindRotation(t *testing.T) {
for _, testSet := range []func(t *testing.T){
testBlindRotation,
} {
testSet(t)
runtime.GC()
}
}
// Function to evaluate
func sign(x float64) float64 {
if x > 0 {
return 1
} else if x == 0 {
return 0
}
return -1
}
var NTTFlag = true
func testBlindRotation(t *testing.T) {
var err error
// RLWE parameters of the BlindRotation
// N=1024, Q=0x7fff801 -> 131 bit secure
paramsBR, err := rlwe.NewParametersFromLiteral(rlwe.ParametersLiteral{
LogN: 10,
Q: []uint64{0x7fff801},
NTTFlag: NTTFlag,
})
require.NoError(t, err)
// RLWE parameters of the samples
// N=512, Q=0x3001 -> 135 bit secure
paramsLWE, err := rlwe.NewParametersFromLiteral(rlwe.ParametersLiteral{
LogN: 9,
Q: []uint64{0x3001},
NTTFlag: NTTFlag,
})
require.NoError(t, err)
evkParams := rlwe.EvaluationKeyParameters{BaseTwoDecomposition: utils.Pointy(7)}
require.NoError(t, err)
t.Run(testString(paramsBR, "BlindRotation/"), func(t *testing.T) {
// Scale of the RLWE samples
scaleLWE := float64(paramsLWE.Q()[0]) / 4.0
// Scale of the test poly
scaleBR := float64(paramsBR.Q()[0]) / 4.0
// Number of values samples stored in the RLWE sample
slots := 16
// Test poly
testPoly := InitTestPolynomial(sign, rlwe.NewScale(scaleBR), paramsBR.RingQ(), -1, 1)
// Index map of which test poly to evaluate on which slot
testPolyMap := make(map[int]*ring.Poly)
for i := 0; i < slots; i++ {
testPolyMap[i] = &testPoly
}
// RLWE secret for the samples
skLWE := rlwe.NewKeyGenerator(paramsLWE).GenSecretKeyNew()
// RLWE encryptor for the samples
encryptorLWE := rlwe.NewEncryptor(paramsLWE, skLWE)
// Values to encrypt in the RLWE sample
values := make([]float64, slots)
for i := 0; i < slots; i++ {
values[i] = -1 + float64(2*i)/float64(slots)
}
// Encode multiples values in a single RLWE
ptLWE := rlwe.NewPlaintext(paramsLWE, paramsLWE.MaxLevel())
for i := range values {
if values[i] < 0 {
ptLWE.Value.Coeffs[0][i] = paramsLWE.Q()[0] - uint64(-values[i]*scaleLWE)
} else {
ptLWE.Value.Coeffs[0][i] = uint64(values[i] * scaleLWE)
}
}
if ptLWE.IsNTT {
paramsLWE.RingQ().NTT(ptLWE.Value, ptLWE.Value)
}
// Encrypt the multiples values in a single RLWE
ctLWE := rlwe.NewCiphertext(paramsLWE, 1, paramsLWE.MaxLevel())
encryptorLWE.Encrypt(ptLWE, ctLWE)
// Evaluator for the Blind Rotation evaluation
eval := NewEvaluator(paramsBR, paramsLWE)
// Secret of the RGSW ciphertexts encrypting the bits of skLWE
skBR := rlwe.NewKeyGenerator(paramsBR).GenSecretKeyNew()
// Collection of RGSW ciphertexts encrypting the bits of skLWE under skBR
BRK := GenEvaluationKeyNew(paramsBR, skBR, paramsLWE, skLWE, evkParams)
// Evaluation of BlindRotation(ctLWE)
// Returns one RLWE sample per slot in ctLWE
ctsBR, err := eval.Evaluate(ctLWE, testPolyMap, BRK)
require.NoError(t, err)
// Decrypts, decodes and compares
q := paramsBR.Q()[0]
qHalf := q >> 1
decryptorBR := rlwe.NewDecryptor(paramsBR, skBR)
ptBR := rlwe.NewPlaintext(paramsBR, paramsBR.MaxLevel())
for i := 0; i < slots; i++ {
decryptorBR.Decrypt(ctsBR[i], ptBR)
if ptBR.IsNTT {
paramsBR.RingQ().INTT(ptBR.Value, ptBR.Value)
}
c := ptBR.Value.Coeffs[0][0]
var a float64
if c >= qHalf {
a = -float64(q-c) / scaleBR
} else {
a = float64(c) / scaleBR
}
if values[i] != 0 {
fmt.Printf("%7.4f - %7.4f - %7.4f\n", math.Round(a*32)/32, math.Round(a*8)/8, values[i])
//require.Equal(t, sign(values[i]), math.Round(a*8)/8)
}
}
})
}

View File

@@ -0,0 +1,298 @@
package blind_rotation
import (
"fmt"
"math/big"
"github.com/tuneinsight/lattigo/v5/core/rgsw"
"github.com/tuneinsight/lattigo/v5/core/rlwe"
"github.com/tuneinsight/lattigo/v5/ring"
"github.com/tuneinsight/lattigo/v5/utils/bignum"
)
// Evaluator is a struct that stores the necessary
// data to handle LWE <-> RLWE conversion and
// blind rotations.
type Evaluator struct {
*rgsw.Evaluator
paramsBR rlwe.Parameters
paramsLWE rlwe.Parameters
poolMod2N [2]ring.Poly
accumulator *rlwe.Ciphertext
galoisGenDiscreteLog map[uint64]int
}
// NewEvaluator instantiates a new [Evaluator].
func NewEvaluator(paramsBR, paramsLWE rlwe.ParameterProvider) (eval *Evaluator) {
eval = new(Evaluator)
eval.Evaluator = rgsw.NewEvaluator(paramsBR, nil)
eval.paramsBR = *paramsBR.GetRLWEParameters()
eval.paramsLWE = *paramsLWE.GetRLWEParameters()
eval.poolMod2N = [2]ring.Poly{eval.paramsLWE.RingQ().NewPoly(), eval.paramsLWE.RingQ().NewPoly()}
eval.accumulator = rlwe.NewCiphertext(paramsBR, 1, eval.paramsBR.MaxLevel())
eval.accumulator.IsNTT = true // This flag is always true
// Generates a map for the discrete log of (+/- 1) * GaloisGen^k for 0 <= k < N-1.
// galoisGenDiscreteLog: map[+/-G^{k} mod 2N] = k
eval.galoisGenDiscreteLog = getGaloisElementInverseMap(ring.GaloisGen, eval.paramsBR.N())
return
}
// Evaluate extracts on the fly LWE samples and evaluates the provided blind rotation on the LWE.
// testPolyWithSlotIndex : a map with [slot_index] -> blind rotation
// Returns a map[slot_index] -> BlindRotate(ct[slot_index])
func (eval *Evaluator) Evaluate(ct *rlwe.Ciphertext, testPolyWithSlotIndex map[int]*ring.Poly, BRK BlindRotationEvaluationKeySet) (res map[int]*rlwe.Ciphertext, err error) {
bRLWEMod2N := eval.poolMod2N[0]
aRLWEMod2N := eval.poolMod2N[1]
acc := eval.accumulator
brk, err := BRK.GetBlindRotationKey(0)
if err != nil {
return nil, err
}
ringQBR := eval.paramsBR.RingQ().AtLevel(brk.LevelQ())
ringQLWE := eval.paramsLWE.RingQ().AtLevel(ct.Level())
if ct.IsNTT {
ringQLWE.INTT(ct.Value[0], acc.Value[0])
ringQLWE.INTT(ct.Value[1], acc.Value[1])
} else {
acc.Value[0].CopyLvl(ct.Level(), ct.Value[0])
acc.Value[1].CopyLvl(ct.Level(), ct.Value[1])
}
// Switch modulus from Q to 2N and ensure they are odd
eval.modSwitchRLWETo2NLvl(ct.Level(), acc.Value[1], acc.Value[1], true)
// Conversion from Convolution(a, sk) to DotProd(a, sk) for LWE decryption.
// Copy coefficients multiplied by X^{N-1} in reverse order:
// a_{0} -a_{N-1} -a2_{N-2} ... -a_{1}
tmp0 := aRLWEMod2N.Coeffs[0]
tmp1 := acc.Value[1].Coeffs[0]
tmp0[0] = tmp1[0]
NLWE := ringQLWE.N()
mask := uint64(ringQBR.N()<<1) - 1
for j := 1; j < NLWE; j++ {
tmp0[j] = -tmp1[ringQLWE.N()-j] & mask
}
// Switch modulus from Q to 2N
eval.modSwitchRLWETo2NLvl(ct.Level(), acc.Value[0], bRLWEMod2N, false)
res = make(map[int]*rlwe.Ciphertext)
var prevIndex int
for index := 0; index < NLWE; index++ {
if testPoly, ok := testPolyWithSlotIndex[index]; ok {
mulBySmallMonomialMod2N(mask, aRLWEMod2N, index-prevIndex)
prevIndex = index
a := aRLWEMod2N.Coeffs[0]
b := bRLWEMod2N.Coeffs[0][index]
// Line 2 of Algorithm 7 of https://eprint.iacr.org/2022/198
// Acc = (f(X^{-g}) * X^{-g * b}, 0)
Xb := ringQBR.NewMonomialXi(int(b))
ringQBR.NTT(Xb, Xb)
ringQBR.MForm(Xb, Xb)
ringQBR.MulCoeffsMontgomery(*testPoly, Xb, acc.Value[1]) // use unused buffer because AutomorphismNTT is not in place
ringQBR.AutomorphismNTT(acc.Value[1], ringQBR.NthRoot()-ring.GaloisGen, acc.Value[0])
acc.Value[1].Zero()
// Line 3 of Algorithm 7 https://eprint.iacr.org/2022/198 (Algorithm 3 of https://eprint.iacr.org/2022/198)
if err = eval.BlindRotateCore(a, acc, BRK); err != nil {
return nil, fmt.Errorf("BlindRotateCore: %s", err)
}
// f(X) * X^{b + <a, s>}
res[index] = acc.CopyNew()
if !eval.paramsBR.NTTFlag() {
ringQBR.INTT(res[index].Value[0], res[index].Value[0])
ringQBR.INTT(res[index].Value[1], res[index].Value[1])
res[index].IsNTT = false
}
}
}
return
}
// BlindRotateCore implements Algorithm 3 of https://eprint.iacr.org/2022/198
func (eval *Evaluator) BlindRotateCore(a []uint64, acc *rlwe.Ciphertext, BRK BlindRotationEvaluationKeySet) (err error) {
evk, err := BRK.GetEvaluationKeySet()
if err != nil {
return err
}
eval.Evaluator = eval.Evaluator.WithKey(evk)
// GaloisElement(k) = GaloisGen^{k} mod 2N
GaloisElement := eval.paramsBR.GaloisElement
// Maps a[i] to (+/-) g^{k} mod 2N
discreteLogSets := eval.getDiscreteLogSets(a)
Nhalf := eval.paramsBR.N() >> 1
// Algorithm 3 of https://eprint.iacr.org/2022/198
var v int
// Lines 3 to 9 (negative set of a[i] = -g^{k} mod 2N)
for i := Nhalf - 1; i > 0; i-- {
if v, err = eval.evaluateFromDiscreteLogSets(GaloisElement, discreteLogSets, -i, v, acc, BRK); err != nil {
return
}
}
// Line 10 (0 in the negative set is 2N)
if _, err = eval.evaluateFromDiscreteLogSets(GaloisElement, discreteLogSets, eval.paramsBR.N()<<1, 0, acc, BRK); err != nil {
return
}
// Line 12
// acc = acc(X^{-g})
if err = eval.Automorphism(acc, eval.paramsBR.RingQ().NthRoot()-ring.GaloisGen, acc); err != nil {
return
}
// Lines 13 - 19 (positive set of a[i] = g^{k} mod 2N)
for i := Nhalf - 1; i > 0; i-- {
if v, err = eval.evaluateFromDiscreteLogSets(GaloisElement, discreteLogSets, i, v, acc, BRK); err != nil {
return
}
}
// Lines 20 - 21 (0 in the positive set is 0)
if _, err = eval.evaluateFromDiscreteLogSets(GaloisElement, discreteLogSets, 0, 0, acc, BRK); err != nil {
return
}
return
}
// evaluateFromDiscreteLogSets loops of Algorithm 3 of https://eprint.iacr.org/2022/198
func (eval *Evaluator) evaluateFromDiscreteLogSets(GaloisElement func(k int) (galEl uint64), sets map[int][]int, k, v int, acc *rlwe.Ciphertext, BRK BlindRotationEvaluationKeySet) (int, error) {
// Checks if k is in the discrete log sets
if set, ok := sets[k]; ok {
// First condition of line 7 or 17
if v != 0 {
if err := eval.Automorphism(acc, GaloisElement(v), acc); err != nil {
return v, err
}
v = 0
}
for _, j := range set {
brk, err := BRK.GetBlindRotationKey(j)
if err != nil {
return v, err
}
// acc = acc * RGSW(X^{s[j]})
eval.ExternalProduct(acc, brk, acc)
}
}
v++
// Second and third conditions of line 7 or 17
if v == windowSize || k == 1 {
if err := eval.Automorphism(acc, GaloisElement(v), acc); err != nil {
return v, err
}
v = 0
}
return v, nil
}
// getGaloisElementInverseMap generates a map [(+/-) g^{k} mod 2N] = +/- k
func getGaloisElementInverseMap(GaloisGen uint64, N int) (GaloisGenDiscreteLog map[uint64]int) {
twoN := N << 1
NHalf := N >> 1
mask := uint64(twoN - 1)
GaloisGenDiscreteLog = map[uint64]int{}
var pow uint64 = 1
for i := 0; i < NHalf; i++ {
GaloisGenDiscreteLog[pow] = i
GaloisGenDiscreteLog[uint64(twoN)-pow] = -i
pow *= GaloisGen
pow &= mask
}
return
}
// getDiscreteLogSets returns map[+/-k] = [i...] for a[0 <= i < N] = {(+/-) g^{k} mod 2N for +/- k}
func (eval *Evaluator) getDiscreteLogSets(a []uint64) (discreteLogSets map[int][]int) {
GaloisGenDiscreteLog := eval.galoisGenDiscreteLog
// Maps (2*N*a[i]/QLWE) to -N/2 < k <= N/2 for a[i] = (+/- 1) * g^{k}
discreteLogSets = map[int][]int{}
for i, ai := range a {
if ai&1 != 1 && ai != 0 {
panic("getDiscreteLogSets: a[i] is not odd and thus not an element of Z_{2N}^{*} -> a[i] = (+/- 1) * g^{k} does not exist.")
}
dlog := GaloisGenDiscreteLog[ai]
if _, ok := discreteLogSets[dlog]; !ok {
discreteLogSets[dlog] = []int{i}
} else {
discreteLogSets[dlog] = append(discreteLogSets[dlog], i)
}
}
return
}
// modSwitchRLWETo2NLvl applies round(x * 2N / Q) to the coefficients of polQ and returns the result on pol2N.
// makeOdd ensures that output coefficients are odd by xoring with 1 (if not already zero).
func (eval *Evaluator) modSwitchRLWETo2NLvl(level int, polQ, pol2N ring.Poly, makeOdd bool) {
coeffsBigint := make([]*big.Int, len(polQ.Coeffs[0]))
ringQ := eval.paramsLWE.RingQ().AtLevel(level)
ringQ.PolyToBigint(polQ, 1, coeffsBigint)
QBig := ringQ.ModulusAtLevel[level]
twoN := uint64(eval.paramsBR.N() << 1)
twoNBig := bignum.NewInt(twoN)
tmp := pol2N.Coeffs[0]
N := ringQ.N()
for i := 0; i < N; i++ {
coeffsBigint[i].Mul(coeffsBigint[i], twoNBig)
bignum.DivRound(coeffsBigint[i], QBig, coeffsBigint[i])
tmp[i] = coeffsBigint[i].Uint64() & (twoN - 1)
if makeOdd && tmp[i]&1 == 0 && tmp[i] != 0 {
tmp[i] ^= 1
}
}
}

View File

@@ -0,0 +1,108 @@
package blind_rotation
import (
"math/big"
"github.com/tuneinsight/lattigo/v5/core/rgsw"
"github.com/tuneinsight/lattigo/v5/core/rlwe"
"github.com/tuneinsight/lattigo/v5/ring"
"github.com/tuneinsight/lattigo/v5/utils"
)
const (
// Parameter w of Algorithm 3 in https://eprint.iacr.org/2022/198
windowSize = 10
)
// BlindRotationEvaluationKeySet is a interface implementing methods
// to load the blind rotation keys (RGSW) and automorphism keys
// (via the rlwe.EvaluationKeySet interface).
// Implementation of this interface must be safe for concurrent use.
type BlindRotationEvaluationKeySet interface {
// GetBlindRotationKey should return RGSW(X^{s[i]})
GetBlindRotationKey(i int) (brk *rgsw.Ciphertext, err error)
// GetEvaluationKeySet should return an rlwe.EvaluationKeySet
// providing access to all the required automorphism keys.
GetEvaluationKeySet() (evk rlwe.EvaluationKeySet, err error)
}
// MemBlindRotationEvaluationKeySet is a basic in-memory implementation of the BlindRotationEvaluationKeySet interface.
type MemBlindRotationEvaluationKeySet struct {
BlindRotationKeys []*rgsw.Ciphertext
AutomorphismKeys []*rlwe.GaloisKey
}
func (evk MemBlindRotationEvaluationKeySet) GetBlindRotationKey(i int) (*rgsw.Ciphertext, error) {
return evk.BlindRotationKeys[i], nil
}
func (evk MemBlindRotationEvaluationKeySet) GetEvaluationKeySet() (rlwe.EvaluationKeySet, error) {
return rlwe.NewMemEvaluationKeySet(nil, evk.AutomorphismKeys...), nil
}
// GenEvaluationKeyNew generates a new Blind Rotation evaluation key
func GenEvaluationKeyNew(paramsRLWE rlwe.ParameterProvider, skRLWE *rlwe.SecretKey, paramsLWE rlwe.ParameterProvider, skLWE *rlwe.SecretKey, evkParams ...rlwe.EvaluationKeyParameters) (key MemBlindRotationEvaluationKeySet) {
pRLWE := *paramsRLWE.GetRLWEParameters()
pLWE := *paramsLWE.GetRLWEParameters()
skLWECopy := skLWE.CopyNew()
pLWE.RingQ().AtLevel(0).INTT(skLWECopy.Value.Q, skLWECopy.Value.Q)
pLWE.RingQ().AtLevel(0).IMForm(skLWECopy.Value.Q, skLWECopy.Value.Q)
sk := make([]*big.Int, pLWE.N())
for i := range sk {
sk[i] = new(big.Int)
}
pLWE.RingQ().AtLevel(0).PolyToBigintCentered(skLWECopy.Value.Q, 1, sk)
encryptor := rgsw.NewEncryptor(pRLWE, skRLWE)
levelQ, levelP, BaseTwoDecomposition := rlwe.ResolveEvaluationKeyParameters(pRLWE, evkParams)
skiRGSW := make([]*rgsw.Ciphertext, pLWE.N())
ptXi := make(map[int]*rlwe.Plaintext)
for i, si := range sk {
siInt := int(si.Int64())
if _, ok := ptXi[siInt]; !ok {
pt := &rlwe.Plaintext{}
pt.MetaData = &rlwe.MetaData{}
pt.IsNTT = true
pt.Value = pRLWE.RingQ().NewMonomialXi(siInt)
pRLWE.RingQ().NTT(pt.Value, pt.Value)
ptXi[siInt] = pt
}
skiRGSW[i] = rgsw.NewCiphertext(pRLWE, levelQ, levelP, BaseTwoDecomposition)
// Sanity check, this error should never happen unless this algorithm
// has been improperly modified to provides invalid inputs.
if err := encryptor.Encrypt(ptXi[siInt], skiRGSW[i]); err != nil {
panic(err)
}
}
kgen := rlwe.NewKeyGenerator(pRLWE)
galEls := make([]uint64, windowSize)
for i := 0; i < windowSize; i++ {
galEls[i] = pRLWE.GaloisElement(i + 1)
}
galEls = append(galEls, pRLWE.RingQ().NthRoot()-ring.GaloisGen)
gks := kgen.GenGaloisKeysNew(galEls, skRLWE, rlwe.EvaluationKeyParameters{
LevelQ: utils.Pointy(levelQ),
LevelP: utils.Pointy(levelP),
BaseTwoDecomposition: utils.Pointy(BaseTwoDecomposition),
})
return MemBlindRotationEvaluationKeySet{BlindRotationKeys: skiRGSW, AutomorphismKeys: gks}
}

View File

@@ -0,0 +1,53 @@
package blind_rotation
import (
"math/big"
"github.com/tuneinsight/lattigo/v5/ring"
"github.com/tuneinsight/lattigo/v5/utils/bignum"
)
// MulBySmallMonomialMod2N multiplies pol by x^n, with 0 <= n < N
func mulBySmallMonomialMod2N(mask uint64, pol ring.Poly, n int) {
if n != 0 {
N := len(pol.Coeffs[0])
pol.Coeffs[0] = append(pol.Coeffs[0][N-n:], pol.Coeffs[0][:N-n]...)
tmp := pol.Coeffs[0]
for j := 0; j < n; j++ {
tmp[j] = -tmp[j] & mask
}
}
}
func normalizeInv(x, a, b float64) (y float64) {
return (x*(b-a) + b + a) / 2.0
}
func scaleUp(value float64, scale float64, Q uint64) (res uint64) {
var isNegative bool
var xFlo *big.Float
var xInt *big.Int
isNegative = false
if value < 0 {
isNegative = true
xFlo = big.NewFloat(-scale * value)
} else {
xFlo = big.NewFloat(scale * value)
}
xFlo.Add(xFlo, big.NewFloat(0.5))
xInt = new(big.Int)
xFlo.Int(xInt)
xInt.Mod(xInt, bignum.NewInt(Q))
res = xInt.Uint64()
if isNegative {
res = Q - res
}
return
}

View File

@@ -6,8 +6,8 @@ import (
"fmt"
"time"
"github.com/tuneinsight/lattigo/v5/circuits/blind_rotation"
"github.com/tuneinsight/lattigo/v5/core/rlwe"
"github.com/tuneinsight/lattigo/v5/he/hebin"
"github.com/tuneinsight/lattigo/v5/ring"
"github.com/tuneinsight/lattigo/v5/utils"
)
@@ -59,7 +59,7 @@ func main() {
slots := 32
// Test poly
testPoly := hebin.InitTestPolynomial(sign, rlwe.NewScale(scaleBR), paramsBR.RingQ(), -1, 1)
testPoly := blind_rotation.InitTestPolynomial(sign, rlwe.NewScale(scaleBR), paramsBR.RingQ(), -1, 1)
// Index map of which test poly to evaluate on which slot
testPolyMap := make(map[int]*ring.Poly)
@@ -98,13 +98,13 @@ func main() {
}
// Evaluator for the Blind Rotations
eval := hebin.NewEvaluator(paramsBR, paramsLWE)
eval := blind_rotation.NewEvaluator(paramsBR, paramsLWE)
// Secret of the RGSW ciphertexts encrypting the bits of skLWE
skBR := rlwe.NewKeyGenerator(paramsBR).GenSecretKeyNew()
// Collection of RGSW ciphertexts encrypting the bits of skLWE under skBR
blindeRotateKey := hebin.GenEvaluationKeyNew(paramsBR, skBR, paramsLWE, skLWE, evkParams)
blindeRotateKey := blind_rotation.GenEvaluationKeyNew(paramsBR, skBR, paramsLWE, skLWE, evkParams)
// Evaluation of BlindRotate(ctLWE) = testPoly(X) * X^{dec{ctLWE}}
// Returns one RLWE sample per slot in ctLWE

View File

@@ -13,10 +13,11 @@ import (
"math/big"
"time"
"github.com/tuneinsight/lattigo/v5/circuits/blind_rotation"
"github.com/tuneinsight/lattigo/v5/circuits/dft"
"github.com/tuneinsight/lattigo/v5/core/rlwe"
"github.com/tuneinsight/lattigo/v5/he/hebin"
"github.com/tuneinsight/lattigo/v5/he/hefloat"
"github.com/tuneinsight/lattigo/v5/ring"
"github.com/tuneinsight/lattigo/v5/schemes/ckks"
"github.com/tuneinsight/lattigo/v5/utils"
)
@@ -60,8 +61,8 @@ func main() {
// determine the complexity of the BlindRotation:
// each BlindRotation takes ~N RGSW ciphertext-ciphertext mul.
// LogN = 12 & LogQP = ~103 -> >128-bit secure.
var paramsN12 hefloat.Parameters
if paramsN12, err = hefloat.NewParametersFromLiteral(hefloat.ParametersLiteral{
var paramsN12 ckks.Parameters
if paramsN12, err = ckks.NewParametersFromLiteral(ckks.ParametersLiteral{
LogN: LogN,
Q: Q,
P: P,
@@ -73,8 +74,8 @@ func main() {
// BlindRotation RLWE params, N of these params determine
// the test poly degree and therefore precision.
// LogN = 11 & LogQP = ~54 -> 128-bit secure.
var paramsN11 hefloat.Parameters
if paramsN11, err = hefloat.NewParametersFromLiteral(hefloat.ParametersLiteral{
var paramsN11 ckks.Parameters
if paramsN11, err = ckks.NewParametersFromLiteral(ckks.ParametersLiteral{
LogN: LogN - 1,
Q: Q[:1],
P: []uint64{0x42001},
@@ -96,8 +97,8 @@ func main() {
normalization := 2.0 / (b - a) // all inputs are normalized before the BlindRotation evaluation.
// SlotsToCoeffsParameters homomorphic encoding parameters
var SlotsToCoeffsParameters = hefloat.DFTMatrixLiteral{
Type: hefloat.HomomorphicDecode,
var SlotsToCoeffsParameters = dft.DFTMatrixLiteral{
Type: dft.HomomorphicDecode,
LogSlots: LogSlots,
Scaling: new(big.Float).SetFloat64(normalization * diffScale),
LevelQ: 1, // starting level
@@ -106,8 +107,8 @@ func main() {
}
// CoeffsToSlotsParameters homomorphic decoding parameters
var CoeffsToSlotsParameters = hefloat.DFTMatrixLiteral{
Type: hefloat.HomomorphicEncode,
var CoeffsToSlotsParameters = dft.DFTMatrixLiteral{
Type: dft.HomomorphicEncode,
LogSlots: LogSlots,
LevelQ: 1, // starting level
LevelP: 0,
@@ -117,7 +118,7 @@ func main() {
fmt.Printf("Generating Test Poly... ")
now := time.Now()
// Generate test polynomial, provide function, outputscale, ring and interval.
testPoly := hebin.InitTestPolynomial(sign, paramsN12.DefaultScale(), paramsN12.RingQ(), a, b)
testPoly := blind_rotation.InitTestPolynomial(sign, paramsN12.DefaultScale(), paramsN12.RingQ(), a, b)
fmt.Printf("Done (%s)\n", time.Since(now))
// Index of the test poly and repacking after evaluating the BlindRotation.
@@ -133,7 +134,7 @@ func main() {
kgenN12 := rlwe.NewKeyGenerator(paramsN12)
skN12 := kgenN12.GenSecretKeyNew()
encoderN12 := hefloat.NewEncoder(paramsN12)
encoderN12 := ckks.NewEncoder(paramsN12)
encryptorN12 := rlwe.NewEncryptor(paramsN12, skN12)
decryptorN12 := rlwe.NewDecryptor(paramsN12, skN12)
@@ -145,11 +146,11 @@ func main() {
fmt.Printf("Gen SlotsToCoeffs Matrices... ")
now = time.Now()
SlotsToCoeffsMatrix, err := hefloat.NewDFTMatrixFromLiteral(paramsN12, SlotsToCoeffsParameters, encoderN12)
SlotsToCoeffsMatrix, err := dft.NewDFTMatrixFromLiteral(paramsN12, SlotsToCoeffsParameters, encoderN12)
if err != nil {
panic(err)
}
CoeffsToSlotsMatrix, err := hefloat.NewDFTMatrixFromLiteral(paramsN12, CoeffsToSlotsParameters, encoderN12)
CoeffsToSlotsMatrix, err := dft.NewDFTMatrixFromLiteral(paramsN12, CoeffsToSlotsParameters, encoderN12)
if err != nil {
panic(err)
}
@@ -164,15 +165,15 @@ func main() {
evk := rlwe.NewMemEvaluationKeySet(nil, kgenN12.GenGaloisKeysNew(galEls, skN12)...)
// BlindRotation Evaluator
evalBR := hebin.NewEvaluator(paramsN12, paramsN11)
evalBR := blind_rotation.NewEvaluator(paramsN12, paramsN11)
// Evaluator
eval := hefloat.NewEvaluator(paramsN12, evk)
evalHDFT := hefloat.NewDFTEvaluator(paramsN12, eval)
eval := ckks.NewEvaluator(paramsN12, evk)
evalHDFT := dft.NewDFTEvaluator(paramsN12, eval)
fmt.Printf("Encrypting bits of skLWE in RGSW... ")
now = time.Now()
blindRotateKey := hebin.GenEvaluationKeyNew(paramsN12, skN12, paramsN11, skN11, evkParams) // Generate RGSW(sk_i) for all coefficients of sk
blindRotateKey := blind_rotation.GenEvaluationKeyNew(paramsN12, skN12, paramsN11, skN11, evkParams) // Generate RGSW(sk_i) for all coefficients of sk
fmt.Printf("Done (%s)\n", time.Since(now))
// Generates the starting plaintext values.
@@ -182,7 +183,7 @@ func main() {
values[i] = a + float64(i)*interval
}
pt := hefloat.NewPlaintext(paramsN12, paramsN12.MaxLevel())
pt := ckks.NewPlaintext(paramsN12, paramsN12.MaxLevel())
pt.LogDimensions.Cols = LogSlots
if err := encoderN12.Encode(values, pt); err != nil {
panic(err)
@@ -204,7 +205,7 @@ func main() {
ctN12.IsBatched = false
// Key-Switch from LogN = 12 to LogN = 11
ctN11 := hefloat.NewCiphertext(paramsN11, 1, paramsN11.MaxLevel())
ctN11 := ckks.NewCiphertext(paramsN11, 1, paramsN11.MaxLevel())
// key-switch to LWE degree
if err := eval.ApplyEvaluationKey(ctN12, evkN12ToN11, ctN11); err != nil {
panic(err)