EvalRot behaviour

Hello,

I have noticed strange behavior in EvalRot.

So I have a plaintext with the following elements (6 14 24 36 ...). If I encrypt, rotate by two, and then decrypt, I get the following plaintext (24 36 ...) but I need to have (24 36 6 14...).

The rotation is in conjunction with addiction as follows:

    // Homomorphic Operations 
    auto ciphertextAdd = cryptoContext->EvalAddMany(ciphertexts);

    auto ciphertextRot = ciphertextAdd;

    int number_rotation = (int)log2(size_vectors);
    for(int i = 0; i < number_rotation; i++){
        ciphertextRot = cryptoContext->EvalRotate(ciphertextAdd, pow(2, i));

        ciphertextAdd = cryptoContext->EvalAdd(ciphertextAdd, ciphertextRot);
    }

I omitted the code where I decrypt and print the value so it won’t be too much. What can be the cause of this?

Hi, Rotation is the full ciphertext which is ringsize/2 for CKKS and ringsize for BGV\BFV. the values that are not explicitly added when encrypting are zero.

Are you trying to do a summation across the vector components? there is an EvalSum() for that I believe
that uses binary tree addition over a power of two batch size (the non zero elements in the ciphertext).

2 Likes

Please share your code in a minimal reproducible example

Rotation

So I have a plaintext with the following elements (6 14 24 36 ...). If I encrypt, rotate by two, and then decrypt, I get the following plaintext (24 36 ...) but I need to have (24 36 6 14...).

This is expected behavior (not sure if that’s what you’re asking). Here’s how I’d accomplish what you’re asking:

initial_values = [6, 14, 24, 36, ...]
mask = [1, 1, 0, 0]
masked_values = mask * initial_values  # [6, 14, 0, 0, ....]
rotated_initials = [24, 36, ....]
rotated_masked = [0, 0, 6, 14, ...]
finally_rotated = rotated_initials + rotated_masked

There might be a better way to do this, but I’m not sure.

std::out_of_range

Also, if I have a ciphertext where the plaintext is (80 124 110 86 50 ... ) with size 8 and values [ 80 124 110 86 50 0 0 0 ] (this is the output of one of my prints) and I try to rotate by 4 (i = 2), I get the following error:

I can’t say for sure without seeing your code, but see the following code where we try to cc->EvalRotate by an index that we did not generate a rotation key for (cc->EvalRotateKeyGen).

See the below:

#include <fstream>
#include <iostream>
#include <iterator>
#include <random>

#include "openfhe.h"
#include "math/hal.h"

using namespace lbcrypto;
bool shouldTrack;
int main() {
    CCParams<CryptoContextBFVRNS> parameters;

    parameters.SetPlaintextModulus(65537);
    parameters.SetMaxRelinSkDeg(3);

    CryptoContext<DCRTPoly> cc = GenCryptoContext(parameters);
    // enable features that you wish to use
    cc->Enable(PKE);
    cc->Enable(KEYSWITCH);
    cc->Enable(LEVELEDSHE);

    int32_t n = cc->GetCryptoParameters()->GetElementParams()->GetCyclotomicOrder() / 2;

    // Initialize the public key containers.
    KeyPair<DCRTPoly> kp = cc->KeyGen();

    std::vector<int32_t> indexList = {1, 1000};

    cc->EvalRotateKeyGen(kp.secretKey, indexList);

    std::vector<int64_t> vectorOfInts = {80, 124, 110, 86, 50};

    Plaintext intArray = cc->MakePackedPlaintext(vectorOfInts);

    auto ciphertext = cc->Encrypt(kp.publicKey, intArray);

    // Adding an index that we did not generate keys for
    indexList.emplace_back(-1);

    // Lets see what happens now
    for (auto &rotIdx: indexList){
        std::cout << "Starting rotation by:" << rotIdx<< std::endl;
        auto permutedCiphertext = cc->EvalRotate(ciphertext, rotIdx);
        std::cout << "Successfully rotated by:" << rotIdx<< std::endl;
    }
}

I’ll have to look for the source code, but hopefully there’s a way to generate more informative error messages

1 Like

Yes, I am. I didn’t know that existed. Thanks a lot. I’m going to try it.

With my experiments, I understood that I didn’t need what I thought I needed. But I still don’t understand why rotation does that. Why does it put zeros at the end instead of the rotated elements?

Yeah, I also realized yesterday, after searching on the available examples, that I did not change the indexList (I did not even know it was a thing).

Thanks a lot, everybody, and sorry for these simple questions :sweat_smile:

“Why does it put zeros at the end instead of the rotated elements?”

When you encrypt a vector into a ciphertext, it actually stores it in the first available ‘slots’. There are ringsize/2 CKKS slots and ringsize BGV/BFV slots (there is a method that lets you get this value.
ALL other locations are zeroed out.

when you rotate a ciphertext you rotate the entire number of slots left and right. so the zeros come in from the other end.

take this example with a ringsize of 8 (that is artificial, they are never smaller than at least 2^10 slots for realistic security).
ct =[a b c 0 0 0 0] Rotated left one → [b c 0 0 0 0 a] rotated right one → [ 0 a b c 0 0 0]

rotating by negative numbers is left rotate, positive is right rotate. (forgive me if I get that backwards).
As Ian says, you have to generate the rotation keys for all indices you use (each key is large and takes storage, so we let you specify exactly which ones you want to generated).

2 Likes

That was what I would expect. But if I rotate and then print the plaintext, I would get ct = [a b c 0 0 0 0] Rotate left one [b c 0 0 0 0 0]. I’m using BFV scheme with the predefined value of ring size.

That’s because these plaintexts are often gigantic (1 << 17 elements long). So, if you print your values and limit it to the first N elements (size determined by your initial vector), you won’t see it.

1 Like

Ah okay makes a lot of sense. Is there any way to change the Cyclotomic Order in the CCParams? I can only find in the documentation a function to display but not a “SetCyclotomicOrder”.

ringsize = cyclotomic order / 2 is a result of the security settings.

1 Like

Hmm okay okay. Which security setting defines that?

Hi, it seems that in BFV it’s a bit different. It’s actually doing rotations in half of the ring size and in 2 batches. As I also mentioned here: Rotation in BFV

Suppose we have a ciphertext with 8192 slots encrypted using BFV, e.g., C = Enc([1, 2, 3, 4, …, 8192]).

Then we rotate this ciphertext for 1 step and get C’ = EvalRotate(C, 1).

I wanted to get C’ = Enc([8192, 1, 2, 3, 4, …, 8191]) but I noticed that the result is rotated in “2 batches”:
C’ = Enc([4096, 1, 2, 3, 4, …, 4095, 8192, 4097, 4098, 4099, …, 8191]).
That is, both the first and the second half of C are rotated by one step.

Is it possible to disable this feature and just rotate the whole ciphertext by one step? Or can I change the “batch number” and get C’ = Enc([128, 1, 2, 3, …, 127, 256, 129, 130, …, 255, …])?

The latter question has already been answered in Rotation in BFV