// Add a constant polynomial void Ctxt::addConstant(const DoubleCRT& dcrt, double size) { FHE_TIMER_START; // If the size is not given, we use the default value phi(m)*(ptxtSpace/2)^2 if (size < 0.0) { // WARNING: the following line is written just so to prevent overflow size = ((double) context.zMStar.getPhiM()) * ptxtSpace*ptxtSpace /4.0; } // Scale the constant, then add it to the part that points to one long f = (ptxtSpace>2)? rem(context.productOfPrimes(primeSet),ptxtSpace): 1; if (f!=1) { DoubleCRT tmp = dcrt; tmp *= f; addPart(tmp, SKHandle(0,1,0)); } else addPart(dcrt, SKHandle(0,1,0)); noiseVar += (size*f)*f; FHE_TIMER_STOP; }
// Multiply vector of digits by key-switching matrix and add to *this. // It is assumed that W has at least as many b[i]'s as there are digits. // The vector of digits is modified in place. void Ctxt::keySwitchDigits(const KeySwitch& W, vector<DoubleCRT>& digits) { // 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); // Subsequent ai's use the evolving RNG state RandomState state; // backup the NTL PRG seed NTL::SetSeed(W.prgSeed); // Add the columns in, one by one DoubleCRT tmpDCRT(context, IndexSet::emptySet()); for (size_t i=0; i<digits.size(); i++) { FHE_NTIMER_START(KS_loop); ai.randomize(); tmpDCRT = digits[i]; // The operations below all use the IndexSet of tmpDCRT // add digit*a[i] with a handle pointing to base of W.toKeyID { FHE_NTIMER_START(KS_loop_1); tmpDCRT.Mul(ai, /*matchIndexSet=*/false); } { FHE_NTIMER_START(KS_loop_2); this->addPart(tmpDCRT, SKHandle(1,1,W.toKeyID), /*matchPrimeSet=*/true); } // add digit*b[i] with a handle pointing to one { FHE_NTIMER_START(KS_loop_3); digits[i].Mul(W.b[i], /*matchIndexSet=*/false); } { FHE_NTIMER_START(KS_loop_4); this->addPart(digits[i], SKHandle(), /*matchPrimeSet=*/true); } } }
// Add a constant polynomial void Ctxt::addConstant(const DoubleCRT& dcrt, double size) { // If the size is not given, we use the default value phi(m)*(ptxtSpace/2)^2 if (size < 0.0) { // WARNING: the following line is written to prevent integer overflow size = ((double) context.zMStar.getPhiM()) * ptxtSpace*ptxtSpace /4.0; } // Scale the constant, then add it to the part that points to one long f = (ptxtSpace>2)? rem(context.productOfPrimes(primeSet),ptxtSpace): 1; noiseVar += (size*f)*f; IndexSet delta = dcrt.getIndexSet() / primeSet; // set minus if (f==1 && empty(delta)) { // just add it addPart(dcrt, SKHandle(0,1,0)); return; } // work with a local copy DoubleCRT tmp = dcrt; if (!empty(delta)) tmp.removePrimes(delta); if (f!=1) tmp *= f; addPart(tmp, SKHandle(0,1,0)); }
// 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