// Find the IndexSet such that modDown to that set of primes makes the // additive term due to rounding into the dominant noise term void Ctxt::findBaseSet(IndexSet& s) const { if (getNoiseVar()<=0.0) { // an empty ciphertext s = context.ctxtPrimes; return; } assert(verifyPrimeSet()); bool halfSize = context.containsSmallPrime(); double curNoise = log(getNoiseVar())/2; double firstNoise = context.logOfPrime(0); double noiseThreshold = log(modSwitchAddedNoiseVar())*0.55; // FIXME: The above should have been 0.5. Making it a bit more means // that we will mod-switch a little less frequently, whether this is // a good thing needs to be tested. // remove special primes, if they are included in this->primeSet s = getPrimeSet(); if (!s.disjointFrom(context.specialPrimes)) { // scale down noise curNoise -= context.logOfProduct(context.specialPrimes); s.remove(context.specialPrimes); } /* We compare below to noiseThreshold+1 rather than to noiseThreshold * to make sure that if you mod-switch down to c.findBaseSet() and * then immediately call c.findBaseSet() again, it will not tell you * to mod-switch further down. Note that mod-switching adds close to * noiseThreshold to the scaled noise, so if the scaled noise was * equal to noiseThreshold then after mod-switchign you would have * roughly twice as much noise. Since we're mesuring the log, it means * that you may have as much as noiseThreshold+log(2), which we round * up to noiseThreshold+1 in the test below. */ if (curNoise<=noiseThreshold+1) return; // no need to mod down // if the first prime in half size, begin by removing it if (halfSize && s.contains(0)) { curNoise -= firstNoise; s.remove(0); } // while noise is larger than threshold, scale down by the next prime while (curNoise>noiseThreshold && !empty(s)) { curNoise -= context.logOfPrime(s.last()); s.remove(s.last()); } // Add 1st prime if s is empty or if this does not increase noise too much if (empty(s) || (!s.contains(0) && curNoise+firstNoise<=noiseThreshold)) { s.insert(0); curNoise += firstNoise; } if (curNoise>noiseThreshold && log_of_ratio()>-0.5) cerr << "Ctxt::findBaseSet warning: already at lowest level\n"; }
bool IndexSet::disjointFrom(const IndexSet& s) const { // quick tests for some common cases if (card() == 0 || s.card() == 0 || last() < s.first() || s.last() < first()) return true; for (long i = s.first(); i <= s.last(); i = s.next(i)) if (contains(i)) return false; return true; }
// Find the IndexSet such that modDown to that set of primes makes the // additive term due to rounding into the dominant noise term void Ctxt::findBaseSet(IndexSet& s) const { if (getNoiseVar()<=0.0) { // an empty ciphertext s = context.ctxtPrimes; return; } assert(verifyPrimeSet()); bool halfSize = context.containsSmallPrime(); double addedNoise = log(modSwitchAddedNoiseVar())/2; double curNoise = log(getNoiseVar())/2; double firstNoise = context.logOfPrime(0); // remove special primes, if they are included in this->primeSet s = getPrimeSet(); if (!s.disjointFrom(context.specialPrimes)) { // scale down noise curNoise -= context.logOfProduct(context.specialPrimes); s.remove(context.specialPrimes); } if (curNoise<=2*addedNoise) return; // no need to mod down // if the first prime in half size, begin by removing it if (halfSize && s.contains(0)) { curNoise -= firstNoise; s.remove(0); } // while noise is larger than added term, scale down by the next prime while (curNoise>addedNoise && card(s)>1) { curNoise -= context.logOfPrime(s.last()); s.remove(s.last()); } if (halfSize) { // If noise is still too big, drop last big prime and insert half-size prime if (curNoise>addedNoise) { curNoise = firstNoise; s = IndexSet(0); } // Otherwise check if you can add back the half-size prime else if (curNoise+firstNoise <= addedNoise) { curNoise += firstNoise; s.insert(0); } } if (curNoise>addedNoise && log_of_ratio()>-0.5) cerr << "Ctxt::findBaseSet warning: already at lowest level\n"; }
DoubleCRT::DoubleCRT(const ZZX& poly, const FHEcontext &_context, const IndexSet& s) : context(_context), map(new DoubleCRTHelper(_context)) { assert(s.last() < context.numPrimes()); map.insert(s); if (dryRun) return; // convert the integer polynomial to FFT representation modulo the primes for (long i = s.first(); i <= s.last(); i = s.next(i)) { const Cmodulus &pi = context.ithModulus(i); pi.FFT(map[i], poly); // reduce mod pi and store FFT image } }
DoubleCRT::DoubleCRT(const FHEcontext &_context, const IndexSet& s) : context(_context), map(new DoubleCRTHelper(_context)) { assert(s.last() < context.numPrimes()); map.insert(s); if (dryRun) return; long phim = context.zMStar.getPhiM(); for (long i = s.first(); i <= s.last(); i = s.next(i)) { vec_long& row = map[i]; for (long j = 0; j < phim; j++) row[j] = 0; } }
void IndexSet::remove(const IndexSet& s) { if (this == &s) { clear(); return; } if (s.card() == 0) return; if (card() == 0) return; for (long i = s.first(); i <= s.last(); i = s.next(i)) remove(i); // NOTE: traversal order should not matter here }
SingleCRT::SingleCRT(const ZZX&poly, const FHEcontext& _context, const IndexSet& s) : context(_context) { assert(s.last() < context.numPrimes()); map.insert(s); *this = poly; // convert polynomial to singleCRT representation }
// Without specifying a ZZX, we get the zero polynomial SingleCRT::SingleCRT(const FHEcontext &_context, const IndexSet& s) : context(_context) { assert(s.last() < context.numPrimes()); map.insert(s); // default constructor for ZZX creates the zero polynomial }
void IndexSet::insert(const IndexSet& s) { if (this == &s) return; if (s.card() == 0) return; if (card() == 0) { *this = s; return; } for (long i = s.last(); i >= s.first(); i = s.prev(i)) insert(i); // NOTE: traversal done from high to low so as to trigger at // at most one resize }
DoubleCRT::DoubleCRT(const ZZX& poly) : context(*activeContext), map(new DoubleCRTHelper(*activeContext)) { IndexSet s = IndexSet(0, context.numPrimes()-1); // FIXME: maybe the default index set should be determined by context? map.insert(s); if (dryRun) return; // convert the integer polynomial to FFT representation modulo the primes for (long i = s.first(); i <= s.last(); i = s.next(i)) { const Cmodulus &pi = context.ithModulus(i); pi.FFT(map[i], poly); // reduce mod pi and store FFT image } }
// Expand index set by s1, and multiply by \prod{q \in s1}. s1 is assumed to // be disjoint from the current index set. Returns the logarithm of product. double DoubleCRT::addPrimesAndScale(const IndexSet& s1) { if (empty(s1)) return 0.0; // nothing to do assert(empty(s1 & map.getIndexSet())); // s1 is disjoint from *this // compute factor to scale existing rows ZZ factor = to_ZZ(1); double logFactor = 0.0; for (long i = s1.first(); i <= s1.last(); i = s1.next(i)) { long qi = context.ithPrime(i); factor *= qi; logFactor += log((double)qi); } // scale existing rows long phim = context.zMStar.getPhiM(); const IndexSet& iSet = map.getIndexSet(); for (long i = iSet.first(); i <= iSet.last(); i = iSet.next(i)) { long qi = context.ithPrime(i); long f = rem(factor, qi); // f = factor % qi vec_long& row = map[i]; // scale row by a factor of f modulo qi mulmod_precon_t bninv = PrepMulModPrecon(f, qi, 1.0/(double)qi); for (long j=0; j<phim; j++) row[j] = MulModPrecon(row[j], f, qi, bninv); } // insert new rows and fill them with zeros map.insert(s1); // add new rows to the map for (long i = s1.first(); i <= s1.last(); i = s1.next(i)) { vec_long& row = map[i]; for (long j=0; j<phim; j++) row[j] = 0; } return logFactor; }
// expand index set by s1. // it is assumed that s1 is disjoint from the current index set. void DoubleCRT::addPrimes(const IndexSet& s1) { if (empty(s1)) return; // nothing to do assert( disjoint(s1,map.getIndexSet()) ); // s1 is disjoint from *this ZZX poly; toPoly(poly); // recover in coefficient representation map.insert(s1); // add new rows to the map if (dryRun) return; // fill in new rows for (long i = s1.first(); i <= s1.last(); i = s1.next(i)) { context.ithModulus(i).FFT(map[i], poly); // reduce mod p_i and store FFT image } }
DoubleCRT::DoubleCRT(const FHEcontext &_context) : context(_context), map(new DoubleCRTHelper(_context)) { IndexSet s = IndexSet(0, context.numPrimes()-1); // FIXME: maybe the default index set should be determined by context? map.insert(s); if (dryRun) return; long phim = context.zMStar.getPhiM(); for (long i = s.first(); i <= s.last(); i = s.next(i)) { vec_long& row = map[i]; for (long j = 0; j < phim; j++) row[j] = 0; } }
void SingleCRT::addPrimes(const IndexSet& s1) { assert(card(s1 & map.getIndexSet()) == 0); ZZX poly, poly1; toPoly(poly); // recover in coefficient representation map.insert(s1); // add new rows to the map // fill in new rows for (long i = s1.first(); i <= s1.last(); i = s1.next(i)) { ZZ pi = to_ZZ(context.ithPrime(i)); poly1 = poly; PolyRed(poly1,pi,true); // the flag true means reduce to [0,pi-1) map[i] = poly; } }
// Copy only the primes in s \intersect other.getIndexSet() void DoubleCRT::partialCopy(const DoubleCRT& other, const IndexSet& _s) { if (&context != &other.context) Error("DoubleCRT::partialCopy: incompatible contexts"); // set the primes of *this to s \intersect other.getIndexSet() IndexSet s = _s; s.retain(other.getIndexSet()); map.remove(getIndexSet() / s); map.insert(s / getIndexSet()); long phim = context.zMStar.getPhiM(); for (long i = s.first(); i <= s.last(); i = s.next(i)) { vec_long& row = map[i]; const vec_long& other_row = other.map[i]; for (long j = 0; j < phim; j++) row[j] = other_row[j]; } }
//FIXME: both the reduction from powerful to the individual primes and // the CRT back to poly can be made more efficient void PowerfulDCRT::powerfulToZZX(ZZX& poly, const Vec<ZZ>& powerful, IndexSet set) const { zz_pBak bak; bak.save(); // backup NTL's current modulus if (empty(set)) set = IndexSet(0, pConvVec.length()-1); clear(poly); // poly.SetLength(powerful.length()); ZZ product = conv<ZZ>(1L); for (long i = set.first(); i <= set.last(); i = set.next(i)) { pConvVec[i].restoreModulus(); // long newPrime = zz_p::modulus(); HyperCube<zz_p> oneRowPwrfl(indexes.shortSig); conv(oneRowPwrfl.getData(), powerful); // reduce and convert to Vec<zz_p> zz_pX oneRowPoly; pConvVec[i].powerfulToPoly(oneRowPoly, oneRowPwrfl); CRT(poly, product, oneRowPoly); // NTL :-) } poly.normalize(); }
// If the IndexSet is omitted, default to all the primes in the chain void PowerfulDCRT::ZZXtoPowerful(Vec<ZZ>& out, const ZZX& poly, IndexSet set) const { if (empty(set)) set = IndexSet(0, pConvVec.length()-1); zz_pBak bak; bak.save(); // backup NTL's current modulus ZZ product = conv<ZZ>(1L); for (long i = set.first(); i <= set.last(); i = set.next(i)) { pConvVec[i].restoreModulus(); long newPrime = zz_p::modulus(); zz_pX oneRowPoly; conv(oneRowPoly, poly); // reduce mod p and convert to zz_pX HyperCube<zz_p> oneRowPwrfl(indexes.shortSig); pConvVec[i].polyToPowerful(oneRowPwrfl, oneRowPoly); if (i == set.first()) // just copy conv(out, oneRowPwrfl.getData()); else // CRT intVecCRT(out, product, oneRowPwrfl.getData(), newPrime); // in NumbTh product *= newPrime; } }
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); } }
bool IndexSet::contains(const IndexSet& s) const { for (long i = s.first(); i <= s.last(); i = s.next(i)) if (!contains(i)) return false; return true; }
void FHEcontext::productOfPrimes(ZZ& p, const IndexSet& s) const { p = 1; for (long i = s.first(); i <= s.last(); i = s.next(i)) p *= ithPrime(i); }