Performing gate level operations after scheme switching

Hello,

I am currently discovering scheme switching, and I am getting some wrong results after performing AND in fhew…

I added just extra vector, x3 and encoded and encrypted – ptxt3 and c3 – to the example scheme-switching code.

// Inputs
    std::vector<double> x1  = {0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0};
    std::vector<double> x3 =  {1.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 2.0, 3.0, 4.0, 5.0, 1.0};
    std::vector<double> x2  = {0.0, 271.0, 30000.0, static_cast<double>(pLWE2) - 2};
    uint32_t encodedLength1 = x1.size();
    uint32_t encodedLength2 = x2.size();

    // Encoding as plaintexts
    Plaintext ptxt1 = cc->MakeCKKSPackedPlaintext(x1, 1, 0, nullptr);
    Plaintext ptxt3 = cc->MakeCKKSPackedPlaintext(x3, 1, 0, nullptr);
    Plaintext ptxt2 = cc->MakeCKKSPackedPlaintext(x2, 1, 0, nullptr);

    // Encrypt the encoded vectors
    auto c1 = cc->Encrypt(keys.publicKey, ptxt1);
    auto c3 = cc->Encrypt(keys.publicKey, ptxt3);
    auto c2 = cc->Encrypt(keys.publicKey, ptxt2);

    // Step 4: Scheme switching from CKKS to FHEW

    // A: First scheme switching case

    // Transform the ciphertext from CKKS to FHEW
    auto cTemp = cc->EvalCKKStoFHEW(c1, encodedLength1);
    auto cTemp3 = cc->EvalCKKStoFHEW(c3, encodedLength1);

    std::cout << "\n---Decrypting switched ciphertext with small precision (plaintext modulus " << NativeInteger(pLWE1)
              << ")---\n"
              << std::endl;

    std::vector<int32_t> x1Int(encodedLength1);
    std::transform(x1.begin(), x1.end(), x1Int.begin(), [&](const double& elem) {
        return static_cast<int32_t>(static_cast<int32_t>(std::round(elem)) % pLWE1);
    });
    std::vector<int32_t> x3Int(encodedLength1);
    std::transform(x3.begin(), x3.end(), x3Int.begin(), [&](const double& elem) {
        return static_cast<int32_t>(static_cast<int32_t>(std::round(elem)) % pLWE1);
    });
    ptxt1->SetLength(encodedLength1);
    ptxt3->SetLength(encodedLength1);
    std::cout << "Input x1: " << ptxt1->GetRealPackedValue() << "; which rounds to: " << x1Int << std::endl;
    std::cout << "FHEW decryption: ";
    LWEPlaintext result;
    for (uint32_t i = 0; i < cTemp.size(); ++i) {
        ccLWE.Decrypt(privateKeyFHEW, cTemp[i], &result, pLWE1);
        std::cout << result << " ";
    }
    std::cout << std::endl;
    std::cout << "Input x3: " << ptxt3->GetRealPackedValue() << "; which rounds to: " << x3Int << std::endl;
    std::cout << "FHEW decryption: ";
    LWEPlaintext result3;
    for (uint32_t i = 0; i < cTemp.size(); ++i) {
        ccLWE.Decrypt(privateKeyFHEW, cTemp3[i], &result3, pLWE1);
        std::cout << result3 << " ";
    }
    std::cout << "\n" << std::endl;
    std::cout << "AND OP RESULT" << std::endl;
    for (uint32_t i = 0; i < cTemp.size(); ++i) {
        cTemp[i] = ccLWE.EvalBinGate(AND, cTemp[i],cTemp3[i]);
        ccLWE.Decrypt(privateKeyFHEW, cTemp[i], &result, 2);
        std::cout << result << " ";
    }
    std::cout << "\n" << std::endl;

And I got this following result

---Decrypting switched ciphertext with small precision (plaintext modulus 16)---

Input x1: [ 0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1 2 3 4 5 6 ]; which rounds to: [ 0 0 0 0 0 1 1 1 1 1 1 2 3 4 5 6 ]
FHEW decryption: 0 0 0 0 0 1 1 1 1 1 1 2 3 4 5 6 
Input x3: [ 1 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1 2 3 4 5 1 ]; which rounds to: [ 1 0 0 0 0 1 1 1 1 1 1 2 3 4 5 1 ]
FHEW decryption: 1 0 0 0 0 1 1 1 1 1 1 2 3 4 5 1 

AND OP RESULT
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 

And when I change ccLWE.Decrypt(privateKeyFHEW, cTemp[i], &result, pLWE1); from ccLWE.Decrypt(privateKeyFHEW, cTemp[i], &result, 2);

---Decrypting switched ciphertext with small precision (plaintext modulus 16)---

Input x1: [ 0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1 2 3 4 5 6 ]; which rounds to: [ 0 0 0 0 0 1 1 1 1 1 1 2 3 4 5 6 ]
FHEW decryption: 0 0 0 0 0 1 1 1 1 1 1 2 3 4 5 6 
Input x3: [ 1 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1 2 3 4 5 1 ]; which rounds to: [ 1 0 0 0 0 1 1 1 1 1 1 2 3 4 5 1 ]
FHEW decryption: 1 0 0 0 0 0 1 1 1 1 1 2 3 4 5 1 

AND OP RESULT
4 4 4 4 4 0 4 0 4 0 0 4 4 0 4 4 

Also wrong output…

I am unsure how I should decrypt to get the correct value after performing gate level operations in fhew.

Could you please guide me?

There are several points to keep in mind:

  • The scheme switching capability implemented in OpenFHE was designed to support large precision, which requires large FHEW plaintext modulus and ciphertext modulus. These moduli are different than the ones supported for the FHEW evaluation of binary gates, but are compatible with FHEW EvalDecomp, EvalSign, EvalFunc.
  • For additional context for the disparity of parameters, you can check out this thread.
  • EvalBinGate works with Boolean inputs, so for other inputs, the results will be incorrect.
  • The plaintext modulus for the Boolean case is actually p = 4, see the original paper.

This being said, if you really need to switch Boolean inputs from CKKS, specify logQ_ccLWE = 12 and pLWE1 = 4, which match the default FHEW parameters for EvalBinGate (with the caveat that the approximation of values far from 0 or 1 in CKKS to FHEW, can lead to errors in the output of EvalBinGate. The reason is that the expected sum of the Boolean inputs should be 0, 1 or 2; sum values around 0.5 or 1.5 can be pushed by noise in either direction). Otherwise, you can define your own functionality using EvalFunc for p\le 8, trying to mimic the logic of EvalBinGate, where the inputs are first summed, and then there is some lookup table that is applied on the sum.

1 Like

Thank you for your detailed explanation!

I have a follow-up question regarding this point:

“The reason is that the expected sum of the Boolean inputs should be 0, 1, or 2; sum values around 0.5 or 1.5 can be pushed by noise in either direction.”

My understanding is that during scheme switching from CKKS to FHEW, CKKS inputs in the [0,1] interval are rounded, effectively making them Boolean (0 or 1) in the FHEW representation. Assuming this rounding is accurate, wouldn’t performing logical operations (AND/OR) in FHEW yield correct results consistently?

However, my experimental results show unexpected behavior:

-----SwitchCKKSToFHEW-----

CKKS scheme is using ring dimension 8192, number of slots 16, and supports a multiplicative depth of 17

FHEW scheme is using lattice parameter 32, logQ 12, and modulus q 4096

---Decrypting switched ciphertext with small precision (plaintext modulus 4)---

Input x1: [ 1 1 0.2 0.3 0.8 0.8 0.6 0.7 0.8 0.9 1 1 1 1 0 0 ]
Rounded to: [ 1 1 0 0 1 1 1 1 1 1 1 1 1 1 0 0 ]
FHEW decryption: 1 1 0 0 1 1 1 1 1 1 1 1 1 1 0 0

Input x3: [ 1 0.1 0.2 0.3 0.3 0.8 0.6 0.7 0.8 0.9 1 1 1 1 0 1 ]
Rounded to: [ 1 0 0 0 0 1 1 1 1 1 1 1 1 1 0 1 ]
FHEW decryption: 1 0 0 0 0 1 1 1 1 1 1 1 1 1 0 1

AND Operation Result:
FHEW decryption: 1 0 0 0 0 1 0 0 1 1 1 1 1 1 0 0

Specifically, the unexpected zeros in the middle positions (e.g., positions corresponding to inputs originally rounded from 0.6 and 0.7) seem incorrect—I anticipated these positions would decrypt to 1. Could you please clarify whether:

  • Noise may still cause incorrect decryptions even after rounding during CKKS-to-FHEW switching, or
  • This behavior arises from how values around the midpoint (I noticed the input values for the wrong results were 0.6 and 0.7) in CKKS are mapped into FHEW, causing boundary-related rounding errors?

I feel like the AND operation does nothing different if we were to perform AND operation by multiplying the two ciphertexts in CKKS because 0.7 * 0.7 = 0.49 which would round to 0 and 0.6 *0.6 also gives 0.36 which rounds to 0.

The switch between CKKS and FHEW does not perform a homomorphic rounding, but rather the ciphertext (in coefficient encoding) is mod-switched and rounded. You can look at the implementation of EvalCKKStoFHEW to see the steps. The further the original message is from an integer mod p, the larger you can interpret the error of the switched ciphertext with respect to that integer value to be. In other words, if the switched value is not an integer, the FHEW ciphertext will not be truly fresh. When the switched ciphertext is immediately decrypted in FHEW, a rounding takes place over the plaintext during decryption, so you see e.g., 0.7 decrypting to 1. However, when you apply a gate over the switched ciphertexts, the approximation error can show up.