How to use MultiPrecisionSignCipher?

Hello, I’m trying to use the MultiPrecisionSignCipher function, but I’m encountering some problems. I’ve noticed some discussions suggesting that bootstrapping places the ciphertext in the last layer, making multiplication impossible. However, if I have a fresh ciphertext, and I want to use the MultiPrecisionSignCipher function, what should the ciphertext input be? I want to use the resulting sign function output in further calculations, such as multiplying it with another ciphertext. Should I use RLWE ciphertexts for this? How can I do this using standard CKKS encryption?

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
)
    {

    //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);

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

By default, the functionality starts with and returns an additive BFV (RLWE) ciphertext, which does not support multiplications. To support multiplications, you should not perform the final homomorphic decoding in CKKS and remain in CKKS. Since the multiprecision sign evaluation performs iterative LUT evaluations, you should modify the last iteration to prevent decoding but keep in mind the scaling. Please see 1, 2 for some examples of performing leveled computations after FBT.

Alternatively, a simpler (you don’t modify the loop) but more expensive option is to take the RLWE output of the multiprecision sign evaluation, then perform an EvalFBTNoDecoding with the identity function.

Thank you for your reply. I want to perform the operation c1 - c2 = c3 with two CKKS ciphertexts, convert c3 to an RLWE ciphertext ctxtBFV1, then use ctxtBFV1 to call a signing function to obtain an RLWE ciphertext, and then convert it back to a CKKS ciphertext ckksign. Finally, I want to multiply ckksign and c3. I encountered many problems, and I made some errors using your method. Could you please help me correct them in detail? Thank you.

#define PROFILE
#include "binfhecontext.h"

#include "math/hermite.h"
#include "openfhe.h"
#include "schemelet/rlwe-mp.h"

#include <functional>

#include <chrono>


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  = 0;
    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);
    // Encoding as plaintexts
    Plaintext ptxt1 = cc->MakeCKKSPackedPlaintext(x_input1, 1, depth - (levelsAvailableBeforeBootstrap > 0));
    Plaintext ptxt2 = cc->MakeCKKSPackedPlaintext(x_input2, 1, depth - (levelsAvailableBeforeBootstrap > 0));
    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 c3: " << std::endl;

    cc->Decrypt(keyPair.secretKey, c3, &result);

    result->SetLength(numSlotsCKKS);
    std::cout << "c3 = " << result;

    //cc->EvalHomDecoding(c1, scaleTHI, 0);

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

    auto ctxtBFV1= SchemeletRLWEMP::ConvertCKKSToRLWE(c3, QBFVINIT);

    auto computed = SchemeletRLWEMP::DecryptCoeff(ctxtBFV1, QBFVINIT, PInput, keyPair.secretKey, ep, numSlotsCKKS, numSlots);

    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
)
    {

    //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->EvalFBTNoDecoding(ctxt, coeffint, pDigitBits, ep->GetModulus(), 
                                        order);
        else
            ctxtAfterFBT = cc->EvalFBTNoDecoding(ctxt, coeffcomp, pDigitBits, ep->GetModulus(), 
                                        order);
        ctxtAfterFBT = cc->EvalMult(ctxtAfterFBT, scaleTHI* (1 << postScalingBits));*/
        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); 
        
        /* 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;
}

  Ciphertext<DCRTPoly> ctxtAfterFBT;
    while (go) {
        auto encryptedDigit = ctxtBFV;

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


        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));
            //ctxtBFV = ctxtAfterFBT;
        } 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);
            auto polys = SchemeletRLWEMP::ConvertCKKSToRLWE(ctxtAfterFBT, Q);
            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;
        } 
return ctxtAfterFBT;
auto ctxtSign11 = MultiPrecisionSignCipher(ctxtBFV33, cc, QBFVINIT, PInput, PDigit, Q, Bigq, scaleTHI, scaleStepTHI, order, numSlots, ringDim, dcrtBits, ep, keyPair, numSlotsCKKS, depth, levelsAvailableBeforeBootstrap);
    ctxtSign11 = cc->EvalRotate(ctxtSign11, -2);
    Plaintext ptxt_mask = cc->MakeCKKSPackedPlaintext(
        Fill<double>({1, 1, 1, 1, 0, 0, 0, 0}, numSlotsCKKS), 1,
        depth - lvlb[1] - levelsAvailableAfterBootstrap , nullptr, numSlotsCKKS);
    ctxtSign11 = cc->EvalMult(ctxtSign11, ptxt_mask);
    ctxtSign11 = cc->ModReduceInPlace(ctxtSign11);

Thanks for your response, @andreea.alexandru! I have modify the last iteration to prevent decoding, but it still has some error. The last result isctxtAfterFBT.