From bf44a706090d57ab4c69f45f6eaa18135e976def Mon Sep 17 00:00:00 2001 From: Andrea Caforio Date: Thu, 4 Jul 2024 13:54:42 +0200 Subject: [PATCH] repackage blind rotations --- circuits/blind_rotation/README.md | 3 + circuits/blind_rotation/blind_rotation.go | 38 +++ .../blind_rotation_benchmark_test.go | 71 +++++ .../blind_rotation/blind_rotation_test.go | 168 ++++++++++ circuits/blind_rotation/evaluator.go | 298 ++++++++++++++++++ circuits/blind_rotation/keys.go | 108 +++++++ circuits/blind_rotation/utils.go | 53 ++++ .../applications/bin_blind_rotations/main.go | 8 +- .../reals_scheme_switching/main.go | 41 +-- 9 files changed, 764 insertions(+), 24 deletions(-) create mode 100644 circuits/blind_rotation/README.md create mode 100644 circuits/blind_rotation/blind_rotation.go create mode 100644 circuits/blind_rotation/blind_rotation_benchmark_test.go create mode 100644 circuits/blind_rotation/blind_rotation_test.go create mode 100644 circuits/blind_rotation/evaluator.go create mode 100644 circuits/blind_rotation/keys.go create mode 100644 circuits/blind_rotation/utils.go diff --git a/circuits/blind_rotation/README.md b/circuits/blind_rotation/README.md new file mode 100644 index 00000000..51041413 --- /dev/null +++ b/circuits/blind_rotation/README.md @@ -0,0 +1,3 @@ +## References + +1. Efficient FHEW Bootstrapping with Small Evaluation Keys, and Applications to Threshold Homomorphic Encryption () diff --git a/circuits/blind_rotation/blind_rotation.go b/circuits/blind_rotation/blind_rotation.go new file mode 100644 index 00000000..f0885d4b --- /dev/null +++ b/circuits/blind_rotation/blind_rotation.go @@ -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 +} diff --git a/circuits/blind_rotation/blind_rotation_benchmark_test.go b/circuits/blind_rotation/blind_rotation_benchmark_test.go new file mode 100644 index 00000000..7966f918 --- /dev/null +++ b/circuits/blind_rotation/blind_rotation_benchmark_test.go @@ -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) + } + } + }) +} diff --git a/circuits/blind_rotation/blind_rotation_test.go b/circuits/blind_rotation/blind_rotation_test.go new file mode 100644 index 00000000..3adb63f8 --- /dev/null +++ b/circuits/blind_rotation/blind_rotation_test.go @@ -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) + } + } + }) +} diff --git a/circuits/blind_rotation/evaluator.go b/circuits/blind_rotation/evaluator.go new file mode 100644 index 00000000..69ce743b --- /dev/null +++ b/circuits/blind_rotation/evaluator.go @@ -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 + } + 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 + } + } +} diff --git a/circuits/blind_rotation/keys.go b/circuits/blind_rotation/keys.go new file mode 100644 index 00000000..2c839080 --- /dev/null +++ b/circuits/blind_rotation/keys.go @@ -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} +} diff --git a/circuits/blind_rotation/utils.go b/circuits/blind_rotation/utils.go new file mode 100644 index 00000000..f27aa1d4 --- /dev/null +++ b/circuits/blind_rotation/utils.go @@ -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 +} diff --git a/examples/single_party/applications/bin_blind_rotations/main.go b/examples/single_party/applications/bin_blind_rotations/main.go index 77d2dcee..8568a1f5 100644 --- a/examples/single_party/applications/bin_blind_rotations/main.go +++ b/examples/single_party/applications/bin_blind_rotations/main.go @@ -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 diff --git a/examples/single_party/applications/reals_scheme_switching/main.go b/examples/single_party/applications/reals_scheme_switching/main.go index a6fa06a2..ffd85be3 100644 --- a/examples/single_party/applications/reals_scheme_switching/main.go +++ b/examples/single_party/applications/reals_scheme_switching/main.go @@ -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)