Hello,
Sorry again for another report related to using an invalid context in OpenFHE. I understand these cases can be addressed under AAHE, but I wanted to share one more scenario in CKKS where an invalid context — specifically, when firstModSize
is smaller than scalingModSize
— does not throw any exceptions and instead leads to silently incorrect results.
Here are two cases of getting incorrect values under insufficient firstModSize
.
Case 1: Decrypting immediately after encryption gives slightly incorrect values
#include <iostream>
#include <chrono>
#include <iomanip>
#include "openfhe.h"
#include "../functional_units/functional_units.hpp"
using namespace lbcrypto;
using namespace std;
int main(void) {
CCParams<CryptoContextCKKSRNS> parameters;
parameters.SetRingDim(32768);
parameters.SetMultiplicativeDepth(3);
parameters.SetFirstModSize(54);
parameters.SetScalingModSize(59);
parameters.SetSecurityLevel(HEStd_256_classic);
parameters.SetScalingTechnique(FLEXIBLEAUTO);
CryptoContext<DCRTPoly> cc = GenCryptoContext(parameters);
cc->Enable(PKE);
cc->Enable(KEYSWITCH);
cc->Enable(LEVELEDSHE);
KeyPair<DCRTPoly> keyPair;
keyPair = cc->KeyGen();
cc->EvalMultKeyGen(keyPair.secretKey);
size_t slots(7098);
vector<complex<double>> tmp_vec_(7098);
Plaintext tmp;
Ciphertext<DCRTPoly> tmp_;
Ciphertext<DCRTPoly> x, y;
double yP;
double yC;
int c;
vector<double> tmp_vec_1 = { 3612029277224623.500000, 2173980662796596.200000, 4324895368175417.000000 };
tmp = cc->MakeCKKSPackedPlaintext(tmp_vec_1);
x = cc->Encrypt(keyPair.publicKey, tmp);
cc->Decrypt(keyPair.secretKey, x, &tmp);
tmp->SetLength(20);
tmp_vec_ = tmp->GetCKKSPackedValue();
for (int64_t tmp_i = 0; tmp_i < 5; ++tmp_i) {
cout << fixed << setprecision(5) << real(tmp_vec_[tmp_i]) << " ";
}
cout << endl;
return 0;
}
- Context issue:
firstModSize
= 54,scalingModSize
= 59 - Observation: Even without any operations, the decrypted data is already slightly different from the original.
- Expected:
3612029277224623.500000 2173980662796596.200000 4324895368175417.000000 0.000000 0.000000
- Output:
3612029277224624.00000 2173980662796595.50000 4324895368175416.50000 0.02938 -0.04829
- Expected:
- Note: Since the initial data is already corrupted, further operations only amplify the errors.
Case 2: Values break after the last multiplication
#include <iostream>
#include <chrono>
#include <iomanip>
#include "openfhe.h"
#include "../functional_units/functional_units.hpp"
using namespace lbcrypto;
using namespace std;
int main(void) {
CCParams<CryptoContextCKKSRNS> parameters;
parameters.SetRingDim(16384);
parameters.SetMultiplicativeDepth(1);
parameters.SetFirstModSize(40);
parameters.SetScalingModSize(59);
parameters.SetSecurityLevel(HEStd_NotSet);
parameters.SetScalingTechnique(FLEXIBLEAUTO);
CryptoContext<DCRTPoly> cc = GenCryptoContext(parameters);
cc->Enable(PKE);
cc->Enable(KEYSWITCH);
cc->Enable(LEVELEDSHE);
KeyPair<DCRTPoly> keyPair;
keyPair = cc->KeyGen();
cc->EvalMultKeyGen(keyPair.secretKey);
size_t slots(897);
vector<complex<double>> tmp_vec_(897);
Plaintext tmp;
Ciphertext<DCRTPoly> tmp_;
Ciphertext<DCRTPoly> x, y;
double yP;
double yC;
int c;
vector<double> tmp_vec_1 = { 1.177856 };
tmp = cc->MakeCKKSPackedPlaintext(tmp_vec_1);
x = cc->Encrypt(keyPair.publicKey, tmp);
vector<double> tmp_vec_2 = { 1.759079 };
tmp = cc->MakeCKKSPackedPlaintext(tmp_vec_2);
y = cc->Encrypt(keyPair.publicKey, tmp);
x = cc->EvalMultNoRelin(x, y);
cc->RescaleInPlace(x);
cc->RelinearizeInPlace(x);
cc->Decrypt(keyPair.secretKey, x, &tmp);
tmp->SetLength(5);
tmp_vec_ = tmp->GetCKKSPackedValue();
for (int64_t tmp_i = 0; tmp_i < 5; ++tmp_i) {
cout << fixed << setprecision(5) << real(tmp_vec_[tmp_i]) << " ";
}
cout << endl;
return 0;
}
- Context issue:
firstModSize
= 40,scalingModSize
= 59 - Observation: Unlike Case 1, simple encryption and decryption don’t show a problem. But applying the allowed number of multiplication, the value breaks.
- Expected:
2.071942 0.000000 0.000000 0.000000 0.000000
- Output:
-0.00009 -0.00009 -0.00020 0.00014 0.00002
- Expected:
- Note: I tested with different multiplicative depths, and in every case, the result becomes incorrect exactly at the n-th multiplication, where n is the multiplicative depth.
Again, I realize that this kind of invalid context may eventually be handled via AAHE. But if this particular check is already implemented and I simply missed it, I’d appreciate it if you could point me to the relevant reference.
__
Thanks as always for your support!