// Constructor Ctxt::Ctxt(ZeroCtxtLike_type, const Ctxt& ctxt): context(ctxt.getPubKey().getContext()), pubKey(ctxt.getPubKey()), ptxtSpace(ctxt.getPtxtSpace()), noiseVar(to_xdouble(0.0)) { // same body as previous constructor if (ptxtSpace<=0) ptxtSpace = pubKey.getPtxtSpace(); else assert (GCD(ptxtSpace, pubKey.getPtxtSpace()) > 1); // sanity check primeSet=context.ctxtPrimes; }
// This procedure assumes that k*(2^e +1) > deg(poly) > k*(2^e -1), // and that babyStep contains >= k + (deg(poly) mod k) powers static void degPowerOfTwo(Ctxt& ret, const ZZX& poly, long k, DynamicCtxtPowers& babyStep, DynamicCtxtPowers& giantStep) { if (deg(poly)<=babyStep.size()) { // Edge condition, use simple eval simplePolyEval(ret, poly, babyStep); return; } long n = deg(poly)/k; // We assume n=2^e or n=2^e -1 n = 1L << NextPowerOfTwo(n); // round up to n=2^e ZZX r = trunc(poly, (n-1)*k); // degree <= k(2^e-1)-1 ZZX q = RightShift(poly, (n-1)*k); // 0 < degree < 2k SetCoeff(r, (n-1)*k); // monic, degree == k(2^e-1) q -= 1; PatersonStockmeyer(ret, r, k, n/2, 0, babyStep, giantStep); Ctxt tmp(ret.getPubKey(), ret.getPtxtSpace()); simplePolyEval(tmp, q, babyStep); // evaluate q // multiply by X^{k(n-1)} with minimum depth for (long i=1; i<n; i*=2) { tmp.multiplyBy(giantStep.getPower(i)); } ret += tmp; }
void extractDigits(vector<Ctxt>& digits, const Ctxt& c, long r) { const FHEcontext& context = c.getContext(); long rr = c.effectiveR(); if (r<=0 || r>rr) r = rr; // how many digits to extract long p = context.zMStar.getP(); ZZX x2p; if (p>3) { buildDigitPolynomial(x2p, p, r); } Ctxt tmp(c.getPubKey(), c.getPtxtSpace()); digits.resize(r, tmp); // allocate space #ifdef DEBUG_PRINTOUT fprintf(stderr, "***\n"); #endif for (long i=0; i<r; i++) { tmp = c; for (long j=0; j<i; j++) { if (p==2) digits[j].square(); else if (p==3) digits[j].cube(); else polyEval(digits[j], x2p, digits[j]); // "in spirit" digits[j] = digits[j]^p #ifdef DEBUG_PRINTOUT fprintf(stderr, "%5ld", digits[j].bitCapacity()); #endif tmp -= digits[j]; tmp.divideByP(); } digits[i] = tmp; // needed in the next round #ifdef DEBUG_PRINTOUT if (dbgKey) { double ratio = log(embeddingLargestCoeff(digits[i], *dbgKey)/digits[i].getNoiseBound())/log(2.0); fprintf(stderr, "%5ld [%f]", digits[i].bitCapacity(), ratio); if (ratio > 0) fprintf(stderr, " BAD-BOUND"); fprintf(stderr, "\n"); } else { fprintf(stderr, "%5ld\n", digits[i].bitCapacity()); } #endif } #ifdef DEBUG_PRINTOUT fprintf(stderr, "***\n"); #endif }
// Apply a permutation network to a ciphertext // FIXME: Do we need to also give an EncryptedArray object as paramter? void PermNetwork::applyToCtxt(Ctxt& c) const { const PAlgebra& al = c.getContext().zMStar; EncryptedArray ea(c.getContext()); // Use G(X)=X for this ea object, this works since we only have 0/1 entries // Apply the layers, one at a time for (long i=0; i<layers.length(); i++) { const PermNetLayer& lyr = layers[i]; if (lyr.isID) continue; // this layer is the identity permutation // This layer is shifted via powers of g^e mod m long g2e = PowerMod(al.ZmStarGen(lyr.genIdx), lyr.e, al.getM()); Vec<long> unused = lyr.shifts; // copy to a new vector vector<long> mask(lyr.shifts.length()); // buffer to hold masks Ctxt sum(c.getPubKey(), c.getPtxtSpace()); // an empty ciphertext long shamt = 0; bool frst = true; while (true) { pair<long,bool> ret=makeMask(mask, unused, shamt); // compute mask if (ret.second) { // non-empty mask Ctxt tmp = c; ZZX maskPoly; ea.encode(maskPoly, mask); // encode mask as polynomial tmp.multByConstant(maskPoly); // multiply by mask if (shamt!=0) // rotate if the shift amount is nonzero tmp.smartAutomorph(PowerMod(g2e, shamt, al.getM())); if (frst) { sum = tmp; frst = false; } else sum += tmp; } if (ret.first >= 0) shamt = unused[ret.first]; // next shift amount to use else break; // unused is all-zero, done with this layer } c = sum; // update the cipehrtext c before the next layer } }
// The recursive procedure in the Paterson-Stockmeyer // polynomial-evaluation algorithm from SIAM J. on Computing, 1973. // This procedure assumes that poly is monic, deg(poly)=k*(2t-1)+delta // with t=2^e, and that babyStep contains >= k+delta powers static void PatersonStockmeyer(Ctxt& ret, const ZZX& poly, long k, long t, long delta, DynamicCtxtPowers& babyStep, DynamicCtxtPowers& giantStep) { if (deg(poly)<=babyStep.size()) { // Edge condition, use simple eval simplePolyEval(ret, poly, babyStep); return; } ZZX r = trunc(poly, k*t); // degree <= k*2^e-1 ZZX q = RightShift(poly, k*t); // degree == k(2^e-1) +delta const ZZ p = to_ZZ(babyStep[0].getPtxtSpace()); const ZZ& coef = coeff(r,deg(q)); SetCoeff(r, deg(q), coef-1); // r' = r - X^{deg(q)} ZZX c,s; DivRem(c,s,r,q); // r' = c*q + s // deg(s)<deg(q), and if c!= 0 then deg(c)<k-delta assert(deg(s)<deg(q)); assert(IsZero(c) || deg(c)<k-delta); SetCoeff(s,deg(q)); // s' = s + X^{deg(q)}, deg(s)==deg(q) // reduce the coefficients modulo p for (long i=0; i<=deg(c); i++) rem(c[i],c[i], p); c.normalize(); for (long i=0; i<=deg(s); i++) rem(s[i],s[i], p); s.normalize(); // Evaluate recursively poly = (c+X^{kt})*q + s' PatersonStockmeyer(ret, q, k, t/2, delta, babyStep, giantStep); Ctxt tmp(ret.getPubKey(), ret.getPtxtSpace()); simplePolyEval(tmp, c, babyStep); tmp += giantStep.getPower(t); ret.multiplyBy(tmp); PatersonStockmeyer(tmp, s, k, t/2, delta, babyStep, giantStep); ret += tmp; }
static void apply(const EncryptedArrayDerived<type>& ea, Ctxt& ctxt, const PlaintextMatrixBaseInterface& mat) { assert(&ea == &mat.getEA().getDerived(type())); assert(&ea.getContext() == &ctxt.getContext()); RBak bak; bak.save(); ea.getTab().restoreContext(); // Get the derived type const PlaintextMatrixInterface<type>& mat1 = dynamic_cast< const PlaintextMatrixInterface<type>& >( mat ); ctxt.cleanUp(); // not sure, but this may be a good idea Ctxt res(ctxt.getPubKey(), ctxt.getPtxtSpace()); // fresh encryption of zero long nslots = ea.size(); long d = ea.getDegree(); RX entry; vector<RX> diag; diag.resize(nslots); // Process the diagonals one at a time for (long i = 0; i < nslots; i++) { // process diagonal i bool zDiag = true; // is this a zero diagonal? long nzLast = -1; // index of last non-zero entry on this diagonal // Compute constants for each entry on this diagonal for (long j = 0; j < nslots; j++) { // process entry j bool zEntry = mat1.get(entry, mcMod(j-i, nslots), j); // callback assert(zEntry || deg(entry) < d); if (!zEntry && IsZero(entry)) zEntry = true; // check for zero if (!zEntry) { // non-zero diagonal entry zDiag = false; // diagonal is non-zero // clear entries between last nonzero entry and this one for (long jj = nzLast+1; jj < j; jj++) clear(diag[jj]); nzLast = j; diag[j] = entry; } } if (zDiag) continue; // zero diagonal, continue // clear trailing zero entries for (long jj = nzLast+1; jj < nslots; jj++) clear(diag[jj]); // Now we have the constants for all the diagonal entries, encode the // diagonal as a single polynomial with these constants in the slots ZZX cpoly; ea.encode(cpoly, diag); // rotate by i, multiply by the polynomial, then add to the result Ctxt shCtxt = ctxt; ea.rotate(shCtxt, i); // rotate by i shCtxt.multByConstant(cpoly); res += shCtxt; } ctxt = res; }
void extendExtractDigits(vector<Ctxt>& digits, const Ctxt& c, long r, long e) { const FHEcontext& context = c.getContext(); long p = context.zMStar.getP(); ZZX x2p; if (p>3) { buildDigitPolynomial(x2p, p, r); } // we should pre-compute this table // for i = 0..r-1, entry i is G_{e+r-i} in Chen and Han Vec<ZZX> G; G.SetLength(r); for (long i: range(r)) { compute_magic_poly(G[i], p, e+r-i); } vector<Ctxt> digits0; Ctxt tmp(c.getPubKey(), c.getPtxtSpace()); digits.resize(r, tmp); // allocate space digits0.resize(r, tmp); #ifdef DEBUG_PRINTOUT fprintf(stderr, "***\n"); #endif for (long i: range(r)) { tmp = c; for (long j: range(i)) { if (digits[j].capacity() >= digits0[j].capacity()) { // optimization: digits[j] is better than digits0[j], // so just use it tmp -= digits[j]; #ifdef DEBUG_PRINTOUT fprintf(stderr, "%5ld*", digits[j].bitCapacity()); #endif } else { if (p==2) digits0[j].square(); else if (p==3) digits0[j].cube(); else polyEval(digits0[j], x2p, digits0[j]); // "in spirit" digits0[j] = digits0[j]^p tmp -= digits0[j]; #ifdef DEBUG_PRINTOUT fprintf(stderr, "%5ld ", digits0[j].bitCapacity()); #endif } tmp.divideByP(); } digits0[i] = tmp; // needed in the next round polyEval(digits[i], G[i], tmp); #ifdef DEBUG_PRINTOUT if (dbgKey) { double ratio = log(embeddingLargestCoeff(digits[i], *dbgKey)/digits[i].getNoiseBound())/log(2.0); fprintf(stderr, "%5ld --- %5ld", digits0[i].bitCapacity(), digits[i].bitCapacity()); fprintf(stderr, " [%f]", ratio); if (ratio > 0) fprintf(stderr, " BAD-BOUND"); fprintf(stderr, "\n"); } else { fprintf(stderr, "%5ld --- %5ld\n", digits0[i].bitCapacity(), digits[i].bitCapacity()); } #endif } }
void EncryptedArrayDerived<type>::mat_mul(Ctxt& ctxt, const PlaintextBlockMatrixBaseInterface& mat) const { FHE_TIMER_START; assert(this == &mat.getEA().getDerived(type())); assert(&context == &ctxt.getContext()); RBak bak; bak.save(); tab.restoreContext(); const PlaintextBlockMatrixInterface<type>& mat1 = dynamic_cast< const PlaintextBlockMatrixInterface<type>& >( mat ); ctxt.cleanUp(); // not sure, but this may be a good idea Ctxt res(ctxt.getPubKey(), ctxt.getPtxtSpace()); // a new ciphertext, encrypting zero long nslots = size(); long d = getDegree(); mat_R entry; entry.SetDims(d, d); vector<RX> entry1; entry1.resize(d); vector< vector<RX> > diag; diag.resize(nslots); for (long j = 0; j < nslots; j++) diag[j].resize(d); for (long i = 0; i < nslots; i++) { // process diagonal i bool zDiag = true; long nzLast = -1; for (long j = 0; j < nslots; j++) { bool zEntry = mat1.get(entry, mcMod(j-i, nslots), j); assert(zEntry || (entry.NumRows() == d && entry.NumCols() == d)); // get(...) returns true if the entry is empty, false otherwise if (!zEntry && IsZero(entry)) zEntry=true; // zero is an empty entry too if (!zEntry) { // non-empty entry zDiag = false; // mark diagonal as non-empty // clear entries between last nonzero entry and this one for (long jj = nzLast+1; jj < j; jj++) { for (long k = 0; k < d; k++) clear(diag[jj][k]); } nzLast = j; // recode entry as a vector of polynomials for (long k = 0; k < d; k++) conv(entry1[k], entry[k]); // compute the lin poly coeffs buildLinPolyCoeffs(diag[j], entry1); } } if (zDiag) continue; // zero diagonal, continue // clear trailing zero entries for (long jj = nzLast+1; jj < nslots; jj++) { for (long k = 0; k < d; k++) clear(diag[jj][k]); } // now diag[j] contains the lin poly coeffs Ctxt shCtxt = ctxt; rotate(shCtxt, i); // apply the linearlized polynomial for (long k = 0; k < d; k++) { // compute the constant bool zConst = true; vector<RX> cvec; cvec.resize(nslots); for (long j = 0; j < nslots; j++) { cvec[j] = diag[j][k]; if (!IsZero(cvec[j])) zConst = false; } if (zConst) continue; ZZX cpoly; encode(cpoly, cvec); // FIXME: record the encoded polynomial for future use Ctxt shCtxt1 = shCtxt; shCtxt1.frobeniusAutomorph(k); shCtxt1.multByConstant(cpoly); res += shCtxt1; } } ctxt = res; }
void EncryptedArrayDerived<type>::shift(Ctxt& ctxt, long k) const { FHE_TIMER_START; const PAlgebra& al = context.zMStar; const vector< vector< RX > >& maskTable = tab.getMaskTable(); RBak bak; bak.save(); tab.restoreContext(); assert(&context == &ctxt.getContext()); // Simple case: just one generator if (al.numOfGens()==1) { shift1D(ctxt, 0, k); return; } long nSlots = al.getNSlots(); // Shifting by more than the number of slots gives an all-zero cipehrtext if (k <= -nSlots || k >= nSlots) { ctxt.multByConstant(to_ZZX(0)); return; } // Make sure that amt is in [1,nslots-1] long amt = k % nSlots; if (amt == 0) return; if (amt < 0) amt += nSlots; // rotate the ciphertext, one dimension at a time long i = al.numOfGens()-1; long v = al.coordinate(i, amt); RX mask = maskTable[i][v]; Ctxt tmp(ctxt.getPubKey()); const RXModulus& PhimXmod = tab.getPhimXMod(); rotate1D(ctxt, i, v); for (i--; i >= 0; i--) { v = al.coordinate(i, amt); DoubleCRT m1(conv<ZZX>(mask), context, ctxt.getPrimeSet()); tmp = ctxt; tmp.multByConstant(m1); // only the slots in which mask=1 ctxt -= tmp; // only the slots in which mask=0 if (i>0) { rotate1D(ctxt, i, v+1); rotate1D(tmp, i, v); ctxt += tmp; // combine the two parts mask = ((mask * (maskTable[i][v] - maskTable[i][v+1])) % PhimXmod) + maskTable[i][v+1]; // update the mask before next iteration } else { // i == 0 if (k < 0) v -= al.OrderOf(0); shift1D(tmp, 0, v); shift1D(ctxt, 0, v+1); ctxt += tmp; } } FHE_TIMER_STOP; }
void EncryptedArrayDerived<type>::rotate(Ctxt& ctxt, long amt) const { FHE_TIMER_START; const PAlgebra& al = context.zMStar; const vector< vector< RX > >& maskTable = tab.getMaskTable(); RBak bak; bak.save(); tab.restoreContext(); assert(&context == &ctxt.getContext()); // Simple case: just one generator if (al.numOfGens()==1) { // VJS: bug fix: <= must be == rotate1D(ctxt, 0, amt); return; } // Make sure that amt is in [1,nslots-1] amt %= (long) al.getNSlots(); if (amt == 0) { return; } if (amt < 0) amt += al.getNSlots(); // rotate the ciphertext, one dimension at a time long i = al.numOfGens()-1; long v = al.coordinate(i, amt); RX mask = maskTable[i][v]; Ctxt tmp(ctxt.getPubKey()); const RXModulus& PhimXmod = tab.getPhimXMod(); // optimize for the common case where the last generator has order in // Zm*/(p) different than its order in Zm*. In this case we can combine // the rotate1D relative to this generator with the masking after the // rotation. This saves one mult-by-constant, since we use the same mask // inside rotate1D as in the loop below. if (al.SameOrd(i) || v==0) rotate1D(ctxt, i, v); // no need to optimize else { long ord = al.OrderOf(i); long val = PowerMod(al.ZmStarGen(i), v, al.getM()); long ival = PowerMod(al.ZmStarGen(i), v-ord, al.getM()); DoubleCRT m1(conv<ZZX>(maskTable[i][ord-v]), context, ctxt.getPrimeSet()); tmp = ctxt; // a copy of the ciphertext tmp.multByConstant(m1); // only the slots in which m1=1 ctxt -= tmp; // only the slots in which m1=0 ctxt.smartAutomorph(val); // shift left by val tmp.smartAutomorph(ival); // shift right by ord-val // apply rotation relative to next generator before combining the parts --i; v = al.coordinate(i, amt); rotate1D(ctxt, i, v); rotate1D(tmp, i, v+1); ctxt += tmp; // combine the two parts if (i <= 0) { return; } // no more generators mask = ((mask * (maskTable[i][v] - maskTable[i][v+1])) % PhimXmod) + maskTable[i][v+1]; // update the mask for next iteration } // Handle rotation relative to all the other generators (if any) for (i--; i >= 0; i--) { v = al.coordinate(i, amt); DoubleCRT m1(conv<ZZX>(mask), context, ctxt.getPrimeSet()); tmp = ctxt; tmp.multByConstant(m1); // only the slots in which mask=1 ctxt -= tmp; // only the slots in which mask=0 rotate1D(tmp, i, v); rotate1D(ctxt, i, v+1); ctxt += tmp; if (i>0) { mask = ((mask * (maskTable[i][v] - maskTable[i][v+1])) % PhimXmod) + maskTable[i][v+1]; // update the mask for next iteration } } FHE_TIMER_STOP; }