void Cmod<type>::iFFT(zpx &x, const zzv& y)const { FHE_TIMER_START; zpBak bak; bak.save(); context.restore(); zp rt; long m = getM(); // convert input to zpx format, initializing only the coeffs i s.t. (i,m)=1 x.rep.SetLength(m); long i,j; for (i=j=0; i<m; i++) if (zMStar->inZmStar(i)) conv(x.rep[i], y[j++]); x.normalize(); conv(rt, rInv); // convert rInv to zp format BluesteinFFT(x, m, rt, *ipowers, ipowers_aux, *iRb, iRb_aux, *Ra); // call the FFT routine // reduce the result mod (Phi_m(X),q) and copy to the output polynomial x FHE_NTIMER_START("iFFT:division") rem(x, x, *phimx); // out %= (Phi_m(X),q) FHE_NTIMER_STOP("iFFT:division") // normalize zp mm_inv; conv(mm_inv, m_inv); x *= mm_inv; FHE_TIMER_STOP; }
/************** Each round consists of the following: 1. c1.multiplyBy(c0) 2. c0 += random constant 3. c2 *= random constant 4. tmp = c1 5. ea.rotate(tmp, random amount in [-nSlots/2, nSlots/2]) 6. c2 += tmp 7. ea.rotate(c2, random amount in [1-nSlots, nSlots-1]) 8. c1.negate() 9. c3.multiplyBy(c2) 10. c0 -= c3 **************/ void testGeneralOps(const FHEPubKey& publicKey, const FHESecKey& secretKey, const EncryptedArrayCx& ea, double epsilon, long nRounds) { long nslots = ea.size(); char buffer[32]; vector<cx_double> p0, p1, p2, p3; ea.random(p0); ea.random(p1); ea.random(p2); ea.random(p3); Ctxt c0(publicKey), c1(publicKey), c2(publicKey), c3(publicKey); ea.encrypt(c0, publicKey, p0, /*size=*/1.0); ea.encrypt(c1, publicKey, p1, /*size=*/1.0); ea.encrypt(c2, publicKey, p2, /*size=*/1.0); ea.encrypt(c3, publicKey, p3, /*size=*/1.0); resetAllTimers(); FHE_NTIMER_START(Circuit); for (long i = 0; i < nRounds; i++) { if (verbose) std::cout << "*** round " << i << "..."<<endl; long shamt = RandomBnd(2*(nslots/2) + 1) - (nslots/2); // random number in [-nslots/2..nslots/2] long rotamt = RandomBnd(2*nslots - 1) - (nslots - 1); // random number in [-(nslots-1)..nslots-1] // two random constants vector<cx_double> const1, const2; ea.random(const1); ea.random(const2); ZZX const1_poly, const2_poly; ea.encode(const1_poly, const1, /*size=*/1.0); ea.encode(const2_poly, const2, /*size=*/1.0); mul(p1, p0); // c1.multiplyBy(c0) c1.multiplyBy(c0); if (verbose) { CheckCtxt(c1, "c1*=c0"); debugCompare(ea, secretKey, p1, c1, epsilon); } add(p0, const1); // c0 += random constant c0.addConstant(const1_poly); if (verbose) { CheckCtxt(c0, "c0+=k1"); debugCompare(ea, secretKey, p0, c0, epsilon); } mul(p2, const2); // c2 *= random constant c2.multByConstant(const2_poly); if (verbose) { CheckCtxt(c2, "c2*=k2"); debugCompare(ea, secretKey, p2, c2, epsilon); } vector<cx_double> tmp_p(p1); // tmp = c1 Ctxt tmp(c1); sprintf(buffer, "tmp=c1>>=%d", (int)shamt); rotate(tmp_p, shamt); // ea.shift(tmp, random amount in [-nSlots/2,nSlots/2]) ea.rotate(tmp, shamt); if (verbose) { CheckCtxt(tmp, buffer); debugCompare(ea, secretKey, tmp_p, tmp, epsilon); } add(p2, tmp_p); // c2 += tmp c2 += tmp; if (verbose) { CheckCtxt(c2, "c2+=tmp"); debugCompare(ea, secretKey, p2, c2, epsilon); } sprintf(buffer, "c2>>>=%d", (int)rotamt); rotate(p2, rotamt); // ea.rotate(c2, random amount in [1-nSlots, nSlots-1]) ea.rotate(c2, rotamt); if (verbose) { CheckCtxt(c2, buffer); debugCompare(ea, secretKey, p2, c2, epsilon); } negateVec(p1); // c1.negate() c1.negate(); if (verbose) { CheckCtxt(c1, "c1=-c1"); debugCompare(ea, secretKey, p1, c1, epsilon); } mul(p3, p2); // c3.multiplyBy(c2) c3.multiplyBy(c2); if (verbose) { CheckCtxt(c3, "c3*=c2"); debugCompare(ea, secretKey, p3, c3, epsilon); } sub(p0, p3); // c0 -= c3 c0 -= c3; if (verbose) { CheckCtxt(c0, "c0=-c3"); debugCompare(ea, secretKey, p0, c0, epsilon); } } c0.cleanUp(); c1.cleanUp(); c2.cleanUp(); c3.cleanUp(); FHE_NTIMER_STOP(Circuit); vector<cx_double> pp0, pp1, pp2, pp3; ea.decrypt(c0, secretKey, pp0); ea.decrypt(c1, secretKey, pp1); ea.decrypt(c2, secretKey, pp2); ea.decrypt(c3, secretKey, pp3); std::cout << "Test "<<nRounds<<" rounds of mixed operations, "; if (cx_equals(pp0, p0,conv<double>(epsilon*c0.getPtxtMag())) && cx_equals(pp1, p1,conv<double>(epsilon*c1.getPtxtMag())) && cx_equals(pp2, p2,conv<double>(epsilon*c2.getPtxtMag())) && cx_equals(pp3, p3,conv<double>(epsilon*c3.getPtxtMag()))) std::cout << "PASS\n\n"; else { std::cout << "FAIL:\n"; std::cout << " max(p0)="<<largestCoeff(p0) << ", max(pp0)="<<largestCoeff(pp0) << ", maxDiff="<<calcMaxDiff(p0,pp0) << endl; std::cout << " max(p1)="<<largestCoeff(p1) << ", max(pp1)="<<largestCoeff(pp1) << ", maxDiff="<<calcMaxDiff(p1,pp1) << endl; std::cout << " max(p2)="<<largestCoeff(p2) << ", max(pp2)="<<largestCoeff(pp2) << ", maxDiff="<<calcMaxDiff(p2,pp2) << endl; std::cout << " max(p3)="<<largestCoeff(p3) << ", max(pp3)="<<largestCoeff(pp3) << ", maxDiff="<<calcMaxDiff(p3,pp3) << endl<<endl; } if (verbose) { std::cout << endl; printAllTimers(); std::cout << endl; } resetAllTimers(); }
// Extract digits from fully packed slots void extractDigitsPacked(Ctxt& ctxt, long botHigh, long r, long ePrime, const vector<ZZX>& unpackSlotEncoding) { FHE_TIMER_START; // Step 1: unpack the slots of ctxt FHE_NTIMER_START(unpack); ctxt.cleanUp(); // Apply the d automorphisms and store them in scratch area long d = ctxt.getContext().zMStar.getOrdP(); vector<Ctxt> scratch; // used below vector<Ctxt> unpacked(d, Ctxt(ZeroCtxtLike, ctxt)); { // explicit scope to force all temporaries to be released vector< shared_ptr<DoubleCRT> > coeff_vector; coeff_vector.resize(d); for (long i = 0; i < d; i++) coeff_vector[i] = shared_ptr<DoubleCRT>(new DoubleCRT(unpackSlotEncoding[i], ctxt.getContext(), ctxt.getPrimeSet()) ); Ctxt tmp1(ZeroCtxtLike, ctxt); Ctxt tmp2(ZeroCtxtLike, ctxt); // FIXME: implement using hoisting! for (long j = 0; j < d; j++) { // process jth Frobenius tmp1 = ctxt; tmp1.frobeniusAutomorph(j); tmp1.cleanUp(); // FIXME: not clear if we should call cleanUp here for (long i = 0; i < d; i++) { tmp2 = tmp1; tmp2.multByConstant(*coeff_vector[mcMod(i+j, d)]); unpacked[i] += tmp2; } } } FHE_NTIMER_STOP(unpack); // Step 2: extract the digits top-1,...,0 from the slots of unpacked[i] long p = ctxt.getContext().zMStar.getP(); long p2r = power_long(p,r); long topHigh = botHigh + r-1; #ifdef DEBUG_PRINTOUT cerr << "+ After unpack "; decryptAndPrint(cerr, unpacked[0], *dbgKey, *dbgEa, printFlag); cerr << " extracting "<<(topHigh+1)<<" digits\n"; #endif if (p==2 && r>2) topHigh--; // For p==2 we sometime get a bit for free FHE_NTIMER_START(extractDigits); for (long i=0; i<(long)unpacked.size(); i++) { if (topHigh<=0) { // extracting LSB = no-op scratch.assign(1,unpacked[i]); } else { // extract digits topHigh...0, store them in scratch extractDigits(scratch, unpacked[i], topHigh+1); } // set upacked[i] = -\sum_{j=botHigh}^{topHigh} scratch[j] * p^{j-botHigh} if (topHigh >= (long)scratch.size()) { topHigh = scratch.size() -1; cerr << " @ suspect: not enough digits in extractDigitsPacked\n"; } unpacked[i] = scratch[topHigh]; for (long j=topHigh-1; j>=botHigh; --j) { unpacked[i].multByP(); unpacked[i] += scratch[j]; } if (p==2 && botHigh>0) // For p==2, subtract also the previous bit unpacked[i] += scratch[botHigh-1]; unpacked[i].negate(); if (r>ePrime) { // Add in digits from the bottom part, if any long topLow = r-1 - ePrime; Ctxt tmp = scratch[topLow]; for (long j=topLow-1; j>=0; --j) { tmp.multByP(); tmp += scratch[j]; } if (ePrime>0) tmp.multByP(ePrime); // multiply by p^e' unpacked[i] += tmp; } unpacked[i].reducePtxtSpace(p2r); // Our plaintext space is now mod p^r } FHE_NTIMER_STOP(extractDigits); #ifdef DEBUG_PRINTOUT cerr << "+ Before repack "; decryptAndPrint(cerr, unpacked[0], *dbgKey, *dbgEa, printFlag); #endif // Step 3: re-pack the slots FHE_NTIMER_START(repack); const EncryptedArray& ea2 = *ctxt.getContext().ea; ZZX xInSlots; vector<ZZX> xVec(ea2.size()); ctxt = unpacked[0]; for (long i=1; i<d; i++) { x2iInSlots(xInSlots, i, xVec, ea2); unpacked[i].multByConstant(xInSlots); ctxt += unpacked[i]; } FHE_NTIMER_STOP(repack); }
// 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); }
void TestIt(long m, long p, long r, long d, long L, long bnd, long B) { cout << "*** TestIt" << (isDryRun()? "(dry run):" : ":") << " m=" << m << ", p=" << p << ", r=" << r << ", d=" << d << ", L=" << L << ", bnd=" << bnd << ", B=" << B << endl; setTimersOn(); FHEcontext context(m, p, r); buildModChain(context, L, /*c=*/2); context.zMStar.printout(); cout << endl; FHESecKey secretKey(context); const FHEPubKey& publicKey = secretKey; secretKey.GenSecKey(/*w=*/64); // A Hamming-weight-w secret key ZZX G; if (d == 0) G = context.alMod.getFactorsOverZZ()[0]; else G = makeIrredPoly(p, d); cout << "G = " << G << "\n"; cout << "generating key-switching matrices... "; addSome1DMatrices(secretKey); // compute key-switching matrices that we need cout << "done\n"; cout << "computing masks and tables for rotation..."; EncryptedArray ea(context, G); cout << "done\n"; PlaintextArray xp0(ea), xp1(ea); xp0.random(); xp1.random(); Ctxt xc0(publicKey); ea.encrypt(xc0, publicKey, xp0); ZZX poly_xp1; ea.encode(poly_xp1, xp1); cout << "** Testing replicate():\n"; bool error = false; Ctxt xc1 = xc0; CheckCtxt(xc1, "before replicate"); replicate(ea, xc1, ea.size()/2); if (!check_replicate(xc1, xc0, ea.size()/2, secretKey, ea)) error = true; CheckCtxt(xc1, "after replicate"); // Get some timing results for (long i=0; i<20 && i<ea.size(); i++) { xc1 = xc0; FHE_NTIMER_START(replicate); replicate(ea, xc1, i); if (!check_replicate(xc1, xc0, i, secretKey, ea)) error = true; FHE_NTIMER_STOP(replicate); } cout << " Replicate test " << (error? "failed :(\n" : "succeeded :)") << endl<< endl; printAllTimers(); cout << "\n** Testing replicateAll()... " << std::flush; #ifdef DEBUG_PRINTOUT replicateVerboseFlag = true; #else replicateVerboseFlag = false; #endif error = false; ReplicateTester *handler = new ReplicateTester(secretKey, ea, xp0, B); try { FHE_NTIMER_START(replicateAll); replicateAll(ea, xc0, handler, bnd); } catch (StopReplicate) { } cout << (handler->error? "failed :(\n" : "succeeded :)") << ", total time=" << handler->t_total << " (" << ((B>0)? B : ea.size()) << " vectors)\n"; delete handler; }
void TestIt(long p, long r, long c, long _k, long w, long L, Vec<long>& mvec, Vec<long>& gens, Vec<long>& ords, long useCache) { if (lsize(mvec)<1) { // use default values mvec.SetLength(3); gens.SetLength(3); ords.SetLength(3); mvec[0] = 7; mvec[1] = 3; mvec[2] = 221; gens[0] = 3979; gens[1] = 3095; gens[2] = 3760; ords[0] = 6; ords[1] = 2; ords[2] = -8; } if (!noPrint) cout << "*** TestIt" << (dry? " (dry run):" : ":") << " p=" << p << ", r=" << r << ", c=" << c << ", k=" << _k << ", w=" << w << ", L=" << L << ", mvec=" << mvec << ", " << ", useCache = " << useCache << endl; setTimersOn(); setDryRun(false); // Need to get a "real context" to test ThinEvalMap // mvec is supposed to include the prime-power factorization of m long nfactors = mvec.length(); for (long i = 0; i < nfactors; i++) for (long j = i+1; j < nfactors; j++) assert(GCD(mvec[i], mvec[j]) == 1); // multiply all the prime powers to get m itself long m = computeProd(mvec); assert(GCD(p, m) == 1); // build a context with these generators and orders vector<long> gens1, ords1; convert(gens1, gens); convert(ords1, ords); FHEcontext context(m, p, r, gens1, ords1); buildModChain(context, L, c); if (!noPrint) { context.zMStar.printout(); // print structure of Zm* /(p) to cout cout << endl; } long d = context.zMStar.getOrdP(); long phim = context.zMStar.getPhiM(); long nslots = phim/d; setDryRun(dry); // Now we can set the dry-run flag if desired FHESecKey secretKey(context); const FHEPubKey& publicKey = secretKey; secretKey.GenSecKey(w); // A Hamming-weight-w secret key addSome1DMatrices(secretKey); // compute key-switching matrices that we need addFrbMatrices(secretKey); // compute key-switching matrices that we need // GG defines the plaintext space Z_p[X]/GG(X) ZZX GG; GG = context.alMod.getFactorsOverZZ()[0]; EncryptedArray ea(context, GG); zz_p::init(context.alMod.getPPowR()); Vec<zz_p> val0(INIT_SIZE, nslots); for (auto& x: val0) random(x); vector<ZZX> val1; val1.resize(nslots); for (long i = 0; i < nslots; i++) { val1[i] = conv<ZZX>(conv<ZZ>(rep(val0[i]))); } Ctxt ctxt(publicKey); ea.encrypt(ctxt, publicKey, val1); resetAllTimers(); FHE_NTIMER_START(ALL); // Compute homomorphically the transformation that takes the // coefficients packed in the slots and produces the polynomial // corresponding to cube if (!noPrint) CheckCtxt(ctxt, "init"); if (!noPrint) cout << "build ThinEvalMap\n"; ThinEvalMap map(ea, /*minimal=*/false, mvec, /*invert=*/false, /*build_cache=*/false); // compute the transformation to apply if (!noPrint) cout << "apply ThinEvalMap\n"; if (useCache) map.upgrade(); map.apply(ctxt); // apply the transformation to ctxt if (!noPrint) CheckCtxt(ctxt, "ThinEvalMap"); if (!noPrint) cout << "check results\n"; if (!noPrint) cout << "build ThinEvalMap\n"; ThinEvalMap imap(ea, /*minimal=*/false, mvec, /*invert=*/true, /*build_cache=*/false); // compute the transformation to apply if (!noPrint) cout << "apply ThinEvalMap\n"; if (useCache) imap.upgrade(); imap.apply(ctxt); // apply the transformation to ctxt if (!noPrint) { CheckCtxt(ctxt, "ThinEvalMap"); cout << "check results\n"; } #if 1 /* create dirty version of ctxt */ Vec<zz_pX> dirty_val0; dirty_val0.SetLength(nslots); for (long i = 0; i < nslots; i++) { random(dirty_val0[i], d); SetCoeff(dirty_val0[i], 0, val0[i]); } vector<ZZX> dirty_val1; dirty_val1.resize(nslots); for (long i = 0; i < nslots; i++) { dirty_val1[i] = conv<ZZX>(dirty_val0[i]); } Ctxt dirty_ctxt(publicKey); ea.encrypt(dirty_ctxt, publicKey, dirty_val1); EvalMap dirty_map(ea, /*minimal=*/false, mvec, /*invert=*/false, /*build_cache=*/false); dirty_map.apply(dirty_ctxt); imap.apply(dirty_ctxt); #endif vector<ZZX> val2; ea.decrypt(ctxt, secretKey, val2); if (val1 == val2) cout << "ThinEvalMap: GOOD\n"; else cout << "ThinEvalMap: BAD\n"; #if 1 vector<ZZX> dirty_val2; ea.decrypt(dirty_ctxt, secretKey, dirty_val2); if (val1 == dirty_val2) cout << "ThinEvalMap: GOOD\n"; else cout << "ThinEvalMap: BAD\n"; #endif FHE_NTIMER_STOP(ALL); if (!noPrint) { cout << "\n*********\n"; printAllTimers(); cout << endl; } }