I want to compute c1-c2, which results in a CKKS ciphertext c3. Then I convert it to an RLWE ciphertext and call the MultiPrecisionSign function. The result, ctxtBFV, is an RLWE ciphertext. I then convert it back to a CKKS ciphertext, ckksSign, and multiply it with c1. I also used the EvalFBTNoDecoding and EvalHomDecoding functions. Is there a problem with the order in which I call these functions, or with the moduli? Calling cc->EvalHomDecoding(c3, scaleTHI, 0) resulted in an error. Thank you for your help.
#define PROFILE
#include "binfhecontext.h"
#include "math/hermite.h"
#include "openfhe.h"
#include "schemelet/rlwe-mp.h"
#include <functional>
#include <chrono>
#include <cmath>
using namespace lbcrypto;
const BigInteger QBFVINIT(BigInteger(1) << 60);
const BigInteger QBFVINITLARGE(BigInteger(1) << 80);
std::vector<lbcrypto::Poly> MultiPrecisionSignCipher(std::vector<lbcrypto::Poly> &ctxtBFV, lbcrypto::CryptoContextCKKSRNS::ContextType &cc,
BigInteger QBFVInit, BigInteger PInput, BigInteger PDigit, BigInteger Q, BigInteger Bigq, uint64_t scaleTHI, uint64_t scaleStepTHI, size_t order, uint32_t numSlots, uint32_t ringDim, uint32_t dcrtBits,
std::shared_ptr<lbcrypto::M4DCRTParams> &ep, lbcrypto::KeyPair<lbcrypto::DCRTPoly> &keyPair, uint32_t numSlotsCKKS, uint32_t depth, uint32_t levelsAvailableBeforeBootstrap);
int main() {
std::cerr << "\n\n Homomorphically evaluate the sign." << std::endl << std::endl;
BigInteger PInput(BigInteger(4096));
BigInteger PDigit(BigInteger(2));
BigInteger Q((BigInteger(1) << 46));
BigInteger Bigq((BigInteger(1) << 35));
uint64_t scaleTHI = 1;
uint64_t scaleStepTHI = 1;
size_t order = 1;
uint32_t ringDim = 2048;
uint32_t numSlots = 32;
std::cout << QBFVINIT << std::endl;
std::cout << Q << std::endl;
std::cout << PInput << std::endl;
bool flagSP = (numSlots <= ringDim / 2); // sparse packing
auto numSlotsCKKS = flagSP ? numSlots : numSlots / 2;
//auto numSlotsCKKS = numSlots;
uint32_t dcrtBits = Bigq.GetMSB() - 1;
uint32_t firstMod = Bigq.GetMSB() - 1;
uint32_t levelsAvailableAfterBootstrap = 2;
uint32_t levelsAvailableBeforeBootstrap = 0;
uint32_t dnum = 3;
SecretKeyDist secretKeyDist = SPARSE_ENCAPSULATED;
std::vector<uint32_t> lvlb = {3, 3};
auto a = PInput.ConvertToInt<int64_t>();
auto b = PDigit.ConvertToInt<int64_t>();
auto funcMod = [b](int64_t x) -> int64_t {
return (x % b);
};
auto funcStep = [a, b](int64_t x) -> int64_t {
return (x % a) >= (b / 2);
};
std::vector<int64_t> coeffintMod;
std::vector<std::complex<double>> coeffcompMod;
std::vector<std::complex<double>> coeffcompStep;
bool binaryLUT = (PDigit.ConvertToInt() == 2) && (order == 1);
if (binaryLUT) {
coeffintMod = {funcMod(1), funcMod(0) - funcMod(1)};
}
else {
coeffcompMod = GetHermiteTrigCoefficients(funcMod, PDigit.ConvertToInt(), order, scaleTHI); // divided by 2
coeffcompStep = GetHermiteTrigCoefficients(funcStep, PDigit.ConvertToInt(), order,
scaleStepTHI); // divided by 2
}
// ===== 2. CryptoContext =====
CCParams<CryptoContextCKKSRNS> parameters;
parameters.SetSecretKeyDist(secretKeyDist);
parameters.SetSecurityLevel(HEStd_NotSet);
parameters.SetScalingModSize(dcrtBits);
parameters.SetScalingTechnique(FIXEDMANUAL);
parameters.SetFirstModSize(firstMod);
parameters.SetNumLargeDigits(dnum);
parameters.SetBatchSize(numSlotsCKKS);
parameters.SetRingDim(ringDim);
uint32_t depth = levelsAvailableAfterBootstrap;
if (binaryLUT)
depth += FHECKKSRNS::GetFBTDepth(lvlb, coeffintMod, PDigit, order, secretKeyDist);
else
depth += FHECKKSRNS::GetFBTDepth(lvlb, coeffcompMod, PDigit, order, secretKeyDist);
parameters.SetMultiplicativeDepth(depth);
auto cc = GenCryptoContext(parameters);
cc->Enable(PKE);
cc->Enable(KEYSWITCH);
cc->Enable(LEVELEDSHE);
cc->Enable(ADVANCEDSHE);
cc->Enable(FHE);
// ===== 3. KeyGen =====
auto keyPair = cc->KeyGen();
std::cout << "CKKS scheme is using ring dimension " << cc->GetRingDimension() << " and a multiplicative depth of "
<< depth << std::endl
<< std::endl;
cc->EvalMultKeyGen(keyPair.secretKey);
if (binaryLUT)
cc->EvalFBTSetup(coeffintMod, numSlotsCKKS, PDigit, PInput, Bigq, keyPair.publicKey, {0, 0}, lvlb,
levelsAvailableAfterBootstrap, 0, order);
else
cc->EvalFBTSetup(coeffcompMod, numSlotsCKKS, PDigit, PInput, Bigq, keyPair.publicKey, {0, 0}, lvlb,
levelsAvailableAfterBootstrap, 0, order);
cc->EvalBootstrapKeyGen(keyPair.secretKey, numSlotsCKKS);
// ===== 4. Encrypt 输入 =====
std::vector<double> x_input1 = {4045,3998,0,5,16,32,128,3096};
if (x_input1.size() < numSlots)
x_input1 = Fill<double>(x_input1, numSlots);
std::vector<double> x_input2 = {4000,4058,0,15,6,32,108,3296};
if (x_input2.size() < numSlots)
x_input2 = Fill<double>(x_input2, numSlots);
std::vector<int64_t> x_input3 = {4045,3998,0,5,16,32,128,3096};
if (x_input3.size() < numSlots)
x_input3 = Fill<int64_t>(x_input3, numSlots);
std::vector<int64_t> x_input4 = {4000,4058,0,15,6,32,108,3296};
if (x_input4.size() < numSlots)
x_input4 = Fill<int64_t>(x_input4, numSlots);
// Encoding as plaintexts
Plaintext ptxt1 = cc->MakeCKKSPackedPlaintext(x_input1);
Plaintext ptxt2 = cc->MakeCKKSPackedPlaintext(x_input2);
//std::cout << "Input x1: " << ptxt1 << std::endl;
//std::cout << "Input x2: " << ptxt2 << std::endl;
auto c1 = cc->Encrypt(keyPair.publicKey, ptxt1);
auto c2 = cc->Encrypt(keyPair.publicKey, ptxt2);
auto c3 = cc->EvalSub(c1, c2);
Plaintext result;
std::cout << std::endl << "Results of ckks-c3: " << std::endl;
cc->Decrypt(keyPair.secretKey, c3, &result);
result->SetLength(numSlotsCKKS);
std::cout << "c3 = " << result;
// Extract CKKS real slots and produce rounded integers (mod PInput) for comparison
auto realSlots = result->GetRealPackedValue();
std::vector<int64_t> roundedSlots;
roundedSlots.reserve(numSlotsCKKS);
int64_t pmod = PInput.ConvertToInt<int64_t>();
for (size_t i = 0; i < numSlotsCKKS && i < realSlots.size(); ++i) {
int64_t r = static_cast<int64_t>(std::llround(realSlots[i]));
// map to [0, p-1]
int64_t m = ((r % pmod) + pmod) % pmod;
roundedSlots.push_back(m);
}
//cc->EvalHomDecoding(c1, scaleTHI, 0);
auto ep = SchemeletRLWEMP::GetElementParams(keyPair.secretKey, depth - (levelsAvailableBeforeBootstrap > 0));
/*
auto ctxtBFV11 = SchemeletRLWEMP::EncryptCoeff(x_input3, QBFVINIT, PInput, keyPair.secretKey, ep);
auto ctxtBFV22 = SchemeletRLWEMP::EncryptCoeff(x_input4, QBFVINIT, PInput, keyPair.secretKey, ep);
std::vector<lbcrypto::Poly> ctxtBFV33 = { ctxtBFV11[0] - ctxtBFV22[0], ctxtBFV11[1] - ctxtBFV22[1]};
auto ctxtSign11 = MultiPrecisionSignCipher(ctxtBFV33, cc, QBFVINIT, PInput, PDigit, Q, Bigq, scaleTHI, scaleStepTHI, order, numSlots, ringDim, dcrtBits, ep, keyPair, numSlotsCKKS, depth, levelsAvailableBeforeBootstrap);
auto ckksSign11 = SchemeletRLWEMP::ConvertRLWEToCKKS(*cc, ctxtSign11, keyPair.publicKey, 34359738368, numSlotsCKKS,
depth - (levelsAvailableBeforeBootstrap > 0));
*/
// auto ckksmult11 = cc->EvalMult(c1, ckksSign11);
// Plaintext resultmult11;
// cc->Decrypt(keyPair.secretKey, ckksSign11, &resultmult11);
// std::cout << "Sign(x) = " << resultmult11;
// std::cerr << "\n\n Convert ep->GetModulus() " << ep->GetModulus() << std::endl << std::endl;
// Convert CKKS ciphertext to RLWE directly (avoid EvalHomDecoding which caused crashes here)
uint64_t postScalingVal = ep->GetModulus().ConvertToInt<uint64_t>();
std::cerr << "DEBUG: scaling CKKS ciphertext by ep modulus postScaling=" << postScalingVal << std::endl;
std::cerr << "DEBUG: after EvalMult scaling" << std::endl;
auto scaledC3 = cc->EvalMult(c3, scaleTHI);
std::cerr << "DEBUG: ep->GetModulus() = " << ep->GetModulus() << std::endl;
std::cerr << "DEBUG: before EvalHomDecoding" << parameters.GetFirstModSize()<< std::endl;
cc->EvalHomDecoding(scaledC3, scaleTHI , 0);
std::cerr << "DEBUG: before ConvertCKKSToRLWE" << std::endl;
auto ctxtBFV1 = SchemeletRLWEMP::ConvertCKKSToRLWE(scaledC3, ep->GetModulus());
std::cerr << "DEBUG: after ConvertCKKSToRLWE" << std::endl;
std::cerr << "DEBUG: before DecryptCoeff" << std::endl;
auto computed = SchemeletRLWEMP::DecryptCoeff(ctxtBFV1, ep->GetModulus(), PInput, keyPair.secretKey, ep, numSlotsCKKS, numSlots);
std::cerr << "DEBUG: after DecryptCoeff" << std::endl;
std::cerr << "First 8 elements of the CKKS real slots: [";
std::copy_n(realSlots.begin(), std::min<size_t>(8, realSlots.size()), std::ostream_iterator<double>(std::cerr, " "));
std::cerr << "]" << std::endl;
std::cerr << "First 8 elements rounded mod PInput: [";
std::copy_n(roundedSlots.begin(), std::min<size_t>(8, roundedSlots.size()), std::ostream_iterator<int64_t>(std::cerr, " "));
std::cerr << "]" << std::endl;
std::cerr << "First 8 elements of the obtained output % POutput: [";
std::copy_n(computed.begin(), 8, std::ostream_iterator<int64_t>(std::cerr, " "));
std::cerr << "]" << std::endl;
auto ctxtSign = MultiPrecisionSignCipher(ctxtBFV1, cc, QBFVINIT, PInput, PDigit, Q, Bigq, scaleTHI, scaleStepTHI, order, numSlots, ringDim, dcrtBits, ep, keyPair, numSlotsCKKS, depth, levelsAvailableBeforeBootstrap);
auto ckksSign = SchemeletRLWEMP::ConvertRLWEToCKKS(*cc, ctxtSign, keyPair.publicKey, 34359738368, numSlotsCKKS,
depth - (levelsAvailableBeforeBootstrap > 0));
auto ckksmult = cc->EvalMult(c1, ckksSign);
Plaintext resultmult;
cc->Decrypt(keyPair.secretKey, ckksSign, &resultmult);
std::cout << "Sign(x) = " << resultmult;
return 0;
}
std::vector<lbcrypto::Poly> MultiPrecisionSignCipher(std::vector<lbcrypto::Poly> &ctxtBFV, lbcrypto::CryptoContextCKKSRNS::ContextType &cc,
//BigInteger PInput, BigInteger PDigit, BigInteger Q, BigInteger Bigq, uint64_t scaleTHI, uint64_t scaleStepTHI, size_t order)
BigInteger QBFVInit, BigInteger PInput, BigInteger PDigit, BigInteger Q, BigInteger Bigq, uint64_t scaleTHI, uint64_t scaleStepTHI, size_t order, uint32_t numSlots, uint32_t ringDim, uint32_t dcrtBits,
std::shared_ptr<lbcrypto::M4DCRTParams> &ep, lbcrypto::KeyPair<lbcrypto::DCRTPoly> &keyPair, uint32_t numSlotsCKKS, uint32_t depth, uint32_t levelsAvailableBeforeBootstrap
)
{
std::cout << "\n\n Multi-Precision Sign Cipher Function. " << std::endl << std::endl;
//auto cc = ctxtBFV->GetCryptoContext();
auto a = PInput.ConvertToInt<int64_t>();
auto b = PDigit.ConvertToInt<int64_t>();
auto funcMod = [b](int64_t x) -> int64_t {
return (x % b);
};
auto funcStep = [a, b](int64_t x) -> int64_t {
return (x % a) >= (b / 2);
};
std::vector<int64_t> coeffintMod;
std::vector<std::complex<double>> coeffcompMod;
std::vector<std::complex<double>> coeffcompStep;
bool binaryLUT = (PDigit.ConvertToInt() == 2) && (order == 1);
if (binaryLUT) {
coeffintMod = {funcMod(1), funcMod(0) - funcMod(1)};
}
else {
coeffcompMod = GetHermiteTrigCoefficients(funcMod, PDigit.ConvertToInt(), order, scaleTHI); // divided by 2
coeffcompStep = GetHermiteTrigCoefficients(funcStep, PDigit.ConvertToInt(), order,
scaleStepTHI); // divided by 2
}
SchemeletRLWEMP::ModSwitch(ctxtBFV, Q, QBFVInit);
uint32_t QBFVBits = Q.GetMSB() - 1;
/* 8. Set up the sign loop parameters. */
std::vector<int64_t> coeffint;
std::vector<std::complex<double>> coeffcomp;
if (binaryLUT)
coeffint = coeffintMod;
else
coeffcomp = coeffcompMod;
const bool checkeq2 = PDigit.ConvertToInt() == 2;
const bool checkgt2 = PDigit.ConvertToInt() > 2;
const uint32_t pDigitBits = PDigit.GetMSB() - 1;
BigInteger QNew;
BigInteger pOrig = PInput;
BigInteger Qorig = Q; // remember original Q so we can restore before returning
bool step = false;
bool go = QBFVBits > dcrtBits;
size_t levelsToDrop = 0;
uint32_t postScalingBits = 0;
/* 9. Start the sign loop. For arbitrary digit size, pNew > 2, the last iteration needs
* to evaluate step pNew not mod pNew.
* Currently this only works when log(pNew) divides log(p).
*/
while (go) {
auto encryptedDigit = ctxtBFV;
/* 9.1. Apply mod Bigq to extract the digit and convert it from RLWE to CKKS. */
encryptedDigit[0].SwitchModulus(Bigq, 1, 0, 0);
encryptedDigit[1].SwitchModulus(Bigq, 1, 0, 0);
auto ctxt = SchemeletRLWEMP::ConvertRLWEToCKKS(*cc, encryptedDigit, keyPair.publicKey, Bigq, numSlotsCKKS,
depth - (levelsAvailableBeforeBootstrap > 0));
/* 9.2 Bootstrap the digit.*/
Ciphertext<DCRTPoly> ctxtAfterFBT;
/*
if (binaryLUT)
ctxtAfterFBT = cc->EvalFBT(ctxt, coeffint, pDigitBits, ep->GetModulus(), scaleTHI * (1 << postScalingBits),
levelsToDrop, order);
else
ctxtAfterFBT = cc->EvalFBT(ctxt, coeffcomp, pDigitBits, ep->GetModulus(), scaleTHI * (1 << postScalingBits),
levelsToDrop, order);
bool isLastIteration = step || (checkeq2 && (QBFVBits - pDigitBits <= dcrtBits));
if (isLastIteration) {
if (binaryLUT)
ctxtAfterFBT = cc->EvalFBTNoDecoding(ctxt, coeffint, pDigitBits, ep->GetModulus(), order);
else
ctxtAfterFBT = cc->EvalFBTNoDecoding(ctxt, coeffcomp, pDigitBits, ep->GetModulus(), order);
ctxtAfterFBT = cc->EvalMult(ctxtAfterFBT, scaleTHI * (1 << postScalingBits));
} else {
if (binaryLUT)
ctxtAfterFBT = cc->EvalFBT(ctxt, coeffint, pDigitBits, ep->GetModulus(), scaleTHI * (1 << postScalingBits),
levelsToDrop, order);
else
ctxtAfterFBT = cc->EvalFBT(ctxt, coeffcomp, pDigitBits, ep->GetModulus(), scaleTHI * (1 << postScalingBits),
levelsToDrop, order);
}
*/
if (binaryLUT)
ctxtAfterFBT = cc->EvalFBTNoDecoding(ctxt, coeffint, pDigitBits, ep->GetModulus(), order);
else
ctxtAfterFBT = cc->EvalFBTNoDecoding(ctxt, coeffcomp, pDigitBits, ep->GetModulus(), order);
ctxtAfterFBT = cc->EvalMult(ctxtAfterFBT, scaleTHI * (1 << postScalingBits));
/* 9.3 Convert the result back to RLWE and update the
* plaintext and ciphertext modulus of the ciphertext for the next iteration.
*/
auto polys = SchemeletRLWEMP::ConvertCKKSToRLWE(ctxtAfterFBT, Q);
if (!step) {
/* 9.4 If not in the last iteration, subtract the digit from the ciphertext. */
ctxtBFV[0] = ctxtBFV[0] - polys[0];
ctxtBFV[1] = ctxtBFV[1] - polys[1];
/* 9.5 Do modulus switching from Q to QNew for the RLWE ciphertext. */
QNew = Q >> pDigitBits;
ctxtBFV[0] = ctxtBFV[0].MultiplyAndRound(QNew, Q);
ctxtBFV[0].SwitchModulus(QNew, 1, 0, 0);
ctxtBFV[1] = ctxtBFV[1].MultiplyAndRound(QNew, Q);
ctxtBFV[1].SwitchModulus(QNew, 1, 0, 0);
Q >>= pDigitBits;
PInput >>= pDigitBits;
QBFVBits -= pDigitBits;
postScalingBits += pDigitBits;
}
else {
/* 9.6 If in the last iteration, return the digit. */
ctxtBFV[0] = std::move(polys[0]);
ctxtBFV[1] = std::move(polys[1]);
}
/* 9.7 If in the last iteration, decrypt and assess correctness. */
go = QBFVBits > dcrtBits;
if (step || (checkeq2 && !go)) {
std::vector<int64_t> one_input = {1};
if (one_input.size() < numSlots)
one_input = Fill<int64_t>(one_input, numSlots);
auto ctxtone = SchemeletRLWEMP::EncryptCoeff(one_input, Q, PInput, keyPair.secretKey, ep);
ctxtBFV = {ctxtone[0] - ctxtBFV[0], ctxtone[1] - ctxtBFV[1]};
// auto computed =
// SchemeletRLWEMP::DecryptCoeff(ctxtBFV, Q, PInput, keyPair.secretKey, ep, numSlotsCKKS, numSlots);
//std::cerr << "First 8 elements of the obtained sign: [";
//std::copy_n(computed.begin(), 8, std::ostream_iterator<int64_t>(std::cerr, " "));
// std::cerr << "]" << std::endl;
//std::cout << Q << std::endl;
//std::cout << PInput << std::endl;
}
/* 9.8 Determine whether it is the last iteration and if not, update the parameters for the next iteration. */
if (checkgt2 && !go && !step) {
if (!binaryLUT)
coeffcomp = coeffcompStep;
scaleTHI = scaleStepTHI;
step = true;
go = true;
if (coeffcompMod.size() > 4 && GetMultiplicativeDepthByCoeffVector(coeffcompMod, true) >
GetMultiplicativeDepthByCoeffVector(coeffcompStep, true)) {
levelsToDrop = GetMultiplicativeDepthByCoeffVector(coeffcompMod, true) -
GetMultiplicativeDepthByCoeffVector(coeffcompStep, true);
}
}
}
return ctxtBFV;
}