NTL_CLIENT #include "FHE.h" #include "timing.h" #include "EncryptedArray.h" #include <cassert> #include <cstdio> // computes ctxt^{2^d-1} using a method that takes // O(log d) automorphisms and multiplications void fastPower(Ctxt& ctxt, long d) { 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; } } }
void Ctxt::multiplyBy2(const Ctxt& other1, const Ctxt& other2) { // Special case: if *this is empty then do nothing if (this->isEmpty()) return; long lvl = findBaseLevel(); long lvl1 = other1.findBaseLevel(); long lvl2 = other2.findBaseLevel(); if (lvl<lvl1 && lvl<lvl2){ // if both others at higher levels than this, Ctxt tmp = other1; // multiply others by each other, then by this if (&other1 == &other2) tmp *= tmp; // squaring rather than multiplication else tmp *= other2; *this *= tmp; } else if (lvl<lvl2) { // lvl1<=lvl<lvl2, multiply by other2, then by other1 *this *= other2; *this *= other1; } else { // multiply first by other1, then by other2 *this *= other1; *this *= other2; } reLinearize(); // re-linearize after all the multiplications }
// 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; }
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); } }
// incrementalZeroTest sets each res[i], for i=0..n-1, to // a ciphertext in which each slot is 0 or 1 according // to whether or not bits 0..i of corresponding slot in ctxt // is zero (1 if not zero, 0 if zero). // It is assumed that res and each res[i] is already initialized // by the caller. // Complexity: O(d + n log d) smart automorphisms // O(n d) void incrementalZeroTest(Ctxt* res[], const EncryptedArray& ea, const Ctxt& ctxt, long n) { FHE_TIMER_START; long nslots = ea.size(); long d = ea.getDegree(); // compute linearized polynomial coefficients vector< vector<ZZX> > Coeff; Coeff.resize(n); for (long i = 0; i < n; i++) { // coeffients for mask on bits 0..i // L[j] = X^j for j = 0..i, L[j] = 0 for j = i+1..d-1 vector<ZZX> L; L.resize(d); for (long j = 0; j <= i; j++) SetCoeff(L[j], j); vector<ZZX> C; ea.buildLinPolyCoeffs(C, L); Coeff[i].resize(d); for (long j = 0; j < d; j++) { // Coeff[i][j] = to the encoding that has C[j] in all slots // FIXME: maybe encrtpted array should have this functionality // built in vector<ZZX> T; T.resize(nslots); for (long s = 0; s < nslots; s++) T[s] = C[j]; ea.encode(Coeff[i][j], T); } } vector<Ctxt> Conj(d, ctxt); // initialize Cong[j] to ctxt^{2^j} for (long j = 0; j < d; j++) { Conj[j].smartAutomorph(1L << j); } for (long i = 0; i < n; i++) { res[i]->clear(); for (long j = 0; j < d; j++) { Ctxt tmp = Conj[j]; tmp.multByConstant(Coeff[i][j]); *res[i] += tmp; } // *res[i] now has 0..i in each slot // next, we raise to the power 2^d-1 fastPower(*res[i], d); } FHE_TIMER_STOP; }
void rotateLeft32(Ctxt &x, int n) { Ctxt other = x; global_ea->shift(x, n); global_ea->shift(other, -(32-n)); x.multByConstant(*global_maxint); other.multByConstant(*global_maxint); x += other; }
void Encrypt(Ctxt &ctxt, const NTL::ZZ &plain) const { assert(*this == ctxt.GetPk()); auto bits = NTL::NumBits(n); NTL::ZZ r, res; NTL::RandomBits(r, bits); NTL::PowerMod(r, r, n, n2); NTL::PowerMod(res, g, plain, n2); NTL::MulMod(res, r, n2); ctxt.SetCtxt(res); }
// 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 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 EncryptedArrayDerived<type>::rotate1D(Ctxt& ctxt, long i, long amt, bool dc) const { FHE_TIMER_START; const PAlgebra& al = context.zMStar; const vector< vector< RX > >& maskTable = tab.getMaskTable(); RBak bak; bak.save(); tab.restoreContext(); assert(&context == &ctxt.getContext()); assert(i >= 0 && i < (long)al.numOfGens()); // Make sure amt is in the range [1,ord-1] long ord = al.OrderOf(i); amt %= ord; if (amt == 0) return; long signed_amt = amt; if (amt < 0) amt += ord; // DIRT: the above assumes division with remainder // follows C++11 and C99 rules if (al.SameOrd(i)) { // a "native" rotation long val = PowerMod(al.ZmStarGen(i), amt, al.getM()); ctxt.smartAutomorph(val); } else if (dc) { // the "don't care" case...it is presumed that any shifts // "off the end" are zero. For this, we have to use // the "signed" version of amt. long val = PowerMod(al.ZmStarGen(i), signed_amt, al.getM()); ctxt.smartAutomorph(val); } else { // more expensive "non-native" rotation assert(maskTable[i].size() > 0); long val = PowerMod(al.ZmStarGen(i), amt, al.getM()); long ival = PowerMod(al.ZmStarGen(i), amt-ord, al.getM()); const RX& mask = maskTable[i][ord-amt]; DoubleCRT m1(conv<ZZX>(mask), context, ctxt.getPrimeSet()); Ctxt tmp(ctxt); // a copy of the ciphertext tmp.multByConstant(m1); // only the slots in which m1=1 ctxt -= tmp; // only the slots in which m1=0 ctxt.smartAutomorph(val); // shift left by val tmp.smartAutomorph(ival); // shift right by ord-val ctxt += tmp; // combine the two parts } FHE_TIMER_STOP; }
static void recursivePolyEval(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 delta = deg(poly) % k; // deg(poly) mod k long n = divc(deg(poly),k); // ceil( deg(poly)/k ) long t = 1L<<(NextPowerOfTwo(n)); // t >= n, so t*k >= deg(poly) // Special case for deg(poly) = k * 2^e +delta if (n==t) { degPowerOfTwo(ret, poly, k, babyStep, giantStep); return; } // When deg(poly) = k*(2^e -1) we use the Paterson-Stockmeyer recursion if (n == t-1 && delta==0) { PatersonStockmeyer(ret, poly, k, t/2, delta, babyStep, giantStep); return; } t = t/2; // In any other case we have kt < deg(poly) < k(2t-1). We then set // u = deg(poly) - k*(t-1) and poly = q*X^u + r with deg(r)<u // and recurse on poly = (q-1)*X^u + (X^u+r) long u = deg(poly) - k*(t-1); ZZX r = trunc(poly, u); // degree <= u-1 ZZX q = RightShift(poly, u); // degree == k*(t-1) q -= 1; SetCoeff(r, u); // degree == u PatersonStockmeyer(ret, q, k, t/2, 0, babyStep, giantStep); Ctxt tmp = giantStep.getPower(u/k); if (delta!=0) { // if u is not divisible by k then compute it tmp.multiplyBy(babyStep.getPower(delta)); } ret.multiplyBy(tmp); recursivePolyEval(tmp, r, k, babyStep, giantStep); ret += tmp; }
// selects range of slots [lo..hi) static void SelectRange(const EncryptedArray& ea, Ctxt& ctxt, long lo, long hi) { ZZX mask; SelectRange(ea, mask, lo, hi); ctxt.multByConstant(mask); }
void applyLinPolyLL(Ctxt& ctxt, const vector<P>& encodedC, long d) { assert(d == lsize(encodedC)); ctxt.cleanUp(); // not sure, but this may be a good idea Ctxt tmp(ctxt); ctxt.multByConstant(encodedC[0]); for (long j = 1; j < d; j++) { Ctxt tmp1(tmp); tmp1.frobeniusAutomorph(j); tmp1.multByConstant(encodedC[j]); ctxt += tmp1; } }
void rotateLeft32Old(Ctxt &x, int n) { Ctxt other = x; global_ea->shift(x, n); global_ea->shift(other, -(32-n)); negate32(x); // bitwise OR negate32(other); x.multiplyBy(other); negate32(x); }
void add_noise_to_coeff(Ctxt& res, long n, long p, long except) { NTL::ZZX noise; for (long i = 0; i < n; i++) { NTL::SetCoeff(noise, i, NTL::RandomBnd(p)); } NTL::SetCoeff(noise, except, 0); res.addConstant(noise); }
void Ctxt::multiplyBy2(const Ctxt& other1, const Ctxt& other2) { FHE_TIMER_START; // Special case: if *this is empty then do nothing if (this->isEmpty()) return; long lvl = findBaseLevel(); long lvl1 = other1.findBaseLevel(); long lvl2 = other2.findBaseLevel(); if (lvl<lvl1 && lvl<lvl2){ // if both others at higher levels than this, Ctxt tmp = other1; // multiply others by each other, then by this if (&other1 == &other2) tmp *= tmp; // squaring rather than multiplication else tmp *= other2; *this *= tmp; reLinearize(); // re-linearize after all the multiplications return; } const Ctxt *first, *second; if (lvl<lvl2) { // lvl1<=lvl<lvl2, multiply by other2, then by other1 first = &other2; second = &other1; } else { // multiply first by other1, then by other2 first = &other1; second = &other2; } if (this == second) { // handle pointer collision Ctxt tmp = *second; *this *= *first; *this *= tmp; if (this == first) // cubing operation noiseVar *= 3; // a correction factor due to dependency else noiseVar *= 2; // a correction factor due to dependency } else { *this *= *first; *this *= *second; } reLinearize(); // re-linearize after all the multiplications }
void replicate(const EncryptedArray& ea, Ctxt& ctxt, long pos) { long nSlots = ea.size(); assert(pos >= 0 && pos < nSlots); ZZX mask; ea.encodeUnitSelector(mask, pos); ctxt.multByConstant(mask); replicate0(ea, ctxt, pos); }
// Apply a permutation network to a ciphertext void PermNetwork::applyToCtxt(Ctxt& c, const EncryptedArray& ea) const { const PAlgebra& al = ea.getPAlgebra(); // 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; }
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"; }
void EncryptedArrayDerived<type>::shift1D(Ctxt& ctxt, long i, long k) const { FHE_TIMER_START; const PAlgebra& al = context.zMStar; const vector< vector< RX > >& maskTable = tab.getMaskTable(); RBak bak; bak.save(); tab.restoreContext(); assert(&context == &ctxt.getContext()); assert(i >= 0 && i < (long)al.numOfGens()); long ord = al.OrderOf(i); if (k <= -ord || k >= ord) { ctxt.multByConstant(to_ZZX(0)); return; } // Make sure amt is in the range [1,ord-1] long amt = k % ord; if (amt == 0) return; if (amt < 0) amt += ord; RX mask = maskTable[i][ord-amt]; long val; if (k < 0) val = PowerMod(al.ZmStarGen(i), amt-ord, al.getM()); else { mask = 1 - mask; val = PowerMod(al.ZmStarGen(i), amt, al.getM()); } DoubleCRT m1(conv<ZZX>(mask), context, ctxt.getPrimeSet()); ctxt.multByConstant(m1); // zero out slots where mask=0 ctxt.smartAutomorph(val); // shift left by val FHE_TIMER_STOP; }
// Main entry point: Evaluate an encrypted polynomial on an encrypted input // return in ret = sum_i poly[i] * x^i void polyEval(Ctxt& ret, const Vec<Ctxt>& poly, const Ctxt& x) { if (poly.length()<=1) { // Some special cases if (poly.length()==0) ret.clear(); // empty polynomial else ret = poly[0]; // constant polynomial return; } long deg = poly.length()-1; long logD = NextPowerOfTwo(divc(poly.length(),3)); long d = 1L << logD; // We have d <= deg(poly) < 3d assert(d <= deg && deg < 3*d); Vec<Ctxt> powers(INIT_SIZE, logD+1, x); if (logD>0) { powers[1].square(); for (long i=2; i<=logD; i++) { // powers[i] = x^{2^i} powers[i] = powers[i-1]; powers[i].square(); } } // Compute in three parts p0(X) + ( p1(X) + p2(X)*X^d )*X^d Ctxt tmp(ZeroCtxtLike, ret); recursivePolyEval(ret, &poly[d], min(d,poly.length()-d), powers); // p1(X) if (poly.length() > 2*d) { // p2 is not empty recursivePolyEval(tmp, &poly[2*d], poly.length()-2*d, powers); // p2(X) tmp.multiplyBy(powers[logD]); ret += tmp; } ret.multiplyBy(powers[logD]); // ( p1(X) + p2(X)*X^d )*X^d recursivePolyEval(tmp, &poly[0], d, powers); // p0(X) 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; } } }
// Simple evaluation sum f_i * X^i, assuming that babyStep has enough powers static void simplePolyEval(Ctxt& ret, const ZZX& poly, DynamicCtxtPowers& babyStep) { ret.clear(); if (deg(poly)<0) return; // the zero polynomial always returns zero assert (deg(poly)<=babyStep.size()); // ensure that we have enough powers ZZ coef; ZZ p = to_ZZ(babyStep[0].getPtxtSpace()); for (long i=1; i<=deg(poly); i++) { rem(coef, coeff(poly,i),p); if (coef > p/2) coef -= p; Ctxt tmp = babyStep.getPower(i); // X^i tmp.multByConstant(coef); // f_i X^i ret += tmp; } // Add the free term rem(coef, ConstTerm(poly), p); if (coef > p/2) coef -= p; ret.addConstant(coef); // if (verbose) checkPolyEval(ret, babyStep[0], poly); }
static void recursivePolyEval(Ctxt& ret, const Ctxt poly[], long nCoeffs, const Vec<Ctxt>& powers) { if (nCoeffs <= 1) { // edge condition if (nCoeffs == 0) ret.clear(); // empty polynomial else ret = poly[0]; // constant polynomial return; } long logD = NextPowerOfTwo(nCoeffs)-1; long d = 1L << logD; Ctxt tmp(ZeroCtxtLike, ret); recursivePolyEval(tmp, &(poly[d]), nCoeffs-d, powers); recursivePolyEval(ret, &(poly[0]), d, powers); tmp.multiplyBy(powers[logD]); ret += tmp; }
// Apply the same linear transformation to all the slots. // C is the output of ea.buildLinPolyCoeffs void applyLinPoly1(const EncryptedArray& ea, Ctxt& ctxt, const vector<ZZX>& C) { assert(&ea.getContext() == &ctxt.getContext()); long d = ea.getDegree(); assert(d == lsize(C)); long nslots = ea.size(); vector<ZZX> encodedC(d); for (long j = 0; j < d; j++) { vector<ZZX> v(nslots); for (long i = 0; i < nslots; i++) v[i] = C[j]; ea.encode(encodedC[j], v); } applyLinPolyLL(ctxt, encodedC, ea.getDegree()); }
// The input is a plaintext table T[] and an array of encrypted bits // I[], holding the binary representation of an index i into T. // The output is the encrypted value T[i]. void tableLookup(Ctxt& out, const vector<zzX>& table, const CtPtrs& idx, std::vector<zzX>* unpackSlotEncoding) { FHE_TIMER_START; out.clear(); vector<Ctxt> products(lsize(table), out); // to hold subset products of idx CtPtrs_vectorCt pWrap(products); // A wrapper // Compute all products of ecnrypted bits =: b_i computeAllProducts(pWrap, idx, unpackSlotEncoding); // Compute the sum b_i * T[i] NTL_EXEC_RANGE(lsize(table), first, last) for(long i=first; i<last; i++) products[i].multByConstant(table[i]); // p[i] = p[i]*T[i] NTL_EXEC_RANGE_END for(long i=0; i<lsize(table); i++) out += products[i]; }
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; } } }
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; }