void replicateAllOrig(const EncryptedArray& ea, const Ctxt& ctxt, ReplicateHandler *handler, RepAux* repAuxPtr) { long nSlots = ea.size(); long n = GreatestPowerOfTwo(nSlots); // 2^n <= nSlots Ctxt ctxt1 = ctxt; if ((1L << n) < nSlots) SelectRange(ea, ctxt1, 0, 1L << n); RepAux repAux; if (repAuxPtr==NULL) repAuxPtr = &repAux; recursiveReplicate(ea, ctxt1, n, n, 0, 1L << n, *repAuxPtr, handler); if ((1L << n) < nSlots) { ctxt1 = ctxt; SelectRange(ea, ctxt1, 1L << n, nSlots); ea.rotate(ctxt1, -(1L << n)); recursiveReplicate(ea, ctxt1, n, n, 1L << n, nSlots, *repAuxPtr, handler); } }
void totalSums(const EncryptedArray& ea, Ctxt& ctxt) { long n = ea.size(); if (n == 1) return; Ctxt orig = ctxt; long k = NumBits(n); long e = 1; for (long i = k-2; i >= 0; i--) { Ctxt tmp1 = ctxt; ea.rotate(tmp1, e); ctxt += tmp1; // ctxt = ctxt + (ctxt >>> e) e = 2*e; if (bit(n, i)) { Ctxt tmp2 = orig; ea.rotate(tmp2, e); ctxt += tmp2; // ctxt = ctxt + (orig >>> e) // NOTE: we could have also computed // ctxt = (ctxt >>> e) + orig, however, // this would give us greater depth/noise e += 1; } } }
NTL_CLIENT #include "FHE.h" #include "timing.h" #include "EncryptedArray.h" #include <cstdio> // Map all non-zero slots to 1, leaving zero slots as zero. // Assumes that r=1, and that all the slot contain elements from GF(p^d). // // We compute x^{p^d-1} = x^{(1+p+...+p^{d-1})*(p-1)} by setting y=x^{p-1} // and then outputting y * y^p * ... * y^{p^{d-1}}, with exponentiation to // powers of p done via Frobenius. // FIXME: the computation of the "norm" y * y^p * ... * y^{p^{d-1}} // can be done using O(log d) automorphisms, rather than O(d). void mapTo01(const EncryptedArray& ea, Ctxt& ctxt) { long p = ctxt.getPtxtSpace(); if (p != ea.getPAlgebra().getP()) // ptxt space is p^r for r>1 throw helib::LogicError("mapTo01 not implemented for r>1"); if (p>2) ctxt.power(p-1); // set y = x^{p-1} long d = ea.getDegree(); if (d>1) { // compute the product of the d automorphisms std::vector<Ctxt> v(d, ctxt); for (long i=1; i<d; i++) v[i].frobeniusAutomorph(i); totalProduct(ctxt, v); } }
// 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; }
// Return in poly a polynomial with X^i encoded in all the slots static void x2iInSlots(ZZX& poly, long i, vector<ZZX>& xVec, const EncryptedArray& ea) { xVec.resize(ea.size()); ZZX x2i = ZZX(i,1); for (long j=0; j<(long)xVec.size(); j++) xVec[j] = x2i; ea.encode(poly, xVec); }
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); }
Ctxt select(Ctxt ctxt, int value, EncryptedArray ea, const FHEPubKey& publicKey) { PlaintextArray mask(ea); mask.encode(getVote(value, ea.size())); Ctxt maskCtxt(publicKey); ea.encrypt(maskCtxt, publicKey, mask); Ctxt ret(ctxt); ret.multiplyBy(maskCtxt); return ret; }
void runningSums(const EncryptedArray& ea, Ctxt& ctxt) { long n = ea.size(); long shamt = 1; while (shamt < n) { Ctxt tmp = ctxt; ea.shift(tmp, shamt); ctxt += tmp; // ctxt = ctxt + (ctxt >> shamt) shamt = 2*shamt; } }
NTL::ZZX Vector<long>::encode(const EncryptedArray &ea) const { assert(this->size() <= ea.size()); NTL::ZZX encoded; if (this->size() < ea.size()) { auto tmp(*this); tmp.resize(ea.size()); ea.encode(encoded, tmp); } else { ea.encode(encoded, *this); } return encoded; }
// selects range of slots [lo..hi) static void SelectRange(const EncryptedArray& ea, ZZX& mask, long lo, long hi) { long nSlots = ea.size(); assert(lo >= 0 && lo <= hi && hi <= nSlots); vector<long> maskArray; maskArray.resize(nSlots); for (long i = 0; i < nSlots; i++) maskArray[i] = 0; for (long i = lo; i < hi; i++) maskArray[i] = 1; ea.encode(mask, maskArray); }
// Use packed bootstrapping, so we can bootstrap all in just one go. void packedRecrypt(const CtPtrs& cPtrs, const std::vector<zzX>& unpackConsts, const EncryptedArray& ea) { FHEPubKey& pKey = (FHEPubKey&)cPtrs[0]->getPubKey(); // Allocate temporary ciphertexts for the recryption int nPacked = divc(cPtrs.size(), ea.getDegree()); // ceil(totoalNum/d) std::vector<Ctxt> cts(nPacked, Ctxt(pKey)); repack(CtPtrs_vectorCt(cts), cPtrs, ea); // pack ciphertexts // cout << "@"<< lsize(cts)<<std::flush; for (Ctxt& c: cts) { // then recrypt them c.reducePtxtSpace(2); // we only have recryption data for binary ctxt #ifdef DEBUG_PRINTOUT ZZX ptxt; decryptAndPrint((cout<<" before recryption "), c, *dbgKey, *dbgEa); dbgKey->Decrypt(ptxt, c); c.DummyEncrypt(ptxt); decryptAndPrint((cout<<" after recryption "), c, *dbgKey, *dbgEa); #else pKey.reCrypt(c); #endif } unpack(cPtrs, CtPtrs_vectorCt(cts), ea, unpackConsts); }
// Returns the result as a vector of ciphertexts void replicateAll(std::vector<Ctxt>& v, const EncryptedArray& ea, const Ctxt& ctxt, long recBound, RepAuxDim* repAuxPtr) { v.resize(ea.size(), ctxt); ExplicitReplicator handler(v); replicateAll(ea, ctxt, &handler, recBound, repAuxPtr); }
// Apply the same linear transformation to all the slots. // C is the output of ea.buildLinPolyCoeffs void applyLinPoly1(const EncryptedArray& ea, Ctxt& ctxt, const vector<ZZX>& C) { assert(&ea.getContext() == &ctxt.getContext()); long d = ea.getDegree(); assert(d == lsize(C)); long nslots = ea.size(); vector<ZZX> encodedC(d); for (long j = 0; j < d; j++) { vector<ZZX> v(nslots); for (long i = 0; i < nslots; i++) v[i] = C[j]; ea.encode(encodedC[j], v); } applyLinPolyLL(ctxt, encodedC, ea.getDegree()); }
void benchmark(const EncryptedArray & ea, const FHEPubKey & pk, const FHESecKey & sk, const MDL::Matrix<long>& data) { const long BATCH_SIZE = 5000; MDL::Timer encTimer, evalTimer; MDL::EncVector mu(pk), sigma(pk); for (long part = 0; part *BATCH_SIZE < data.rows(); part++) { long from = std::min<long>(part * BATCH_SIZE, data.rows()); long to = std::min<long>(from + BATCH_SIZE, data.rows()); encTimer.start(); auto ctxts = encrypt(data, pk, ea, from, to); encTimer.end(); evalTimer.start(); auto sum = summation(ctxts); mu += sum.first; sigma += sum.second; evalTimer.end(); } evalTimer.start(); auto mu_mu = mu.covariance(ea, data.cols()); NTL::ZZX N; std::vector<long> n(ea.size(), data.rows()); ea.encode(N, n); sigma.multByConstant(N); for (size_t col = 0; col < data.cols(); col++) { ea.rotate(mu_mu[col], col * data.cols()); sigma -= mu_mu[col]; } evalTimer.end(); MDL::Vector<long> mat; sigma.unpack(mat, sk, ea, true); for (int i = 0; i < data.cols(); i++) { for (int j = 0; j < data.cols(); j++) { std::cout << mat[i * data.cols() + j] << " "; } std::cout << std::endl; } printf("Covariance of %zd data, enc %f, eval %f\n", data.rows(), encTimer.second(), evalTimer.second()); }
void replicate0(const EncryptedArray& ea, Ctxt& ctxt, long pos) { long dim = ea.dimension(); for (long d = 0; d < dim; d++) { if (!ea.nativeDimension(d)) { long shamt = -ea.coordinate(d, pos); ea.rotate1D(ctxt, d, shamt, true); // "don't care" } Ctxt ctxt_orig = ctxt; long sz = ea.sizeOfDimension(d); long k = NumBits(sz); long e = 1; // now process bits k-2 down to 0 for (long j = k-2; j >= 0; j--) { // e -> 2*e Ctxt tmp = ctxt; ea.rotate1D(tmp, d, e, true); // "don't care" ctxt += tmp; e = 2*e; long b = bit(sz, j); // bit j of sz // e -> e+b if (b) { ea.rotate1D(ctxt, d, 1, true); // "don't care" ctxt += ctxt_orig; e++; } } } }
/** * @brief Extract coefficients from ciphertext polynomial * @param coeffs extracted coefficients * @param ctxt ciphertext * @param n extract "n" lowest degree coefficients */ void extractCoeffs(EncryptedArray& ea, vector<Ctxt>& coeffs, Ctxt& ctxt, long n) { long d = ea.getDegree(); if (d < n) n = d; coeffs.clear(); vector<Ctxt> conj; for (int coeff = 0; coeff < n; ++coeff) { vector<ZZX> LM(d); LM[coeff] = ZZX(0, 1); // "building" the linearized-polynomial coefficients vector<ZZX> C(d); ea.buildLinPolyCoeffs(C, LM); coeffs.push_back(ctxt); applyLinPoly1(ea, coeffs[coeff], C, conj); } }
void decryptAndPrint(ostream& s, const Ctxt& ctxt, const FHESecKey& sk, const EncryptedArray& ea, long flags) { const FHEcontext& context = ctxt.getContext(); xdouble noiseEst = sqrt(ctxt.getNoiseVar()); xdouble modulus = xexp(context.logOfProduct(ctxt.getPrimeSet())); vector<ZZX> ptxt; ZZX p, pp; sk.Decrypt(p, ctxt, pp); s << "plaintext space mod "<<ctxt.getPtxtSpace() << ", level="<<ctxt.findBaseLevel() << ", \n |noise|=q*" << (coeffsL2Norm(pp)/modulus) << ", |noiseEst|=q*" << (noiseEst/modulus) <<endl; if (flags & FLAG_PRINT_ZZX) { s << " before mod-p reduction="; printZZX(s,pp) <<endl; } if (flags & FLAG_PRINT_POLY) { s << " after mod-p reduction="; printZZX(s,p) <<endl; } if (flags & FLAG_PRINT_VEC) { ea.decode(ptxt, p); if (ea.getAlMod().getTag() == PA_zz_p_tag && ctxt.getPtxtSpace() != ea.getAlMod().getPPowR()) { long g = GCD(ctxt.getPtxtSpace(), ea.getAlMod().getPPowR()); for (long i=0; i<ea.size(); i++) PolyRed(ptxt[i], g, true); } s << " decoded to "; if (deg(p) < 40) // just pring the whole thing s << ptxt << endl; else if (ptxt.size()==1) // a single slot printZZX(s, ptxt[0]) <<endl; else { // print first and last slots printZZX(s, ptxt[0],20) << "--"; printZZX(s, ptxt[ptxt.size()-1], 20) <<endl; } } }
vector<PlaintextArray> decryptVotes(vector<Ctxt> votes, EncryptedArray ea, const FHESecKey secretKey) { vector<PlaintextArray> ret; for(int i = 0; i < votes.size(); i++) { PlaintextArray p(ea); ea.decrypt(votes[i], secretKey, p); ret.push_back(p); } return ret; }
vector<Ctxt> encryptVotes(vector<PlaintextArray> votes, EncryptedArray ea, const FHEPubKey& publicKey) { vector<Ctxt> ret; for(int i = 0; i < votes.size(); i++) { Ctxt c(publicKey); ea.encrypt(c, publicKey, votes[i]); ret.push_back(c); } return ret; }
// Apply a permutation network to a ciphertext void PermNetwork::applyToCtxt(Ctxt& c, const EncryptedArray& ea) const { const PAlgebra& al = ea.getPAlgebra(); // 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 } }
// selects range of slots [lo..hi) in dimension d static void SelectRangeDim(const EncryptedArray& ea, ZZX& mask, long lo, long hi, long d) { long nSlots = ea.size(); assert(d >= 0 && d < ea.dimension()); assert(lo >= 0 && lo <= hi && hi <= ea.sizeOfDimension(d)); vector<long> maskArray; maskArray.resize(nSlots); for (long i = 0; i < nSlots; i++) { long c = ea.coordinate(d, i); if (c >= lo && c < hi) maskArray[i] = 1; else maskArray[i] = 0; } ea.encode(mask, maskArray); }
static void replicateOneBlock(const EncryptedArray& ea, Ctxt& ctxt, long pos, long blockSize, long d) { long dSize = ea.sizeOfDimension(d); // Move this block to position 0. We can skip this step in // "good dimensions" of size divisible by the block size if (pos != 0 && (!ea.nativeDimension(d) || dSize % blockSize != 0) ) { ea.rotate1D(ctxt, d, -pos*blockSize, true); } long sz = dSize/blockSize; // how many blocks fit in this dimension if (sz == 1) return; // nothing to do, only one block in this dimension // do the actual replication using "shift and add" long k = NumBits(sz); long e = 1; Ctxt ctxt_orig = ctxt; // now process bits k-2 down to 0 for (long j = k-2; j >= 0; j--) { // e -> 2*e Ctxt tmp = ctxt; ea.rotate1D(tmp, d, e*blockSize, /*don't-care-flag=*/true); ctxt += tmp; e = 2*e; long b = bit(sz, j); // bit j of sz // e -> e+b if (b) { ea.rotate1D(ctxt, d, 1*blockSize, /*don't-care-flag=*/true); ctxt += ctxt_orig; e++; } } }
NTL_CLIENT #include "FHE.h" #include "replicate.h" #include "timing.h" static bool check_replicate(const Ctxt& c1, const Ctxt& c0, long i, const FHESecKey& sKey, const EncryptedArray& ea) { PlaintextArray pa0(ea), pa1(ea); ea.decrypt(c0, sKey, pa0); ea.decrypt(c1, sKey, pa1); pa0.replicate(i); return pa1.equals(pa0); // returns true if replication succeeded }
static void recursiveReplicate(const EncryptedArray& ea, const Ctxt& ctxt, long n, long k, long pos, long limit, RepAux& repAux, ReplicateHandler *handler) { if (pos >= limit) return; if (replicateVerboseFlag) { // DEBUG code cerr << "check: " << k; CheckCtxt(ctxt, ""); } long nSlots = ea.size(); if (k == 0) { if ( (1L << n) >= nSlots) { handler->handle(ctxt); return; } // need to replicate to fill positions [ (1L << n) .. nSlots ) if (repAux.tab(0).null()) { // need to generate mask ZZX mask; SelectRange(ea, mask, 0, nSlots - (1L << n)); repAux.tab(0).set_ptr(new DoubleCRT(mask, ea.getContext())); } Ctxt ctxt_tmp = ctxt; ctxt_tmp.multByConstant(*repAux.tab(0)); ea.rotate(ctxt_tmp, 1L << n); ctxt_tmp += ctxt; handler->handle(ctxt_tmp); return; } k--; Ctxt ctxt_masked = ctxt; { // artificial scope to miminize storage in // the recursion { // another artificial scope // mask should be at index k+1 if (repAux.tab(k+1).null()) { // need to generate mask vector< long > maskArray; maskArray.resize(nSlots); for (long i = 0; i < (1L << n); i++) maskArray[i] = 1- bit(i, k); // the reverse of bit k of i for (long i = (1L << n); i < nSlots; i++) maskArray[i] = 0; ZZX mask; ea.encode(mask, maskArray); repAux.tab(k+1).set_ptr(new DoubleCRT(mask, ea.getContext())); } ctxt_masked.multByConstant(*repAux.tab(k+1)); } Ctxt ctxt_left = ctxt_masked; ea.rotate(ctxt_left, 1L << k); ctxt_left += ctxt_masked; recursiveReplicate(ea, ctxt_left, n, k, pos, limit, repAux, handler); } pos += (1L << k); if (pos >= limit) return; Ctxt ctxt_right = ctxt; ctxt_right -= ctxt_masked; ctxt_masked = ctxt_right; // reuse ctxt_masked as a temp ea.rotate(ctxt_masked, -(1L << k)); ctxt_right += ctxt_masked; recursiveReplicate(ea, ctxt_right, n, k, pos, limit, repAux, handler); }
// recursiveReplicateDim: // d = dimension // ea.sizeOfDimension(d)/2 <= extent <= ea.sizeOfDimension(d), // only positions [0..extent) are non-zero // 1 <= 2^k <= extent: size of current interval // 0 <= pos < ea.sizeOfDimension(d): relative position of first vector // 0 <= limit < ea.sizeOfDimension(): max # of positions to process // dimProd: product of dimensions 0..d // recBound: recursion bound (controls noise) // // SHAI: limit and extent are always the same, it seems static void recursiveReplicateDim(const EncryptedArray& ea, const Ctxt& ctxt, long d, long extent, long k, long pos, long limit, long dimProd, long recBound, RepAuxDim& repAux, ReplicateHandler *handler) { if (pos >= limit) return; if (replicateVerboseFlag) { // DEBUG code cerr << "check: " << k; CheckCtxt(ctxt, ""); } long dSize = ea.sizeOfDimension(d); long nSlots = ea.size(); if (k == 0) { // last level in this dimension: blocks of size 2^k=1 if ( extent >= dSize) { // nothing to do in this dimension replicateAllNextDim(ea, ctxt, d+1, dimProd, recBound, repAux, handler); return; } // SHAI: Will we ever have extent > dSize?? // need to replicate to fill positions [ (1L << n) .. dSize-1 ] if (repAux.tab(d,0).null()) { // generate mask if not there already ZZX mask; SelectRangeDim(ea, mask, 0, dSize - extent, d); repAux.tab(d, 0).set_ptr(new DoubleCRT(mask, ea.getContext())); } Ctxt ctxt_tmp = ctxt; ctxt_tmp.multByConstant(*repAux.tab(d, 0)); ea.rotate1D(ctxt_tmp, d, extent, /*don't-care-flag=*/true); ctxt_tmp += ctxt; replicateAllNextDim(ea, ctxt_tmp, d+1, dimProd, recBound, repAux, handler); return; } // If we need to stop early, call the handler if (handler->earlyStop(d, k, dimProd)) { handler->handle(ctxt); return; } k--; Ctxt ctxt_masked = ctxt; { // artificial scope to miminize storage in the recursion { // another artificial scope (SHAI: this seems redundant) // generate mask at index k+1, if not there yet if (repAux.tab(d, k+1).null()) { // need to generate vector< long > maskArray(nSlots,0); for (long i = 0; i < nSlots; i++) { long c = ea.coordinate(d, i); if (c < extent && bit(c, k) == 0) maskArray[i] = 1; } // store this mask in the repAux table ZZX mask; ea.encode(mask, maskArray); repAux.tab(d, k+1).set_ptr(new DoubleCRT(mask, ea.getContext())); } // Apply mask to zero out slots in ctxt ctxt_masked.multByConstant(*repAux.tab(d, k+1)); } Ctxt ctxt_left = ctxt_masked; ea.rotate1D(ctxt_left, d, 1L << k, /*don't-care-flag=*/true); ctxt_left += ctxt_masked; recursiveReplicateDim(ea, ctxt_left, d, extent, k, pos, limit, dimProd, recBound, repAux, handler); } pos += (1L << k); if (pos >= limit) return; Ctxt ctxt_right = ctxt; ctxt_right -= ctxt_masked; ctxt_masked = ctxt_right; // reuse ctxt_masked as a temp ea.rotate1D(ctxt_masked, d, -(1L << k), /*don't-care-flag=*/true); ctxt_right += ctxt_masked; recursiveReplicateDim(ea, ctxt_right, d, extent, k, pos, limit, dimProd, recBound, repAux, handler); }
PlaintextArray decrypt(Ctxt ctxt, EncryptedArray ea, const FHESecKey secretKey) { PlaintextArray p(ea); ea.decrypt(ctxt, secretKey, p); return p; }
void show(Ctxt ctxt, EncryptedArray ea, const FHESecKey secretKey) { PlaintextArray p(ea); ea.decrypt(ctxt, secretKey, p); printVoteFull(p); }
int main(int argc, char *argv[]) { ArgMapping amap; long m=53; amap.arg("m", m, "use specified value as modulus"); long p=17; amap.arg("p", p, "plaintext base"); long r=1; amap.arg("r", r, "lifting"); long levels=5; amap.arg("L", levels, "levels"); long nb_coeffs=5; amap.arg("n", nb_coeffs, "nb coefficients to extract"); amap.parse(argc, argv); cout << "\n\n******** generate parameters" << " m=" << m << ", p=" << p << ", r=" << r << ", n=" << nb_coeffs << endl; setTimersOn(); FHEcontext context(m, p, r); buildModChain(context, /*L=*/levels); // cout << context << endl; // context.zMStar.printout(); // cout << endl; cout << "Generating keys and key-switching matrices... " << std::flush; FHESecKey secretKey(context); secretKey.GenSecKey(/*w=*/64);// A Hamming-weight-w secret key addFrbMatrices(secretKey); // compute key-switching matrices that we need add1DMatrices(secretKey); // compute key-switching matrices that we need const FHEPubKey& publicKey = secretKey; cout << "done\n"; resetAllTimers(); EncryptedArray ea = *(context.ea); ea.buildLinPolyMat(false); Ctxt ctxt(publicKey); NewPlaintextArray ptxt(ea); random(ea, ptxt); // ea.encrypt(ctxt, publicKey, ptxt); ea.skEncrypt(ctxt, secretKey, ptxt); cout << "Extracting " << nb_coeffs << " coefficients..."; vector<Ctxt> coeffs; extractCoeffs(ea, coeffs, ctxt, nb_coeffs); cout << "done\n"; vector<ZZX> ptxtDec; ea.decrypt(ctxt, secretKey, ptxtDec); for (long i=0; i<(long)coeffs.size(); i++) { if (!coeffs[i].isCorrect()) { cerr << " potential decryption error for "<<i<<"th coeffs " << endl; CheckCtxt(coeffs[i], ""); exit(0); } vector<ZZX> pCoeffs; ea.decrypt(coeffs[i], secretKey, pCoeffs); assert(pCoeffs.size() == ptxtDec.size()); for (int j = 0; j < pCoeffs.size(); ++j) { if (coeff(pCoeffs[j], 0) != coeff(ptxtDec[j], i)) { cerr << "error: extracted coefficient " << i << " from " "slot " << j << " is " << coeff(pCoeffs[j], 0) << " instead of " << coeff(ptxtDec[j], i) << endl; exit(0); } } } cerr << "Extracted coefficient successfully verified!\n"; }
void replicateAllNextDim(const EncryptedArray& ea, const Ctxt& ctxt, long d, long dimProd, long recBound, RepAuxDim& repAux, ReplicateHandler *handler) { assert(d >= 0); // If already fully replicated (or we need to stop early), call the handler if (d >= ea.dimension() || handler->earlyStop(d,/*k=*/-1,dimProd)) { handler->handle(ctxt); return; } long dSize = ea.sizeOfDimension(d); dimProd *= dSize; // product of all dimensions including this one long n = GreatestPowerOfTwo(dSize); // 2^n <= dSize long k = n; // We replicate 2^k-size blocks along this dimension, then call the // recursive procedure to handle the smaller subblocks. Consider for // example a 2D 5x2 cube, so the original slots are // // ( s0 s2 s4 s6 s8 ) // ( s1 s3 s5 s7 s9 ) // // Say that we start with k=2 in the 1st dimension (of size 5), we // will prepare floor(5/2)=2 ciphertexts as follows: // // ( s0 s2 s0 s2 0 ) ( s4 s6 s4 s6 0 ) // ( s1 s3 s1 s3 0 ) ( s5 s7 s5 s7 0 ) // // The call to recursiveReplicateDim (still with k=2) will first copy // s0/s1 and s4/s5 to the zero column at the end, then make a recursive // call with k=1 that will complete the replication along the current // dimension, resulting in the 4 ciphertexts // // (s0 s0 s0 s0 s0) (s2 s2 s2 s2 s2) (s4 s4 s4 s4 s4) (s6 s6 s6 s6 s6) // (s1 s1 s1 s1 s1) (s3 s3 s3 s3 s3) (s5 s5 s5 s5 s5) (s7 s7 s7 s7 s7) // // Then a recursive call for the next dimension will complete the // replication of these entries, and a final step will deal with the // "leftover" positions s8 s9 // The logic below cut the recursion depth by starting from smaller // blocks (by default size approx n rather than 2^n). // The inital block size is controlled by the recBound parameter: // + recBound>0: blocks of size min(~n, 2^recBound). this ensures // recursion depth <= recBound, and typically much smaller (~log n) // + recBound=0: blocks of size 1 (no recursion) // + recBound<0: blocks of size 2^n (full recursion) if (recBound >= 0) { // use heuristic recursion bound k = 0; if (dSize > 2 && dimProd*NumBits(dSize) > ea.size() / 8) { k = NumBits(NumBits(dSize))-1; if (k > n) k = n; if (k > recBound) k = recBound; } } else { // SHAI: I don't understand this else case k = -recBound; if (k > n) k = n; } long blockSize = 1L << k; // blocks of size 2^k long numBlocks = dSize/blockSize; long extent = numBlocks * blockSize; // extent is an integral multiple of the block size, the recursive // call replicates only these slots, and then we have a separate // call for the leftover slots. Ctxt ctxt1 = ctxt; if (extent < dSize) { // select only the slots 0..extent-1 in this dimension if (repAux.tab1(d, 0).null()) { // generate mask if not already there ZZX mask; SelectRangeDim(ea, mask, 0, extent, d); repAux.tab1(d, 0).set_ptr(new DoubleCRT(mask, ea.getContext())); // store mask in 2nd table (tab1) } ctxt1.multByConstant(*repAux.tab1(d, 0)); // mult by mask to zero out slots } if (numBlocks == 1) { // just one block, call the recursive replication recursiveReplicateDim(ea, ctxt1, d, extent, k, 0, extent, dimProd, recBound, repAux, handler); } else { // replicate the slots in each block separately for (long pos = 0; pos < numBlocks; pos++) { Ctxt ctxt2 = ctxt1; // zero-out all the slots outside the current block SelectRangeDim(ea, ctxt2, pos*blockSize, (pos+1)*blockSize, d); // replicate the current block across this dimenssion using a // simple shift-and-add procedure. replicateOneBlock(ea, ctxt2, pos, blockSize, d); // now call the recursive replication to do the rest of the work recursiveReplicateDim(ea, ctxt2, d, extent, k, 0, extent, dimProd, recBound, repAux, handler); } } // If dSize is not an integral number of blocks, then we still need // to deal with the leftover slots. if (extent < dSize) { // zero-out the slots from before, leaving only the leftover slots ctxt1 = ctxt; if (repAux.tab1(d, 1).null()) { // generate mask if not already there ZZX mask; SelectRangeDim(ea, mask, extent, dSize, d); repAux.tab1(d, 1).set_ptr(new DoubleCRT(mask, ea.getContext())); } ctxt1.multByConstant(*repAux.tab1(d,1)); // mult by mask to zero out slots // move relevant slots to the beginning ea.rotate1D(ctxt1, d, -extent, /*don't-care-flag=*/true); // replicate the leftover block across this dimenssion using a // simple shift-and-add procedure. replicateOneBlock(ea, ctxt1, 0, blockSize, d); // now call the recursive replication to do the rest of the work recursiveReplicateDim(ea, ctxt1, d, extent, k, extent, dSize, dimProd, recBound, repAux, handler); } }