// Create a tensor product of c1,c2. It is assumed that *this,c1,c2 // are defined relative to the same set of primes and plaintext space, // and that *this DOES NOT point to the same object as c1,c2 void Ctxt::tensorProduct(const Ctxt& c1, const Ctxt& c2) { // c1,c2 may be scaled, so multiply by the inverse scalar if needed long f = 1; if (c1.ptxtSpace>2) f = rem(context.productOfPrimes(c1.primeSet),c1.ptxtSpace); if (f!=1) f = InvMod(f,c1.ptxtSpace); clear(); // clear *this, before we start adding things to it primeSet = c1.primeSet; // set the correct prime-set before we begin // The actual tensoring CtxtPart tmpPart(context, IndexSet::emptySet()); // a scratch CtxtPart for (size_t i=0; i<c1.parts.size(); i++) { CtxtPart thisPart = c1.parts[i]; if (f!=1) thisPart *= f; for (size_t j=0; j<c2.parts.size(); j++) { tmpPart = c2.parts[j]; // What secret key will the product point to? if (!tmpPart.skHandle.mul(thisPart.skHandle, tmpPart.skHandle)) Error("Ctxt::tensorProduct: cannot multiply secret-key handles"); tmpPart *= thisPart; // The element of the tensor product // Check if we already have a part relative to this secret-key handle long k = getPartIndexByHandle(tmpPart.skHandle); if (k >= 0) // found a matching part parts[k] += tmpPart; else parts.push_back(tmpPart); } } /* Compute the noise estimate as c1.noiseVar * c2.noiseVar * factor * where the factor depends on the handles of c1,c2. Specifically, * if the largest powerOfS in c1,c2 are n1,n2, respectively, then we * have factor = ((n1+n2) choose n2). */ long n1=0, n2=0; for (size_t i=0; i<c1.parts.size(); i++) // get largest powerOfS in c1 if (c1.parts[i].skHandle.getPowerOfS() > n1) n1 = c1.parts[i].skHandle.getPowerOfS(); for (size_t i=0; i<c2.parts.size(); i++) // get largest powerOfS in c2 if (c2.parts[i].skHandle.getPowerOfS() > n2) n2 = c2.parts[i].skHandle.getPowerOfS(); // compute ((n1+n2) choose n2) long factor = 1; for (long i=n1+1; i<=n1+n2; i++) factor *= i; for (long i=n2 ; i>1 ; i--) factor /= i; noiseVar = c1.noiseVar * c2.noiseVar * factor; if (f!=1) { // WARNING: the following line is written just so to prevent overflow noiseVar = (noiseVar*f)*f; // because every product was scaled by f } }
// Add/subtract another ciphertxt (depending on the negative flag) void Ctxt::addCtxt(const Ctxt& other, bool negative) { FHE_TIMER_START; // Sanity check: same context and public key assert (&context==&other.context && &pubKey==&other.pubKey); // Special case: if *this is empty then just copy other if (this->isEmpty()) { *this = other; if (negative) negate(); return; } // Sanity check: verify that the plaintext spaces are compatible long g = GCD(this->ptxtSpace, other.ptxtSpace); assert (g>1); this->ptxtSpace = g; // Match the prime-sets, mod-UP the arguments if needed IndexSet s = other.primeSet / primeSet; // set-minus if (!empty(s)) modUpToSet(s); const Ctxt* other_pt = &other; Ctxt tmp(pubKey, other.ptxtSpace); // a temporaty empty ciphertext s = primeSet / other.primeSet; // set-minus if (!empty(s)) { // need to mod-UP the other, use a temporary copy tmp = other; tmp.modUpToSet(s); other_pt = &tmp; } // Go over the parts of other, for each one check if // there is a matching part in *this for (size_t i=0; i<other_pt->parts.size(); i++) { const CtxtPart& part = other_pt->parts[i]; long j = getPartIndexByHandle(part.skHandle); if (j>=0) { // found a matching part, add them up if (negative) parts[j] -= part; else parts[j] += part; } else { // no mathing part found, just append this part parts.push_back(part); if (negative) parts.back().Negate(); // not thread safe?? } } noiseVar += other_pt->noiseVar; FHE_TIMER_STOP; }
// Add/subtract a ciphertext part to a ciphertext. // With negative=true we subtract, otherwise we add. void Ctxt::addPart(const DoubleCRT& part, const SKHandle& handle, bool matchPrimeSet, bool negative) { FHE_TIMER_START; assert (&part.getContext() == &context); if (parts.size()==0) { // inserting 1st part primeSet = part.getIndexSet(); parts.push_back(CtxtPart(part,handle)); if (negative) parts.back().Negate(); // not thread-safe?? } else { // adding to a ciphertext with existing parts if (!(part.getIndexSet() <= primeSet)) { // add to the the prime-set of *this, if needed (this is expensive) if (matchPrimeSet) { IndexSet setDiff = part.getIndexSet() / primeSet; // set minus for (size_t i=0; i<parts.size(); i++) parts[i].addPrimes(setDiff); primeSet.insert(setDiff); } else // this should never happen throw std::logic_error("part has too many primes and matchPrimeSet==false"); } DoubleCRT tmp(context, IndexSet::emptySet()); const DoubleCRT* ptr = ∂ // mod-UP the part if needed IndexSet s = primeSet / part.getIndexSet(); if (!empty(s)) { // if need to mod-UP, do it on a temporary copy tmp = part; tmp.addPrimesAndScale(s); ptr = &tmp; } long j = getPartIndexByHandle(handle); if (j>=0) { // found a matching part, add them up if (negative) parts[j] -= *ptr; else parts[j] += *ptr; } else { // no mathing part found, just append this part parts.push_back(CtxtPart(*ptr,handle)); if (negative) parts.back().Negate(); // not thread-safe?? } } }