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); }
long FindM(long k, long L, long c, long p, long d, long s, long chosen_m, bool verbose) { // get a lower-bound on the parameter N=phi(m): // 1. Each level in the modulus chain corresponds to pSize=p2Size/2 // bits (where we have one prime of this size, and all the others are of // size p2Size). // When using DoubleCRT, we need 2m to divide q-1 for every prime q. // 2. With L levels, the largest modulus for "fresh ciphertexts" has size // Q0 ~ p^{L+1} ~ 2^{(L+1)*pSize} // 3. We break each ciphertext into upto c digits, do each digit is as large // as D=2^{(L+1)*pSize/c} // 4. The added noise variance term from the key-switching operation is // c*N*sigma^2*D^2, and this must be mod-switched down to w*N (so it is // on par with the added noise from modulus-switching). Hence the ratio // P that we use for mod-switching must satisfy c*N*sigma^2*D^2/P^2<w*N, // or P > sqrt(c/w) * sigma * 2^{(L+1)*pSize/c} // 5. With this extra P factor, the key-switching matrices are defined // relative to a modulus of size // Q0 = q0*P ~ sqrt{c/w} sigma 2^{(L+1)*pSize*(1+1/c)} // 6. To get k-bit security we need N>log(Q0/sigma)(k+110)/7.2, i.e. roughly // N > (L+1)*pSize*(1+1/c)(k+110) / 7.2 // Compute a bound on m, and make sure that it is not too large double cc = 1.0+(1.0/(double)c); double dN = ceil((L+1)*FHE_pSize*cc*(k+110)/7.2); long N = NTL_SP_BOUND; if (N > dN) N = dN; else { cerr << "Cannot support a bound of " << dN; Error(", aborting.\n"); } long m = 0; size_t i=0; // find the first m satisfying phi(m)>=N and d | ord(p) in Z_m^* // and phi(m)/ord(p) >= s if (chosen_m) { if (GCD(p, chosen_m) == 1) { long ordP = multOrd(p, chosen_m); if (d == 0 || ordP % d == 0) { // chosen_m is OK m = chosen_m; } } } else if (p==2) { // use pre-computed table, divisors of 2^n-1 for some n's static long ms[][4] = { // pre-computed values of [phi(m),m,d] //phi(m), m, ord(2),c_m*1000 (not used anymore) { 1176, 1247, 28, 3736}, // gens=5(42) { 2880, 3133, 24, 3254}, // gens=6(60), 7(!2) { 4050, 4051, 50, 0}, // gens=130(81) { 4096, 4369, 16, 3422}, // gens=129(16),3(!16) { 4704, 4859, 28, 0}, // gens=7(42),3(!4) { 5292, 5461, 14, 4160}, // gens=3(126),509(3) { 5760, 8435, 24, 8935}, // gens=58(60),1686(2),11(!2) { 7500, 7781, 50, 0}, // gens=353(30),3(!5) { 8190, 8191, 13, 1273}, // gens=39(630) { 9900, 10261, 30, 0}, // gens=3(330) {10752, 11441, 48, 3607}, // gens=7(112),5(!2) {10800, 11023, 45, 0}, // gens=270(24),2264(2),3(!5) {12000, 13981, 20, 2467}, // gens=10(30),23(10),3(!2) {11520, 15665, 24, 14916}, // gens=6(60),177(4),7(!2) {14112, 14351, 18, 0}, // gens=7(126),3(!4) {15004, 15709, 22, 3867}, // gens=5(682) {18000, 18631, 25, 4208}, // gens=17(120),1177(6) {15360, 20485, 24, 12767}, // gens=6(80),242(4),7(2) {16384, 21845, 16, 12798}, // gens=129(16),273(4),3(!16) {17280 ,21931, 24, 18387}, // gens=6(60),467(6),11(!2) {19200, 21607, 40, 35633}, // gens=13(120),2789(2),3(!2) {21168, 27305, 28, 15407}, // gens=6(126),781(6) {23040, 23377, 48, 5292}, // gens=35(240),5(!2) {23310, 23311, 45, 0}, // gens=489(518) {24576, 24929, 48, 5612}, // gens=12(256),5(!2) {27000, 32767, 15, 20021}, // gens=3(150),873(6),6945(2) {31104, 31609, 72, 5149}, // gens=11(216),5(!2) {43690, 43691, 34, 0}, // gens=69(1285) {49500, 49981, 30, 0}, // gens=3(1650) {46080, 53261, 24, 33409}, // gens=3(240),242(4),7(!2) {54000, 55831, 25, 0}, // gens=22(360),3529(6) {49140, 57337, 39, 2608}, // gens=39(630),40956(2) {51840, 59527, 72, 21128}, // gens=58(60),1912(6),7(!2) {61680, 61681, 40, 1273}, // gens=33(771),17(!2) {65536, 65537, 32, 1273}, // gens=2(32),3(!2048) {75264, 82603, 56, 36484}, // gens=3(336),24294(2),7(!2) {84672, 92837, 56, 38520} // gens=18(126),1886(6),3(!2) }; for (i=0; i<sizeof(ms)/sizeof(long[4]); i++) { if (ms[i][0] < N || GCD(p, ms[i][1]) != 1) continue; long ordP = multOrd(p, ms[i][1]); long nSlots = ms[i][0]/ordP; if (d != 0 && ordP % d != 0) continue; if (nSlots < s) continue; m = ms[i][1]; break; } } // If m is not set yet, just set it close to N. This may be a lousy // choice of m for this p, since you will get a small number of slots. if (m==0) { // search only for odd values of m, to make phi(m) a little closer to m for (long candidate=N|1; candidate<10*N; candidate+=2) { if (GCD(p,candidate)!=1) continue; long ordP = multOrd(p,candidate); // the multiplicative order of p mod m if (d>1 && ordP%d!=0 ) continue; if (ordP > 100) continue; // order too big, we will get very few slots long n = phi_N(candidate); // compute phi(m) if (n < N) continue; // phi(m) too small m = candidate; // all tests passed, return this value of m break; } } if (verbose) { cerr << "*** Bound N="<<N<<", choosing m="<<m <<", phi(m)="<< phi_N(m) << endl; } return m; }
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(); }
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)); } }