// The main method void ThinRecryptData::init(const FHEcontext& context, const Vec<long>& mvec_, long t, bool consFlag, bool build_cache_, bool minimal) { if (alMod != NULL) { // were we called for a second time? cerr << "@Warning: multiple calls to ThinRecryptData::init\n"; return; } assert(computeProd(mvec_) == (long)context.zMStar.getM()); // sanity check // Record the arguments to this function mvec = mvec_; conservative = consFlag; build_cache = build_cache_; if (t <= 0) t = defSkHwt+1; // recryption key Hwt hwt = t; long p = context.zMStar.getP(); long phim = context.zMStar.getPhiM(); long r = context.alMod.getR(); long p2r = context.alMod.getPPowR(); double logp = log((double)p); double noise = p2r * sqrt((t+1)*phim/3.0); double gamma = 2*(t+noise)/((t+1)*p2r); // ratio between numerators long logT = ceil(log((double)(t+2))/logp); // ceil(log_p(t+2)) double rho = (t+1)/pow(p,logT); if (!conservative) { // try alpha, e with this "aggresive" setting setAlphaE(alpha, e, rho, gamma, noise, logp, p2r, t); ePrime = e -r +1 -logT; // If e is too large, try again with rho/p instead of rho long bound = (1L << (context.bitsPerLevel-1)); // halfSizePrime/2 if (pow(p,e) > bound) { // try the conservative setting instead cerr << "* p^e="<<pow(p,e)<<" is too big (bound="<<bound<<")\n"; conservative = true; } } if (conservative) { // set alpha, e with a "conservative" rho/p setAlphaE(alpha, e, rho/p, gamma, noise, logp, p2r, t); ePrime = e -r -logT; } // Compute highest key-Hamming-weight that still works (not more than 256) double qOver4 = (pow(p,e)+1)/4; for (t-=10; qOver4>=lowerBound2(p,r,ePrime,t,alpha) && qOver4>=lowerBound1(p,r,ePrime,t,alpha,noise) && t<257; t++); skHwt = t-1; // First part of Bootstrapping works wrt plaintext space p^{r'} alMod = new PAlgebraMod(context.zMStar, e-ePrime+r); ea = new EncryptedArray(context, *alMod); // Polynomial defaults to F0, PAlgebraMod explicitly given coeffToSlot = new ThinEvalMap(*ea, minimal, mvec, true, build_cache); slotToCoeff = new ThinEvalMap(*context.ea, minimal, mvec, false, build_cache); }
PowerfulTranslationIndexes::PowerfulTranslationIndexes(const Vec<long>& mv): mvec(mv) // copy the vector of factors { // mvec contains the prime-power factorization of m = \prod_{i=1}^k mi long nfactors = mvec.length(); // = k m = computeProd(mvec); // compute m itself // phivec holds phi(mi) for all factors mi phivec.SetLength(nfactors); for (long i = 0; i < nfactors; i++) phivec[i] = phi_N(mvec[i]); phim = computeProd(phivec); // phi(m) = prod_i phi(mi) computeDivVec(divvec, m, mvec); // divvec[i] = m/mi computeInvVec(invvec, divvec, mvec); // invvec[i] = (m/mi)^{-1} mod mi // Let (i_1,...,i_k) be the representation of i in base // (m/m1,...,m/mk), namely i = i_1 (m/m_1)+...+i_k (m/m_k) mod m. // Then polyToCubeMap[i] is the lexicographic index of the tuple // (i_1,...,i_k) in the cube with dimensions (m_1, ..., m_k). // cubeToPolyMap is the inverse map, polyToCubeMap[cubeToPolyMap[j]]=j. longSig.initSignature(mvec); shortSig.initSignature(phivec); computePowerToCubeMap(polyToCubeMap, cubeToPolyMap, m, mvec, invvec, longSig); // shortSig is a CubeSignature for (phi(m_1),..., phi(m_k)), and longSig // is a CubeSignature for (m_1, ..., m_k). shortToLongMap[i] maps an // index i wrt shortSig to an index i' wrt longSig so that both indexes // correspond to the same tuple (i_1,...,i_k). computeShortToLongMap(shortToLongMap, shortSig, longSig); cycVec.SetLength(nfactors); for (long d = 0; d < nfactors; d++) cycVec[d] = Cyclotomic(mvec[d]); phimX = Cyclotomic(m); }
void mapIndexToPowerful(Vec<long>& pow, long j, const Vec<long>& phiVec) // this maps an index j in [phi(m)] to a vector // representing the powerful basis coordinates { long k = phiVec.length(); long phim = computeProd(phiVec); assert(j >= 0 && j < phim); pow.SetLength(k); for (long i = k-1; i >= 0; i--) { pow[i] = j % phiVec[i]; j = (j - pow[i])/phiVec[i]; } }
long computeProd(const vector<long>& vec) {return computeProd(vec, vec.size());}
long computeProd(const Vec<long>& vec) { return computeProd(vec, vec.length());}
// The main method void RecryptData::init(const FHEcontext& context, const Vec<long>& mvec_, long t, bool consFlag, bool build_cache_, bool minimal) { if (alMod != NULL) { // were we called for a second time? cerr << "@Warning: multiple calls to RecryptData::init\n"; return; } assert(computeProd(mvec_) == (long)context.zMStar.getM()); // sanity check // Record the arguments to this function mvec = mvec_; conservative = consFlag; build_cache = build_cache_; if (t <= 0) t = defSkHwt+1; // recryption key Hwt hwt = t; long p = context.zMStar.getP(); long phim = context.zMStar.getPhiM(); long r = context.alMod.getR(); long p2r = context.alMod.getPPowR(); double logp = log((double)p); double noise = p2r * sqrt((t+1)*phim/3.0); double gamma = 2*(t+noise)/((t+1)*p2r); // ratio between numerators long logT = ceil(log((double)(t+2))/logp); // ceil(log_p(t+2)) double rho = (t+1)/pow(p,logT); if (!conservative) { // try alpha, e with this "aggresive" setting setAlphaE(alpha, e, rho, gamma, noise, logp, p2r, t); ePrime = e -r +1 -logT; // If e is too large, try again with rho/p instead of rho long bound = (1L << (context.bitsPerLevel-1)); // halfSizePrime/2 if (pow(p,e) > bound) { // try the conservative setting instead cerr << "* p^e="<<pow(p,e)<<" is too big (bound="<<bound<<")\n"; conservative = true; } } if (conservative) { // set alpha, e with a "conservative" rho/p setAlphaE(alpha, e, rho/p, gamma, noise, logp, p2r, t); ePrime = e -r -logT; } // Compute highest key-Hamming-weight that still works (not more than 256) double qOver4 = (pow(p,e)+1)/4; for (t-=10; qOver4>=lowerBound2(p,r,ePrime,t,alpha) && qOver4>=lowerBound1(p,r,ePrime,t,alpha,noise) && t<257; t++); skHwt = t-1; // First part of Bootstrapping works wrt plaintext space p^{r'} alMod = new PAlgebraMod(context.zMStar, e-ePrime+r); ea = new EncryptedArray(context, *alMod); // Polynomial defaults to F0, PAlgebraMod explicitly given p2dConv = new PowerfulDCRT(context, mvec); // Initialize the linear polynomial for unpacking the slots zz_pBak bak; bak.save(); ea->getAlMod().restoreContext(); long nslots = ea->size(); long d = ea->getDegree(); const Mat<zz_p>& CBi=ea->getDerived(PA_zz_p()).getNormalBasisMatrixInverse(); vector<ZZX> LM; LM.resize(d); for (long i = 0; i < d; i++) // prepare the linear polynomial LM[i] = rep(CBi[i][0]); vector<ZZX> C; ea->buildLinPolyCoeffs(C, LM); // "build" the linear polynomial unpackSlotEncoding.resize(d); // encode the coefficients for (long j = 0; j < d; j++) { vector<ZZX> v(nslots); for (long k = 0; k < nslots; k++) v[k] = C[j]; ea->encode(unpackSlotEncoding[j], v); } firstMap = new EvalMap(*ea, minimal, mvec, true, build_cache); secondMap = new EvalMap(*context.ea, minimal, mvec, false, build_cache); }
ThinEvalMap::ThinEvalMap(const EncryptedArray& _ea, bool minimal, const Vec<long>& mvec, bool _invert, bool build_cache) : ea(_ea), invert(_invert) { const FHEcontext& context = ea.getContext(); const PAlgebra& zMStar = ea.getPAlgebra(); long p = zMStar.getP(); long d = zMStar.getOrdP(); // FIXME: we should check that ea was initilized with // G == factors[0], but this is a slight pain to check // currently // NOTE: this code is derived from a more general setting, and // could certainly be greatly simplified nfactors = mvec.length(); //OLD: assert(nfactors > 0); helib::assertTrue(nfactors > 0, "Invalid argument: mvec must have positive length"); for (long i = 0; i < nfactors; i++) { for (long j = i+1; j < nfactors; j++) { helib::assertEq(GCD(mvec[i], mvec[j]), 1l, "Invalid argument: mvec must have pairwise-disjoint entries"); } } long m = computeProd(mvec); //OLD: assert(m == long(zMStar.getM())); helib::assertEq(m, (long)zMStar.getM(), "Invalid argument: mvec's product does not match ea's m"); Vec<long> phivec(INIT_SIZE, nfactors); for (long i = 0; i < nfactors; i++) phivec[i] = phi_N(mvec[i]); long phim = computeProd(phivec); Vec<long> dprodvec(INIT_SIZE, nfactors+1); dprodvec[nfactors] = 1; for (long i = nfactors-1; i >= 0; i--) dprodvec[i] = dprodvec[i+1] * multOrd(PowerMod(p % mvec[i], dprodvec[i+1], mvec[i]), mvec[i]); Vec<long> dvec(INIT_SIZE, nfactors); for (long i = 0; i < nfactors; i++) dvec[i] = dprodvec[i] / dprodvec[i+1]; long nslots = phim/d; //OLD: assert(d == dprodvec[0]); helib::assertEq(d, dprodvec[0], "d must match the first entry of dprodvec"); //OLD: assert(nslots == long(zMStar.getNSlots())); helib::assertEq(nslots, (long)zMStar.getNSlots(), "Invalid argument: mismatch of number of slots"); long inertPrefix = 0; for (long i = 0; i < nfactors && dvec[i] == 1; i++) { inertPrefix++; } if (inertPrefix != nfactors-1) throw helib::LogicError("ThinEvalMap: case not handled: bad inertPrefix"); Vec< Vec<long> > local_reps(INIT_SIZE, nfactors); for (long i = 0; i < nfactors; i++) init_representatives(local_reps[i], i, mvec, zMStar); Vec<long> crtvec(INIT_SIZE, nfactors); for (long i = 0; i < nfactors; i++) crtvec[i] = (m/mvec[i]) * InvMod((m/mvec[i]) % mvec[i], mvec[i]); Vec<long> redphivec(INIT_SIZE, nfactors); for (long i = 0; i < nfactors; i++) redphivec[i] = phivec[i]/dvec[i]; CubeSignature redphisig(redphivec); Vec< shared_ptr<CubeSignature> > sig_sequence; sig_sequence.SetLength(nfactors+1); sig_sequence[nfactors] = shared_ptr<CubeSignature>(new CubeSignature(phivec)); Vec<long> reduced_phivec = phivec; for (long dim = nfactors-1; dim >= 0; dim--) { reduced_phivec[dim] /= dvec[dim]; sig_sequence[dim] = shared_ptr<CubeSignature>(new CubeSignature(reduced_phivec)); } matvec.SetLength(nfactors); if (invert) { long dim = nfactors - 1; unique_ptr<MatMul1D> mat1_data; mat1_data.reset(buildThinStep1Matrix(ea, sig_sequence[dim], local_reps[dim], dim, m/mvec[dim])); matvec[dim].reset(new MatMul1DExec(*mat1_data, minimal)); } else { long dim = nfactors - 1; unique_ptr<MatMul1D> mat1_data; mat1_data.reset(buildThinStep2Matrix(ea, sig_sequence[dim], local_reps[dim], dim, m/mvec[dim], invert, /*inflate=*/true)); matvec[dim].reset(new MatMul1DExec(*mat1_data, minimal)); } for (long dim=nfactors-2; dim>=0; --dim) { unique_ptr<MatMul1D> mat_data; mat_data.reset(buildThinStep2Matrix(ea, sig_sequence[dim], local_reps[dim], dim, m/mvec[dim], invert)); matvec[dim].reset(new MatMul1DExec(*mat_data, minimal)); } if (build_cache) upgrade(); }
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; } }
EvalMap::EvalMap(const EncryptedArray& _ea, const Vec<long>& mvec, bool _invert, bool normal_basis) : ea(_ea), invert(_invert) { const FHEcontext& context = ea.getContext(); const PAlgebra& zMStar = context.zMStar; long p = zMStar.getP(); long d = zMStar.getOrdP(); // FIXME: we should check that ea was initilized with // G == factors[0], but this is a slight pain to check // currently // NOTE: this code is derived from a more general setting, and // could certainly be greatly simplified nfactors = mvec.length(); assert(nfactors > 0); for (long i = 0; i < nfactors; i++) for (long j = i+1; j < nfactors; j++) assert(GCD(mvec[i], mvec[j]) == 1); long m = computeProd(mvec); assert(m == long(zMStar.getM())); Vec<long> phivec(INIT_SIZE, nfactors); for (long i = 0; i < nfactors; i++) phivec[i] = phi_N(mvec[i]); long phim = computeProd(phivec); Vec<long> dprodvec(INIT_SIZE, nfactors+1); dprodvec[nfactors] = 1; for (long i = nfactors-1; i >= 0; i--) dprodvec[i] = dprodvec[i+1] * multOrd(PowerMod(p % mvec[i], dprodvec[i+1], mvec[i]), mvec[i]); Vec<long> dvec(INIT_SIZE, nfactors); for (long i = 0; i < nfactors; i++) dvec[i] = dprodvec[i] / dprodvec[i+1]; long nslots = phim/d; assert(d == dprodvec[0]); assert(nslots == long(zMStar.getNSlots())); long inertPrefix = 0; for (long i = 0; i < nfactors && dvec[i] == 1; i++) { inertPrefix++; } if (inertPrefix != nfactors-1) Error("EvalMap: case not handled: bad inertPrefix"); Vec< Vec<long> > local_reps(INIT_SIZE, nfactors); for (long i = 0; i < nfactors; i++) init_representatives(local_reps[i], i, mvec, zMStar); Vec<long> crtvec(INIT_SIZE, nfactors); for (long i = 0; i < nfactors; i++) crtvec[i] = (m/mvec[i]) * InvMod((m/mvec[i]) % mvec[i], mvec[i]); Vec<long> redphivec(INIT_SIZE, nfactors); for (long i = 0; i < nfactors; i++) redphivec[i] = phivec[i]/dvec[i]; CubeSignature redphisig(redphivec); Vec< shared_ptr<CubeSignature> > sig_sequence; sig_sequence.SetLength(nfactors+1); sig_sequence[nfactors] = shared_ptr<CubeSignature>(new CubeSignature(phivec)); Vec<long> reduced_phivec = phivec; for (long dim = nfactors-1; dim >= 0; dim--) { reduced_phivec[dim] /= dvec[dim]; sig_sequence[dim] = shared_ptr<CubeSignature>(new CubeSignature(reduced_phivec)); } long dim = nfactors - 1; mat1.reset(buildStep1Matrix(ea, sig_sequence[dim], local_reps[dim], dim, m/mvec[dim], invert, normal_basis)); matvec.SetLength(nfactors-1); for (dim=nfactors-2; dim>=0; --dim) { matvec[dim].reset(buildStep2Matrix(ea, sig_sequence[dim], local_reps[dim], dim, m/mvec[dim], invert)); } }