No Exception thrown on multiplication with SetMultiplicativeDepth(0) context (BFV/BGV)

Hi,

I encountered an issue when operating within an invalid encryption context.

When using encryption parameters with multiplicative depth set to 0, attempting a multiplication (e.g., EvalMult) does not throw any exceptions in BFV/BGV. Instead, it produces meaningless values.

While I understand that performing a multiplication in this context is not allowed, I believe the library should fail explicitly — for example, by throwing a "Not enough towers" or similar descriptive exception — rather than silently producing invalid results.

Code Examples

Here’s the simple version of code examples (v1.2.4):

  • In both cases (BFV and BGV), the output varies across executions, yielding inconsistent values.
  • In the BFV example, I intentionally used EvalMult with two ciphertexts, while in the BGV example, I used it with a ciphertext and a plaintext. However, the same issue occurs in the reverse scenario as well — that is, when multiplying a ciphertext with a plaintext in BFV, or two ciphertexts in BGV.

BFV

int main(void)
{
  CCParams<CryptoContextBFVRNS> parameters;
  parameters.SetRingDim(32768);
  parameters.SetMultiplicativeDepth(0);
  parameters.SetPlaintextModulus(65537);
  parameters.SetScalingModSize(54);
  parameters.SetSecurityLevel(HEStd_192_classic);

  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(4795);
  vector<int64_t> tmp_vec_(4795);
  Plaintext tmp;
  int rots_num = 20;
  vector<int> rots(rots_num + 1);
  for (int i = 0; i < rots_num + 1; ++i)
  {
    rots[i] = i;
  }
  cc->EvalRotateKeyGen(keyPair.secretKey, rots);
  Ciphertext<DCRTPoly> tmp_;

  Ciphertext<DCRTPoly> x, y;
  int yP;
  int yC;
  int c;
  vector<int64_t> tmp_vec_1 = {320, 539, 94, 741, 167};
  tmp = cc->MakePackedPlaintext(tmp_vec_1);
  x = cc->Encrypt(keyPair.publicKey, tmp);
  vector<int64_t> tmp_vec_2 = {1, 1, 1, 1, 1};
  tmp = cc->MakePackedPlaintext(tmp_vec_2);
  y = cc->Encrypt(keyPair.publicKey, tmp);
  x = cc->EvalMult(x, y);
  cc->Decrypt(keyPair.secretKey, x, &tmp);
  tmp->SetLength(5);
  tmp_vec_ = tmp->GetPackedValue();
  for (auto v : tmp_vec_)
  {
    cout << v << " ";
  }
  cout << endl;
  return 0;
}

BGV

int main(void) {
  CCParams<CryptoContextBGVRNS> parameters;
  parameters.SetRingDim(8192);
  parameters.SetMultiplicativeDepth(0);
  parameters.SetPlaintextModulus(65537);
  parameters.SetSecurityLevel(HEStd_192_classic);
  parameters.SetScalingTechnique(FIXEDMANUAL);
  
  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(2352);
  vector<int64_t> tmp_vec_(2352);
  Plaintext tmp;
  int rots_num = 20;
  vector<int> rots(rots_num + 1);
  for(int i = 0; i < rots_num + 1; ++i) {
    rots[i] = i;
  }
  cc->EvalRotateKeyGen(keyPair.secretKey, rots);
  Ciphertext<DCRTPoly> tmp_;

  Ciphertext<DCRTPoly> x, y;
  int yP;
  int yC;
  int c;
  vector<int64_t> tmp_vec_1 = { 156, 480, 303, 314, 241 };
  tmp = cc->MakePackedPlaintext(tmp_vec_1);
  x = cc->Encrypt(keyPair.publicKey, tmp);
  yP = 1;
  fill(tmp_vec_.begin(), tmp_vec_.end(), yP);
  tmp = cc->MakePackedPlaintext(tmp_vec_);
  x = cc->EvalMult(x, tmp);
  cc->Decrypt(keyPair.secretKey, x, &tmp);
  tmp->SetLength(5);
  tmp_vec_  = tmp->GetPackedValue();
  for (auto v : tmp_vec_) {
    cout << v << " ";
  }
  cout << endl;
  return 0;
}

Would love to hear your thoughts. Thanks in advance!

Hi @hyerinpark,

Thank you for your question. At a high level, this question is related to the notion of application-aware homomorphic encryption introduced in Application-Aware Approximate Homomorphic Encryption: Configuring FHE for Practical Use Here, you are trying to evaluate a circuit that is not compliant with the circuit (with 0-depth) used when instantiating the cryptocontenxt. Currently, OpenFHE does not throw an exception in this case. There are two fundamental solutions to this problem: 1) ask for a circuit specification during crypto context generation and check the evaluated circuit against this circuit specification (for this, we introduce the concept of application specification language in the paper), 2) use dynamic noise tracking (similar to how it is done for BGV in Helib). We describe both methods in our paper. We are planning to add such a capability to OpenFHE in later versions (note both of these are quite involved for implementation and assume a more powerful adversary than the classical honest-but-curios model used for instantiating FHE).

Thank you for the detailed and insightful response — I really appreciate it.

I also had a chance to read through the paper you mentioned, and I found it very interesting. The approach of incorporating an application specification to guide parameter selection and circuit validation feels like a very practical and much-needed direction, especially from the perspective of users trying to apply FHE in real-world settings.

I’m glad to hear that OpenFHE is planning to support this kind of validation and dynamic tracking in future versions — it sounds like a solid step toward making FHE more robust and user-friendly.

That said, while I understand this behavior will likely be addressed more thoroughly in the future, I wonder if a minimal runtime warning or exception (e.g., when attempting a multiplication with zero-depth parameters) could still be helpful in the meantime. The same idea applies to another report I submitted, where a single EvalRotate silently produced incorrect results due to insufficient parameters. I fully understand this may not be the current design focus, but from a user experience standpoint, such lightweight checks could help catch configuration issues early and reduce confusion during experimentation.

Thanks again — I’m looking forward to future updates!

Thank you for your comments. We would prefer not to implement half-measures as this might confuse the OpenFHE user (since we cannot capture all scenarios until the application-aware model or at least dynamic noise tracking is implemented). Some exceptions already get thrown a the DCRTPoly layer, for example, if the multiplicative depth is exceeded by more than 1.