From 98fa0eb8a67a8ee398eef71e8a06454ecccdc3f7 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Bossuat Date: Mon, 14 Dec 2020 16:16:33 +0100 Subject: [PATCH] - --- bfv/evaluator.go | 2 +- bfv/keygen.go | 3 +- bfv/params.go | 21 ++++++++---- ckks/bootstrapp.go | 4 +-- ckks/evaluator.go | 7 ++-- ckks/keygen.go | 4 +-- ckks/marshaler.go | 6 +++- ckks/params.go | 16 +++++++-- dbfv/public_keyswitching.go | 2 +- dbfv/public_refresh.go | 2 +- dbfv/relinkey_gen.go | 2 +- dckks/keyswitching.go | 28 +++++++--------- dckks/public_keyswitching.go | 2 ++ dckks/relinkey_gen.go | 2 -- dckks/utils.go | 26 +++++++++++++++ ring/primes.go | 64 ++---------------------------------- ring/ring_operations.go | 2 +- utils/prng_test.go | 6 +++- 18 files changed, 94 insertions(+), 105 deletions(-) create mode 100644 dckks/utils.go diff --git a/bfv/evaluator.go b/bfv/evaluator.go index 2c0414ad..654b0f04 100644 --- a/bfv/evaluator.go +++ b/bfv/evaluator.go @@ -184,7 +184,7 @@ func (eval *evaluator) getElemAndCheckUnary(op0, opOut Operand, opOutMinDegree u panic("receiver operand degree is too small") } el0, elOut = op0.El(), opOut.El() - return // TODO: more checks on elements + return } // evaluateInPlaceBinary applies the provided function in place on el0 and el1 and returns the result in elOut. diff --git a/bfv/keygen.go b/bfv/keygen.go index 31fe9e86..a24bf98b 100644 --- a/bfv/keygen.go +++ b/bfv/keygen.go @@ -166,8 +166,7 @@ func (keygen *keyGenerator) GenPublicKey(sk *SecretKey) (pk *PublicKey) { ringQP.NTT(pk.pk[0], pk.pk[0]) pk.pk[1] = keygen.uniformSampler.ReadNew() - ringQP.MulCoeffsMontgomeryAndAdd(sk.sk, pk.pk[1], pk.pk[0]) - ringQP.Neg(pk.pk[0], pk.pk[0]) + ringQP.MulCoeffsMontgomeryAndSub(sk.sk, pk.pk[1], pk.pk[0]) return pk } diff --git a/bfv/params.go b/bfv/params.go index 49e2dd81..f5bf60a2 100644 --- a/bfv/params.go +++ b/bfv/params.go @@ -13,6 +13,9 @@ import ( // MaxLogN is the log2 of the largest supported polynomial modulus degree. const MaxLogN = 16 +// MinLogN is the log2 of the smallest supported polynomial modulus degree. +const MinLogN = 4 + // MaxModuliCount is the largest supported number of moduli in the RNS representation. const MaxModuliCount = 34 @@ -178,7 +181,7 @@ func NewParametersFromModuli(logN uint64, m *Moduli, t uint64) (p *Parameters, e p = new(Parameters) - if logN < 0 || logN > MaxLogN { + if logN < MinLogN || logN > MaxLogN { return nil, fmt.Errorf("invalid polynomial ring log degree: %d", logN) } @@ -431,13 +434,19 @@ func (p *Parameters) MarshalBinary() ([]byte, error) { return []byte{}, nil } + // data : 19 byte + len(QPi) * 8 byte + // 1 byte : logN + // 1 byte : #pi + // 1 byte : #pi + // 8 byte : t + // 8 byte : sigma b := utils.NewBuffer(make([]byte, 0, 19+(len(p.qi)+len(p.pi))<<3)) b.WriteUint8(uint8(p.logN)) b.WriteUint8(uint8(len(p.qi))) b.WriteUint8(uint8(len(p.pi))) b.WriteUint64(p.t) - b.WriteUint64(uint64(p.sigma * (1 << 32))) + b.WriteUint64(math.Float64bits(p.sigma)) b.WriteUint64Slice(p.qi) b.WriteUint64Slice(p.pi) @@ -446,7 +455,7 @@ func (p *Parameters) MarshalBinary() ([]byte, error) { // UnmarshalBinary decodes a []byte into a parameter set struct. func (p *Parameters) UnmarshalBinary(data []byte) error { - if len(data) < 3 { + if len(data) < 19 { return errors.New("invalid parameters encoding") } b := utils.NewBuffer(data) @@ -461,14 +470,14 @@ func (p *Parameters) UnmarshalBinary(data []byte) error { lenPi := b.ReadUint8() p.t = b.ReadUint64() - p.sigma = math.Round((float64(b.ReadUint64())/float64(1<<32))*100) / 100 + p.sigma = math.Float64frombits(b.ReadUint64()) p.qi = make([]uint64, lenQi, lenQi) p.pi = make([]uint64, lenPi, lenPi) b.ReadUint64Slice(p.qi) b.ReadUint64Slice(p.pi) - err := checkModuli(p.Moduli(), p.logN) // TODO: check more than moduli. + err := checkModuli(p.Moduli(), p.logN) if err != nil { return err } @@ -518,7 +527,7 @@ func checkLogModuli(lm *LogModuli) (err error) { // Checks if the parameters are empty if lm.LogQi == nil || len(lm.LogQi) == 0 { - return fmt.Errorf("nil or empty slice provided as LogModuli.LogQi") // TODO: are our algorithm working with empty mult basis ? + return fmt.Errorf("nil or empty slice provided as LogModuli.LogQi") } if len(lm.LogQi) > MaxModuliCount { diff --git a/ckks/bootstrapp.go b/ckks/bootstrapp.go index 9a2c975c..b7134fab 100644 --- a/ckks/bootstrapp.go +++ b/ckks/bootstrapp.go @@ -398,13 +398,13 @@ func (eval *evaluator) rotateHoistedNoModDown(ct0 *Ciphertext, rotations []uint6 ringP := eval.ringP c2NTT := ct0.value[1] - c2InvNTT := ringQ.NewPoly() // TODO : maybe have a pre-allocated memory pool ? + c2InvNTT := ringQ.NewPoly() // IMPROVEMENT: maybe have a pre-allocated memory pool ? ringQ.InvNTTLvl(ct0.Level(), c2NTT, c2InvNTT) alpha := eval.params.Alpha() beta := uint64(math.Ceil(float64(ct0.Level()+1) / float64(alpha))) - // TODO : maybe have a pre-allocated memory pool ? + // IMPROVEMENT: maybe have a pre-allocated memory pool ? c2QiQDecomp := make([]*ring.Poly, beta) c2QiPDecomp := make([]*ring.Poly, beta) diff --git a/ckks/evaluator.go b/ckks/evaluator.go index 8edd0ef4..14b47a8a 100644 --- a/ckks/evaluator.go +++ b/ckks/evaluator.go @@ -76,7 +76,6 @@ type evaluator struct { poolQ [4]*ring.Poly // Memory pool in order : Decomp(c2), for NTT^-1(c2), res(c0', c1') poolP [3]*ring.Poly // Memory pool in order : Decomp(c2), res(c0', c1') - // TODO use the other pools ctxpool *Ciphertext // Memory pool for ciphertext that need to be scaled up (to be removed eventually) baseconverter *ring.FastBasisExtender @@ -136,7 +135,7 @@ func (eval *evaluator) getElemAndCheckBinary(op0, op1, opOut Operand, opOutMinDe panic("receiver operand degree is too small") } el0, el1, elOut = op0.El(), op1.El(), opOut.El() - return // TODO: more checks on elements + return } func (eval *evaluator) getElemAndCheckUnary(op0, opOut Operand, opOutMinDegree uint64) (el0, elOut *Element) { @@ -152,7 +151,7 @@ func (eval *evaluator) getElemAndCheckUnary(op0, opOut Operand, opOutMinDegree u panic("receiver operand degree is too small") } el0, elOut = op0.El(), opOut.El() - return // TODO: more checks on elements + return } func (eval *evaluator) newCiphertextBinary(op0, op1 Operand) (ctOut *Ciphertext) { @@ -240,7 +239,7 @@ func (eval *evaluator) SubNoModNew(op0, op1 Operand) (ctOut *Ciphertext) { func (eval *evaluator) evaluateInPlace(c0, c1, ctOut *Element, evaluate func(uint64, *ring.Poly, *ring.Poly, *ring.Poly)) { - var tmp0, tmp1 *Element // TODO : use eval mem pool + var tmp0, tmp1 *Element level := utils.MinUint64(utils.MinUint64(c0.Level(), c1.Level()), ctOut.Level()) diff --git a/ckks/keygen.go b/ckks/keygen.go index c5a65dc1..84b8033c 100644 --- a/ckks/keygen.go +++ b/ckks/keygen.go @@ -192,8 +192,8 @@ func (keygen *keyGenerator) GenPublicKey(sk *SecretKey) (pk *PublicKey) { ringQP.NTT(pk.pk[0], pk.pk[0]) pk.pk[1] = keygen.uniformSampler.ReadNew() - ringQP.MulCoeffsMontgomeryAndAdd(sk.sk, pk.pk[1], pk.pk[0]) - ringQP.Neg(pk.pk[0], pk.pk[0]) + ringQP.MulCoeffsMontgomeryAndSub(sk.sk, pk.pk[1], pk.pk[0]) + return pk } diff --git a/ckks/marshaler.go b/ckks/marshaler.go index 56ec598d..d1275b0d 100644 --- a/ckks/marshaler.go +++ b/ckks/marshaler.go @@ -10,6 +10,10 @@ import ( // GetDataLen returns the length in bytes of the target Ciphertext. func (ciphertext *Ciphertext) GetDataLen(WithMetaData bool) (dataLen uint64) { + // MetaData is : + // 1 byte : Degree + // 9 byte : Scale + // 1 byte : isNTT if WithMetaData { dataLen += 11 } @@ -55,7 +59,7 @@ func (ciphertext *Ciphertext) MarshalBinary() (data []byte, err error) { // The target Ciphertext must be of the appropriate format and size, it can be created with the // method NewCiphertext(uint64). func (ciphertext *Ciphertext) UnmarshalBinary(data []byte) (err error) { - if len(data) < 11 { + if len(data) < 11 { // cf. ciphertext.GetDataLen() return errors.New("too small bytearray") } diff --git a/ckks/params.go b/ckks/params.go index f817054a..116e6f80 100644 --- a/ckks/params.go +++ b/ckks/params.go @@ -14,6 +14,9 @@ import ( // MaxLogN is the log2 of the largest supported polynomial modulus degree. const MaxLogN = 16 +// MinLogN is the log2 of the smallest supported polynomial modulus degree +const MinLogN = 4 + // MaxModuliCount is the largest supported number of moduli in the RNS representation. const MaxModuliCount = 34 @@ -239,7 +242,7 @@ type Parameters struct { func NewParametersFromModuli(logN uint64, m *Moduli) (p *Parameters, err error) { p = new(Parameters) - if (logN < 3) || (logN > MaxLogN) { + if (logN < MinLogN) || (logN > MaxLogN) { return nil, fmt.Errorf("invalid polynomial ring log degree: %d", logN) } @@ -546,7 +549,14 @@ func (p *Parameters) MarshalBinary() ([]byte, error) { return []byte{}, nil } - b := utils.NewBuffer(make([]byte, 0, 21+(p.QPiCount())<<3)) + // Data 21 byte + QPiCount * 8 byte: + // 1 byte : logN + // 1 byte : logSlots + // 8 byte : scale + // 8 byte : sigma + // 1 byte : #qi + // 1 byte : #pi + b := utils.NewBuffer(make([]byte, 0, 20+(p.QPiCount())<<3)) b.WriteUint8(uint8(p.logN)) b.WriteUint8(uint8(p.logSlots)) @@ -563,7 +573,7 @@ func (p *Parameters) MarshalBinary() ([]byte, error) { // UnmarshalBinary decodes a []byte into a parameter set struct func (p *Parameters) UnmarshalBinary(data []byte) (err error) { - if len(data) < 3 { + if len(data) < 20 { return errors.New("invalid parameters encoding") } diff --git a/dbfv/public_keyswitching.go b/dbfv/public_keyswitching.go index 3feddfcf..008c1063 100644 --- a/dbfv/public_keyswitching.go +++ b/dbfv/public_keyswitching.go @@ -30,7 +30,7 @@ type PCKSShare [2]*ring.Poly // MarshalBinary encodes a PCKS share on a slice of bytes. func (share *PCKSShare) MarshalBinary() ([]byte, error) { - //TODO discuss choice here. + //Discuss choice here. //Maybe not worth it to have the metadata separated. we "lose" two bytes but complexity of the code would be higher in Unmarshalling. lenR1 := share[0].GetDataLen(true) lenR2 := share[1].GetDataLen(true) diff --git a/dbfv/public_refresh.go b/dbfv/public_refresh.go index c6c7bdef..eeeb2d59 100644 --- a/dbfv/public_refresh.go +++ b/dbfv/public_refresh.go @@ -127,7 +127,7 @@ func (rfp *RefreshProtocol) GenShares(sk *ring.Poly, ciphertext *bfv.Ciphertext, ringQ.MulScalarBigint(share.RefreshShareDecrypt, rfp.context.ringP.ModulusBigint, share.RefreshShareDecrypt) // h0 = s*ct[1]*P + e - rfp.gaussianSampler.ReadLvl(uint64(len(ringQP.Modulus)-1), rfp.tmp1) // TODO : add smudging noise + rfp.gaussianSampler.ReadLvl(uint64(len(ringQP.Modulus)-1), rfp.tmp1) ringQ.Add(share.RefreshShareDecrypt, rfp.tmp1, share.RefreshShareDecrypt) for x, i := 0, uint64(len(ringQ.Modulus)); i < uint64(len(rfp.context.ringQP.Modulus)); x, i = x+1, i+1 { diff --git a/dbfv/relinkey_gen.go b/dbfv/relinkey_gen.go index 2af83e7e..db3f65a2 100644 --- a/dbfv/relinkey_gen.go +++ b/dbfv/relinkey_gen.go @@ -177,7 +177,7 @@ func (ekg *RKGProtocol) GenShareRoundOne(u, sk *ring.Poly, crp []*ring.Poly, sha ringQP.MulCoeffsMontgomeryAndAdd(sk, crp[i], shareOut[i][1]) } - ekg.polypool.Zero() // TODO: check if we can remove this one + ekg.polypool.Zero() return } diff --git a/dckks/keyswitching.go b/dckks/keyswitching.go index 6bad2625..7ca5188e 100644 --- a/dckks/keyswitching.go +++ b/dckks/keyswitching.go @@ -12,7 +12,8 @@ type CKSProtocol struct { sigmaSmudging float64 - tmp *ring.Poly + tmpQ *ring.Poly + tmpP *ring.Poly tmpDelta *ring.Poly hP *ring.Poly @@ -34,7 +35,8 @@ func NewCKSProtocol(params *ckks.Parameters, sigmaSmudging float64) (cks *CKSPro cks.dckksContext = dckksContext - cks.tmp = dckksContext.ringQP.NewPoly() + cks.tmpQ = dckksContext.ringQ.NewPoly() + cks.tmpP = dckksContext.ringP.NewPoly() cks.tmpDelta = dckksContext.ringQ.NewPoly() cks.hP = dckksContext.ringP.NewPoly() @@ -75,24 +77,18 @@ func (cks *CKSProtocol) genShareDelta(skDelta *ring.Poly, ct *ckks.Ciphertext, s ringQ.MulScalarBigintLvl(ct.Level(), shareOut, ringP.ModulusBigint, shareOut) - // TODO : improve by only computing the NTT for the required primes - cks.gaussianSampler.Read(cks.tmp) - cks.dckksContext.ringQP.NTT(cks.tmp, cks.tmp) + cks.gaussianSampler.ReadLvl(ct.Level(), cks.tmpQ) + extendBasisSmallNormAndCenter(ringQ, ringP, cks.tmpQ, cks.tmpP) - ringQ.AddLvl(ct.Level(), shareOut, cks.tmp, shareOut) + ringQ.NTTLvl(ct.Level(), cks.tmpQ, cks.tmpQ) + ringP.NTT(cks.tmpP, cks.tmpP) - for x, i := 0, uint64(len(ringQ.Modulus)); i < uint64(len(cks.dckksContext.ringQP.Modulus)); x, i = x+1, i+1 { - tmp0 := cks.tmp.Coeffs[i] - tmp1 := cks.hP.Coeffs[x] - for j := uint64(0); j < ringQ.N; j++ { - tmp1[j] += tmp0[j] - } - } + ringQ.AddLvl(ct.Level(), shareOut, cks.tmpQ, shareOut) - cks.baseconverter.ModDownSplitNTTPQ(ct.Level(), shareOut, cks.hP, shareOut) + cks.baseconverter.ModDownSplitNTTPQ(ct.Level(), shareOut, cks.tmpP, shareOut) - cks.hP.Zero() - cks.tmp.Zero() + cks.tmpQ.Zero() + cks.tmpP.Zero() } // AggregateShares is the second part of the unique round of the CKSProtocol protocol. Upon receiving the j-1 elements each party computes : diff --git a/dckks/public_keyswitching.go b/dckks/public_keyswitching.go index 969699c4..29772ac4 100644 --- a/dckks/public_keyswitching.go +++ b/dckks/public_keyswitching.go @@ -64,6 +64,8 @@ func (pcks *PCKSProtocol) AllocateShares(level uint64) (s PCKSShare) { // and broadcasts the result to the other j-1 parties. func (pcks *PCKSProtocol) GenShare(sk *ring.Poly, pk *ckks.PublicKey, ct *ckks.Ciphertext, shareOut PCKSShare) { + // Planned improvement : adapt share size to ct.Level() to improve efficiency. + ringQ := pcks.dckksContext.ringQ ringQP := pcks.dckksContext.ringQP diff --git a/dckks/relinkey_gen.go b/dckks/relinkey_gen.go index 395d99a5..7e2f681d 100644 --- a/dckks/relinkey_gen.go +++ b/dckks/relinkey_gen.go @@ -176,8 +176,6 @@ func (ekg *RKGProtocol) GenShareRoundOne(u, sk *ring.Poly, crp []*ring.Poly, sha ringQP.MulCoeffsMontgomeryAndAdd(sk, crp[i], shareOut[i][1]) } - ekg.polypool.Zero() // TODO: check if we can remove this one - return } diff --git a/dckks/utils.go b/dckks/utils.go new file mode 100644 index 00000000..6237815c --- /dev/null +++ b/dckks/utils.go @@ -0,0 +1,26 @@ +package dckks + +import ( + "github.com/ldsec/lattigo/v2/ring" +) + +func extendBasisSmallNormAndCenter(ringQ, ringP *ring.Ring, polQ, polP *ring.Poly) { + var coeff, Q, QHalf, sign uint64 + Q = ringQ.Modulus[0] + QHalf = Q >> 1 + + for j := uint64(0); j < ringQ.N; j++ { + + coeff = polQ.Coeffs[0][j] + + sign = 1 + if coeff > QHalf { + coeff = Q - coeff + sign = 0 + } + + for i, pi := range ringP.Modulus { + polP.Coeffs[i][j] = (coeff * sign) | (pi-coeff)*(sign^1) + } + } +} diff --git a/ring/primes.go b/ring/primes.go index da29a3e4..061f78ab 100644 --- a/ring/primes.go +++ b/ring/primes.go @@ -3,69 +3,11 @@ package ring import ( "fmt" "math/bits" - - "github.com/ldsec/lattigo/v2/utils" ) -// IsPrime applies a Miller-Rabin test on the given uint64 variable, returning true if the input is probably prime, and false otherwise. -func IsPrime(num uint64) bool { - - if num < 2 { - return false - } - - for _, smallPrime := range smallPrimes { - if num == smallPrime { - return true - } - } - - for _, smallPrime := range smallPrimes { - if num%smallPrime == 0 { - return false - } - } - - s := num - 1 - k := 0 - for (s & 1) == 0 { - s >>= 1 - k++ - } - - bredParams := BRedParams(num) - var mask, b uint64 - mask = (1 << uint64(bits.Len64(num))) - 1 - - prng, err := utils.NewPRNG() - if err != nil { - panic(err) - } - - for trial := 0; trial < 50; trial++ { - - b = RandUniform(prng, num-1, mask) - - for b < 2 { - b = RandUniform(prng, num-1, mask) - } - - x := ModExp(b, s, num) - - if x != 1 { - i := 0 - for x != num-1 { - - if i == k-1 { - return false - } - - i++ - x = BRed(x, x, num, bredParams) - } - } - } - return true +// IsPrime applies the Baillie-PSW, which is 100% accurate for numbers bellow 2^64 +func IsPrime(x uint64) bool { + return NewUint(x).ProbablyPrime(0) } // GenerateNTTPrimes generates n NthRoot NTT friendly primes given logQ = size of the primes. diff --git a/ring/ring_operations.go b/ring/ring_operations.go index 4b69e097..b335e56a 100644 --- a/ring/ring_operations.go +++ b/ring/ring_operations.go @@ -855,7 +855,7 @@ func (r *Ring) MulPolyNaiveMontgomery(p1, p2, p3 *Poly) { } // Exp raises p1 to p1^e and writes the result on p2. -// TODO : implement Montgomery ladder +// IMPROVEMENT : implement Montgomery ladder func (r *Ring) Exp(p1 *Poly, e uint64, p2 *Poly) { r.NTT(p1, p1) diff --git a/utils/prng_test.go b/utils/prng_test.go index dae51d7f..3e2dff37 100644 --- a/utils/prng_test.go +++ b/utils/prng_test.go @@ -21,7 +21,11 @@ func Test_PRNG(t *testing.T) { sum1 := make([]byte, 512) Ha.SetClock(sum0, 256) - Hb.SetClock(sum1, 256) + Hb.SetClock(sum1, 128) + + for i := 0; i < 128; i++ { + Hb.Clock(sum1) + } Ha.Clock(sum0) Hb.Clock(sum1)