CKKS EvalAdd in some cases pop up random numbers

Hi OpenFHE team!
In my algorithm implementation, I found a series of weird outputs. For example, I set MultiplicativeDepth = 0 and scaleModSize = 58. In a for loop, EvalAdd(c1, c2) will pop up random number when c1 = 5 and c2 = 8188. Such as added 8188.000000: (7869.013, ... ); Estimated precision: 43 bits. And also pop up random numbers in scaleModSize = 59 and 57. scaleModSize = 59 case will pop up random number in c1 = 5 and c2 = 4092. Such as added 4092.000000: (3867.0186, ... ); Estimated precision: 44 bits. scaleModSize=57 case: added 16380.000000: (15929.009, ... ); Estimated precision: 42 bits
A weird question. But when you add some MultiplicativeDepth, it returns normal. I know MultipcativeDepth = 0 is some kind of edge value for parameter setting. But seems I don’t use the Mult function in all calculation processes, it shouldn’t be abnormal, If Multipcative Depth = 0 isn’t allowed, then the result should pop up the random number at the begin of the loop instead of the right result. By the way, I tried all four modes (FLEXIBLEAUTO、FLEXIBLEAUTOEXT、FIXEDMANUAL、FIXEDAUTO) and still came up with the same result.

Here is the MWE

#include <iostream>
#include "openfhe.h"

using namespace std;
using namespace lbcrypto;


int main() {

    uint32_t multDepth = 0;

 
    uint32_t scaleModSize = 58;


    CCParams<CryptoContextCKKSRNS> parameters;
    parameters.SetMultiplicativeDepth(multDepth);
    parameters.SetScalingModSize(scaleModSize);
    parameters.SetFirstModSize(60);

    std::cout << "depth:" << parameters.GetMultiplicativeDepth() << std::endl;

    std::cout << "scaleModSize:" << parameters.GetScalingModSize() << std::endl;


    CryptoContext<DCRTPoly> cc = GenCryptoContext(parameters);


    cc->Enable(PKE);
    cc->Enable(KEYSWITCH);
    cc->Enable(LEVELEDSHE);
    cc->Enable(ADVANCEDSHE);
    std::cout << "CKKS scheme is using ring dimension " << cc->GetRingDimension() << std::endl << std::endl;



    auto keys = cc->KeyGen();

    cc->EvalMultKeyGen(keys.secretKey);

    
    std::vector<double> x1 = {5};


    Plaintext ptxt1 = cc->MakeCKKSPackedPlaintext(x1);


    std::cout << "Input x1: " << ptxt1 << std::endl;
    std::cout.precision(8);

    auto c1 = cc->Encrypt(keys.publicKey, ptxt1);

    
    for(double i = 0; i <  65537; i++) {
        auto c2 = cc->Encrypt(keys.publicKey, cc->MakeCKKSPackedPlaintext(vector<double>{i}));      
        auto added = cc->EvalAdd(c1, c2);
        Plaintext p;
        cc->Decrypt(keys.secretKey, added, &p);
        p->SetLength(1);
        cout << "added " << to_string(i) << ":\t" << p << endl; 
    }


    
    return 0;
}

Is this a bug?

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

Best regards,
wowblk

Please refer to this thread. You are noticing a similar behavior. Basically, you are running out of precision budget - (scaleModSize\cdot message) is larger than what can be represented by the underlying modulus). So, you either decrease the magnitude of the message or the scaleModSize, or increase the underlying modulus (by pumping up the multiplicative depth).

I will suggest the team will throw an excepetion to remind the user will be better!! Since a lot of people count the same problem.

Thank you for kindly response.
Best regards,
wowblk

It is not straightforward to prevent overflow in the encrypted domain. Even many conventional programming languages in the plaintext domain do not address this issue. Moreover, overflow can sometimes be intentionally utilized for efficient implementations - especially in integer applications. When developing fixed-precision applications, developers are responsible for selecting data types that prevent overflow unless it is intended.