long BKZ_XD(mat_ZZ& BB, mat_ZZ& UU, double delta, long beta, long prune, LLLCheckFct check, long verb) { verbose = verb; NumSwaps = 0; if (verbose) { StartTime = GetTime(); LastTime = StartTime; } if (delta < 0.50 || delta >= 1) LogicError("BKZ_XD: bad delta"); if (beta < 2) LogicError("BKZ_XD: bad block size"); return BKZ_XD(BB, &UU, to_xdouble(delta), beta, prune, check); }
xdouble sqrt(const xdouble& a) { if (a == 0) return to_xdouble(0); if (a < 0) ArithmeticError("xdouble: sqrt of negative number"); xdouble t; if (a.e & 1) { t.e = (a.e - 1)/2; t.x = sqrt(a.x * NTL_XD_BOUND); } else { t.e = a.e/2; t.x = sqrt(a.x); } t.normalize(); return t; }
// Takes as arguments a ciphertext-part p relative to s' and a key-switching // matrix W = W[s'->s], uses W to switch p relative to (1,s), and adds the // result to *this. // It is assumed that the part p does not include any of the special primes, // and that if *this is not an empty ciphertext then its primeSet is // p.getIndexSet() \union context.specialPrimes void Ctxt::keySwitchPart(const CtxtPart& p, const KeySwitch& W) { FHE_TIMER_START; // no special primes in the input part assert(context.specialPrimes.disjointFrom(p.getIndexSet())); // For parts p that point to 1 or s, only scale and add if (p.skHandle.isOne() || p.skHandle.isBase(W.toKeyID)) { CtxtPart pp = p; pp.addPrimesAndScale(context.specialPrimes); addPart(pp, /*matchPrimeSet=*/true); return; } // some sanity checks assert(W.fromKey == p.skHandle); // the handles must match // Compute the number of digits that we need and the esitmated added noise // from switching this ciphertext part. long pSpace = W.ptxtSpace; long nDigits = 0; xdouble addedNoise = to_xdouble(0.0); double sizeLeft = context.logOfProduct(p.getIndexSet()); for (size_t i=0; i<context.digits.size() && sizeLeft>0.0; i++) { nDigits++; double digitSize = context.logOfProduct(context.digits[i]); if (sizeLeft<digitSize) digitSize=sizeLeft; // need only part of this digit // Added noise due to this digit is phi(m) * sigma^2 * pSpace^2 * |Di|^2/4, // where |Di| is the magnitude of the digit // WARNING: the following line is written just so to prevent overflow addedNoise += to_xdouble(context.zMStar.getPhiM()) * pSpace*pSpace * xexp(2*digitSize) * context.stdev*context.stdev / 4.0; sizeLeft -= digitSize; } // Sanity-check: make sure that the added noise is not more than the special // primes can handle: After dividing the added noise by the product of all // the special primes, it should be smaller than the added noise term due // to modulus switching, i.e., keyWeight * phi(m) * pSpace^2 / 12 long keyWeight = pubKey.getSKeyWeight(p.skHandle.getSecretKeyID()); double phim = context.zMStar.getPhiM(); double logModSwitchNoise = log((double)keyWeight) +2*log((double)pSpace) +log(phim) -log(12.0); double logKeySwitchNoise = log(addedNoise) -2*context.logOfProduct(context.specialPrimes); assert(logKeySwitchNoise < logModSwitchNoise); // Break the ciphertext part into digits, if needed, and scale up these // digits using the special primes. This is the most expensive operation // during homormophic evaluation, so it should be thoroughly optimized. vector<DoubleCRT> polyDigits; p.breakIntoDigits(polyDigits, nDigits); // Finally we multiply the vector of digits by the key-switching matrix // An object to hold the pseudorandom ai's, note that it must be defined // with the maximum number of levels, else the PRG will go out of synch. // FIXME: This is a bug waiting to happen. DoubleCRT ai(context); // Set the first ai using the seed, subsequent ai's (if any) will // use the evolving RNG state (NOTE: this is not thread-safe) RandomState state; SetSeed(W.prgSeed); // Add the columns in, one by one DoubleCRT tmp(context, IndexSet::emptySet()); for (unsigned long i=0; i<polyDigits.size(); i++) { ai.randomize(); tmp = polyDigits[i]; // The operations below all use the IndexSet of tmp // add part*a[i] with a handle pointing to base of W.toKeyID tmp.Mul(ai, /*matchIndexSet=*/false); addPart(tmp, SKHandle(1,1,W.toKeyID), /*matchPrimeSet=*/true); // add part*b[i] with a handle pointing to one polyDigits[i].Mul(W.b[i], /*matchIndexSet=*/false); addPart(polyDigits[i], SKHandle(), /*matchPrimeSet=*/true); } noiseVar += addedNoise; } // restore random state upon destruction of the RandomState, see NumbTh.h
static long ll_LLL_XD(mat_ZZ& B, mat_ZZ* U, xdouble delta, long deep, LLLCheckFct check, xdouble **B1, xdouble **mu, xdouble *b, xdouble *c, long m, long init_k, long &quit) { long n = B.NumCols(); long i, j, k, Fc1; ZZ MU; xdouble mu1; xdouble t1; ZZ T1; xdouble *tp; NTL_THREAD_LOCAL static xdouble bound = to_xdouble(0); if (bound == 0) { // we tolerate a 15% loss of precision in computing // inner products in ComputeGS. bound = 1; for (i = 2*long(0.15*NTL_DOUBLE_PRECISION); i > 0; i--) { bound = bound * 2; } } xdouble half = to_xdouble(0.5); xdouble half_plus_fudge = 0.5 + red_fudge; quit = 0; k = init_k; vec_long st_mem; st_mem.SetLength(m+2); long *st = st_mem.elts(); for (i = 1; i < k; i++) st[i] = i; for (i = k; i <= m+1; i++) st[i] = 1; UniqueArray<xdouble> buf_store; buf_store.SetLength(m+1); xdouble *buf = buf_store.get(); long rst; long counter; long trigger_index; long small_trigger; long cnt; long max_k = 0; double tt; while (k <= m) { if (k > max_k) { max_k = k; } if (verbose) { tt = GetTime(); if (tt > LastTime + LLLStatusInterval) LLLStatus(max_k, tt, m, B); } if (st[k] == k) rst = 1; else rst = k; if (st[k] < st[k+1]) st[k+1] = st[k]; ComputeGS(B, B1, mu, b, c, k, bound, st[k], buf); st[k] = k; counter = 0; trigger_index = k; small_trigger = 0; cnt = 0; do { // size reduction counter++; if (counter > 10000) { cerr << "LLL_XD: warning--possible infinite loop\n"; counter = 0; } Fc1 = 0; for (j = rst-1; j >= 1; j--) { t1 = fabs(mu[k][j]); if (t1 > half_plus_fudge) { if (!Fc1) { if (j > trigger_index || (j == trigger_index && small_trigger)) { cnt++; if (cnt > 10) { inc_red_fudge(); half_plus_fudge = 0.5 + red_fudge; cnt = 0; } } trigger_index = j; small_trigger = (t1 < 4); } Fc1 = 1; mu1 = mu[k][j]; if (mu1 >= 0) mu1 = ceil(mu1-half); else mu1 = floor(mu1+half); xdouble *mu_k = mu[k]; xdouble *mu_j = mu[j]; if (mu1 == 1) { for (i = 1; i <= j-1; i++) mu_k[i] -= mu_j[i]; } else if (mu1 == -1) { for (i = 1; i <= j-1; i++) mu_k[i] += mu_j[i]; } else { for (i = 1; i <= j-1; i++) MulSub(mu_k[i], mu_k[i], mu1, mu_j[i]); } mu_k[j] -= mu1; conv(MU, mu1); // cout << j << " " << MU << "\n"; RowTransform(B(k), B(j), MU); if (U) RowTransform((*U)(k), (*U)(j), MU); } } if (Fc1) { for (i = 1; i <= n; i++) conv(B1[k][i], B(k, i)); b[k] = InnerProduct(B1[k], B1[k], n); ComputeGS(B, B1, mu, b, c, k, bound, 1, buf); } } while (Fc1); if (check && (*check)(B(k))) quit = 1; if (b[k] == 0) { for (i = k; i < m; i++) { // swap i, i+1 swap(B(i), B(i+1)); tp = B1[i]; B1[i] = B1[i+1]; B1[i+1] = tp; t1 = b[i]; b[i] = b[i+1]; b[i+1] = t1; if (U) swap((*U)(i), (*U)(i+1)); } for (i = k; i <= m+1; i++) st[i] = 1; m--; if (quit) break; continue; } if (quit) break; if (deep > 0) { // deep insertions xdouble cc = b[k]; long l = 1; while (l <= k-1 && delta*c[l] <= cc) { cc = cc - mu[k][l]*mu[k][l]*c[l]; l++; } if (l <= k-1 && (l <= deep || k-l <= deep)) { // deep insertion at position l for (i = k; i > l; i--) { // swap rows i, i-1 swap(B(i), B(i-1)); tp = B1[i]; B1[i] = B1[i-1]; B1[i-1] = tp; tp = mu[i]; mu[i] = mu[i-1]; mu[i-1] = tp; t1 = b[i]; b[i] = b[i-1]; b[i-1] = t1; if (U) swap((*U)(i), (*U)(i-1)); } k = l; continue; } } // end deep insertions // test LLL reduction condition if (k > 1 && delta*c[k-1] > c[k] + mu[k][k-1]*mu[k][k-1]*c[k-1]) { // swap rows k, k-1 swap(B(k), B(k-1)); tp = B1[k]; B1[k] = B1[k-1]; B1[k-1] = tp; tp = mu[k]; mu[k] = mu[k-1]; mu[k-1] = tp; t1 = b[k]; b[k] = b[k-1]; b[k-1] = t1; if (U) swap((*U)(k), (*U)(k-1)); k--; NumSwaps++; // cout << "- " << k << "\n"; } else { k++; // cout << "+ " << k << "\n"; } } if (verbose) { LLLStatus(m+1, GetTime(), m, B); } return m; }
t1 = 0; for (i = 1; i <= j-1; i++) MulAdd(t1, t1, mu_j[i], buf[i]); mu_k[j] = (buf[j] = (s - t1))/c[j]; } s = 0; for (j = 1; j <= k-1; j++) MulAdd(s, s, mu_k[j], buf[j]); c[k] = b[k] - s; } NTL_THREAD_LOCAL static xdouble red_fudge = to_xdouble(0); NTL_THREAD_LOCAL static long log_red = 0; static void init_red_fudge() { long i; log_red = long(0.50*NTL_DOUBLE_PRECISION); red_fudge = 1; for (i = log_red; i > 0; i--) red_fudge = red_fudge*0.5; } static void inc_red_fudge() {
// Encrypts plaintext, result returned in the ciphertext argument. The // returned value is the plaintext-space for that ciphertext. When called // with highNoise=true, returns a ciphertext with noise level~q/8. long FHEPubKey::Encrypt(Ctxt &ctxt, const ZZX& ptxt, long ptxtSpace, bool highNoise) const { FHE_TIMER_START; assert(this == &ctxt.pubKey); if (ptxtSpace != pubEncrKey.ptxtSpace) { // plaintext-space mistamtch ptxtSpace = GCD(ptxtSpace, pubEncrKey.ptxtSpace); if (ptxtSpace <= 1) Error("Plaintext-space mismatch on encryption"); } // generate a random encryption of zero from the public encryption key ctxt = pubEncrKey; // already an encryption of zero, just not a random one // choose a random small scalar r and a small random error vector e, // then set ctxt = r*pubEncrKey + ptstSpace*e + (ptxt,0) DoubleCRT e(context, context.ctxtPrimes); DoubleCRT r(context, context.ctxtPrimes); r.sampleSmall(); for (size_t i=0; i<ctxt.parts.size(); i++) { // add noise to all the parts ctxt.parts[i] *= r; if (highNoise && i == 0) { // we sample e so that coefficients are uniform over // [-Q/(8*ptxtSpace)..Q/(8*ptxtSpace)] ZZ B; B = context.productOfPrimes(context.ctxtPrimes); B /= ptxtSpace; B /= 8; e.sampleUniform(B); } else { e.sampleGaussian(); } e *= ptxtSpace; ctxt.parts[i] += e; } // add in the plaintext // FIXME: This relies on the first part, ctxt[0], to have handle to 1 if (ptxtSpace==2) ctxt.parts[0] += ptxt; else { // The general case of ptxtSpace>2: for a ciphertext // relative to modulus Q, we add ptxt * Q mod ptxtSpace. long QmodP = rem(context.productOfPrimes(ctxt.primeSet), ptxtSpace); ctxt.parts[0] += MulMod(ptxt,QmodP,ptxtSpace); // MulMod from module NumbTh } // fill in the other ciphertext data members ctxt.ptxtSpace = ptxtSpace; if (highNoise) { // hack: we set noiseVar to Q^2/8, which is just below threshold // that will signal an error ctxt.noiseVar = xexp(2*context.logOfProduct(context.ctxtPrimes) - log(8.0)); } else { // We have <skey,ctxt>= r*<skey,pkey> +p*(e0+e1*s) +m, where VAR(<skey,pkey>) // is recorded in pubEncrKey.noiseVar, VAR(ei)=sigma^2*phi(m), and VAR(s) is // determined by the secret-key Hamming weight (skHwt). // VAR(r)=phi(m)/2, hence the expected size squared is bounded by: // E(X^2) <= pubEncrKey.noiseVar *phi(m) *stdev^2 // + p^2*sigma^2 *phi(m) *(skHwt+1) + p^2 long hwt = skHwts[0]; xdouble phim = to_xdouble(context.zMStar.getPhiM()); xdouble sigma2 = context.stdev * context.stdev; xdouble p2 = to_xdouble(ptxtSpace) * to_xdouble(ptxtSpace); ctxt.noiseVar = pubEncrKey.noiseVar*phim*0.5 + p2*sigma2*phim*(hwt+1)*context.zMStar.get_cM() + p2; } return ptxtSpace; }
t1 = 0; for (i = 1; i <= j-1; i++) MulAdd(t1, t1, mu_j[i], buf[i]); mu_k[j] = (buf[j] = (s - t1))/c[j]; } s = 0; for (j = 1; j <= k-1; j++) MulAdd(s, s, mu_k[j], buf[j]); c[k] = b[k] - s; } static xdouble red_fudge = to_xdouble(0); static long log_red = 0; static void init_red_fudge() { long i; log_red = long(0.50*NTL_DOUBLE_PRECISION); red_fudge = 1; for (i = log_red; i > 0; i--) red_fudge = red_fudge*0.5; } static void inc_red_fudge() {
static long ll_G_LLL_XD(mat_ZZ& B, mat_ZZ* U, xdouble delta, long deep, LLLCheckFct check, xdouble **B1, xdouble **mu, xdouble **aux, long m, long init_k, long &quit, GivensCache_XD& cache) { long n = B.NumCols(); long i, j, k, Fc1; ZZ MU; xdouble mu1; xdouble t1; ZZ T1; xdouble *tp; xdouble half = to_xdouble(0.5); xdouble half_plus_fudge = 0.5 + red_fudge; quit = 0; k = init_k; long counter; long trigger_index; long small_trigger; long cnt; long max_k = 0; double tt; cache.flush(); while (k <= m) { if (k > max_k) { max_k = k; } if (verbose) { tt = GetTime(); if (tt > LastTime + LLLStatusInterval) G_LLLStatus(max_k, tt, m, B); } GivensComputeGS(B1, mu, aux, k, n, cache); counter = 0; trigger_index = k; small_trigger = 0; cnt = 0; do { // size reduction counter++; if (counter > 10000) { cerr << "G_LLL_XD: warning--possible infinite loop\n"; counter = 0; } Fc1 = 0; for (j = k-1; j >= 1; j--) { t1 = fabs(mu[k][j]); if (t1 > half_plus_fudge) { if (!Fc1) { if (j > trigger_index || (j == trigger_index && small_trigger)) { cnt++; if (cnt > 10) { inc_red_fudge(); half_plus_fudge = 0.5 + red_fudge; cnt = 0; } } trigger_index = j; small_trigger = (t1 < 4); } Fc1 = 1; mu1 = mu[k][j]; if (mu1 >= 0) mu1 = ceil(mu1-half); else mu1 = floor(mu1+half); xdouble *mu_k = mu[k]; xdouble *mu_j = mu[j]; if (mu1 == 1) { for (i = 1; i <= j-1; i++) mu_k[i] -= mu_j[i]; } else if (mu1 == -1) { for (i = 1; i <= j-1; i++) mu_k[i] += mu_j[i]; } else { for (i = 1; i <= j-1; i++) MulSub(mu_k[i], mu_k[i], mu1, mu_j[i]); } mu_k[j] -= mu1; conv(MU, mu1); // cout << j << " " << MU << "\n"; RowTransform(B(k), B(j), MU); if (U) RowTransform((*U)(k), (*U)(j), MU); } } if (Fc1) { for (i = 1; i <= n; i++) conv(B1[k][i], B(k, i)); cache.touch(); GivensComputeGS(B1, mu, aux, k, n, cache); } } while (Fc1); if (check && (*check)(B(k))) quit = 1; if (IsZero(B(k))) { for (i = k; i < m; i++) { // swap i, i+1 swap(B(i), B(i+1)); tp = B1[i]; B1[i] = B1[i+1]; B1[i+1] = tp; if (U) swap((*U)(i), (*U)(i+1)); } cache.flush(); m--; if (quit) break; continue; } if (quit) break; if (deep > 0) { // deep insertions Error("sorry...deep insertions not implemented"); } // end deep insertions // test G_LLL reduction condition if (k > 1 && (delta - mu[k][k-1]*mu[k][k-1])*(mu[k-1][k-1])*(mu[k-1][k-1]) > (mu[k][k])*(mu[k][k])) { // swap rows k, k-1 swap(B(k), B(k-1)); tp = B1[k]; B1[k] = B1[k-1]; B1[k-1] = tp; if (U) swap((*U)(k), (*U)(k-1)); cache.swap(); k--; NumSwaps++; // cout << "- " << k << "\n"; } else { cache.incr(); k++; // cout << "+ " << k << "\n"; } } if (verbose) { G_LLLStatus(m+1, GetTime(), m, B); } return m; }
int main(int argc, char *argv[]) { if (argc<2) { cout << "\nUsage: " << argv[0] << " L [c=2 w=64 k=80 d=1]" << endl; cout << " L is the number of levels\n"; cout << " optional c is number of columns in the key-switching matrices (default=2)\n"; cout << " optional w is Hamming weight of the secret key (default=64)\n"; cout << " optional k is the security parameter (default=80)\n"; cout << " optional d specifies GF(2^d) arithmetic (default=1, must be <=16)\n"; // cout << " k is the security parameter\n"; // cout << " m determines the ring mod Phi_m(X)" << endl; cout << endl; exit(0); } cout.unsetf(ios::floatfield); cout.precision(4); long L = atoi(argv[1]); long c = 2; long w = 64; long k = 80; long d = 1; if (argc>2) c = atoi(argv[2]); if (argc>3) w = atoi(argv[3]); if (argc>4) k = atoi(argv[4]); if (argc>5) d = atoi(argv[5]); if (d>16) Error("d cannot be larger than 16\n"); cout << "\nTesting FHE with parameters L="<<L << ", c="<<c<<", w="<<w<<", k="<<k<<", d="<<d<< endl; // get a lower-bound on the parameter N=phi(m): // 1. Empirically, we use ~20-bit small primes in the modulus chain (the main // constraints is that 2m must divide p-1 for every prime p). The first // prime is larger, a 40-bit prime. (If this is a 32-bit machine then we // use two 20-bit primes instead.) // 2. With L levels, the largest modulus for "fresh ciphertexts" has size // q0 ~ p0 * p^{L} ~ 2^{40+20L} // 3. We break each ciphertext into upto c digits, do each digit is as large // as D=2^{(40+20L)/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 part 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^{(40+20L)/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^{(40+20L)(1+1/c)} // 6. To get k-bit security we need N>log(Q0/sigma)(k+110)/7.2, i.e. roughly // N > (40+20L)(1+1/c)(k+110) / 7.2 long ptxtSpace = 2; double cc = 1.0+(1.0/(double)c); long N = (long) ceil((pSize*L+p0Size)*cc*(k+110)/7.2); cout << " bounding phi(m) > " << N << endl; #if 0 // A small m for debugging purposes long m = 15; #else // pre-computed values of [phi(m),m,d] long ms[][4] = { //phi(m) m ord(2) c_m*1000 { 1176, 1247, 28, 3736}, { 1936, 2047, 11, 3870}, { 2880, 3133, 24, 3254}, { 4096, 4369, 16, 3422}, { 5292, 5461, 14, 4160}, { 5760, 8435, 24, 8935}, { 8190, 8191, 13, 1273}, {10584, 16383, 14, 8358}, {10752, 11441, 48, 3607}, {12000, 13981, 20, 2467}, {11520, 15665, 24, 14916}, {14112, 18415, 28, 11278}, {15004, 15709, 22, 3867}, {15360, 20485, 24, 12767}, // {16384, 21845, 16, 12798}, {17208 ,21931, 24, 18387}, {18000, 18631, 25, 4208}, {18816, 24295, 28, 16360}, {19200, 21607, 40, 35633}, {21168, 27305, 28, 15407}, {23040, 23377, 48, 5292}, {24576, 24929, 48, 5612}, {27000, 32767, 15, 20021}, {31104, 31609, 71, 5149}, {42336, 42799, 21, 5952}, {46080, 53261, 24, 33409}, {49140, 57337, 39, 2608}, {51840, 59527, 72, 21128}, {61680, 61681, 40, 1273}, {65536, 65537, 32, 1273}, {75264, 82603, 56, 36484}, {84672, 92837, 56, 38520} }; #if 0 for (long i = 0; i < 25; i++) { long m = ms[i][1]; PAlgebra alg(m); alg.printout(); cout << "\n"; // compute phi(m) directly long phim = 0; for (long j = 0; j < m; j++) if (GCD(j, m) == 1) phim++; if (phim != alg.phiM()) cout << "ERROR\n"; } exit(0); #endif // find the first m satisfying phi(m)>=N and d | ord(2) in Z_m^* long m = 0; for (unsigned i=0; i<sizeof(ms)/sizeof(long[3]); i++) if (ms[i][0]>=N && (ms[i][2] % d) == 0) { m = ms[i][1]; c_m = 0.001 * (double) ms[i][3]; break; } if (m==0) Error("Cannot support this L,d combination"); #endif // m = 257; FHEcontext context(m); #if 0 context.stdev = to_xdouble(0.5); // very low error #endif activeContext = &context; // Mark this as the "current" context context.zMstar.printout(); cout << endl; // Set the modulus chain #if 1 // The first 1-2 primes of total p0size bits #if (NTL_SP_NBITS > p0Size) AddPrimesByNumber(context, 1, 1UL<<p0Size); // add a single prime #else AddPrimesByNumber(context, 2, 1UL<<(p0Size/2)); // add two primes #endif #endif // The next L primes, as small as possible AddPrimesByNumber(context, L); ZZ productOfCtxtPrimes = context.productOfPrimes(context.ctxtPrimes); double productSize = context.logOfProduct(context.ctxtPrimes); // might as well test that the answer is roughly correct cout << " context.logOfProduct(...)-log(context.productOfPrimes(...)) = " << productSize-log(productOfCtxtPrimes) << endl; // calculate the size of the digits context.digits.resize(c); IndexSet s1; #if 0 for (long i=0; i<c-1; i++) context.digits[i] = IndexSet(i,i); context.digits[c-1] = context.ctxtPrimes / IndexSet(0,c-2); AddPrimesByNumber(context, 2, 1, true); #else double sizeSoFar = 0.0; double maxDigitSize = 0.0; if (c>1) { // break ciphetext into a few digits double dsize = productSize/c; // initial estimate double target = dsize-(pSize/3.0); long idx = context.ctxtPrimes.first(); for (long i=0; i<c-1; i++) { // compute next digit IndexSet s; while (idx <= context.ctxtPrimes.last() && sizeSoFar < target) { s.insert(idx); sizeSoFar += log((double)context.ithPrime(idx)); idx = context.ctxtPrimes.next(idx); } context.digits[i] = s; s1.insert(s); double thisDigitSize = context.logOfProduct(s); if (maxDigitSize < thisDigitSize) maxDigitSize = thisDigitSize; cout << " digit #"<<i+1<< " " <<s << ": size " << thisDigitSize << endl; target += dsize; } IndexSet s = context.ctxtPrimes / s1; // all the remaining primes context.digits[c-1] = s; double thisDigitSize = context.logOfProduct(s); if (maxDigitSize < thisDigitSize) maxDigitSize = thisDigitSize; cout << " digit #"<<c<< " " <<s << ": size " << thisDigitSize << endl; } else { maxDigitSize = context.logOfProduct(context.ctxtPrimes); context.digits[0] = context.ctxtPrimes; } // Add primes to the chain for the P factor of key-switching double sizeOfSpecialPrimes = maxDigitSize + log(c/(double)w)/2 + log(context.stdev *2); AddPrimesBySize(context, sizeOfSpecialPrimes, true); #endif cout << "* ctxtPrimes: " << context.ctxtPrimes << ", log(q0)=" << context.logOfProduct(context.ctxtPrimes) << endl; cout << "* specialPrimes: " << context.specialPrimes << ", log(P)=" << context.logOfProduct(context.specialPrimes) << endl; for (long i=0; i<context.numPrimes(); i++) { cout << " modulus #" << i << " " << context.ithPrime(i) << endl; } cout << endl; setTimersOn(); const ZZX& PhimX = context.zMstar.PhimX(); // The polynomial Phi_m(X) long phim = context.zMstar.phiM(); // The integer phi(m) FHESecKey secretKey(context); const FHEPubKey& publicKey = secretKey; #if 0 // Debug mode: use sk=1,2 DoubleCRT newSk(to_ZZX(2), context); long id1 = secretKey.ImportSecKey(newSk, 64, ptxtSpace); newSk -= 1; long id2 = secretKey.ImportSecKey(newSk, 64, ptxtSpace); #else long id1 = secretKey.GenSecKey(w,ptxtSpace); // A Hamming-weight-w secret key long id2 = secretKey.GenSecKey(w,ptxtSpace); // A second Hamming-weight-w secret key #endif ZZX zero = to_ZZX(0); // Ctxt zeroCtxt(publicKey); /******************************************************************/ /** TESTS BEGIN HERE ***/ /******************************************************************/ cout << "ptxtSpace = " << ptxtSpace << endl; GF2X G; // G is the AES polynomial, G(X)= X^8 +X^4 +X^3 +X +1 SetCoeff(G,8); SetCoeff(G,4); SetCoeff(G,3); SetCoeff(G,1); SetCoeff(G,0); GF2X X; SetX(X); #if 1 // code for rotations... { GF2X::HexOutput = 1; const PAlgebra& al = context.zMstar; const PAlgebraModTwo& al2 = context.modTwo; long ngens = al.numOfGens(); long nslots = al.NSlots(); DoubleCRT tmp(context); vector< vector< DoubleCRT > > maskTable; maskTable.resize(ngens); for (long i = 0; i < ngens; i++) { if (i==0 && al.SameOrd(i)) continue; long ord = al.OrderOf(i); maskTable[i].resize(ord+1, tmp); for (long j = 0; j <= ord; j++) { // initialize the mask that is 1 whenever // the ith coordinate is at least j vector<GF2X> maps, alphas, betas; al2.mapToSlots(maps, G); // Change G to X to get bits in the slots alphas.resize(nslots); for (long k = 0; k < nslots; k++) if (coordinate(al, i, k) >= j) alphas[k] = 1; else alphas[k] = 0; GF2X ptxt; al2.embedInSlots(ptxt, alphas, maps); // Sanity-check, make sure that encode/decode works as expected al2.decodePlaintext(betas, ptxt, G, maps); for (long k = 0; k < nslots; k++) { if (alphas[k] != betas[k]) { cout << " Mask computation failed, i="<<i<<", j="<<j<<"\n"; return 0; } } maskTable[i][j] = to_ZZX(ptxt); } } vector<GF2X> maps; al2.mapToSlots(maps, G); vector<GF2X> alphas(nslots); for (long i=0; i < nslots; i++) random(alphas[i], 8); // random degree-7 polynomial mod 2 for (long amt = 0; amt < 20; amt++) { cout << "."; GF2X ptxt; al2.embedInSlots(ptxt, alphas, maps); DoubleCRT pp(context); pp = to_ZZX(ptxt); rotate(pp, amt, maskTable); GF2X ptxt1 = to_GF2X(to_ZZX(pp)); vector<GF2X> betas; al2.decodePlaintext(betas, ptxt1, G, maps); for (long i = 0; i < nslots; i++) { if (alphas[i] != betas[(i+amt)%nslots]) { cout << " amt="<<amt<<" oops\n"; return 0; } } } cout << "\n"; #if 0 long ord0 = al.OrderOf(0); for (long i = 0; i < nslots; i++) { cout << alphas[i] << " "; if ((i+1) % (nslots/ord0) == 0) cout << "\n"; } cout << "\n\n"; cout << betas.size() << "\n"; for (long i = 0; i < nslots; i++) { cout << betas[i] << " "; if ((i+1) % (nslots/ord0) == 0) cout << "\n"; } #endif return 0; } #endif // an initial sanity check on noise estimates, // comparing the estimated variance to the actual average cout << "pk:"; checkCiphertext(publicKey.pubEncrKey, zero, secretKey); ZZX ptxt[6]; // first four are plaintext, last two are constants std::vector<Ctxt> ctxt(4, Ctxt(publicKey)); // Initialize the plaintext and constants to random 0-1 polynomials for (size_t j=0; j<6; j++) { ptxt[j].rep.SetLength(phim); for (long i = 0; i < phim; i++) ptxt[j].rep[i] = RandomBnd(ptxtSpace); ptxt[j].normalize(); if (j<4) { publicKey.Encrypt(ctxt[j], ptxt[j], ptxtSpace); cout << "c"<<j<<":"; checkCiphertext(ctxt[j], ptxt[j], secretKey); } } // perform upto 2L levels of computation, each level computing: // 1. c0 += c1 // 2. c1 *= c2 // L1' = max(L1,L2)+1 // 3. c1.reLinearlize // 4. c2 *= p4 // 5. c2.automorph(k) // k is the first generator of Zm^* /(2) // 6. c2.reLinearlize // 7. c3 += p5 // 8. c3 *= c0 // L3' = max(L3,L0,L1)+1 // 9. c2 *= c3 // L2' = max(L2,L0+1,L1+1,L3+1)+1 // 10. c0 *= c0 // L0' = max(L0,L1)+1 // 11. c0.reLinearlize // 12. c2.reLinearlize // 13. c3.reLinearlize // // The levels of the four ciphertexts behave as follows: // 0, 0, 0, 0 => 1, 1, 2, 1 => 2, 3, 3, 2 // => 4, 4, 5, 4 => 5, 6, 6, 5 // => 7, 7, 8, 7 => 8,,9, 9, 10 => [...] // // We perform the same operations on the plaintext, and after each operation // we check that decryption still works, and print the curretn modulus and // noise estimate. We stop when we get the first decryption error, or when // we reach 2L levels (which really should not happen). zz_pContext zzpc; zz_p::init(ptxtSpace); zzpc.save(); const zz_pXModulus F = to_zz_pX(PhimX); long g = context.zMstar.ZmStarGen(0); // the first generator in Zm* zz_pX x2g(g, 1); zz_pX p2; // generate a key-switching matrix from s(X^g) to s(X) secretKey.GenKeySWmatrix(/*powerOfS= */ 1, /*powerOfX= */ g, 0, 0, /*ptxtSpace=*/ ptxtSpace); // generate a key-switching matrix from s^2 to s secretKey.GenKeySWmatrix(/*powerOfS= */ 2, /*powerOfX= */ 1, 0, 0, /*ptxtSpace=*/ ptxtSpace); // generate a key-switching matrix from s^3 to s secretKey.GenKeySWmatrix(/*powerOfS= */ 3, /*powerOfX= */ 1, 0, 0, /*ptxtSpace=*/ ptxtSpace); for (long lvl=0; lvl<2*L; lvl++) { cout << "=======================================================\n"; ctxt[0] += ctxt[1]; ptxt[0] += ptxt[1]; PolyRed(ptxt[0], ptxtSpace, true); cout << "c0+=c1: "; checkCiphertext(ctxt[0], ptxt[0], secretKey); ctxt[1].multiplyBy(ctxt[2]); ptxt[1] = (ptxt[1] * ptxt[2]) % PhimX; PolyRed(ptxt[1], ptxtSpace, true); cout << "c1*=c2: "; checkCiphertext(ctxt[1], ptxt[1], secretKey); ctxt[2].multByConstant(ptxt[4]); ptxt[2] = (ptxt[2] * ptxt[4]) % PhimX; PolyRed(ptxt[2], ptxtSpace, true); cout << "c2*=p4: "; checkCiphertext(ctxt[2], ptxt[2], secretKey); ctxt[2] >>= g; zzpc.restore(); p2 = to_zz_pX(ptxt[2]); CompMod(p2, p2, x2g, F); ptxt[2] = to_ZZX(p2); cout << "c2>>="<<g<<":"; checkCiphertext(ctxt[2], ptxt[2], secretKey); ctxt[2].reLinearize(); cout << "c2.relin:"; checkCiphertext(ctxt[2], ptxt[2], secretKey); ctxt[3].addConstant(ptxt[5]); ptxt[3] += ptxt[5]; PolyRed(ptxt[3], ptxtSpace, true); cout << "c3+=p5: "; checkCiphertext(ctxt[3], ptxt[3], secretKey); ctxt[3].multiplyBy(ctxt[0]); ptxt[3] = (ptxt[3] * ptxt[0]) % PhimX; PolyRed(ptxt[3], ptxtSpace, true); cout << "c3*=c0: "; checkCiphertext(ctxt[3], ptxt[3], secretKey); ctxt[0].square(); ptxt[0] = (ptxt[0] * ptxt[0]) % PhimX; PolyRed(ptxt[0], ptxtSpace, true); cout << "c0*=c0: "; checkCiphertext(ctxt[0], ptxt[0], secretKey); ctxt[2].multiplyBy(ctxt[3]); ptxt[2] = (ptxt[2] * ptxt[3]) % PhimX; PolyRed(ptxt[2], ptxtSpace, true); cout << "c2*=c3: "; checkCiphertext(ctxt[2], ptxt[2], secretKey); } /******************************************************************/ /** TESTS END HERE ***/ /******************************************************************/ cout << endl; return 0; }
t1 = 0; for (i = 1; i <= j-1; i++) MulAdd(t1, t1, mu_j[i], buf[i]); mu_k[j] = (buf[j] = (s - t1))/c[j]; } s = 0; for (j = 1; j <= k-1; j++) MulAdd(s, s, mu_k[j], buf[j]); c[k] = b[k] - s; } NTL_TLS_GLOBAL_DECL_INIT(xdouble, red_fudge, (to_xdouble(0))) static NTL_CHEAP_THREAD_LOCAL long log_red = 0; static void init_red_fudge() { NTL_TLS_GLOBAL_ACCESS(red_fudge); long i; log_red = long(0.50*NTL_DOUBLE_PRECISION); red_fudge = 1; for (i = log_red; i > 0; i--) red_fudge = red_fudge*0.5;