Program Listing for File rns-leveledshe.cpp
↰ Return to documentation for file (pke/lib/schemerns/rns-leveledshe.cpp)
//==================================================================================
// BSD 2-Clause License
//
// Copyright (c) 2014-2022, NJIT, Duality Technologies Inc. and other contributors
//
// All rights reserved.
//
// Author TPOC: contact@openfhe.org
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//==================================================================================
#define PROFILE
#include "cryptocontext.h"
#include "schemerns/rns-leveledshe.h"
#include <memory>
#include <vector>
namespace lbcrypto {
// SHE ADDITION
Ciphertext<DCRTPoly> LeveledSHERNS::EvalAdd(ConstCiphertext<DCRTPoly>& ciphertext1,
ConstCiphertext<DCRTPoly>& ciphertext2) const {
auto result = ciphertext1->Clone();
EvalAddInPlace(result, ciphertext2);
return result;
}
void LeveledSHERNS::EvalAddInPlace(Ciphertext<DCRTPoly>& ciphertext1, ConstCiphertext<DCRTPoly>& ciphertext2) const {
auto st = std::dynamic_pointer_cast<CryptoParametersRNS>(ciphertext1->GetCryptoParameters())->GetScalingTechnique();
if (st == NORESCALE) {
EvalAddCoreInPlace(ciphertext1, ciphertext2);
}
else {
auto c2 = ciphertext2->Clone();
AdjustForAddOrSubInPlace(ciphertext1, c2);
EvalAddCoreInPlace(ciphertext1, c2);
}
}
Ciphertext<DCRTPoly> LeveledSHERNS::EvalAddMutable(Ciphertext<DCRTPoly>& ciphertext1,
Ciphertext<DCRTPoly>& ciphertext2) const {
AdjustForAddOrSubInPlace(ciphertext1, ciphertext2);
return EvalAddCore(ciphertext1, ciphertext2);
}
void LeveledSHERNS::EvalAddMutableInPlace(Ciphertext<DCRTPoly>& ciphertext1, Ciphertext<DCRTPoly>& ciphertext2) const {
AdjustForAddOrSubInPlace(ciphertext1, ciphertext2);
EvalAddCoreInPlace(ciphertext1, ciphertext2);
}
// SHE ADDITION PLAINTEXT
Ciphertext<DCRTPoly> LeveledSHERNS::EvalAdd(ConstCiphertext<DCRTPoly>& ciphertext, ConstPlaintext& plaintext) const {
auto result = ciphertext->Clone();
EvalAddInPlace(result, plaintext);
return result;
}
void LeveledSHERNS::EvalAddInPlace(Ciphertext<DCRTPoly>& ciphertext, ConstPlaintext& plaintext) const {
auto st = std::dynamic_pointer_cast<CryptoParametersRNS>(ciphertext->GetCryptoParameters())->GetScalingTechnique();
if (st == NORESCALE) {
EvalAddCoreInPlace(ciphertext, plaintext->GetElement<DCRTPoly>());
}
else {
auto ctmorphed = MorphPlaintext(plaintext, ciphertext);
AdjustForAddOrSubInPlace(ciphertext, ctmorphed);
EvalAddCoreInPlace(ciphertext, ctmorphed->GetElements()[0]);
}
}
Ciphertext<DCRTPoly> LeveledSHERNS::EvalAddMutable(Ciphertext<DCRTPoly>& ciphertext, Plaintext& plaintext) const {
auto ctmorphed = MorphPlaintext(plaintext, ciphertext);
AdjustForAddOrSubInPlace(ciphertext, ctmorphed);
return EvalAddCore(ciphertext, ctmorphed->GetElements()[0]);
}
void LeveledSHERNS::EvalAddMutableInPlace(Ciphertext<DCRTPoly>& ciphertext, Plaintext& plaintext) const {
auto ctmorphed = MorphPlaintext(plaintext, ciphertext);
AdjustForAddOrSubInPlace(ciphertext, ctmorphed);
EvalAddCoreInPlace(ciphertext, ctmorphed->GetElements()[0]);
}
// SHE SUBTRACTION
Ciphertext<DCRTPoly> LeveledSHERNS::EvalSub(ConstCiphertext<DCRTPoly>& ciphertext1,
ConstCiphertext<DCRTPoly>& ciphertext2) const {
auto result = ciphertext1->Clone();
EvalSubInPlace(result, ciphertext2);
return result;
}
void LeveledSHERNS::EvalSubInPlace(Ciphertext<DCRTPoly>& ciphertext1, ConstCiphertext<DCRTPoly>& ciphertext2) const {
auto st = std::dynamic_pointer_cast<CryptoParametersRNS>(ciphertext1->GetCryptoParameters())->GetScalingTechnique();
if (st == NORESCALE) {
EvalSubCoreInPlace(ciphertext1, ciphertext2);
}
else {
auto c2 = ciphertext2->Clone();
AdjustForAddOrSubInPlace(ciphertext1, c2);
EvalSubCoreInPlace(ciphertext1, c2);
}
}
Ciphertext<DCRTPoly> LeveledSHERNS::EvalSubMutable(Ciphertext<DCRTPoly>& ciphertext1,
Ciphertext<DCRTPoly>& ciphertext2) const {
AdjustForAddOrSubInPlace(ciphertext1, ciphertext2);
return EvalSubCore(ciphertext1, ciphertext2);
}
void LeveledSHERNS::EvalSubMutableInPlace(Ciphertext<DCRTPoly>& ciphertext1, Ciphertext<DCRTPoly>& ciphertext2) const {
AdjustForAddOrSubInPlace(ciphertext1, ciphertext2);
EvalSubCoreInPlace(ciphertext1, ciphertext2);
}
// SHE SUBTRACTION PLAINTEXT
Ciphertext<DCRTPoly> LeveledSHERNS::EvalSub(ConstCiphertext<DCRTPoly>& ciphertext, ConstPlaintext& plaintext) const {
auto result = ciphertext->Clone();
EvalSubInPlace(result, plaintext);
return result;
}
void LeveledSHERNS::EvalSubInPlace(Ciphertext<DCRTPoly>& ciphertext, ConstPlaintext& plaintext) const {
auto st = std::dynamic_pointer_cast<CryptoParametersRNS>(ciphertext->GetCryptoParameters())->GetScalingTechnique();
if (st == NORESCALE) {
EvalAddCoreInPlace(ciphertext, plaintext->GetElement<DCRTPoly>());
}
else {
auto ctmorphed = MorphPlaintext(plaintext, ciphertext);
AdjustForAddOrSubInPlace(ciphertext, ctmorphed);
EvalSubCoreInPlace(ciphertext, ctmorphed->GetElements()[0]);
}
}
Ciphertext<DCRTPoly> LeveledSHERNS::EvalSubMutable(Ciphertext<DCRTPoly>& ciphertext, Plaintext& plaintext) const {
auto ctmorphed = MorphPlaintext(plaintext, ciphertext);
AdjustForAddOrSubInPlace(ciphertext, ctmorphed);
return EvalSubCore(ciphertext, ctmorphed->GetElements()[0]);
}
void LeveledSHERNS::EvalSubMutableInPlace(Ciphertext<DCRTPoly>& ciphertext, Plaintext& plaintext) const {
auto ctmorphed = MorphPlaintext(plaintext, ciphertext);
AdjustForAddOrSubInPlace(ciphertext, ctmorphed);
EvalSubCoreInPlace(ciphertext, ctmorphed->GetElements()[0]);
}
// SHE MULTIPLICATION
Ciphertext<DCRTPoly> LeveledSHERNS::EvalMult(ConstCiphertext<DCRTPoly>& ciphertext1,
ConstCiphertext<DCRTPoly>& ciphertext2) const {
auto st = std::dynamic_pointer_cast<CryptoParametersRNS>(ciphertext1->GetCryptoParameters())->GetScalingTechnique();
if (st == NORESCALE)
return EvalMultCore(ciphertext1, ciphertext2);
auto c1 = ciphertext1->Clone();
auto c2 = ciphertext2->Clone();
AdjustForMultInPlace(c1, c2);
return EvalMultCore(c1, c2);
}
Ciphertext<DCRTPoly> LeveledSHERNS::EvalMultMutable(Ciphertext<DCRTPoly>& ciphertext1,
Ciphertext<DCRTPoly>& ciphertext2) const {
AdjustForMultInPlace(ciphertext1, ciphertext2);
return EvalMultCore(ciphertext1, ciphertext2);
}
Ciphertext<DCRTPoly> LeveledSHERNS::EvalSquare(ConstCiphertext<DCRTPoly>& ciphertext) const {
const auto cryptoParams = std::dynamic_pointer_cast<CryptoParametersRNS>(ciphertext->GetCryptoParameters());
auto st = cryptoParams->GetScalingTechnique();
if (st == NORESCALE || st == FIXEDMANUAL || ciphertext->GetNoiseScaleDeg() == 1)
return EvalSquareCore(ciphertext);
size_t lvls = (st == COMPOSITESCALINGAUTO || st == COMPOSITESCALINGMANUAL) ? cryptoParams->GetCompositeDegree() :
BASE_NUM_LEVELS_TO_DROP;
return EvalSquareCore(ModReduceInternal(ciphertext, lvls));
}
Ciphertext<DCRTPoly> LeveledSHERNS::EvalSquareMutable(Ciphertext<DCRTPoly>& ciphertext) const {
const auto cryptoParams = std::dynamic_pointer_cast<CryptoParametersRNS>(ciphertext->GetCryptoParameters());
auto st = cryptoParams->GetScalingTechnique();
if (st != NORESCALE && st != FIXEDMANUAL && ciphertext->GetNoiseScaleDeg() == 2) {
size_t lvls = (st == COMPOSITESCALINGAUTO || st == COMPOSITESCALINGMANUAL) ?
cryptoParams->GetCompositeDegree() :
BASE_NUM_LEVELS_TO_DROP;
ModReduceInternalInPlace(ciphertext, lvls);
}
return EvalSquareCore(ciphertext);
}
Ciphertext<DCRTPoly> LeveledSHERNS::EvalMult(ConstCiphertext<DCRTPoly>& ciphertext, ConstPlaintext& plaintext) const {
auto result = ciphertext->Clone();
EvalMultInPlace(result, plaintext);
return result;
}
void LeveledSHERNS::EvalMultInPlace(Ciphertext<DCRTPoly>& ciphertext, ConstPlaintext& plaintext) const {
auto st = std::dynamic_pointer_cast<CryptoParametersRNS>(ciphertext->GetCryptoParameters())->GetScalingTechnique();
if (st == NORESCALE) {
EvalMultCoreInPlace(ciphertext, plaintext->GetElement<DCRTPoly>());
}
else {
auto ctmorphed = MorphPlaintext(plaintext, ciphertext);
AdjustForMultInPlace(ciphertext, ctmorphed);
EvalMultCoreInPlace(ciphertext, ctmorphed->GetElements()[0]);
ciphertext->SetNoiseScaleDeg(ciphertext->GetNoiseScaleDeg() + ctmorphed->GetNoiseScaleDeg());
}
}
Ciphertext<DCRTPoly> LeveledSHERNS::EvalMultMutable(Ciphertext<DCRTPoly>& ciphertext, Plaintext& plaintext) const {
auto ctmorphed = MorphPlaintext(plaintext, ciphertext);
AdjustForMultInPlace(ciphertext, ctmorphed);
auto result = EvalMultCore(ciphertext, ctmorphed->GetElements()[0]);
result->SetNoiseScaleDeg(ciphertext->GetNoiseScaleDeg() + ctmorphed->GetNoiseScaleDeg());
// TODO (Andrey) : This part is only used in CKKS scheme
result->SetScalingFactor(ciphertext->GetScalingFactor() * ctmorphed->GetScalingFactor());
// TODO (Andrey) : This part is only used in BGV scheme
auto st = std::dynamic_pointer_cast<CryptoParametersRNS>(ciphertext->GetCryptoParameters())->GetScalingTechnique();
if (st == FLEXIBLEAUTO || st == FLEXIBLEAUTOEXT)
result->SetScalingFactorInt(ciphertext->GetScalingFactorInt().ModMul(
ctmorphed->GetScalingFactorInt(), ciphertext->GetCryptoParameters()->GetPlaintextModulus()));
return result;
}
// TODO (Andrey) : currently do same as EvalMultInPlace, as Plaintext element is immutable
void LeveledSHERNS::EvalMultMutableInPlace(Ciphertext<DCRTPoly>& ciphertext, Plaintext& plaintext) const {
auto ctmorphed = MorphPlaintext(plaintext, ciphertext);
AdjustForMultInPlace(ciphertext, ctmorphed);
EvalMultCoreInPlace(ciphertext, ctmorphed->GetElements()[0]);
ciphertext->SetNoiseScaleDeg(ciphertext->GetNoiseScaleDeg() + ctmorphed->GetNoiseScaleDeg());
// TODO (Andrey) : This part is only used in CKKS scheme
ciphertext->SetScalingFactor(ciphertext->GetScalingFactor() * ctmorphed->GetScalingFactor());
// TODO (Andrey) : This part is only used in BGV scheme
auto st = std::dynamic_pointer_cast<CryptoParametersRNS>(ciphertext->GetCryptoParameters())->GetScalingTechnique();
if (st == FLEXIBLEAUTO || st == FLEXIBLEAUTOEXT)
ciphertext->SetScalingFactorInt(ciphertext->GetScalingFactorInt().ModMul(
ctmorphed->GetScalingFactorInt(), ciphertext->GetCryptoParameters()->GetPlaintextModulus()));
}
Ciphertext<DCRTPoly> LeveledSHERNS::MultByMonomial(ConstCiphertext<DCRTPoly>& ciphertext, uint32_t power) const {
auto result = ciphertext->Clone();
MultByMonomialInPlace(result, power);
return result;
}
void LeveledSHERNS::MultByMonomialInPlace(Ciphertext<DCRTPoly>& ciphertext, uint32_t power) const {
auto& cv = ciphertext->GetElements();
auto elemParams = cv[0].GetParams();
auto paramsNative = elemParams->GetParams()[0];
uint32_t N = elemParams->GetRingDimension();
uint32_t M = 2 * N;
NativePoly monomial(paramsNative, Format::COEFFICIENT, true);
uint32_t powerReduced = power % M;
monomial[power % N] = powerReduced < N ? NativeInteger(1) : paramsNative->GetModulus() - NativeInteger(1);
DCRTPoly monomialDCRT(elemParams, Format::COEFFICIENT, true);
monomialDCRT = monomial;
monomialDCRT.SetFormat(Format::EVALUATION);
for (uint32_t i = 0; i < ciphertext->NumberCiphertextElements(); ++i)
cv[i] *= monomialDCRT;
}
// SHE AUTOMORPHISM
// SHE LEVELED Mod Reduce
Ciphertext<DCRTPoly> LeveledSHERNS::ModReduce(ConstCiphertext<DCRTPoly>& ciphertext, size_t levels) const {
auto result = ciphertext->Clone();
ModReduceInPlace(result, levels);
return result;
}
void LeveledSHERNS::ModReduceInPlace(Ciphertext<DCRTPoly>& ciphertext, size_t levels) const {
auto st = std::dynamic_pointer_cast<CryptoParametersRNS>(ciphertext->GetCryptoParameters())->GetScalingTechnique();
if (st == FIXEDMANUAL)
ModReduceInternalInPlace(ciphertext, levels);
}
// SHE LEVELED Level Reduce
// TODO (Andrey) : remove evalKey as unused
Ciphertext<DCRTPoly> LeveledSHERNS::LevelReduce(ConstCiphertext<DCRTPoly>& ciphertext, const EvalKey<DCRTPoly> evalKey,
size_t levels) const {
auto result = ciphertext->Clone();
LevelReduceInPlace(result, evalKey, levels);
return result;
}
// TODO (Andrey) : remove evalKey as unused
void LeveledSHERNS::LevelReduceInPlace(Ciphertext<DCRTPoly>& ciphertext, const EvalKey<DCRTPoly> evalKey,
size_t levels) const {
auto st = std::dynamic_pointer_cast<CryptoParametersRNS>(ciphertext->GetCryptoParameters())->GetScalingTechnique();
if (st == NORESCALE)
OPENFHE_THROW("Not implemented for NORESCALE rescaling technique");
if (st == FIXEDMANUAL && levels > 0)
LevelReduceInternalInPlace(ciphertext, levels);
}
// SHE LEVELED Compress
/*
* On COMPOSITESCALING technique, the number of towers to drop passed
* must be a multiple of composite degree.
*/
Ciphertext<DCRTPoly> LeveledSHERNS::Compress(ConstCiphertext<DCRTPoly>& ciphertext, size_t towersLeft,
size_t noiseScaleDeg) const {
const auto cryptoParams = std::dynamic_pointer_cast<CryptoParametersRNS>(ciphertext->GetCryptoParameters());
uint32_t levelsToDrop = BASE_NUM_LEVELS_TO_DROP;
if (cryptoParams->GetScalingTechnique() == COMPOSITESCALINGAUTO ||
cryptoParams->GetScalingTechnique() == COMPOSITESCALINGMANUAL) {
uint32_t compositeDegree = cryptoParams->GetCompositeDegree();
levelsToDrop = compositeDegree;
if (towersLeft % compositeDegree != 0)
OPENFHE_THROW("Number of towers to drop must be a multiple of composite degree.");
}
auto result = std::make_shared<CiphertextImpl<DCRTPoly>>(*ciphertext);
while (result->GetNoiseScaleDeg() > noiseScaleDeg)
ModReduceInternalInPlace(result, levelsToDrop);
size_t sizeQl = result->GetElements()[0].GetNumOfElements();
if (towersLeft < sizeQl)
LevelReduceInternalInPlace(result, sizeQl - towersLeft);
return result;
}
// SHE CORE OPERATION
Ciphertext<DCRTPoly> LeveledSHERNS::ModReduceInternal(ConstCiphertext<DCRTPoly>& ciphertext, size_t levels) const {
auto result = ciphertext->Clone();
ModReduceInternalInPlace(result, levels);
return result;
}
Ciphertext<DCRTPoly> LeveledSHERNS::LevelReduceInternal(ConstCiphertext<DCRTPoly>& ciphertext, size_t levels) const {
auto result = ciphertext->Clone();
LevelReduceInternalInPlace(result, levels);
return result;
}
void LeveledSHERNS::AdjustLevelsInPlace(Ciphertext<DCRTPoly>& ciphertext1, Ciphertext<DCRTPoly>& ciphertext2) const {
auto sizeQl1 = ciphertext1->GetElements()[0].GetNumOfElements();
auto sizeQl2 = ciphertext2->GetElements()[0].GetNumOfElements();
if (sizeQl1 < sizeQl2)
LevelReduceInternalInPlace(ciphertext2, sizeQl2 - sizeQl1);
if (sizeQl1 > sizeQl2)
LevelReduceInternalInPlace(ciphertext1, sizeQl1 - sizeQl2);
}
void LeveledSHERNS::AdjustForAddOrSubInPlace(Ciphertext<DCRTPoly>& ciphertext1,
Ciphertext<DCRTPoly>& ciphertext2) const {
const auto cryptoParams = std::dynamic_pointer_cast<CryptoParametersRNS>(ciphertext1->GetCryptoParameters());
if (cryptoParams->GetScalingTechnique() == FIXEDMANUAL) {
AdjustLevelsInPlace(ciphertext1, ciphertext2);
double scFactor = cryptoParams->GetScalingFactorReal();
// supported only for CKKS
if (scFactor == 0.0)
return;
DCRTPoly ptxt;
uint32_t ptxtDepth = 0;
uint32_t ctxtDepth = 0;
uint32_t sizeQl = 0;
uint32_t ptxtIndex = 0;
// Get moduli chain to create CRT representation of powP
std::vector<DCRTPoly::Integer> moduli;
if (ciphertext1->NumberCiphertextElements() == 1) {
ptxt = ciphertext1->GetElements()[0];
ptxtDepth = ciphertext1->GetNoiseScaleDeg();
ctxtDepth = ciphertext2->GetNoiseScaleDeg();
sizeQl = ciphertext2->GetElements()[0].GetNumOfElements();
moduli.resize(sizeQl);
for (uint32_t i = 0; i < sizeQl; i++) {
moduli[i] = ciphertext2->GetElements()[0].GetElementAtIndex(i).GetModulus();
}
ptxtIndex = 1;
}
else if (ciphertext2->NumberCiphertextElements() == 1) {
ptxt = ciphertext2->GetElements()[0];
ptxtDepth = ciphertext2->GetNoiseScaleDeg();
ctxtDepth = ciphertext1->GetNoiseScaleDeg();
sizeQl = ciphertext1->GetElements()[0].GetNumOfElements();
moduli.resize(sizeQl);
for (uint32_t i = 0; i < sizeQl; i++) {
moduli[i] = ciphertext1->GetElements()[0].GetElementAtIndex(i).GetModulus();
}
ptxtIndex = 2;
}
else
return;
// Bring to same depth if not already same
if (ptxtDepth < ctxtDepth) {
// Find out how many levels to scale plaintext up.
size_t diffDepth = ctxtDepth - ptxtDepth;
DCRTPoly::Integer intSF = static_cast<NativeInteger::Integer>(scFactor + 0.5);
std::vector<DCRTPoly::Integer> crtSF(sizeQl, intSF);
auto crtPowSF = crtSF;
for (uint32_t j = 0; j < diffDepth - 1; j++) {
crtPowSF = CKKSPackedEncoding::CRTMult(crtPowSF, crtSF, moduli);
}
if (ptxtIndex == 1) {
ciphertext1->SetElements(std::vector<DCRTPoly>{ptxt.Times(crtPowSF)});
ciphertext1->SetNoiseScaleDeg(ctxtDepth);
}
else {
ciphertext2->SetElements(std::vector<DCRTPoly>{ptxt.Times(crtPowSF)});
ciphertext2->SetNoiseScaleDeg(ctxtDepth);
}
}
else if (ptxtDepth > ctxtDepth) {
OPENFHE_THROW("plaintext cannot be encoded at a larger depth than that of the ciphertext.");
}
}
else if (cryptoParams->GetScalingTechnique() != NORESCALE) {
AdjustLevelsAndDepthInPlace(ciphertext1, ciphertext2);
}
}
void LeveledSHERNS::AdjustForMultInPlace(Ciphertext<DCRTPoly>& ciphertext1, Ciphertext<DCRTPoly>& ciphertext2) const {
auto st = std::dynamic_pointer_cast<CryptoParametersRNS>(ciphertext1->GetCryptoParameters())->GetScalingTechnique();
if (st == FIXEDMANUAL)
AdjustLevelsInPlace(ciphertext1, ciphertext2);
else if (st != NORESCALE)
AdjustLevelsAndDepthToOneInPlace(ciphertext1, ciphertext2);
}
Ciphertext<DCRTPoly> LeveledSHERNS::ComposedEvalMult(ConstCiphertext<DCRTPoly>& ciphertext1,
ConstCiphertext<DCRTPoly>& ciphertext2,
const EvalKey<DCRTPoly> evalKey) const {
const auto cryptoParams = std::dynamic_pointer_cast<CryptoParametersRNS>(ciphertext1->GetCryptoParameters());
auto st = cryptoParams->GetScalingTechnique();
size_t lvls = (st == COMPOSITESCALINGAUTO || st == COMPOSITESCALINGMANUAL) ? cryptoParams->GetCompositeDegree() :
BASE_NUM_LEVELS_TO_DROP;
auto ciphertext = EvalMult(ciphertext1, ciphertext2);
ciphertext->GetCryptoContext()->GetScheme()->KeySwitchInPlace(ciphertext, evalKey);
ModReduceInPlace(ciphertext, lvls);
return ciphertext;
}
} // namespace lbcrypto