// Apply F(X)->F(X^k) followed by re-liearization. The automorphism is possibly // evaluated via a sequence of steps, to ensure that we can re-linearize the // result of every step. void Ctxt::smartAutomorph(long k) { FHE_TIMER_START; // Special case: if *this is empty then do nothing if (this->isEmpty()) return; long m = context.zMStar.getM(); k = mcMod(k, m); // Sanity check: verify that k \in Zm* assert (context.zMStar.inZmStar(k)); long keyID=getKeyID(); if (!inCanonicalForm(keyID)) { // Re-linearize the input, if needed reLinearize(keyID); assert (inCanonicalForm(keyID)); // ensure that re-linearization succeeded } assert (pubKey.isReachable(k,keyID)); // reachable from 1 while (k != 1) { const KeySwitch& matrix = pubKey.getNextKSWmatrix(k,keyID); long amt = matrix.fromKey.getPowerOfX(); automorph(amt); reLinearize(keyID); k = MulMod(k, InvMod(amt,m), m); } FHE_TIMER_STOP; }
// applies the Frobenius automorphism p^j void Ctxt::frobeniusAutomorph(long j) { // Special case: if *this is empty then do nothing if (this->isEmpty()) return; long m = context.zMStar.getM(); long p = context.zMStar.getP(); long d = context.zMStar.getOrdP(); j = mcMod(j, d); long val = PowerMod(p, j, m); smartAutomorph(val); }
// Apply F(X)->F(X^k) followed by re-liearization. The automorphism is possibly // evaluated via a sequence of steps, to ensure that we can re-linearize the // result of every step. void Ctxt::smartAutomorph(long k) { FHE_TIMER_START; // A hack: record this automorphism rather than actually performing it if (isSetAutomorphVals()) { // defined in NumbTh.h recordAutomorphVal(k); return; } // Special case: if *this is empty then do nothing if (this->isEmpty()) return; // Sanity check: verify that k \in Zm* long m = context.zMStar.getM(); k = mcMod(k, m); assert (context.zMStar.inZmStar(k)); long keyID=getKeyID(); if (!pubKey.isReachable(k,keyID)) {// must have key-switching matrices for it throw std::logic_error("no key-switching matrices for k="+std::to_string(k) + ", keyID="+std::to_string(keyID)); } if (!inCanonicalForm(keyID)) { // Re-linearize the input, if needed reLinearize(keyID); assert (inCanonicalForm(keyID)); // ensure that re-linearization succeeded } while (k != 1) { const KeySwitch& matrix = pubKey.getNextKSWmatrix(k,keyID); long amt = matrix.fromKey.getPowerOfX(); // A hack: record this automorphism rather than actually performing it if (isSetAutomorphVals2()) { // defined in NumbTh.h recordAutomorphVal2(amt); return; } //cerr << "********* automorph " << amt << "\n"; automorph(amt); reLinearize(keyID); k = MulMod(k, InvMod(amt,m), m); } FHE_TIMER_STOP; }
static void apply(const EncryptedArrayDerived<type>& ea, Ctxt& ctxt, const PlaintextMatrixBaseInterface& mat) { assert(&ea == &mat.getEA().getDerived(type())); assert(&ea.getContext() == &ctxt.getContext()); RBak bak; bak.save(); ea.getTab().restoreContext(); // Get the derived type const PlaintextMatrixInterface<type>& mat1 = dynamic_cast< const PlaintextMatrixInterface<type>& >( mat ); ctxt.cleanUp(); // not sure, but this may be a good idea Ctxt res(ctxt.getPubKey(), ctxt.getPtxtSpace()); // fresh encryption of zero long nslots = ea.size(); long d = ea.getDegree(); RX entry; vector<RX> diag; diag.resize(nslots); // Process the diagonals one at a time for (long i = 0; i < nslots; i++) { // process diagonal i bool zDiag = true; // is this a zero diagonal? long nzLast = -1; // index of last non-zero entry on this diagonal // Compute constants for each entry on this diagonal for (long j = 0; j < nslots; j++) { // process entry j bool zEntry = mat1.get(entry, mcMod(j-i, nslots), j); // callback assert(zEntry || deg(entry) < d); if (!zEntry && IsZero(entry)) zEntry = true; // check for zero if (!zEntry) { // non-zero diagonal entry zDiag = false; // diagonal is non-zero // clear entries between last nonzero entry and this one for (long jj = nzLast+1; jj < j; jj++) clear(diag[jj]); nzLast = j; diag[j] = entry; } } if (zDiag) continue; // zero diagonal, continue // clear trailing zero entries for (long jj = nzLast+1; jj < nslots; jj++) clear(diag[jj]); // Now we have the constants for all the diagonal entries, encode the // diagonal as a single polynomial with these constants in the slots ZZX cpoly; ea.encode(cpoly, diag); // rotate by i, multiply by the polynomial, then add to the result Ctxt shCtxt = ctxt; ea.rotate(shCtxt, i); // rotate by i shCtxt.multByConstant(cpoly); res += shCtxt; } ctxt = res; }
static void apply(const EncryptedArrayDerived<type>& ea, Ctxt& ctxt, const PlaintextBlockMatrixBaseInterface& mat) { assert(&ea == &mat.getEA().getDerived(type())); assert(&ea.getContext() == &ctxt.getContext()); const PAlgebra& zMStar = ea.getContext().zMStar; long p = zMStar.getP(); long m = zMStar.getM(); const RXModulus& F = ea.getTab().getPhimXMod(); RBak bak; bak.save(); ea.getTab().restoreContext(); const PlaintextBlockMatrixInterface<type>& mat1 = dynamic_cast< const PlaintextBlockMatrixInterface<type>& >( mat ); ctxt.cleanUp(); // not sure, but this may be a good idea long nslots = ea.size(); long d = ea.getDegree(); Vec< shared_ptr<Ctxt> > acc; acc.SetLength(d); for (long k = 0; k < d; k++) acc[k] = shared_ptr<Ctxt>(new Ctxt(ZeroCtxtLike, ctxt)); mat_R entry; entry.SetDims(d, d); vector<RX> entry1; entry1.resize(d); vector< vector<RX> > diag; diag.resize(nslots); for (long j = 0; j < nslots; j++) diag[j].resize(d); for (long i = 0; i < nslots; i++) { // process diagonal i bool zDiag = true; long nzLast = -1; for (long j = 0; j < nslots; j++) { bool zEntry = mat1.get(entry, mcMod(j-i, nslots), j); assert(zEntry || (entry.NumRows() == d && entry.NumCols() == d)); // get(...) returns true if the entry is empty, false otherwise if (!zEntry && IsZero(entry)) zEntry=true; // zero is an empty entry too if (!zEntry) { // non-empty entry zDiag = false; // mark diagonal as non-empty // clear entries between last nonzero entry and this one for (long jj = nzLast+1; jj < j; jj++) { for (long k = 0; k < d; k++) clear(diag[jj][k]); } nzLast = j; // recode entry as a vector of polynomials for (long k = 0; k < d; k++) conv(entry1[k], entry[k]); // compute the lin poly coeffs ea.buildLinPolyCoeffs(diag[j], entry1); } } if (zDiag) continue; // zero diagonal, continue // clear trailing zero entries for (long jj = nzLast+1; jj < nslots; jj++) { for (long k = 0; k < d; k++) clear(diag[jj][k]); } // now diag[j] contains the lin poly coeffs Ctxt shCtxt = ctxt; ea.rotate(shCtxt, i); shCtxt.cleanUp(); RX cpoly1, cpoly2; ZZX cpoly; // apply the linearlized polynomial for (long k = 0; k < d; k++) { // compute the constant bool zConst = true; vector<RX> cvec; cvec.resize(nslots); for (long j = 0; j < nslots; j++) { cvec[j] = diag[j][k]; if (!IsZero(cvec[j])) zConst = false; } if (zConst) continue; ea.encode(cpoly, cvec); conv(cpoly1, cpoly); // apply inverse automorphism to constant plaintextAutomorph(cpoly2, cpoly1, PowerMod(p, mcMod(-k, d), m), zMStar, F); conv(cpoly, cpoly2); Ctxt shCtxt1 = shCtxt; shCtxt1.multByConstant(cpoly); *acc[k] += shCtxt1; } } Ctxt res(ZeroCtxtLike, ctxt); for (long k = 0; k < d; k++) { acc[k]->frobeniusAutomorph(k); res += *acc[k]; } ctxt = res; }
// Extract digits from fully packed slots void extractDigitsPacked(Ctxt& ctxt, long botHigh, long r, long ePrime, const vector<ZZX>& unpackSlotEncoding) { FHE_TIMER_START; // Step 1: unpack the slots of ctxt FHE_NTIMER_START(unpack); ctxt.cleanUp(); // Apply the d automorphisms and store them in scratch area long d = ctxt.getContext().zMStar.getOrdP(); vector<Ctxt> scratch; // used below vector<Ctxt> unpacked(d, Ctxt(ZeroCtxtLike, ctxt)); { // explicit scope to force all temporaries to be released vector< shared_ptr<DoubleCRT> > coeff_vector; coeff_vector.resize(d); for (long i = 0; i < d; i++) coeff_vector[i] = shared_ptr<DoubleCRT>(new DoubleCRT(unpackSlotEncoding[i], ctxt.getContext(), ctxt.getPrimeSet()) ); Ctxt tmp1(ZeroCtxtLike, ctxt); Ctxt tmp2(ZeroCtxtLike, ctxt); // FIXME: implement using hoisting! for (long j = 0; j < d; j++) { // process jth Frobenius tmp1 = ctxt; tmp1.frobeniusAutomorph(j); tmp1.cleanUp(); // FIXME: not clear if we should call cleanUp here for (long i = 0; i < d; i++) { tmp2 = tmp1; tmp2.multByConstant(*coeff_vector[mcMod(i+j, d)]); unpacked[i] += tmp2; } } } FHE_NTIMER_STOP(unpack); // Step 2: extract the digits top-1,...,0 from the slots of unpacked[i] long p = ctxt.getContext().zMStar.getP(); long p2r = power_long(p,r); long topHigh = botHigh + r-1; #ifdef DEBUG_PRINTOUT cerr << "+ After unpack "; decryptAndPrint(cerr, unpacked[0], *dbgKey, *dbgEa, printFlag); cerr << " extracting "<<(topHigh+1)<<" digits\n"; #endif if (p==2 && r>2) topHigh--; // For p==2 we sometime get a bit for free FHE_NTIMER_START(extractDigits); for (long i=0; i<(long)unpacked.size(); i++) { if (topHigh<=0) { // extracting LSB = no-op scratch.assign(1,unpacked[i]); } else { // extract digits topHigh...0, store them in scratch extractDigits(scratch, unpacked[i], topHigh+1); } // set upacked[i] = -\sum_{j=botHigh}^{topHigh} scratch[j] * p^{j-botHigh} if (topHigh >= (long)scratch.size()) { topHigh = scratch.size() -1; cerr << " @ suspect: not enough digits in extractDigitsPacked\n"; } unpacked[i] = scratch[topHigh]; for (long j=topHigh-1; j>=botHigh; --j) { unpacked[i].multByP(); unpacked[i] += scratch[j]; } if (p==2 && botHigh>0) // For p==2, subtract also the previous bit unpacked[i] += scratch[botHigh-1]; unpacked[i].negate(); if (r>ePrime) { // Add in digits from the bottom part, if any long topLow = r-1 - ePrime; Ctxt tmp = scratch[topLow]; for (long j=topLow-1; j>=0; --j) { tmp.multByP(); tmp += scratch[j]; } if (ePrime>0) tmp.multByP(ePrime); // multiply by p^e' unpacked[i] += tmp; } unpacked[i].reducePtxtSpace(p2r); // Our plaintext space is now mod p^r } FHE_NTIMER_STOP(extractDigits); #ifdef DEBUG_PRINTOUT cerr << "+ Before repack "; decryptAndPrint(cerr, unpacked[0], *dbgKey, *dbgEa, printFlag); #endif // Step 3: re-pack the slots FHE_NTIMER_START(repack); const EncryptedArray& ea2 = *ctxt.getContext().ea; ZZX xInSlots; vector<ZZX> xVec(ea2.size()); ctxt = unpacked[0]; for (long i=1; i<d; i++) { x2iInSlots(xInSlots, i, xVec, ea2); unpacked[i].multByConstant(xInSlots); ctxt += unpacked[i]; } FHE_NTIMER_STOP(repack); }
void EncryptedArrayDerived<type>::mat_mul(Ctxt& ctxt, const PlaintextBlockMatrixBaseInterface& mat) const { FHE_TIMER_START; assert(this == &mat.getEA().getDerived(type())); assert(&context == &ctxt.getContext()); RBak bak; bak.save(); tab.restoreContext(); const PlaintextBlockMatrixInterface<type>& mat1 = dynamic_cast< const PlaintextBlockMatrixInterface<type>& >( mat ); ctxt.cleanUp(); // not sure, but this may be a good idea Ctxt res(ctxt.getPubKey(), ctxt.getPtxtSpace()); // a new ciphertext, encrypting zero long nslots = size(); long d = getDegree(); mat_R entry; entry.SetDims(d, d); vector<RX> entry1; entry1.resize(d); vector< vector<RX> > diag; diag.resize(nslots); for (long j = 0; j < nslots; j++) diag[j].resize(d); for (long i = 0; i < nslots; i++) { // process diagonal i bool zDiag = true; long nzLast = -1; for (long j = 0; j < nslots; j++) { bool zEntry = mat1.get(entry, mcMod(j-i, nslots), j); assert(zEntry || (entry.NumRows() == d && entry.NumCols() == d)); // get(...) returns true if the entry is empty, false otherwise if (!zEntry && IsZero(entry)) zEntry=true; // zero is an empty entry too if (!zEntry) { // non-empty entry zDiag = false; // mark diagonal as non-empty // clear entries between last nonzero entry and this one for (long jj = nzLast+1; jj < j; jj++) { for (long k = 0; k < d; k++) clear(diag[jj][k]); } nzLast = j; // recode entry as a vector of polynomials for (long k = 0; k < d; k++) conv(entry1[k], entry[k]); // compute the lin poly coeffs buildLinPolyCoeffs(diag[j], entry1); } } if (zDiag) continue; // zero diagonal, continue // clear trailing zero entries for (long jj = nzLast+1; jj < nslots; jj++) { for (long k = 0; k < d; k++) clear(diag[jj][k]); } // now diag[j] contains the lin poly coeffs Ctxt shCtxt = ctxt; rotate(shCtxt, i); // apply the linearlized polynomial for (long k = 0; k < d; k++) { // compute the constant bool zConst = true; vector<RX> cvec; cvec.resize(nslots); for (long j = 0; j < nslots; j++) { cvec[j] = diag[j][k]; if (!IsZero(cvec[j])) zConst = false; } if (zConst) continue; ZZX cpoly; encode(cpoly, cvec); // FIXME: record the encoded polynomial for future use Ctxt shCtxt1 = shCtxt; shCtxt1.frobeniusAutomorph(k); shCtxt1.multByConstant(cpoly); res += shCtxt1; } } ctxt = res; }