Question about CKKS scheme oprations

The code I used to do the test is as follows:

int main(int argc, char* argv[]) {
     CCParams<CryptoContextCKKSRNS> parameters;

    SecretKeyDist secretKeyDist = UNIFORM_TERNARY;

    parameters.SetSecretKeyDist(secretKeyDist);    
    parameters.SetSecurityLevel(HEStd_128_classic);
    parameters.SetRingDim(131072);


#if NATIVEINT == 128 && !defined(__EMSCRIPTEN__)
    ScalingTechnique rescaleTech = FIXEDAUTO;
    usint dcrtBits               = 78;
    usint firstMod               = 89;
#else
    ScalingTechnique rescaleTech = FLEXIBLEAUTO;
    usint dcrtBits               = 59;
    usint firstMod               = 60;
#endif

    parameters.SetScalingModSize(dcrtBits);
    parameters.SetScalingTechnique(rescaleTech);
    parameters.SetFirstModSize(firstMod);

    std::vector<uint32_t> levelBudget = {4, 4};

    uint32_t levelsAvailableAfterBootstrap = 10;
    usint depth = levelsAvailableAfterBootstrap + FHECKKSRNS::GetBootstrapDepth(levelBudget, secretKeyDist);
    parameters.SetMultiplicativeDepth(depth);

    CryptoContext<DCRTPoly> cryptoContext = GenCryptoContext(parameters);

    cryptoContext->Enable(PKE);
    cryptoContext->Enable(KEYSWITCH);
    cryptoContext->Enable(LEVELEDSHE);
    cryptoContext->Enable(ADVANCEDSHE);
    cryptoContext->Enable(FHE);

    usint ringDim = cryptoContext->GetRingDimension();
    // This is the maximum number of slots that can be used for full packing.
    usint numSlots = ringDim / 2;
    std::cout << "CKKS scheme is using ring dimension " << ringDim << std::endl << std::endl;

    cryptoContext->EvalBootstrapSetup(levelBudget);

    auto keyPair = cryptoContext->KeyGen();
    cryptoContext->EvalMultKeyGen(keyPair.secretKey);
    cryptoContext->EvalBootstrapKeyGen(keyPair.secretKey, numSlots);

    std::vector<double> x = {0.25, 0.5, 0.75, 1.0, 2.0, 3.0, 4.0, 5.0};
    size_t encodedLength  = x.size();

    Plaintext ptxt = cryptoContext->MakeCKKSPackedPlaintext(x, 1, depth - 1);

    ptxt->SetLength(encodedLength);
    std::cout << "Input: " << ptxt << std::endl;

    Ciphertext<DCRTPoly> ciph = cryptoContext->Encrypt(keyPair.publicKey, ptxt);

    std::cout << "Initial number of levels remaining: " << depth - ciph->GetLevel() << std::endl;

    ciph = cryptoContext->EvalMult(ciph, ciph);
    ciph = cryptoContext->EvalAdd(ciph, 4.0);
    ciph = cryptoContext->EvalAdd(ciph, 4.0);
    ciph = cryptoContext->EvalAdd(ciph, 4.0);
    ciph = cryptoContext->EvalAdd(ciph, 4.0);


    std::cout << "Num of levels:" << depth - ciph->GetLevel() << std::endl;

    Plaintext mid;

    cryptoContext->Decrypt(keyPair.secretKey, ciph, &mid);
    mid->SetLength(encodedLength);
    std::cout << "Output:" << mid << std::endl;
}

I want to ask about this addition operation of CKKS, we set the initial value of levels remaining to 1, and after 1 multiplication, I check again to see the levels remaining are still 1, and then I go through a few more additions, but the result of the addition done at this time doesn’t change and the levels don’t change; still remain level:1. The question I want to ask is what is the level consumed in one addition? Also, should the consumed levels be displayed? It should be a bug if it’s not shown, and allowing the user to keep adding without consuming the levels without throwing any exceptions or errors will cause some applications built on OpenFHE to throw errors. I think these are two serious bugs.
1.The levels that have already been consumed are not displayed.
2.Even when the levels have been fully consumed, the library still allow the user to perform addition.

I would really appreciate any insights or explanations regarding this behavior.
Thank you for your help!

Best regards,
wowblk

Please read more about the modes of execution for CKKS https://eprint.iacr.org/2022/915.pdf. In FLEXIBLEAUTO, the scaling is done right before the next multiplication. You can print also std::cout << ciph->GetNoiseScaleDeg() << std::endl; and see that after the multiplication, this returns 2. In FIXEDMANUAL, you should manually do the rescaling via ModReduce() after the multiplication, as it is not done automatically; then, if you print the number of levels, you will see them decreased.

Thank you for great response!
But I have another question about this MWE

int main(int argc, char* argv[]) {
     CCParams<CryptoContextCKKSRNS> parameters;

    SecretKeyDist secretKeyDist = UNIFORM_TERNARY;

    parameters.SetSecretKeyDist(secretKeyDist);    
    parameters.SetSecurityLevel(HEStd_128_classic);
    parameters.SetRingDim(131072);


#if NATIVEINT == 128 && !defined(__EMSCRIPTEN__)
    ScalingTechnique rescaleTech = FIXEDAUTO;
    usint dcrtBits               = 78;
    usint firstMod               = 89;
#else
    // ScalingTechnique rescaleTech = FLEXIBLEAUTO;
    usint dcrtBits               = 59;
    usint firstMod               = 60;
#endif


    // uint32_t multDepth = 3;

    

    parameters.SetScalingModSize(dcrtBits);
    // parameters.SetScalingTechnique(rescaleTech);
    parameters.SetFirstModSize(firstMod);
    parameters.SetScalingTechnique(FIXEDMANUAL);

    std::vector<uint32_t> levelBudget = {4, 4};

    uint32_t levelsAvailableAfterBootstrap = 10;
    usint depth = levelsAvailableAfterBootstrap + FHECKKSRNS::GetBootstrapDepth(levelBudget, secretKeyDist);
    parameters.SetMultiplicativeDepth(depth);

    CryptoContext<DCRTPoly> cryptoContext = GenCryptoContext(parameters);

    std::cout << parameters << std::endl;

    cryptoContext->Enable(PKE);
    cryptoContext->Enable(KEYSWITCH);
    cryptoContext->Enable(LEVELEDSHE);
    cryptoContext->Enable(ADVANCEDSHE);
    cryptoContext->Enable(FHE);

    usint ringDim = cryptoContext->GetRingDimension();
    std::cout << "CKKS scheme is using ring dimension " << ringDim << std::endl << std::endl;

    auto keyPair = cryptoContext->KeyGen();
    cryptoContext->EvalMultKeyGen(keyPair.secretKey);


    std::vector<double> x = {0.25, 0.5, 0.75, 1.0, 2.0, 3.0, 4.0, 5.0};
    size_t encodedLength  = x.size();

    Plaintext ptxt = cryptoContext->MakeCKKSPackedPlaintext(x, 1, depth - 1);

    ptxt->SetLength(encodedLength);
    std::cout << "Input: " << ptxt << std::endl;

    Ciphertext<DCRTPoly> ciph = cryptoContext->Encrypt(keyPair.publicKey, ptxt);

    std::cout << "Initial number of levels remaining: " << depth - ciph->GetLevel() << std::endl;

    auto noiseScale = ciph->GetNoiseScaleDeg();
    std::cout << "Current noise scale degree: " << noiseScale << std::endl;


    ciph = cryptoContext->EvalMult(ciph, ciph);;
    std::cout << "Num of levels:" << depth - ciph->GetLevel() << std::endl;

    noiseScale = ciph->GetNoiseScaleDeg();
    std::cout << "Current noise scale degree: " << noiseScale << std::endl;

    ciph = cryptoContext->EvalAdd(ciph, 4.0);

    ciph = cryptoContext->ModReduce(ciph);

    ciph = cryptoContext->EvalAdd(ciph, 4.0);

    noiseScale = ciph->GetNoiseScaleDeg();
    std::cout << "Current noise scale degree: " << noiseScale << std::endl;


    std::cout << "Num of levels:" << depth - ciph->GetLevel() << std::endl;

    Plaintext mid;

    cryptoContext->Decrypt(keyPair.secretKey, ciph, &mid);
    mid->SetLength(encodedLength);
    std::cout << "Output:" << mid << std::endl;
}

I was thinking about whether or not the addition consumes the levels. But I don’t think levels affect addition, the addition is associated with multiplication. Then please tell me why I can’t do addition after multiplication. Even after having performed ModReduce before and after. I tried another MWE, i.e. addition before multiplication, the addition succeeded but doing another self-multiplication later resulted in an error; pop up a weird result than i never thought before.

I would really appreciate any insights or explanations regarding this behavior.
Thank you for your help!

Best regards,
wowblk

The problem is that you are creating a depleted plaintext when you do

Plaintext ptxt = cryptoContext->MakeCKKSPackedPlaintext(x, 1, depth - 1);

but then you perform a multiplication, leaving only the last limb. Then on this last limb you do further operations. However you set firstMod to be only one bit larger than dcrtBits, which leaves very little room for the magnitude of the message, see EvalAdd does not add in some cases - #7 by ypolyakov. You can try adding 0.1 instead of 4.0 and see a correct result.
You can also change dcrtBits to 55 bits and you will get a correct value even when adding 4.
Finally, if you are not immediately performing bootstrapping, then you should do at least
Plaintext ptxt = cryptoContext->MakeCKKSPackedPlaintext(x, 1, depth - 2) if you do not want to worry about the magnitude of the message.