NTL_CLIENT #include "FHE.h" #include "timing.h" #include "EncryptedArray.h" #include <cstdio> // Map all non-zero slots to 1, leaving zero slots as zero. // Assumes that r=1, and that all the slot contain elements from GF(p^d). // // We compute x^{p^d-1} = x^{(1+p+...+p^{d-1})*(p-1)} by setting y=x^{p-1} // and then outputting y * y^p * ... * y^{p^{d-1}}, with exponentiation to // powers of p done via Frobenius. // FIXME: the computation of the "norm" y * y^p * ... * y^{p^{d-1}} // can be done using O(log d) automorphisms, rather than O(d). void mapTo01(const EncryptedArray& ea, Ctxt& ctxt) { long p = ctxt.getPtxtSpace(); if (p != ea.getPAlgebra().getP()) // ptxt space is p^r for r>1 throw helib::LogicError("mapTo01 not implemented for r>1"); if (p>2) ctxt.power(p-1); // set y = x^{p-1} long d = ea.getDegree(); if (d>1) { // compute the product of the d automorphisms std::vector<Ctxt> v(d, ctxt); for (long i=1; i<d; i++) v[i].frobeniusAutomorph(i); totalProduct(ctxt, v); } }
// This procedure assumes that k*(2^e +1) > deg(poly) > k*(2^e -1), // and that babyStep contains >= k + (deg(poly) mod k) powers static void degPowerOfTwo(Ctxt& ret, const ZZX& poly, long k, DynamicCtxtPowers& babyStep, DynamicCtxtPowers& giantStep) { if (deg(poly)<=babyStep.size()) { // Edge condition, use simple eval simplePolyEval(ret, poly, babyStep); return; } long n = deg(poly)/k; // We assume n=2^e or n=2^e -1 n = 1L << NextPowerOfTwo(n); // round up to n=2^e ZZX r = trunc(poly, (n-1)*k); // degree <= k(2^e-1)-1 ZZX q = RightShift(poly, (n-1)*k); // 0 < degree < 2k SetCoeff(r, (n-1)*k); // monic, degree == k(2^e-1) q -= 1; PatersonStockmeyer(ret, r, k, n/2, 0, babyStep, giantStep); Ctxt tmp(ret.getPubKey(), ret.getPtxtSpace()); simplePolyEval(tmp, q, babyStep); // evaluate q // multiply by X^{k(n-1)} with minimum depth for (long i=1; i<n; i*=2) { tmp.multiplyBy(giantStep.getPower(i)); } ret += tmp; }
// Constructor Ctxt::Ctxt(ZeroCtxtLike_type, const Ctxt& ctxt): context(ctxt.getPubKey().getContext()), pubKey(ctxt.getPubKey()), ptxtSpace(ctxt.getPtxtSpace()), noiseVar(to_xdouble(0.0)) { // same body as previous constructor if (ptxtSpace<=0) ptxtSpace = pubKey.getPtxtSpace(); else assert (GCD(ptxtSpace, pubKey.getPtxtSpace()) > 1); // sanity check primeSet=context.ctxtPrimes; }
void decryptAndPrint(ostream& s, const Ctxt& ctxt, const FHESecKey& sk, const EncryptedArray& ea, long flags) { const FHEcontext& context = ctxt.getContext(); xdouble noiseEst = sqrt(ctxt.getNoiseVar()); xdouble modulus = xexp(context.logOfProduct(ctxt.getPrimeSet())); vector<ZZX> ptxt; ZZX p, pp; sk.Decrypt(p, ctxt, pp); s << "plaintext space mod "<<ctxt.getPtxtSpace() << ", level="<<ctxt.findBaseLevel() << ", \n |noise|=q*" << (coeffsL2Norm(pp)/modulus) << ", |noiseEst|=q*" << (noiseEst/modulus) <<endl; if (flags & FLAG_PRINT_ZZX) { s << " before mod-p reduction="; printZZX(s,pp) <<endl; } if (flags & FLAG_PRINT_POLY) { s << " after mod-p reduction="; printZZX(s,p) <<endl; } if (flags & FLAG_PRINT_VEC) { ea.decode(ptxt, p); if (ea.getAlMod().getTag() == PA_zz_p_tag && ctxt.getPtxtSpace() != ea.getAlMod().getPPowR()) { long g = GCD(ctxt.getPtxtSpace(), ea.getAlMod().getPPowR()); for (long i=0; i<ea.size(); i++) PolyRed(ptxt[i], g, true); } s << " decoded to "; if (deg(p) < 40) // just pring the whole thing s << ptxt << endl; else if (ptxt.size()==1) // a single slot printZZX(s, ptxt[0]) <<endl; else { // print first and last slots printZZX(s, ptxt[0],20) << "--"; printZZX(s, ptxt[ptxt.size()-1], 20) <<endl; } } }
void extractDigits(vector<Ctxt>& digits, const Ctxt& c, long r) { const FHEcontext& context = c.getContext(); long rr = c.effectiveR(); if (r<=0 || r>rr) r = rr; // how many digits to extract long p = context.zMStar.getP(); ZZX x2p; if (p>3) { buildDigitPolynomial(x2p, p, r); } Ctxt tmp(c.getPubKey(), c.getPtxtSpace()); digits.resize(r, tmp); // allocate space #ifdef DEBUG_PRINTOUT fprintf(stderr, "***\n"); #endif for (long i=0; i<r; i++) { tmp = c; for (long j=0; j<i; j++) { if (p==2) digits[j].square(); else if (p==3) digits[j].cube(); else polyEval(digits[j], x2p, digits[j]); // "in spirit" digits[j] = digits[j]^p #ifdef DEBUG_PRINTOUT fprintf(stderr, "%5ld", digits[j].bitCapacity()); #endif tmp -= digits[j]; tmp.divideByP(); } digits[i] = tmp; // needed in the next round #ifdef DEBUG_PRINTOUT if (dbgKey) { double ratio = log(embeddingLargestCoeff(digits[i], *dbgKey)/digits[i].getNoiseBound())/log(2.0); fprintf(stderr, "%5ld [%f]", digits[i].bitCapacity(), ratio); if (ratio > 0) fprintf(stderr, " BAD-BOUND"); fprintf(stderr, "\n"); } else { fprintf(stderr, "%5ld\n", digits[i].bitCapacity()); } #endif } #ifdef DEBUG_PRINTOUT fprintf(stderr, "***\n"); #endif }
void checkCiphertext(const Ctxt& ctxt, const ZZX& ptxt, const FHESecKey& sk) { const FHEcontext& context = ctxt.getContext(); /* IndexSet base = baseSetOf(ctxt); double addedNoise = log(ctxt.modSwitchAddedNoiseVar()); Ctxt tmp = ctxt; tmp.modDownToSet(base); double totalNoise = log(tmp.getNoiseVar()); cout << " @@@ log(added-noise)="<<addedNoise << ", log(total-noise)="<<totalNoise<<endl; */ cout << " ln(q)="<< context.logOfProduct(ctxt.getPrimeSet()) << ", ln(nVar)/2="<< log(ctxt.getNoiseVar())/2; // << ", ln(nMag)="<< log(ctxt.getNoiseMag()); ZZX res; // sk.Decrypt(res, ctxt); ZZX f; sk.Decrypt(res, ctxt, f); cout << ", ln(mxPtxtCoef)=" << log(largestCoeff(f)); // ensure we reduce the same way on both PolyRed((ZZX&)res,res,ctxt.getPtxtSpace(),true); PolyRed((ZZX&)ptxt,ptxt,ctxt.getPtxtSpace(),true); if (res != ptxt) { cout << ", failed\n"; for (long i=0; i<=deg(ptxt); i++) if (coeff(res,i)!=coeff(ptxt,i)) { cout << "first mismatch in coeff "<<i<<": " << coeff(res,i)<<"!="<<coeff(ptxt,i)<<"\n"; break; } cout << "Timing information:\n"; printAllTimers(); cout << "\n"; exit(0); } else cout << ", succeeded\n"; }
// Apply a permutation network to a ciphertext // FIXME: Do we need to also give an EncryptedArray object as paramter? void PermNetwork::applyToCtxt(Ctxt& c) const { const PAlgebra& al = c.getContext().zMStar; EncryptedArray ea(c.getContext()); // Use G(X)=X for this ea object, this works since we only have 0/1 entries // Apply the layers, one at a time for (long i=0; i<layers.length(); i++) { const PermNetLayer& lyr = layers[i]; if (lyr.isID) continue; // this layer is the identity permutation // This layer is shifted via powers of g^e mod m long g2e = PowerMod(al.ZmStarGen(lyr.genIdx), lyr.e, al.getM()); Vec<long> unused = lyr.shifts; // copy to a new vector vector<long> mask(lyr.shifts.length()); // buffer to hold masks Ctxt sum(c.getPubKey(), c.getPtxtSpace()); // an empty ciphertext long shamt = 0; bool frst = true; while (true) { pair<long,bool> ret=makeMask(mask, unused, shamt); // compute mask if (ret.second) { // non-empty mask Ctxt tmp = c; ZZX maskPoly; ea.encode(maskPoly, mask); // encode mask as polynomial tmp.multByConstant(maskPoly); // multiply by mask if (shamt!=0) // rotate if the shift amount is nonzero tmp.smartAutomorph(PowerMod(g2e, shamt, al.getM())); if (frst) { sum = tmp; frst = false; } else sum += tmp; } if (ret.first >= 0) shamt = unused[ret.first]; // next shift amount to use else break; // unused is all-zero, done with this layer } c = sum; // update the cipehrtext c before the next layer } }
// The recursive procedure in the Paterson-Stockmeyer // polynomial-evaluation algorithm from SIAM J. on Computing, 1973. // This procedure assumes that poly is monic, deg(poly)=k*(2t-1)+delta // with t=2^e, and that babyStep contains >= k+delta powers static void PatersonStockmeyer(Ctxt& ret, const ZZX& poly, long k, long t, long delta, DynamicCtxtPowers& babyStep, DynamicCtxtPowers& giantStep) { if (deg(poly)<=babyStep.size()) { // Edge condition, use simple eval simplePolyEval(ret, poly, babyStep); return; } ZZX r = trunc(poly, k*t); // degree <= k*2^e-1 ZZX q = RightShift(poly, k*t); // degree == k(2^e-1) +delta const ZZ p = to_ZZ(babyStep[0].getPtxtSpace()); const ZZ& coef = coeff(r,deg(q)); SetCoeff(r, deg(q), coef-1); // r' = r - X^{deg(q)} ZZX c,s; DivRem(c,s,r,q); // r' = c*q + s // deg(s)<deg(q), and if c!= 0 then deg(c)<k-delta assert(deg(s)<deg(q)); assert(IsZero(c) || deg(c)<k-delta); SetCoeff(s,deg(q)); // s' = s + X^{deg(q)}, deg(s)==deg(q) // reduce the coefficients modulo p for (long i=0; i<=deg(c); i++) rem(c[i],c[i], p); c.normalize(); for (long i=0; i<=deg(s); i++) rem(s[i],s[i], p); s.normalize(); // Evaluate recursively poly = (c+X^{kt})*q + s' PatersonStockmeyer(ret, q, k, t/2, delta, babyStep, giantStep); Ctxt tmp(ret.getPubKey(), ret.getPtxtSpace()); simplePolyEval(tmp, c, babyStep); tmp += giantStep.getPower(t); ret.multiplyBy(tmp); PatersonStockmeyer(tmp, s, k, t/2, delta, babyStep, giantStep); ret += tmp; }
// computes ctxt^{2^d-1} using a method that takes // O(log d) automorphisms and multiplications void fastPower(Ctxt& ctxt, long d) { assert(ctxt.getPtxtSpace()==2); if (d <= 1) return; Ctxt orig = ctxt; long k = NumBits(d); long e = 1; for (long i = k-2; i >= 0; i--) { Ctxt tmp1 = ctxt; tmp1.smartAutomorph(1L << e); ctxt.multiplyBy(tmp1); e = 2*e; if (bit(d, i)) { ctxt.smartAutomorph(2); ctxt.multiplyBy(orig); e += 1; } } }
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; }
void extendExtractDigits(vector<Ctxt>& digits, const Ctxt& c, long r, long e) { const FHEcontext& context = c.getContext(); long p = context.zMStar.getP(); ZZX x2p; if (p>3) { buildDigitPolynomial(x2p, p, r); } // we should pre-compute this table // for i = 0..r-1, entry i is G_{e+r-i} in Chen and Han Vec<ZZX> G; G.SetLength(r); for (long i: range(r)) { compute_magic_poly(G[i], p, e+r-i); } vector<Ctxt> digits0; Ctxt tmp(c.getPubKey(), c.getPtxtSpace()); digits.resize(r, tmp); // allocate space digits0.resize(r, tmp); #ifdef DEBUG_PRINTOUT fprintf(stderr, "***\n"); #endif for (long i: range(r)) { tmp = c; for (long j: range(i)) { if (digits[j].capacity() >= digits0[j].capacity()) { // optimization: digits[j] is better than digits0[j], // so just use it tmp -= digits[j]; #ifdef DEBUG_PRINTOUT fprintf(stderr, "%5ld*", digits[j].bitCapacity()); #endif } else { if (p==2) digits0[j].square(); else if (p==3) digits0[j].cube(); else polyEval(digits0[j], x2p, digits0[j]); // "in spirit" digits0[j] = digits0[j]^p tmp -= digits0[j]; #ifdef DEBUG_PRINTOUT fprintf(stderr, "%5ld ", digits0[j].bitCapacity()); #endif } tmp.divideByP(); } digits0[i] = tmp; // needed in the next round polyEval(digits[i], G[i], tmp); #ifdef DEBUG_PRINTOUT if (dbgKey) { double ratio = log(embeddingLargestCoeff(digits[i], *dbgKey)/digits[i].getNoiseBound())/log(2.0); fprintf(stderr, "%5ld --- %5ld", digits0[i].bitCapacity(), digits[i].bitCapacity()); fprintf(stderr, " [%f]", ratio); if (ratio > 0) fprintf(stderr, " BAD-BOUND"); fprintf(stderr, "\n"); } else { fprintf(stderr, "%5ld --- %5ld\n", digits0[i].bitCapacity(), digits[i].bitCapacity()); } #endif } }
// 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); }
// Main entry point: Evaluate a cleartext polynomial on an encrypted input void polyEval(Ctxt& ret, ZZX poly, const Ctxt& x, long k) // Note: poly is passed by value, so caller keeps the original { if (deg(poly)<=2) { // nothing to optimize here if (deg(poly)<1) { // A constant ret.clear(); ret.addConstant(coeff(poly, 0)); } else { // A linear or quadratic polynomial DynamicCtxtPowers babyStep(x, deg(poly)); simplePolyEval(ret, poly, babyStep); } return; } // How many baby steps: set k~sqrt(n/2), rounded up/down to a power of two // FIXME: There may be some room for optimization here: it may be possible // to choose k as something other than a power of two and still maintain // optimal depth, in principle we can try all possible values of k between // two consecutive powers of two and choose the one that gives the least // number of multiplies, conditioned on minimum depth. if (k<=0) { long kk = (long) sqrt(deg(poly)/2.0); k = 1L << NextPowerOfTwo(kk); // heuristic: if k>>kk then use a smaler power of two if ((k==16 && deg(poly)>167) || (k>16 && k>(1.44*kk))) k /= 2; } #ifdef DEBUG_PRINTOUT cerr << " k="<<k; #endif long n = divc(deg(poly),k); // n = ceil(deg(p)/k), deg(p) >= k*n DynamicCtxtPowers babyStep(x, k); const Ctxt& x2k = babyStep.getPower(k); // Special case when deg(p)>k*(2^e -1) if (n==(1L << NextPowerOfTwo(n))) { // n is a power of two DynamicCtxtPowers giantStep(x2k, n/2); degPowerOfTwo(ret, poly, k, babyStep, giantStep); return; } // If n is not a power of two, ensure that poly is monic and that // its degree is divisible by k, then call the recursive procedure const ZZ p = to_ZZ(x.getPtxtSpace()); ZZ top = LeadCoeff(poly); ZZ topInv; // the inverse mod p of the top coefficient of poly (if any) bool divisible = (n*k == deg(poly)); // is the degree divisible by k? long nonInvertibe = InvModStatus(topInv, top, p); // 0 if invertible, 1 if not // FIXME: There may be some room for optimization below: instead of // adding a term X^{n*k} we can add X^{n'*k} for some n'>n, so long // as n' is smaller than the next power of two. We could save a few // multiplications since giantStep[n'] may be easier to compute than // giantStep[n] when n' has fewer 1's than n in its binary expansion. ZZ extra = ZZ::zero(); // extra!=0 denotes an added term extra*X^{n*k} if (!divisible || nonInvertibe) { // need to add a term top = to_ZZ(1); // new top coefficient is one topInv = top; // also the new inverse is one // set extra = 1 - current-coeff-of-X^{n*k} extra = SubMod(top, coeff(poly,n*k), p); SetCoeff(poly, n*k); // set the top coefficient of X^{n*k} to one } long t = IsZero(extra)? divc(n,2) : n; DynamicCtxtPowers giantStep(x2k, t); if (!IsOne(top)) { poly *= topInv; // Multiply by topInv to make into a monic polynomial for (long i=0; i<=n*k; i++) rem(poly[i], poly[i], p); poly.normalize(); } recursivePolyEval(ret, poly, k, babyStep, giantStep); if (!IsOne(top)) { ret.multByConstant(top); } if (!IsZero(extra)) { // if we added a term, now is the time to subtract back Ctxt topTerm = giantStep.getPower(n); topTerm.multByConstant(extra); ret -= topTerm; } }
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; }