Hello,
I’m trying to convert two CKKS ciphertexts into their encrypted bits (FHEW ciphertexts) using scheme switching. Extracting the bits from the converted CKKS ciphertext is working fine. But, when I perform the XOR operation between the bits, I get 00.
Here is my entire code:
#include "openfhe.h"
#include "binfhecontext.h"
using namespace lbcrypto;
using namespace std;
int main() {
// =========================================================================
// STEP 1: SETUP (CKKS + FHEW Linked)
// =========================================================================
// 1.1 CKKS Parameters
uint32_t multDepth = 3;
uint32_t firstModSize = 60;
uint32_t scaleModSize = 50;
uint32_t ringDim = 4096;
uint32_t slots = 16;
SecurityLevel sl = HEStd_NotSet;
// 1.2 FHEW Parameters
BINFHE_PARAMSET slBin = STD128;
uint32_t logQ_ccLWE = 25;
CCParams<CryptoContextCKKSRNS> parameters;
parameters.SetMultiplicativeDepth(multDepth);
parameters.SetFirstModSize(firstModSize);
parameters.SetScalingModSize(scaleModSize);
parameters.SetScalingTechnique(FLEXIBLEAUTOEXT);
parameters.SetSecurityLevel(sl);
parameters.SetRingDim(ringDim);
parameters.SetBatchSize(slots);
CryptoContext<DCRTPoly> cc = GenCryptoContext(parameters);
cc->Enable(PKE);
cc->Enable(KEYSWITCH);
cc->Enable(LEVELEDSHE);
cc->Enable(SCHEMESWITCH);
auto keys = cc->KeyGen();
// 1.3 Setup Scheme Switching
SchSwchParams params;
params.SetSecurityLevelCKKS(sl);
params.SetSecurityLevelFHEW(slBin);
params.SetCtxtModSizeFHEWLargePrec(logQ_ccLWE);
params.SetNumSlotsCKKS(slots);
auto privateKeyFHEW = cc->EvalCKKStoFHEWSetup(params);
auto ccLWE = cc->GetBinCCForSchemeSwitch();
cc->EvalCKKStoFHEWKeyGen(keys, privateKeyFHEW);
// 1.4 Generate Bootstrapping Keys for EvalFunc (Bit Extraction)
std::cout << "Generating Bootstrapping Keys..." << std::endl;
ccLWE->BTKeyGen(privateKeyFHEW);
std::cout << "Setup Complete.\n" << std::endl;
// =========================================================================
// STEP 2: PREPARE LUTS (For Bit Extraction)
// =========================================================================
// We are using STD128, so q=4. We can represent values 0,1,2,3.
// This equals 2 bits: 00, 01, 10, 11.
int p = ccLWE->GetMaxPlaintextSpace().ConvertToInt();
cout << "p = " << p << endl;
int numBits = 2;
std::vector<std::vector<NativeInteger> > lut_bits;
auto fp0 = [](NativeInteger m, NativeInteger p1) -> NativeInteger {
return (m.ConvertToInt() >> 0) & 1;
};
lut_bits.push_back(ccLWE->GenerateLUTviaFunction(fp0, p));
auto fp1 = [](NativeInteger m, NativeInteger p1) -> NativeInteger {
return (m.ConvertToInt() >> 1) & 1;
};
lut_bits.push_back(ccLWE->GenerateLUTviaFunction(fp1, p));
// =========================================================================
// STEP 3: ENCRYPT DATA IN CKKS
// =========================================================================
std::vector<double> input1 = {3.0};
uint32_t len = input1.size();
Plaintext ptxt1 = cc->MakeCKKSPackedPlaintext(input1, 1, 0, nullptr);
auto ct_ckks1 = cc->Encrypt(keys.publicKey, ptxt1);
std::vector<double> input2 = {0};
Plaintext ptxt2 = cc->MakeCKKSPackedPlaintext(input2, 1, 0, nullptr);
auto ct_ckks2 = cc->Encrypt(keys.publicKey, ptxt2);
// =========================================================================
// STEP 4: SCHEME SWITCH (CKKS -> FHEW)
// =========================================================================
// Precompute scale factors
//auto pLWE1 = ccLWE->GetMaxPlaintextSpace().ConvertToInt();
auto modulus_LWE = 1 << logQ_ccLWE;
auto beta = ccLWE->GetBeta().ConvertToInt();
auto pLWE2 = modulus_LWE / (2 * beta);
//double scale1 = 1.0 / pLWE1;
double scale2 = 1.0 / pLWE2;
cc->EvalCKKStoFHEWPrecompute(scale2);
auto ct_fhew_vector1 = cc->EvalCKKStoFHEW(ct_ckks1, len);
auto ct_fhew_vector2 = cc->EvalCKKStoFHEW(ct_ckks2, len);
// =========================================================================
// STEP 5: EXTRACT BITS
// =========================================================================
std::cout << "\nExtracting Bits via EvalFunc...\n";
LWEPlaintext bitResult;
for (uint32_t i = 0; i < ct_fhew_vector1.size(); ++i) {
auto decomposed1 =ccLWE->EvalDecomp(ct_fhew_vector1[i]);
auto decomposed2 =ccLWE->EvalDecomp(ct_fhew_vector2[i]);
auto ct_small1 = decomposed1[0];
auto ct_small2 = decomposed2[0];
vector<int> bits1;
vector<int> bits2;
vector<int> res;
for (int b = numBits - 1; b >= 0; b--) {
// Apply LUT to extract specific bit from the valid small ciphertext
auto ct_single_bit1 = ccLWE->EvalFunc(ct_small1, lut_bits[b]);
auto ct_single_bit2 = ccLWE->EvalFunc(ct_small2, lut_bits[b]);
// Decrypt to verify
ccLWE->Decrypt(privateKeyFHEW, ct_single_bit1, &bitResult, p);
bits1.push_back(bitResult);
ccLWE->Decrypt(privateKeyFHEW, ct_single_bit2, &bitResult, p);
bits2.push_back(bitResult);
auto r = ccLWE->EvalBinGate(XOR, ct_single_bit1, ct_single_bit2);
ccLWE->Decrypt(privateKeyFHEW, r, &bitResult, p);
res.push_back(bitResult);
}
if (i==0) {
reverse(bits1.begin(), bits1.end());
//reverse(bits2.begin(), bits2.end());
}
std::cout << "input1 Index " << i << " (Value " << input1[i] << "): ";
for (auto e: bits1) {
cout << e << "";
}
std::cout << " (Binary)" << std::endl;
std::cout << "input2 Index " << i << " (Value " << input2[i] << "): ";
for (auto e: bits2) {
cout << e << "";
}
std::cout << " (Binary)" << std::endl;
std::cout << "Result: " ;
for (auto e: res) {
cout << e << "";
}
std::cout << " (Binary)" << std::endl;
cout << "----------------\n";
}
return 0;
}
This is the output:
Generating Bootstrapping Keys...
Setup Complete.
p = 16
Extracting Bits via EvalFunc...
input1 Index 0 (Value 3): 11 (Binary)
input2 Index 0 (Value 0): 00 (Binary)
Result: 00 (Binary)
I checked the examples on the Github repo, but the output after FHEW switching seems to be in base 16. My goal is just to perform homomorphic bin gates between CKKS ciphertexts.
I checked the discussion of this question, but it was not helpful for me!
Thanks in advance!