Hello,
I encountered an issue where using EvalMultNoRelin followed by Relinearize produces incorrect results in subsequent computations, such as EvalAdd or EvalRotate. This problem does not occur when EvalMult is used instead. This seems to indicate a bug in how the ciphertext behaves after delayed relinearization.
Code Example
Here’s the simple version of code examples (BFV, v1.2.4):
Working Case: Using EvalMultNoRelin + Relinearize
Just ending up with relinearization after EvalMultNoRelin does not produce incorrect result. (Same result with when EvalMult is used instead.)
int main(void) {
CCParams<CryptoContextBFVRNS> parameters;
parameters.SetRingDim(32768);
size_t plaintext_modulus = 65537;
parameters.SetPlaintextModulus(plaintext_modulus);
parameters.SetMultiplicativeDepth(5);
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(cc->GetRingDimension());
vector<int64_t> tmp_vec_(slots);
Plaintext tmp;
int rots_num = 20;
vector<int> rots(rots_num + 1);
for (int tmp_i = 2; tmp_i< rots_num+2; tmp_i += 2) {
rots[tmp_i - 2] = tmp_i / 2;
rots[tmp_i - 1] = -(tmp_i / 2);
}
rots[rots_num] = 0;
cc->EvalRotateKeyGen(keyPair.secretKey, rots);
Ciphertext<DCRTPoly> tmp_;
Ciphertext<DCRTPoly> x, y;
int yP;
int yC;
int c;
vector<int64_t> tmp_vec_1 = { 1, 2, 3 };
tmp = cc->MakePackedPlaintext(tmp_vec_1);
x = cc->Encrypt(keyPair.publicKey, tmp);
vector<int64_t> tmp_vec_2 = { 10, 10, 10 };
tmp = cc->MakePackedPlaintext(tmp_vec_2);
y = cc->Encrypt(keyPair.publicKey, tmp);
x = cc->EvalMultNoRelin(x, y);
cc->Relinearize(x);
cc->ModReduceInPlace(x);
// *** Place where additional operations comes in following cases *** //
cc->Decrypt(keyPair.secretKey, x, &tmp);
tmp->SetLength(5);
tmp_vec_ = tmp->GetPackedValue();
for (auto v : tmp_vec_) {
cout << v << " ";
}
cout << endl;
return 0;
}
- Output (correct):
10 20 30 0 0
Failing Case: Subsequent operations like addition or rotation after EvalMultNoRelin + Relinearize
It seems that when additional operations which come after EvalMultNoRelin followed by Relinearize produce incorrect results. Here are example operations to be added at the above code before decryption.
-
Case 1:
EvalAddyP = 1; fill(tmp_vec_.begin(), tmp_vec_.end(), yP); tmp = cc->MakePackedPlaintext(tmp_vec_); x = cc->EvalAdd(x, tmp);- Output (incorrect):
9 23310 12377 29529 -11219 - Expected:
11 21 31 1 1
- Output (incorrect):
-
Case 2:
EvalRotatec = 1; x = cc->EvalRotate(x, c);- Outputs (incorrect): (Each execution yields a different value.)
23585 -1249 23093 -12131 926824908 3152 9860 -1723 -78099060 7869 -25765 -15593 -30658- … and more
- Expected:
20 30 0 0 0
- Outputs (incorrect): (Each execution yields a different value.)
Questions
Q1. Even after applying Relinearize and ModReduce, the result of subsequent operations like EvalAdd is incorrect. Since the ciphertext has already been relinearized and modulus-reduced, I would expect its size and scale to be normalized. Are there any known issues or subtle requirements regarding the state of a ciphertext after delayed relinearization?
Q2. EvalRotate produces different results when applied to the same ciphertext, suggesting non-determinism or possible corruption. Given that this occurs after relinearization, shouldn’t the ciphertext be stable for rotation? What could be the cause of this instability?
Q3. When replacing EvalMultNoRelin + Relinearize with a direct EvalMult, everything works fine. What is the internal difference that could cause such divergence in behavior between these two approaches?
__
Thank you for your attention!