This commit is contained in:
Jean-Philippe Bossuat
2020-12-14 16:16:33 +01:00
parent 47ae03783c
commit 98fa0eb8a6
18 changed files with 94 additions and 105 deletions

View File

@@ -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.

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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())

View File

@@ -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
}

View File

@@ -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")
}

View File

@@ -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")
}

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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 :

View File

@@ -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

View File

@@ -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
}

26
dckks/utils.go Normal file
View File

@@ -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)
}
}
}

View File

@@ -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.

View File

@@ -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)

View File

@@ -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)