int main (void) { struct xpr z, h, f, u; printf (" Test of Exp Functions\n"); z = xneg (xOne); h = atox ("0.5"); u = atox ("3.01"); for (; xprcmp (&z, &u) < 0; z = xadd (z, h, 0)) { /* compute extended precision exponential */ f = xexp (z); printf (" %8.4f ", xtodbl (z)); xprxpr (f, decd); f = xexp2 (z); printf ("\n "); xprxpr (f, decd); f = xexp10 (z); printf ("\n "); xprxpr (f, decd); putchar ('\n'); } return 0; }
// key-switch to (1,s_i), s_i is the base key with index keyID. If // keyID<0 then re-linearize to any key for which a switching matrix exists void Ctxt::reLinearize(long keyID) { // Special case: if *this is empty or already re-linearized then do nothing if (this->isEmpty() || this->inCanonicalForm(keyID)) return; FHE_TIMER_START; this->reduce(); // To relinearize, the primeSet must be disjoint from the special primes if (!primeSet.disjointFrom(context.specialPrimes)) { modDownToSet(primeSet / context.specialPrimes); // cout << "<<< special primes\n"; } long g = ptxtSpace; Ctxt tmp(pubKey, ptxtSpace); // an empty ciphertext, same plaintext space double logProd = context.logOfProduct(context.specialPrimes); tmp.noiseVar = noiseVar * xexp(2*logProd); // The noise after mod-UP for (size_t i=0; i<parts.size(); i++) { CtxtPart& part = parts[i]; // For a part relative to 1 or base, only scale and add if (part.skHandle.isOne() || part.skHandle.isBase(keyID)) { part.addPrimesAndScale(context.specialPrimes); tmp.addPart(part, /*matchPrimeSet=*/true); continue; } // Look for a key-switching matrix to re-linearize this part const KeySwitch& W = (keyID>=0)? pubKey.getKeySWmatrix(part.skHandle,keyID) : pubKey.getAnyKeySWmatrix(part.skHandle); assert(W.toKeyID>=0); // verify that a switching matrix exists g = GCD(W.ptxtSpace, g); // verify that the plaintext spaces match assert (g>1); tmp.ptxtSpace = g; tmp.keySwitchPart(part, W); // switch this part & update noiseVar } *this = tmp; #if 0 // alternative strategy: get rid of special primes // after each reLin... if (!primeSet.disjointFrom(context.specialPrimes)) { modDownToSet(primeSet / context.specialPrimes); // cout << ">>> special primes\n"; } #endif FHE_TIMER_STOP; }
double log_sum(const double *ptr, const size_t size) { if (size == 1) /* do special case faster */ return *ptr; else { const double max = *max_dbl(ptr, size); const double *end = ptr + size; double sum = 0; assert(size > 0); for (; ptr != end; ++ptr) sum += xexp(*ptr - max); return xlog(sum) + max; } }
static void ComputeBKZThresh(xdouble *c, long beta) { BKZThresh.SetLength(beta-1); long i; double x; x = 0; for (i = 1; i <= beta-1; i++) { x += log(c[i-1]); BKZThresh(i) = xexp(x/double(i))*BKZConstant(i); } }
// mod-switch down to primeSet \intersect s, after this call we have // primeSet<=s. s must contain either all special primes or none of them. void Ctxt::modDownToSet(const IndexSet &s) { IndexSet intersection = primeSet & s; // assert(!empty(intersection)); // some primes must be left if (empty(intersection)) { cerr << "modDownToSet called from "<<primeSet<<" to "<<s<<endl; exit(1); } if (intersection==primeSet) return; // nothing to do, removing no primes FHE_TIMER_START; IndexSet setDiff = primeSet / intersection; // set-minus // Scale down all the parts: use either a simple "drop down" (just removing // primes, i.e., reducing the ctxt modulo the samaller modulus), or a "real // modulus switching" with rounding, basically whichever yeilds smaller // noise. Recall that we keep the invariant that a ciphertext mod Q is // decrypted to Q*m (mod p), so if we just "drop down" we still need to // multiply by (Q^{-1} mod p). // Get an estimate for the added noise term for modulus switching xdouble addedNoiseVar = modSwitchAddedNoiseVar(); if (noiseVar*ptxtSpace*ptxtSpace < addedNoiseVar) { // just "drop down" long prodInv = InvMod(rem(context.productOfPrimes(setDiff),ptxtSpace), ptxtSpace); for (size_t i=0; i<parts.size(); i++) { parts[i].removePrimes(setDiff); // remove the primes not in s parts[i] *= prodInv; // WARNING: the following line is written just so to prevent overflow noiseVar = noiseVar*prodInv*prodInv; } // cerr << "DEGENERATE DROP\n"; } else { // do real mod switching for (size_t i=0; i<parts.size(); i++) parts[i].scaleDownToSet(intersection, ptxtSpace); // update the noise estimate double f = context.logOfProduct(setDiff); noiseVar /= xexp(2*f); noiseVar += addedNoiseVar; } primeSet.remove(setDiff); // remove the primes not in s assert(verifyPrimeSet()); // sanity-check: ensure primeSet is still valid FHE_TIMER_STOP; }
void decryptAndPrint(ostream& s, const Ctxt& ctxt, const FHESecKey& sk, const EncryptedArray& ea, long flags) { const FHEcontext& context = ctxt.getContext(); xdouble noiseEst = sqrt(ctxt.getNoiseVar()); xdouble modulus = xexp(context.logOfProduct(ctxt.getPrimeSet())); vector<ZZX> ptxt; ZZX p, pp; sk.Decrypt(p, ctxt, pp); s << "plaintext space mod "<<ctxt.getPtxtSpace() << ", level="<<ctxt.findBaseLevel() << ", \n |noise|=q*" << (coeffsL2Norm(pp)/modulus) << ", |noiseEst|=q*" << (noiseEst/modulus) <<endl; if (flags & FLAG_PRINT_ZZX) { s << " before mod-p reduction="; printZZX(s,pp) <<endl; } if (flags & FLAG_PRINT_POLY) { s << " after mod-p reduction="; printZZX(s,p) <<endl; } if (flags & FLAG_PRINT_VEC) { ea.decode(ptxt, p); if (ea.getAlMod().getTag() == PA_zz_p_tag && ctxt.getPtxtSpace() != ea.getAlMod().getPPowR()) { long g = GCD(ctxt.getPtxtSpace(), ea.getAlMod().getPPowR()); for (long i=0; i<ea.size(); i++) PolyRed(ptxt[i], g, true); } s << " decoded to "; if (deg(p) < 40) // just pring the whole thing s << ptxt << endl; else if (ptxt.size()==1) // a single slot printZZX(s, ptxt[0]) <<endl; else { // print first and last slots printZZX(s, ptxt[0],20) << "--"; printZZX(s, ptxt[ptxt.size()-1], 20) <<endl; } } }
// mod-switch up to add the primes in s \setminus primeSet, after this call we // have s<=primeSet. s must contain either all special primes or none of them. void Ctxt::modUpToSet(const IndexSet &s) { // FHE_TIMER_START; IndexSet setDiff = s/primeSet; // set minus (primes in s but not in primeSet) if (empty(setDiff)) return; // nothing to do, no primes are added // scale up all the parts to use also the primes in setDiff double f = 0.0; for (long i=0; i<lsize(parts); i++) { // addPrimesAndScale returns the logarithm of the product of added primes, // all calls should return the same value = log(prod. of primes in setDiff) f = parts[i].addPrimesAndScale(setDiff); } // The variance estimate grows by a factor of exp(f)^2 = exp(2f) noiseVar *= xexp(2*f); primeSet.insert(setDiff); // add setDiff to primeSet assert(verifyPrimeSet()); // sanity-check: ensure primeSet is still valid // FHE_TIMER_STOP; }
// Compute the number of digits that we need and the esitmated // added noise from switching this ciphertext part. static std::pair<long, NTL::xdouble> keySwitchNoise(const CtxtPart& p, const FHEPubKey& pubKey, long pSpace) { const FHEcontext& context = p.getContext(); 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); return std::pair<long, NTL::xdouble>(nDigits,addedNoise); }
// 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
// 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; }
void adjustLevelForMult(Ctxt& c1, const char name1[], const ZZX& p1, Ctxt& c2, const char name2[], const ZZX& p2, const FHESecKey& sk) { const FHEcontext& context = c1.getContext(); // The highest possible level for this multiplication is the // intersection of the two primeSets, without the special primes. IndexSet primes = c1.getPrimeSet() & c2.getPrimeSet(); primes.remove(context.specialPrimes); assert (!empty(primes)); // double phim = (double) context.zMstar.phiM(); // double factor = c_m*sqrt(log(phim))*4; xdouble n1,n2,d1,d2; xdouble dvar1 = c1.modSwitchAddedNoiseVar(); xdouble dvar2 = c2.modSwitchAddedNoiseVar(); // xdouble dmag1 = c1.modSwitchAddedNoiseMag(c_m); // xdouble dmag2 = c2.modSwitchAddedNoiseMag(c_m); // cout << " ** log(dvar1)=" << log(dvar1) // << ", log(dvar2)=" << log(dvar2) <<endl; double logF1, logF2; xdouble n1var, n2var, modSize; // n1mag, n2mag, // init to large number xdouble noiseVarRatio=xexp(2*(context.logOfProduct(context.ctxtPrimes) + context.logOfProduct(context.specialPrimes))); // xdouble noiseMagRatio=noiseVarRatio; // Find the level that minimizes the noise-to-modulus ratio bool oneLevelMore = false; for (IndexSet levelDown = primes; !empty(levelDown); levelDown.remove(levelDown.last())) { // compute noise variane/magnitude after mod-switchign to this level logF1 = context.logOfProduct(c1.getPrimeSet() / levelDown); n1var = c1.getNoiseVar()/xexp(2*logF1); logF2 = context.logOfProduct(c2.getPrimeSet() / levelDown); n2var = c2.getNoiseVar()/xexp(2*logF2); // compute modulus/noise ratio at this level modSize = xexp(context.logOfProduct(levelDown)); xdouble nextNoiseVarRatio = sqrt((n1var+dvar1)*(n2var+dvar2))/modSize; if (nextNoiseVarRatio < 2*noiseVarRatio || oneLevelMore) { noiseVarRatio = nextNoiseVarRatio; primes = levelDown; // record the currently best prime set n1 = n1var; d1=dvar1; n2 = n2var; d2=dvar2; } oneLevelMore = (n1var > dvar1 || n2var > dvar2); } if (primes < c1.getPrimeSet()) { cout << " ** " << c1.getPrimeSet()<<"=>"<<primes << endl; cout << " n1var="<<n1<<", d1var="<<d1<<endl;; c1.modDownToSet(primes); cout << name1 << ".mDown:"; checkCiphertext(c1, p1, sk); } if (primes < c2.getPrimeSet()) { cout << " ** " << c2.getPrimeSet()<<"=>"<<primes << endl; cout << " n2var="<<n2<<", d2var="<<d2<<endl;; c2.modDownToSet(primes); cout << name2 << ".mDown:"; checkCiphertext(c2, p2, sk); } }