Help for MultiPrecisionSign function?

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;
}



The conversion from RLWE to CKKS ConvertRLWEToCKKS maintains the coefficient encoding, which does not allow multiplication. Furthermore, the conversion happens to a small CKKS modulus, which typically does not allow many leveled computations. Therefore, a modulus raising step needs to be performed (which introduces overflows that will be cleaned later), followed by a homomorphic transformation from coefficient to slot encoding, followed by the homomorphic function evaluation which also cleans the overflows and should be identity if all you want to do is to convert. Only after this steps you can perform a multiplication (keeping in mind the order of the elements in the factors has to be the same).

Moreover, EvalHomDecoding is designed to be performed after the steps outlined above (to cancel out the homomorphic encoding or coefficients to slot transformation and reverse the bit order if the level budget > 1) and the rescaling mode is FIXEDMANUAL, which means you have to manually align the level and scaling degree with the FFT matrix. In your particular case, this is

cc->ModReduceInPlace(scaledC3);
cc->LevelReduceInPlace(scaledC3, nullptr, 11);

You don’t need to multiply by scaleTHI before (if ciphertexts would have resulted from the normal flow, they would have been divided by scaleTHI). What you need to ensure is that the scaling of the message has the right Q/P scaling for RLWE. Anyway, you do not seem to be assigning the result of EvalHomDecoding to any ciphertext.

RLWE can support additions and subtractions (by adding or subtracting the components).

Importantly, please first spend time to understand what is happening in the algorithms you want to apply (here and here) since there are many points of confusion in your questions and your code. I also suggest you split your task into atomic subtasks and try to make each of them work individually.

Thank you! First, I want to perform subtraction on CKKS ciphertexts and then convert the result to an RLWE ciphertext. Using SchemeletRLWEMP::DecryptCoeff to decrypt the result gives an incorrect output. I understand this is related to coefficient and slot encoding issues, but I don’t know how to solve it. I also tried converting the RLWE ciphertext back to a CKKS ciphertext, and using cc->encrypt to decrypt it also gives an incorrect result. There shouldn’t be any coefficient or slot encoding issues here, right? Can the resulting ciphertext be used for further operations?


    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-1: " << std::endl;
    cc->Decrypt(keyPair.secretKey, c3, &result);
    result->SetLength(numSlotsCKKS);
    std::cout  << result;

    auto ep = SchemeletRLWEMP::GetElementParams(keyPair.secretKey, depth - (levelsAvailableBeforeBootstrap > 0));

    auto ctxtBFV1 = SchemeletRLWEMP::ConvertCKKSToRLWE(c3, ep->GetModulus());
    std::cerr << "Results of RLWE:" << std::endl;
    auto computed = SchemeletRLWEMP::DecryptCoeff(ctxtBFV1, ep->GetModulus(), PInput, keyPair.secretKey, ep, numSlotsCKKS, numSlots);
    std::copy_n(computed.begin(), 8, std::ostream_iterator<int64_t>(std::cerr, " "));

    auto pt = SchemeletRLWEMP::ConvertRLWEToCKKS(*cc, ctxtBFV1, keyPair.publicKey, ep->GetModulus(), numSlotsCKKS, depth);
 
    Plaintext result1;
    std::cout << std::endl << "Results of RLWE-CKKS: " << std::endl;
    cc->Decrypt(keyPair.secretKey, pt, &result1);
    result1->SetLength(numSlotsCKKS);
    std::cout << "c3 = " << result1;

    
    
There shouldn’t be any coefficient or slot encoding issues here, right?

There is always a difference in encoding between RLWE (always in coefficient encoding) and default CKKS (decryption function assumes slot encoding).

Again, please make sure you understand all the encoding differences: slots vs coefficients, bit order of the underlying message, and scaling required for correct decryption in both RLWE and CKKS. This extends to the setting of PInput and POutput/PDigit especially if you want to perform computations afterwards and want to decrypt them in RLWE. As mentioned before, understanding Algorithm 1 in the referenced paper is the best place to start.

I have created an atomic example for you that converts a CKKS ciphertext (corresponding to your difference) to an RLWE ciphertext and then applies a single-precision LUT that computes the sign (rather than multi-precision; again, first try to make things work in simpler settings and build understanding). Read all the comments I have included. Then try to separately obtain a single multiplication in the intermediate EvalFBTNoDecoding as shown in the examples, and finally combine the steps. Unfortunately, myself or other OpenFHE developers do not have the bandwidth to write all the code for large user-requested applications.

int main() {
    
    BigInteger PInput(4096);
    BigInteger POutput(4096);
    BigInteger Q(BigInteger(1) << 55);
    BigInteger Bigq(BigInteger(1) << 55);
    uint64_t scaleTHI = 32;
    size_t order = 1;
    uint32_t numSlots = 8;
    uint32_t ringDim = 1024;
    auto func = [](int64_t x) -> int64_t {
        return (x % 4096 > 2048) % 4096;
    }; // Sign Function
    /* 1. Figure out whether sparse packing or full packing should be used.
     * numSlots represents the number of values to be encrypted in BFV.
     * If this number is the same as the ring dimension, then the CKKS slots is half.
     */
    bool flagSP       = (numSlots <= ringDim / 2);  // sparse packing
    auto numSlotsCKKS = flagSP ? numSlots : numSlots / 2;

    /* 2. Input */
    std::vector<int64_t> x = {45, -60, 0, -10, 10, 0, 20, -200};
    if (x.size() < numSlots)
        x = Fill<int64_t>(x, numSlots);
    std::cerr << "Input: " << x << std::endl;

    /* 3. The case of Boolean LUTs using the first order Trigonometric Hermite Interpolation
     * supports an optimized implementation.
     * In particular, it supports real coefficients as opposed to complex coefficients.
     * Therefore, we separate between this case and the general case.
     * There is no need to scale the coefficients in the Boolean case.
     * However, in the general case, it is recommended to scale down the Hermite
     * coefficients in order to bring their magnitude close to one. This scaling
     * is reverted later.
     */
    std::vector<int64_t> coeffint;
    std::vector<std::complex<double>> coeffcomp;

    coeffcomp = GetHermiteTrigCoefficients(func, PInput.ConvertToInt(), order, scaleTHI);  // divided by 2

    /* 4. Set up the cryptoparameters.
     * The scaling factor in CKKS should have the same bit length as the RLWE ciphertext modulus.
     * The number of levels to be reserved before and after the LUT evaluation should be specified.
    * The secret key distribution for CKKS should either be SPARSE_TERNARY or SPARSE_ENCAPSULATED.
    * The SPARSE_TERNARY distribution is for testing purposes as it gives a larger probability of
    * failure but less noise, while the SPARSE_ENCAPSULATED distribution gives a smaller probability
    * of failure at a cost of slightly more noise.
    */
    uint32_t dcrtBits                       = Bigq.GetMSB() - 1;
    uint32_t firstMod                       = Bigq.GetMSB() - 1;
    uint32_t levelsAvailableAfterBootstrap  = 0;
    uint32_t levelsAvailableBeforeBootstrap = 0;
    uint32_t dnum                           = 3;
    SecretKeyDist secretKeyDist             = SPARSE_ENCAPSULATED;
    std::vector<uint32_t> lvlb              = {1, 1}; // {3, 3};

    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;
    depth += FHECKKSRNS::GetFBTDepth(lvlb, coeffcomp, PInput, order, secretKeyDist);
    parameters.SetMultiplicativeDepth(depth);

    auto cc = GenCryptoContext(parameters);
    cc->Enable(PKE);
    cc->Enable(KEYSWITCH);
    cc->Enable(LEVELEDSHE);
    cc->Enable(ADVANCEDSHE);
    cc->Enable(FHE);

    std::cout << "CKKS scheme is using ring dimension " << cc->GetRingDimension() << " and a multiplicative depth of "
              << depth << std::endl
              << std::endl;

    /* 5. Compute various moduli and scaling sizes, used for scheme conversions.
     * Then generate the setup parameters and necessary keys.
     */
    auto keyPair = cc->KeyGen();
    cc->EvalFBTSetup(coeffcomp, numSlotsCKKS, PInput, POutput, Bigq, keyPair.publicKey, {0, 0}, lvlb,
                         levelsAvailableAfterBootstrap, 0, order);

    cc->EvalBootstrapKeyGen(keyPair.secretKey, numSlotsCKKS);
    cc->EvalMultKeyGen(keyPair.secretKey);

    /* 6. Perform encryption in the RLWE scheme, using a larger initial ciphertext modulus.
     * Switching the modulus to a smaller ciphertext modulus helps offset the encryption error.
     */
    auto ep = SchemeletRLWEMP::GetElementParams(keyPair.secretKey, depth - (levelsAvailableBeforeBootstrap > 0));

    auto ctxtBFV = SchemeletRLWEMP::EncryptCoeff(x, QBFVINIT, PInput, keyPair.secretKey, ep);

    SchemeletRLWEMP::ModSwitch(ctxtBFV, Q, QBFVINIT);

    /* 7. Convert from the RLWE ciphertext to a CKKS ciphertext (both use the same secret key).
    */
    auto ctxt = SchemeletRLWEMP::ConvertRLWEToCKKS(*cc, ctxtBFV, keyPair.publicKey, Bigq, numSlotsCKKS,
                                                   depth - (levelsAvailableBeforeBootstrap > 0));

    // Try to obtain the same RLWE when starting from CKKS
    std::vector<double> xDouble(x.begin(), x.end());
    // Set the levels to the minimum necessary for conversion to not have to reduce levels later; level reduction can be done manaully too.
    auto ptxt = cc->MakeCKKSPackedPlaintext(xDouble, 1, depth - lvlb[1], nullptr, numSlotsCKKS);
    auto ctxtCKKS = cc->Encrypt(keyPair.publicKey, ptxt);

    // The default scaling done in EvalHomDecoding is the same as the one needed in this example (QPrime / (Bigq * PInput)), so no need to do other scaling here.
    ctxtCKKS = cc->EvalHomDecoding(ctxtCKKS, 1, 0);

    auto ctxtBFV_fromCKKS = SchemeletRLWEMP::ConvertCKKSToRLWE(ctxtCKKS, Q);

    /* Setting lvlb to anything other than {1,1} will trigger bit reversal. This means that the RLWE encrypted message will be in bit-reversed order compared to the original CKKS input.
     * There are several ways to deal with this depending on how ciphertexts are used in the intermediate computations, but note that the bit-reversal should be done in the clear as 
     * part of pre- or post-processing, because it is very expensive to do homomorphically.
     * One way is to bit reverse the CKKS input. Another way is to bit-reverse the output after decryption.
     */
    bool flagBR = (lvlb[0] != 1 || lvlb[1] != 1);

    auto decryptedRLWE = SchemeletRLWEMP::DecryptCoeff(ctxtBFV_fromCKKS, Q, POutput, keyPair.secretKey, ep, numSlotsCKKS, numSlots);
    std::cerr << "Decrypted converted input: " << decryptedRLWE << std::endl;

    auto ctxtBFV_backToCKKS = SchemeletRLWEMP::ConvertRLWEToCKKS(*cc, ctxtBFV_fromCKKS, keyPair.publicKey, Bigq, numSlotsCKKS,
                                                   depth - (levelsAvailableBeforeBootstrap > 0));

    /* 8. Apply the LUT over the ciphertext.
    */
    Ciphertext<DCRTPoly> ctxtAfterFBT;
    // ctxtAfterFBT = cc->EvalFBT(ctxt, coeffcomp, PInput.GetMSB() - 1, ep->GetModulus(), scaleTHI, 0, order);
    ctxtAfterFBT = cc->EvalFBT(ctxtBFV_backToCKKS, coeffcomp, PInput.GetMSB() - 1, ep->GetModulus(), scaleTHI, 0, order);

    /* 9. Convert the result back to RLWE.
    */
    auto polys = SchemeletRLWEMP::ConvertCKKSToRLWE(ctxtAfterFBT, Q);

    auto computed = SchemeletRLWEMP::DecryptCoeff(polys, Q, POutput, keyPair.secretKey, ep, numSlotsCKKS, numSlots, flagBR);

    std::cerr << "Obtained output % POutput: " << computed << std::endl;

    auto exact(x);
    std::transform(x.begin(), x.end(), exact.begin(), [&](const int64_t& elem) {
        return elem < 0;
    });

    std::cerr << exact << std::endl;

    std::transform(exact.begin(), exact.end(), computed.begin(), exact.begin(), std::minus<int64_t>());
    std::transform(exact.begin(), exact.end(), exact.begin(),
                   [&](const int64_t& elem) { return (std::abs(elem)) % (POutput.ConvertToInt()); });
    auto max_error_it = std::max_element(exact.begin(), exact.end());
    std::cerr << "Max absolute error obtained: " << *max_error_it << std::endl << std::endl;

    return 0;
}