void PAlgebraModDerived<type>::decodePlaintext( vector<RX>& alphas, const RX& ptxt, const MappingData<type>& mappingData) const { long nSlots = zMStar.getNSlots(); if (isDryRun()) { alphas.assign(nSlots, RX::zero()); return; } // First decompose p into CRT components vector<RX> CRTcomps(nSlots); // allocate space for CRT component CRT_decompose(CRTcomps, ptxt); // CRTcomps[i] = p mod facors[i] if (mappingData.degG==1) { alphas = CRTcomps; return; } alphas.resize(nSlots); REBak bak; bak.save(); mappingData.contextForG.restore(); for (long i=0; i<nSlots; i++) { REX te; conv(te, CRTcomps[i]); // lift i'th CRT componnet to mod G(X) te %= mappingData.rmaps[i]; // reduce CRTcomps[i](Y) mod Qi(Y), over (Z_2[X]/G(X)) // the free term (no Y component) should be our answer (as a poly(X)) alphas[i] = rep(ConstTerm(te)); } }
void PAlgebraModDerived<type>::embedInAllSlots(RX& H, const RX& alpha, const MappingData<type>& mappingData) const { if (isDryRun()) { H = RX::zero(); return; } FHE_TIMER_START; long nSlots = zMStar.getNSlots(); vector<RX> crt(nSlots); // alloate space for CRT components // The i'th CRT component is (H mod F_t) = alpha(maps[i]) mod F_t, // where with t=T[i]. if (IsX(mappingData.G) || deg(alpha) <= 0) { // special case...no need for CompMod, which is // is not optimized for this case for (long i=0; i<nSlots; i++) // crt[i] = alpha(maps[i]) mod Ft crt[i] = ConstTerm(alpha); } else { // general case... for (long i=0; i<nSlots; i++) // crt[i] = alpha(maps[i]) mod Ft CompMod(crt[i], alpha, mappingData.maps[i], factors[i]); } CRT_reconstruct(H,crt); // interpolate to get H FHE_TIMER_STOP; }
void PAlgebraModDerived<type>::mapToFt(RX& w, const RX& G,unsigned long t,const RX* rF1) const { if (isDryRun()) { w = RX::zero(); return; } long i = zMStar.indexOfRep(t); if (i < 0) { clear(w); return; } if (rF1==NULL) { // Compute the representation "from scratch" // special case if (G == factors[i]) { SetX(w); return; } //special case if (deg(G) == 1) { w = -ConstTerm(G); return; } // the general case: currently only works when r == 1 assert(r == 1); REBak bak; bak.save(); RE::init(factors[i]); // work with the extension field GF_p[X]/Ft(X) REX Ga; conv(Ga, G); // G as a polynomial over the extension field vec_RE roots; FindRoots(roots, Ga); // Find roots of G in this field RE* first = &roots[0]; RE* last = first + roots.length(); RE* smallest = min_element(first, last); // make a canonical choice w=rep(*smallest); return; } // if rF1 is set, then use it instead, setting w = rF1(X^t) mod Ft(X) RXModulus Ft(factors[i]); // long tInv = InvMod(t,m); RX X2t = PowerXMod(t,Ft); // X2t = X^t mod Ft w = CompMod(*rF1,X2t,Ft); // w = F1(X2t) mod Ft /* Debugging sanity-check: G(w)=0 in the extension field (Z/2Z)[X]/Ft(X) RE::init(factors[i]); REX Ga; conv(Ga, G); // G as a polynomial over the extension field RE ra; conv(ra, w); // w is an element in the extension field eval(ra,Ga,ra); // ra = Ga(ra) if (!IsZero(ra)) {// check that Ga(w)=0 in this extension field cout << "rF1(X^t) mod Ft(X) != root of G mod Ft, t=" << t << endl; exit(0); }*******************************************************************/ }
long PAlgebra::coordinate(long i, long k) const { if (isDryRun()) return 0; long t = ith_rep(k); // element of Zm^* representing the k'th slot // dLog returns the representation of t along the generators, so the // i'th entry there is the coordinate relative to i'th geneator return dLog(t)[i]; }
void PAlgebraModDerived<type>::CRT_decompose(vector<RX>& crt, const RX& H) const { unsigned long nSlots = zMStar.getNSlots(); if (isDryRun()) { crt.clear(); return; } crt.resize(nSlots); for (unsigned long i=0; i<nSlots; i++) rem(crt[i], H, factors[i]); // crt[i] = H % factors[i] }
unsigned long PAlgebra::exponentiate(const vector<unsigned long>& exps, bool onlySameOrd) const { if (isDryRun()) return 1; unsigned long t = 1; unsigned long n = min(exps.size(),gens.size()); for (unsigned long i=0; i<n; i++) { if (onlySameOrd && !SameOrd(i)) continue; unsigned long g = PowerMod(gens[i] ,exps[i], m); t = MulMod(t, g, m); } return t; }
bool PAlgebra::nextExpVector(vector<unsigned long>& buffer) const { // increment the vector in lexicographic order if (!isDryRun()) for (long i=gens.size()-1; i>=0; i--) { if (i>=(long)buffer.size()) continue; // sanity check // increment current index, set all the ones after it to zero if (buffer[i] < OrderOf(i)-1) { buffer[i]++; for (unsigned long j=i+1; j<buffer.size(); j++) buffer[j] = 0; return true; // succeeded in incrementing the vector } // if buffer[i] >= OrderOf(i)-1, mover to previous index i } return false; // cannot increment the vector anymore }
long PAlgebra::addCoord(long i, long k, long offset) const { if (isDryRun()) return 0; assert(k >= 0 && k < (long) nSlots); assert(i >= 0 && i < (long) gens.size()); offset = offset % ((long) OrderOf(i)); if (offset < 0) offset += OrderOf(i); long k_i = coordinate(i, k); long k_i1 = (k_i + offset) % OrderOf(i); long k1 = k + (k_i1 - k_i) * prods[i+1]; return k1; }
void PAlgebra::printout() const { cout << "m = " << m << ", p = " << p; if (isDryRun()) { cout << " (dry run)\n"; return; } cout << ", phi(m) = " << phiM << endl; cout << " ord(p)=" << ordP << endl; unsigned long i; for (i=0; i<gens.size(); i++) if (gens[i]) { cout << " generator " << gens[i] << " has order (" << (SameOrd(i)? "=":"!") << "= Z_m^*) of " << OrderOf(i) << endl; } if (qGrpOrd()<100) { cout << " T = ["; for (i=0; i<T.size(); i++) cout << T[i] << " "; cout << "]\n"; } }
void PAlgebraModDerived<type>::embedInSlots(RX& H, const vector<RX>& alphas, const MappingData<type>& mappingData) const { if (isDryRun()) { H = RX::zero(); return; } FHE_TIMER_START; long nSlots = zMStar.getNSlots(); assert(lsize(alphas) == nSlots); for (long i = 0; i < nSlots; i++) assert(deg(alphas[i]) < mappingData.degG); vector<RX> crt(nSlots); // alloate space for CRT components // The i'th CRT component is (H mod F_t) = alphas[i](maps[i]) mod F_t, // where with t=T[i]. if (IsX(mappingData.G)) { // special case...no need for CompMod, which is // is not optimized for this case for (long i=0; i<nSlots; i++) // crt[i] = alpha(maps[i]) mod Ft crt[i] = ConstTerm(alphas[i]); } else { // general case...still try to avoid CompMod when possible, // which is the common case for encoding masks for (long i=0; i<nSlots; i++) { // crt[i] = alpha(maps[i]) mod Ft if (deg(alphas[i]) <= 0) crt[i] = alphas[i]; else CompMod(crt[i], alphas[i], mappingData.maps[i], factors[i]); } } CRT_reconstruct(H,crt); // interpolate to get p FHE_TIMER_STOP; }
void PAlgebraModDerived<type>::CRT_reconstruct(RX& H, vector<RX>& crt) const { if (isDryRun()) { H = RX::zero(); return; } FHE_TIMER_START; long nslots = zMStar.getNSlots(); const vector<RX>& ctab = crtTable; clear(H); RX tmp1, tmp2; bool easy = true; for (long i = 0; i < nslots; i++) if (!IsZero(crt[i]) && !IsOne(crt[i])) { easy = false; break; } if (easy) { for (long i=0; i<nslots; i++) if (!IsZero(crt[i])) H += ctab[i]; } else { vector<RX> crt1; crt1.resize(nslots); for (long i = 0; i < nslots; i++) MulMod(crt1[i], crt[i], crtCoeffs[i], factors[i]); evalTree(H, crtTree, crt1, 0, nslots); } FHE_TIMER_STOP; }
PAlgebraModDerived<type>::PAlgebraModDerived(const PAlgebra& _zMStar, long _r) : zMStar(_zMStar), r(_r) { long p = zMStar.getP(); long m = zMStar.getM(); // For dry-run, use a tiny m value for the PAlgebra tables if (isDryRun()) m = (p==3)? 4 : 3; assert(r > 0); ZZ BigPPowR = power_ZZ(p, r); assert(BigPPowR.SinglePrecision()); pPowR = to_long(BigPPowR); long nSlots = zMStar.getNSlots(); RBak bak; bak.save(); SetModulus(p); // Compute the factors Ft of Phi_m(X) mod p, for all t \in T RX phimxmod; conv(phimxmod, zMStar.getPhimX()); // Phi_m(X) mod p vec_RX localFactors; EDF(localFactors, phimxmod, zMStar.getOrdP()); // equal-degree factorization RX* first = &localFactors[0]; RX* last = first + localFactors.length(); RX* smallest = min_element(first, last); swap(*first, *smallest); // We make the lexicographically smallest factor have index 0. // The remaining factors are ordered according to their representives. RXModulus F1(localFactors[0]); for (long i=1; i<nSlots; i++) { unsigned long t =zMStar.ith_rep(i); // Ft is minimal polynomial of x^{1/t} mod F1 unsigned long tInv = InvMod(t, m); // tInv = t^{-1} mod m RX X2tInv = PowerXMod(tInv,F1); // X2tInv = X^{1/t} mod F1 IrredPolyMod(localFactors[i], X2tInv, F1); } /* Debugging sanity-check #1: we should have Ft= GCD(F1(X^t),Phi_m(X)) for (i=1; i<nSlots; i++) { unsigned long t = T[i]; RX X2t = PowerXMod(t,phimxmod); // X2t = X^t mod Phi_m(X) RX Ft = GCD(CompMod(F1,X2t,phimxmod),phimxmod); if (Ft != localFactors[i]) { cout << "Ft != F1(X^t) mod Phi_m(X), t=" << t << endl; exit(0); } }*******************************************************************/ if (r == 1) { build(PhimXMod, phimxmod); factors = localFactors; pPowRContext.save(); // Compute the CRT coefficients for the Ft's crtCoeffs.SetLength(nSlots); for (long i=0; i<nSlots; i++) { RX te = phimxmod / factors[i]; // \prod_{j\ne i} Fj te %= factors[i]; // \prod_{j\ne i} Fj mod Fi InvMod(crtCoeffs[i], te, factors[i]); // \prod_{j\ne i} Fj^{-1} mod Fi } } else { PAlgebraLift(zMStar.getPhimX(), localFactors, factors, crtCoeffs, r); RX phimxmod1; conv(phimxmod1, zMStar.getPhimX()); build(PhimXMod, phimxmod1); pPowRContext.save(); } // set factorsOverZZ factorsOverZZ.resize(nSlots); for (long i = 0; i < nSlots; i++) conv(factorsOverZZ[i], factors[i]); genCrtTable(); genMaskTable(); }
PAlgebra::PAlgebra(unsigned long mm, unsigned long pp, const vector<long>& _gens, const vector<long>& _ords ) { assert( ProbPrime(pp) ); assert( (mm % pp) != 0 ); assert( mm < NTL_SP_BOUND ); assert( mm > 1 ); cM = 1.0; // default value for the ring constant m = mm; p = pp; long k = NextPowerOfTwo(m); if (mm == (1UL << k)) pow2 = k; else pow2 = 0; // For dry-run, use a tiny m value for the PAlgebra tables if (isDryRun()) mm = (p==3)? 4 : 3; // Compute the generators for (Z/mZ)^* (defined in NumbTh.cpp) if (_gens.size() == 0 || isDryRun()) ordP = findGenerators(this->gens, this->ords, mm, pp); else { assert(_gens.size() == _ords.size()); gens = _gens; ords = _ords; ordP = multOrd(pp, mm); } nSlots = qGrpOrd(); phiM = ordP * nSlots; // Allocate space for the various arrays T.resize(nSlots); dLogT.resize(nSlots*gens.size()); Tidx.assign(mm,-1); // allocate m slots, initialize them to -1 zmsIdx.assign(mm,-1); // allocate m slots, initialize them to -1 long i, idx; for (i=idx=0; i<(long)mm; i++) if (GCD(i,mm)==1) zmsIdx[i] = idx++; // Now fill the Tidx and dLogT translation tables. We identify an element // t\in T with its representation t = \prod_{i=0}^n gi^{ei} mod m (where // the gi's are the generators in gens[]) , represent t by the vector of // exponents *in reverse order* (en,...,e1,e0), and order these vectors // in lexicographic order. // FIXME: is the comment above about reverse order true? It doesn't // seem like it to me. VJS. // buffer is initialized to all-zero, which represents 1=\prod_i gi^0 vector<unsigned long> buffer(gens.size()); // temporaty holds exponents i = idx = 0; long ctr = 0; do { ctr++; unsigned long t = exponentiate(buffer); for (unsigned long j=0; j<buffer.size(); j++) dLogT[idx++] = buffer[j]; assert(GCD(t,mm) == 1); // sanity check for user-supplied gens assert(Tidx[t] == -1); T[i] = t; // The i'th element in T it t Tidx[t] = i++; // the index of t in T is i // increment buffer by one (in lexigoraphic order) } while (nextExpVector(buffer)); // until we cover all the group assert(ctr == long(nSlots)); // sanity check for user-supplied gens PhimX = Cyclotomic(mm); // compute and store Phi_m(X) // initialize prods array long ndims = gens.size(); prods.resize(ndims+1); prods[ndims] = 1; for (long j = ndims-1; j >= 0; j--) { prods[j] = OrderOf(j) * prods[j+1]; } // pp_factorize(mFactors,mm); // prime-power factorization from NumbTh.cpp }
void TestIt(long idx, long p, long r, long L, long c, long skHwt, int build_cache=0) { Vec<long> mvec; vector<long> gens; vector<long> ords; long phim = mValues[idx][1]; long m = mValues[idx][2]; assert(GCD(p, m) == 1); append(mvec, mValues[idx][4]); if (mValues[idx][5]>1) append(mvec, mValues[idx][5]); if (mValues[idx][6]>1) append(mvec, mValues[idx][6]); gens.push_back(mValues[idx][7]); if (mValues[idx][8]>1) gens.push_back(mValues[idx][8]); if (mValues[idx][9]>1) gens.push_back(mValues[idx][9]); ords.push_back(mValues[idx][10]); if (abs(mValues[idx][11])>1) ords.push_back(mValues[idx][11]); if (abs(mValues[idx][12])>1) ords.push_back(mValues[idx][12]); if (!noPrint) { cout << "*** TestIt"; if (isDryRun()) cout << " (dry run)"; cout << ": p=" << p << ", r=" << r << ", L=" << L << ", t=" << skHwt << ", c=" << c << ", m=" << m << " (=" << mvec << "), gens="<<gens<<", ords="<<ords << endl; cout << "Computing key-independent tables..." << std::flush; } setTimersOn(); setDryRun(false); // Need to get a "real context" to test bootstrapping double t = -GetTime(); FHEcontext context(m, p, r, gens, ords); if (scale) { context.scale = scale; } context.zMStar.set_cM(mValues[idx][13]/100.0); buildModChain(context, L, c, /*willBeBootstrappable=*/true); if (!noPrint) { std::cout << "security=" << context.securityLevel()<<endl; std::cout << "# small primes = " << context.smallPrimes.card() << "\n"; std::cout << "# ctxt primes = " << context.ctxtPrimes.card() << "\n"; std::cout << "# bits in ctxt primes = " << long(context.logOfProduct(context.ctxtPrimes)/log(2.0) + 0.5) << "\n"; std::cout << "# special primes = " << context.specialPrimes.card() << "\n"; std::cout << "# bits in special primes = " << long(context.logOfProduct(context.specialPrimes)/log(2.0) + 0.5) << "\n"; std::cout << "scale=" << context.scale<<endl; } context.makeBootstrappable(mvec,/*t=*/skHwt,build_cache,/*alsoThick=*/false); // save time...disable some fat boot precomputation t += GetTime(); //if (skHwt>0) context.rcData.skHwt = skHwt; if (!noPrint) { cout << " done in "<<t<<" seconds\n"; cout << " e=" << context.rcData.e << ", e'=" << context.rcData.ePrime << ", a="<< context.rcData.a << ", t=" << context.rcData.skHwt << "\n "; context.zMStar.printout(); } setDryRun(dry); // Now we can set the dry-run flag if desired long p2r = context.alMod.getPPowR(); for (long numkey=0; numkey<OUTER_REP; numkey++) { // test with 3 keys t = -GetTime(); if (!noPrint) cout << "Generating keys, " << std::flush; FHESecKey secretKey(context); secretKey.GenSecKey(64); // A Hamming-weight-64 secret key addSome1DMatrices(secretKey); // compute key-switching matrices that we need addFrbMatrices(secretKey); if (!noPrint) cout << "computing key-dependent tables..." << std::flush; secretKey.genRecryptData(); t += GetTime(); if (!noPrint) cout << " done in "<<t<<" seconds\n"; FHEPubKey publicKey = secretKey; long d = context.zMStar.getOrdP(); long phim = context.zMStar.getPhiM(); long nslots = phim/d; // GG defines the plaintext space Z_p[X]/GG(X) ZZX GG; GG = context.alMod.getFactorsOverZZ()[0]; EncryptedArray ea(context, GG); if (debug) { dbgKey = &secretKey; dbgEa = &ea; } zz_p::init(p2r); 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]))); } vector<ZZX> val_const1; val_const1.resize(nslots); for (long i = 0; i < nslots; i++) { val_const1[i] = 1; } Ctxt c1(publicKey); ea.encrypt(c1, publicKey, val1); Ctxt c2(c1); if (!noPrint) CheckCtxt(c2, "before recryption"); publicKey.thinReCrypt(c2); if (!noPrint) CheckCtxt(c2, "after recryption"); vector<ZZX> val2; ea.decrypt(c2, secretKey, val2); if (val1 == val2) cout << "GOOD\n"; else cout << "BAD\n"; } }
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; }
int main(int argc, char *argv[]) { argmap_t argmap; argmap["test"] = "1"; argmap["m"] = "4369"; argmap["p"] = "2"; argmap["r"] = "1"; argmap["depth"] = "5"; argmap["L"] = "0"; argmap["ord1"] = "30"; argmap["ord2"] = "0"; argmap["ord3"] = "0"; argmap["ord4"] = "0"; argmap["good1"] = "1"; argmap["good2"] = "1"; argmap["good3"] = "1"; argmap["good4"] = "1"; argmap["dry"] = "0"; argmap["noPrint"] = "1"; // get parameters from the command line if (!parseArgs(argc, argv, argmap)) usage(argv[0]); long test = atoi(argmap["test"]); long p = atoi(argmap["p"]); long r = atoi(argmap["r"]); long m = atoi(argmap["m"]); long depth = atoi(argmap["depth"]); long L = atoi(argmap["L"]); long ord1 = atoi(argmap["ord1"]); long ord2 = atoi(argmap["ord2"]); long ord3 = atoi(argmap["ord3"]); long ord4 = atoi(argmap["ord4"]); long good1 = atoi(argmap["good1"]); long good2 = atoi(argmap["good2"]); long good3 = atoi(argmap["good3"]); long good4 = atoi(argmap["good4"]); bool dry = atoi(argmap["dry"]); noPrint = atoi(argmap["noPrint"]); setDryRun(dry); if (test==0 || dry!=0) { Vec<GenDescriptor> vec; long nGens; if (ord2<=1) nGens=1; else if (ord3<=1) nGens=2; else if (ord4<=1) nGens=3; else nGens=4; vec.SetLength(nGens); switch (nGens) { case 4: vec[3] = GenDescriptor(ord4, good4, /*genIdx=*/3); case 3: vec[2] = GenDescriptor(ord3, good3, /*genIdx=*/2); case 2: vec[1] = GenDescriptor(ord2, good2, /*genIdx=*/1); default: vec[0] = GenDescriptor(ord1, good1, /*genIdx=*/0); } if (!noPrint) { cout << "***Testing "; if (isDryRun()) cout << "(dry run) "; for (long i=0; i<vec.length(); i++) cout << "("<<vec[i].order<<","<<vec[i].good<<")"; cout << ", depth="<<depth<<"\n"; } testCube(vec, depth); } else { setTimersOn(); if (!noPrint) cout << "***Testing m="<<m<<", p="<<p<<", depth="<<depth<< endl; testCtxt(m,p,depth,L,r); } }