// 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 nDigits; NTL::xdouble addedNoise; std::tie(nDigits,addedNoise)= keySwitchNoise(p, pubKey, W.ptxtSpace); // 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 keySwitchDigits(W, polyDigits); noiseVar += addedNoise; // update the noise estimate } // restore random state upon destruction of the RandomState, see NumbTh.h
// 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