void rotateLeft32(Ctxt &x, int n) { Ctxt other = x; global_ea->shift(x, n); global_ea->shift(other, -(32-n)); x.multByConstant(*global_maxint); other.multByConstant(*global_maxint); x += other; }
// selects range of slots [lo..hi) static void SelectRange(const EncryptedArray& ea, Ctxt& ctxt, long lo, long hi) { ZZX mask; SelectRange(ea, mask, lo, hi); ctxt.multByConstant(mask); }
// incrementalZeroTest sets each res[i], for i=0..n-1, to // a ciphertext in which each slot is 0 or 1 according // to whether or not bits 0..i of corresponding slot in ctxt // is zero (1 if not zero, 0 if zero). // It is assumed that res and each res[i] is already initialized // by the caller. // Complexity: O(d + n log d) smart automorphisms // O(n d) void incrementalZeroTest(Ctxt* res[], const EncryptedArray& ea, const Ctxt& ctxt, long n) { FHE_TIMER_START; long nslots = ea.size(); long d = ea.getDegree(); // compute linearized polynomial coefficients vector< vector<ZZX> > Coeff; Coeff.resize(n); for (long i = 0; i < n; i++) { // coeffients for mask on bits 0..i // L[j] = X^j for j = 0..i, L[j] = 0 for j = i+1..d-1 vector<ZZX> L; L.resize(d); for (long j = 0; j <= i; j++) SetCoeff(L[j], j); vector<ZZX> C; ea.buildLinPolyCoeffs(C, L); Coeff[i].resize(d); for (long j = 0; j < d; j++) { // Coeff[i][j] = to the encoding that has C[j] in all slots // FIXME: maybe encrtpted array should have this functionality // built in vector<ZZX> T; T.resize(nslots); for (long s = 0; s < nslots; s++) T[s] = C[j]; ea.encode(Coeff[i][j], T); } } vector<Ctxt> Conj(d, ctxt); // initialize Cong[j] to ctxt^{2^j} for (long j = 0; j < d; j++) { Conj[j].smartAutomorph(1L << j); } for (long i = 0; i < n; i++) { res[i]->clear(); for (long j = 0; j < d; j++) { Ctxt tmp = Conj[j]; tmp.multByConstant(Coeff[i][j]); *res[i] += tmp; } // *res[i] now has 0..i in each slot // next, we raise to the power 2^d-1 fastPower(*res[i], d); } FHE_TIMER_STOP; }
void replicate(const EncryptedArray& ea, Ctxt& ctxt, long pos) { long nSlots = ea.size(); assert(pos >= 0 && pos < nSlots); ZZX mask; ea.encodeUnitSelector(mask, pos); ctxt.multByConstant(mask); replicate0(ea, ctxt, pos); }
void EncryptedArrayDerived<type>::shift1D(Ctxt& ctxt, long i, 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()); assert(i >= 0 && i < (long)al.numOfGens()); long ord = al.OrderOf(i); if (k <= -ord || k >= ord) { ctxt.multByConstant(to_ZZX(0)); return; } // Make sure amt is in the range [1,ord-1] long amt = k % ord; if (amt == 0) return; if (amt < 0) amt += ord; RX mask = maskTable[i][ord-amt]; long val; if (k < 0) val = PowerMod(al.ZmStarGen(i), amt-ord, al.getM()); else { mask = 1 - mask; val = PowerMod(al.ZmStarGen(i), amt, al.getM()); } DoubleCRT m1(conv<ZZX>(mask), context, ctxt.getPrimeSet()); ctxt.multByConstant(m1); // zero out slots where mask=0 ctxt.smartAutomorph(val); // shift left by val FHE_TIMER_STOP; }
void applyLinPolyLL(Ctxt& ctxt, const vector<P>& encodedC, long d) { assert(d == lsize(encodedC)); ctxt.cleanUp(); // not sure, but this may be a good idea Ctxt tmp(ctxt); ctxt.multByConstant(encodedC[0]); for (long j = 1; j < d; j++) { Ctxt tmp1(tmp); tmp1.frobeniusAutomorph(j); tmp1.multByConstant(encodedC[j]); ctxt += tmp1; } }
// 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 } }
// Simple evaluation sum f_i * X^i, assuming that babyStep has enough powers static void simplePolyEval(Ctxt& ret, const ZZX& poly, DynamicCtxtPowers& babyStep) { ret.clear(); if (deg(poly)<0) return; // the zero polynomial always returns zero assert (deg(poly)<=babyStep.size()); // ensure that we have enough powers ZZ coef; ZZ p = to_ZZ(babyStep[0].getPtxtSpace()); for (long i=1; i<=deg(poly); i++) { rem(coef, coeff(poly,i),p); if (coef > p/2) coef -= p; Ctxt tmp = babyStep.getPower(i); // X^i tmp.multByConstant(coef); // f_i X^i ret += tmp; } // Add the free term rem(coef, ConstTerm(poly), p); if (coef > p/2) coef -= p; ret.addConstant(coef); // if (verbose) checkPolyEval(ret, babyStep[0], poly); }
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; }
// bootstrap a ciphertext to reduce noise void FHEPubKey::reCrypt(Ctxt &ctxt) { FHE_TIMER_START; // Some sanity checks for dummy ciphertext long ptxtSpace = ctxt.getPtxtSpace(); if (ctxt.isEmpty()) return; if (ctxt.parts.size()==1 && ctxt.parts[0].skHandle.isOne()) { // Dummy encryption, just ensure that it is reduced mod p ZZX poly = to_ZZX(ctxt.parts[0]); for (long i=0; i<poly.rep.length(); i++) poly[i] = to_ZZ( rem(poly[i],ptxtSpace) ); poly.normalize(); ctxt.DummyEncrypt(poly); return; } assert(recryptKeyID>=0); // check that we have bootstrapping data long p = getContext().zMStar.getP(); long r = getContext().alMod.getR(); long p2r = getContext().alMod.getPPowR(); // the bootstrapping key is encrypted relative to plaintext space p^{e-e'+r}. long e = getContext().rcData.e; long ePrime = getContext().rcData.ePrime; long p2ePrime = power_long(p,ePrime); long q = power_long(p,e)+1; assert(e>=r); #ifdef DEBUG_PRINTOUT cerr << "reCrypt: p="<<p<<", r="<<r<<", e="<<e<<" ePrime="<<ePrime << ", q="<<q<<endl; #endif // can only bootstrap ciphertext with plaintext-space dividing p^r assert(p2r % ptxtSpace == 0); FHE_NTIMER_START(preProcess); // Make sure that this ciphertxt is in canonical form if (!ctxt.inCanonicalForm()) ctxt.reLinearize(); // Mod-switch down if needed IndexSet s = ctxt.getPrimeSet() / getContext().specialPrimes; // set minus if (s.card()>2) { // leave only bottom two primes long frst = s.first(); long scnd = s.next(frst); IndexSet s2(frst,scnd); s.retain(s2); // retain only first two primes } ctxt.modDownToSet(s); // key-switch to the bootstrapping key ctxt.reLinearize(recryptKeyID); // "raw mod-switch" to the bootstrapping mosulus q=p^e+1. vector<ZZX> zzParts; // the mod-switched parts, in ZZX format double noise = ctxt.rawModSwitch(zzParts, q); noise = sqrt(noise); // Add multiples of p2r and q to make the zzParts divisible by p^{e'} long maxU=0; for (long i=0; i<(long)zzParts.size(); i++) { // make divisible by p^{e'} long newMax = makeDivisible(zzParts[i].rep, p2ePrime, p2r, q, getContext().rcData.alpha); zzParts[i].normalize(); // normalize after working directly on the rep if (maxU < newMax) maxU = newMax; } // Check that the estimated noise is still low if (noise + maxU*p2r*(skHwts[recryptKeyID]+1) > q/2) cerr << " * noise/q after makeDivisible = " << ((noise + maxU*p2r*(skHwts[recryptKeyID]+1))/q) << endl; for (long i=0; i<(long)zzParts.size(); i++) zzParts[i] /= p2ePrime; // divide by p^{e'} // Multiply the post-processed cipehrtext by the encrypted sKey #ifdef DEBUG_PRINTOUT cerr << "+ Before recryption "; decryptAndPrint(cerr, recryptEkey, *dbgKey, *dbgEa, printFlag); #endif double p0size = to_double(coeffsL2Norm(zzParts[0])); double p1size = to_double(coeffsL2Norm(zzParts[1])); ctxt = recryptEkey; ctxt.multByConstant(zzParts[1], p1size*p1size); ctxt.addConstant(zzParts[0], p0size*p0size); #ifdef DEBUG_PRINTOUT cerr << "+ Before linearTrans1 "; decryptAndPrint(cerr, ctxt, *dbgKey, *dbgEa, printFlag); #endif FHE_NTIMER_STOP(preProcess); // Move the powerful-basis coefficients to the plaintext slots FHE_NTIMER_START(LinearTransform1); ctxt.getContext().rcData.firstMap->apply(ctxt); FHE_NTIMER_STOP(LinearTransform1); #ifdef DEBUG_PRINTOUT cerr << "+ After linearTrans1 "; decryptAndPrint(cerr, ctxt, *dbgKey, *dbgEa, printFlag); #endif // Extract the digits e-e'+r-1,...,e-e' (from fully packed slots) extractDigitsPacked(ctxt, e-ePrime, r, ePrime, context.rcData.unpackSlotEncoding); #ifdef DEBUG_PRINTOUT cerr << "+ Before linearTrans2 "; decryptAndPrint(cerr, ctxt, *dbgKey, *dbgEa, printFlag); #endif // Move the slots back to powerful-basis coefficients FHE_NTIMER_START(LinearTransform2); ctxt.getContext().rcData.secondMap->apply(ctxt); FHE_NTIMER_STOP(LinearTransform2); }
// Main entry point: Evaluate a cleartext polynomial on an encrypted input void polyEval(Ctxt& ret, ZZX poly, const Ctxt& x, long k) // Note: poly is passed by value, so caller keeps the original { if (deg(poly)<=2) { // nothing to optimize here if (deg(poly)<1) { // A constant ret.clear(); ret.addConstant(coeff(poly, 0)); } else { // A linear or quadratic polynomial DynamicCtxtPowers babyStep(x, deg(poly)); simplePolyEval(ret, poly, babyStep); } return; } // How many baby steps: set k~sqrt(n/2), rounded up/down to a power of two // FIXME: There may be some room for optimization here: it may be possible // to choose k as something other than a power of two and still maintain // optimal depth, in principle we can try all possible values of k between // two consecutive powers of two and choose the one that gives the least // number of multiplies, conditioned on minimum depth. if (k<=0) { long kk = (long) sqrt(deg(poly)/2.0); k = 1L << NextPowerOfTwo(kk); // heuristic: if k>>kk then use a smaler power of two if ((k==16 && deg(poly)>167) || (k>16 && k>(1.44*kk))) k /= 2; } #ifdef DEBUG_PRINTOUT cerr << " k="<<k; #endif long n = divc(deg(poly),k); // n = ceil(deg(p)/k), deg(p) >= k*n DynamicCtxtPowers babyStep(x, k); const Ctxt& x2k = babyStep.getPower(k); // Special case when deg(p)>k*(2^e -1) if (n==(1L << NextPowerOfTwo(n))) { // n is a power of two DynamicCtxtPowers giantStep(x2k, n/2); degPowerOfTwo(ret, poly, k, babyStep, giantStep); return; } // If n is not a power of two, ensure that poly is monic and that // its degree is divisible by k, then call the recursive procedure const ZZ p = to_ZZ(x.getPtxtSpace()); ZZ top = LeadCoeff(poly); ZZ topInv; // the inverse mod p of the top coefficient of poly (if any) bool divisible = (n*k == deg(poly)); // is the degree divisible by k? long nonInvertibe = InvModStatus(topInv, top, p); // 0 if invertible, 1 if not // FIXME: There may be some room for optimization below: instead of // adding a term X^{n*k} we can add X^{n'*k} for some n'>n, so long // as n' is smaller than the next power of two. We could save a few // multiplications since giantStep[n'] may be easier to compute than // giantStep[n] when n' has fewer 1's than n in its binary expansion. ZZ extra = ZZ::zero(); // extra!=0 denotes an added term extra*X^{n*k} if (!divisible || nonInvertibe) { // need to add a term top = to_ZZ(1); // new top coefficient is one topInv = top; // also the new inverse is one // set extra = 1 - current-coeff-of-X^{n*k} extra = SubMod(top, coeff(poly,n*k), p); SetCoeff(poly, n*k); // set the top coefficient of X^{n*k} to one } long t = IsZero(extra)? divc(n,2) : n; DynamicCtxtPowers giantStep(x2k, t); if (!IsOne(top)) { poly *= topInv; // Multiply by topInv to make into a monic polynomial for (long i=0; i<=n*k; i++) rem(poly[i], poly[i], p); poly.normalize(); } recursivePolyEval(ret, poly, k, babyStep, giantStep); if (!IsOne(top)) { ret.multByConstant(top); } if (!IsZero(extra)) { // if we added a term, now is the time to subtract back Ctxt topTerm = giantStep.getPower(n); topTerm.multByConstant(extra); ret -= topTerm; } }
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; }
// A recursive matrix-by-vector multiply, used by the dense matrix code. // This routine is optimized to use only the rotate1D routine rather // than the more expensive linear-array rotations. long rec_mul(const Ctxt* pdata, long dim, long idx, const vector<long>& idxes) { if (dim >= ea.dimension()) { // Last dimension (recursion edge condition) zzX pt; zzX* zxPtr=nullptr; DoubleCRT* dxPtr=nullptr; // Check if we have the relevant constant in cache CachedzzxMatrix* zcp; CachedDCRTMatrix* dcp; mat.getCache(&zcp, &dcp); if (dcp != nullptr) // DoubleCRT cache exists dxPtr = (*dcp)[idx].get(); else if (zcp != nullptr) // zzx cache exists but no DoubleCRT zxPtr = (*zcp)[idx].get(); else if (!processDiagonal(pt, idxes)) // no cache, compute const zxPtr = &pt; // if it is not a zero value, point to it // if constant is zero, return without doing anything if (zxPtr==nullptr && dxPtr==nullptr) return idx+1; // Constant is non-zero, store it in cache and/or multiply/add it if (pdata!=nullptr && res!=nullptr) { Ctxt tmp = *pdata; if (dxPtr!=nullptr) tmp.multByConstant(*dxPtr); // mult by DCRT else tmp.multByConstant(*zxPtr); // mult by zzx *res += tmp; } if (buildCache==cachezzX) { (*zCache)[idx].reset(new zzX(*zxPtr)); } else if (buildCache==cacheDCRT) { (*dCache)[idx].reset(new DoubleCRT(*zxPtr, ea.getContext())); } return idx+1; } // not the last dimension, make a recursive call long sdim = ea.sizeOfDimension(dims[dim]); // compute "in spirit" sum_i (pdata >> i) * i'th-diagonal, but // adjust the indexes so that we only need to rotate the cipehrtext // along the different dimensions separately for (long offset = 0; offset < sdim; offset++) { vector<long> idxes1; ea.EncryptedArrayBase::rotate1D(idxes1, idxes, dims[dim], offset); if (pdata!=nullptr && res!=nullptr) { Ctxt pdata1 = *pdata; ea.rotate1D(pdata1, dims[dim], offset); // indexes adjusted, make the recursive call idx = rec_mul(&pdata1, dim+1, idx, idxes1); } else // don't bother with the ciphertext idx = rec_mul(pdata, dim+1, idx, idxes1); } return idx; }