mirror of
https://github.com/tuneinsight/lattigo.git
synced 2025-09-13 03:27:14 +00:00
repackage blind rotations
This commit is contained in:
3
circuits/blind_rotation/README.md
Normal file
3
circuits/blind_rotation/README.md
Normal 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>)
|
||||
38
circuits/blind_rotation/blind_rotation.go
Normal file
38
circuits/blind_rotation/blind_rotation.go
Normal 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
|
||||
}
|
||||
71
circuits/blind_rotation/blind_rotation_benchmark_test.go
Normal file
71
circuits/blind_rotation/blind_rotation_benchmark_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
168
circuits/blind_rotation/blind_rotation_test.go
Normal file
168
circuits/blind_rotation/blind_rotation_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
298
circuits/blind_rotation/evaluator.go
Normal file
298
circuits/blind_rotation/evaluator.go
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
108
circuits/blind_rotation/keys.go
Normal file
108
circuits/blind_rotation/keys.go
Normal 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}
|
||||
}
|
||||
53
circuits/blind_rotation/utils.go
Normal file
53
circuits/blind_rotation/utils.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user