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 }
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; }
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 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 } }
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; } }
//------------------------------getfreeset------------------------------------- // Pull from free list, or allocate. Internal allocation on the returned set // is always from thread local storage. IndexSet *PhaseLive::getfreeset( ) { IndexSet *f = _free_IndexSet; if( !f ) { f = new IndexSet; // f->set_arena(Thread::current()->resource_area()); f->initialize(_maxlrg, Thread::current()->resource_area()); } else { // Pull from free list _free_IndexSet = f->next(); //f->_cnt = 0; // Reset to empty // f->set_arena(Thread::current()->resource_area()); f->initialize(_maxlrg, Thread::current()->resource_area()); } return f; }
// 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; }
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; } }
// 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 } }
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 PhaseLive::compute(uint maxlrg) { _maxlrg = maxlrg; _worklist = new (_arena) Block_List(); // Init the sparse live arrays. This data is live on exit from here! // The _live info is the live-out info. _live = (IndexSet*)_arena->Amalloc(sizeof(IndexSet)*_cfg._num_blocks); uint i; for( i=0; i<_cfg._num_blocks; i++ ) { _live[i].initialize(_maxlrg); } // Init the sparse arrays for delta-sets. ResourceMark rm; // Nuke temp storage on exit // Does the memory used by _defs and _deltas get reclaimed? Does it matter? TT // Array of values defined locally in blocks _defs = NEW_RESOURCE_ARRAY(IndexSet,_cfg._num_blocks); for( i=0; i<_cfg._num_blocks; i++ ) { _defs[i].initialize(_maxlrg); } // Array of delta-set pointers, indexed by block pre_order-1. _deltas = NEW_RESOURCE_ARRAY(IndexSet*,_cfg._num_blocks); memset( _deltas, 0, sizeof(IndexSet*)* _cfg._num_blocks); _free_IndexSet = NULL; // Blocks having done pass-1 VectorSet first_pass(Thread::current()->resource_area()); // Outer loop: must compute local live-in sets and push into predecessors. uint iters = _cfg._num_blocks; // stat counters for( uint j=_cfg._num_blocks; j>0; j-- ) { Block *b = _cfg._blocks[j-1]; // Compute the local live-in set. Start with any new live-out bits. IndexSet *use = getset( b ); IndexSet *def = &_defs[b->_pre_order-1]; uint i; for( i=b->_nodes.size(); i>1; i-- ) { Node *n = b->_nodes[i-1]; if( n->is_Phi() ) break; // BoxNodes keep their input alive as long as their uses. If we // see a BoxNode then make its input live to the Root block. // Because we are solving LIVEness, the input now becomes live // over the whole procedure, interferencing with everything else // and getting a private unshared stack slot. YeeeHaw! MachNode *mach = n->is_Mach(); if( mach && mach->ideal_Opcode() == Op_Box ) getset(_cfg._broot)->insert( _names[n->in(1)->_idx] ); uint r = _names[n->_idx]; def->insert( r ); use->remove( r ); uint cnt = n->req(); for( uint k=1; k<cnt; k++ ) { Node *nk = n->in(k); uint nkidx = nk->_idx; if( _cfg._bbs[nkidx] != b ) use->insert( _names[nkidx] ); } } // Remove anything defined by Phis and the block start instruction for( uint k=i; k>0; k-- ) { uint r = _names[b->_nodes[k-1]->_idx]; def->insert( r ); use->remove( r ); } // Push these live-in things to predecessors for( uint l=1; l<b->num_preds(); l++ ) { Block *p = _cfg._bbs[b->pred(l)->_idx]; add_liveout( p, use, first_pass ); // PhiNode uses go in the live-out set of prior blocks. for( uint k=i; k>0; k-- ) add_liveout( p, _names[b->_nodes[k-1]->in(l)->_idx], first_pass ); } freeset( b ); first_pass.set(b->_pre_order); // Inner loop: blocks that picked up new live-out values to be propagated while( _worklist->size() ) { // !!!!! // #ifdef ASSERT iters++; // #endif Block *b = _worklist->pop(); IndexSet *delta = getset(b); assert( delta->count(), "missing delta set" ); // Add new-live-in to predecessors live-out sets for( uint l=1; l<b->num_preds(); l++ ) add_liveout( _cfg._bbs[b->pred(l)->_idx], delta, first_pass ); freeset(b); } // End of while-worklist-not-empty } // End of for-all-blocks-outer-loop // We explicitly clear all of the IndexSets which we are about to release. // This allows us to recycle their internal memory into IndexSet's free list. for( i=0; i<_cfg._num_blocks; i++ ) { _defs[i].clear(); if (_deltas[i]) { // Is this always true? _deltas[i]->clear(); } } IndexSet *free = _free_IndexSet; while (free != NULL) { IndexSet *temp = free; free = free->next(); temp->clear(); } }
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); }
// bootstrap a ciphertext to reduce noise void FHEPubKey::reCrypt(Ctxt &ctxt) { FHE_TIMER_START; // Some sanity checks for dummy ciphertext long ptxtSpace = ctxt.getPtxtSpace(); if (ctxt.isEmpty()) return; if (ctxt.parts.size()==1 && ctxt.parts[0].skHandle.isOne()) { // Dummy encryption, just ensure that it is reduced mod p ZZX poly = to_ZZX(ctxt.parts[0]); for (long i=0; i<poly.rep.length(); i++) poly[i] = to_ZZ( rem(poly[i],ptxtSpace) ); poly.normalize(); ctxt.DummyEncrypt(poly); return; } assert(recryptKeyID>=0); // check that we have bootstrapping data long p = getContext().zMStar.getP(); long r = getContext().alMod.getR(); long p2r = getContext().alMod.getPPowR(); // the bootstrapping key is encrypted relative to plaintext space p^{e-e'+r}. long e = getContext().rcData.e; long ePrime = getContext().rcData.ePrime; long p2ePrime = power_long(p,ePrime); long q = power_long(p,e)+1; assert(e>=r); #ifdef DEBUG_PRINTOUT cerr << "reCrypt: p="<<p<<", r="<<r<<", e="<<e<<" ePrime="<<ePrime << ", q="<<q<<endl; #endif // can only bootstrap ciphertext with plaintext-space dividing p^r assert(p2r % ptxtSpace == 0); FHE_NTIMER_START(preProcess); // Make sure that this ciphertxt is in canonical form if (!ctxt.inCanonicalForm()) ctxt.reLinearize(); // Mod-switch down if needed IndexSet s = ctxt.getPrimeSet() / getContext().specialPrimes; // set minus if (s.card()>2) { // leave only bottom two primes long frst = s.first(); long scnd = s.next(frst); IndexSet s2(frst,scnd); s.retain(s2); // retain only first two primes } ctxt.modDownToSet(s); // key-switch to the bootstrapping key ctxt.reLinearize(recryptKeyID); // "raw mod-switch" to the bootstrapping mosulus q=p^e+1. vector<ZZX> zzParts; // the mod-switched parts, in ZZX format double noise = ctxt.rawModSwitch(zzParts, q); noise = sqrt(noise); // Add multiples of p2r and q to make the zzParts divisible by p^{e'} long maxU=0; for (long i=0; i<(long)zzParts.size(); i++) { // make divisible by p^{e'} long newMax = makeDivisible(zzParts[i].rep, p2ePrime, p2r, q, getContext().rcData.alpha); zzParts[i].normalize(); // normalize after working directly on the rep if (maxU < newMax) maxU = newMax; } // Check that the estimated noise is still low if (noise + maxU*p2r*(skHwts[recryptKeyID]+1) > q/2) cerr << " * noise/q after makeDivisible = " << ((noise + maxU*p2r*(skHwts[recryptKeyID]+1))/q) << endl; for (long i=0; i<(long)zzParts.size(); i++) zzParts[i] /= p2ePrime; // divide by p^{e'} // Multiply the post-processed cipehrtext by the encrypted sKey #ifdef DEBUG_PRINTOUT cerr << "+ Before recryption "; decryptAndPrint(cerr, recryptEkey, *dbgKey, *dbgEa, printFlag); #endif double p0size = to_double(coeffsL2Norm(zzParts[0])); double p1size = to_double(coeffsL2Norm(zzParts[1])); ctxt = recryptEkey; ctxt.multByConstant(zzParts[1], p1size*p1size); ctxt.addConstant(zzParts[0], p0size*p0size); #ifdef DEBUG_PRINTOUT cerr << "+ Before linearTrans1 "; decryptAndPrint(cerr, ctxt, *dbgKey, *dbgEa, printFlag); #endif FHE_NTIMER_STOP(preProcess); // Move the powerful-basis coefficients to the plaintext slots FHE_NTIMER_START(LinearTransform1); ctxt.getContext().rcData.firstMap->apply(ctxt); FHE_NTIMER_STOP(LinearTransform1); #ifdef DEBUG_PRINTOUT cerr << "+ After linearTrans1 "; decryptAndPrint(cerr, ctxt, *dbgKey, *dbgEa, printFlag); #endif // Extract the digits e-e'+r-1,...,e-e' (from fully packed slots) extractDigitsPacked(ctxt, e-ePrime, r, ePrime, context.rcData.unpackSlotEncoding); #ifdef DEBUG_PRINTOUT cerr << "+ Before linearTrans2 "; decryptAndPrint(cerr, ctxt, *dbgKey, *dbgEa, printFlag); #endif // Move the slots back to powerful-basis coefficients FHE_NTIMER_START(LinearTransform2); ctxt.getContext().rcData.secondMap->apply(ctxt); FHE_NTIMER_STOP(LinearTransform2); }