CKKS Plaintext rotation using AutomorphismTransform (for BSGS matrix multiplication)

Hello,

I am trying to implement matrix multiplication using the Baby-Step Giant-Step (BSGS) method in CKKS.

To reduce the number of encryptions, I would like to reuse rotated Plaintexts (not Ciphertexts).
So I attempted to implement a Plaintext version of EvalRotate using AutomorphismTransform.

My implementation is based on the same logic used in EvalRotate for Ciphertext.

cryptocontext.cpp

template<>
Plaintext CryptoContextImpl<DCRTPoly>::EvalRotate(
    const Plaintext &pt, int32_t index) const
{
    auto derived = std::dynamic_pointer_cast<CKKSPackedEncoding>(pt);
    if (!derived) {
        OPENFHE_THROW("Plaintext is not CKKSPackedEncoding");
    }

    Plaintext result =
        std::make_shared<CKKSPackedEncoding>(*derived);

    uint32_t N = pt->GetElementRingDimension();
    usint M = N * 2;

    uint32_t autoIndex =
        FindAutomorphismIndex2nComplex(index, M);

    std::vector<usint> vec(N);
    PrecomputeAutoMap(N, autoIndex, &vec);

    auto& res = result->GetElement<DCRTPoly>();
    res = res.AutomorphismTransform(autoIndex, vec);

    return result;
}

main.cpp

#include "openfhe.h"
#include <vector>
using namespace lbcrypto;

CryptoContext<DCRTPoly> generate_params(){
    CCParams<CryptoContextCKKSRNS> parameters;
    SecretKeyDist secretKeyDist = UNIFORM_TERNARY;
    usint firstMod = 60;

    parameters.SetSecretKeyDist(secretKeyDist);
    parameters.SetSecurityLevel(HEStd_NotSet);
    parameters.SetMultiplicativeDepth(0);
    parameters.SetScalingModSize(40);
    parameters.SetFirstModSize(firstMod);
    parameters.SetRingDim(8);
    parameters.SetKeySwitchTechnique(HYBRID); 

    CryptoContext<DCRTPoly> cc = GenCryptoContext(parameters);

    return cc;
}


int main() {
    
    CryptoContext<DCRTPoly> cc = generate_params();
    cc->Enable(PKE);
    cc->Enable(KEYSWITCH);
    cc->Enable(LEVELEDSHE);


    std::vector<double> input({0.1, 0.2, 0.3, 0.4});

    size_t encodedLength = input.size();

    Plaintext plaintext = cc->MakeCKKSPackedPlaintext(input);

    Plaintext plaintextDec = cc->EvalRotate(plaintext, -1);

    plaintextDec->SetLength(encodedLength);
    std::cout << std::setprecision(15) << std::endl;
    std::cout << plaintextDec << std::endl;
    return 0;
}

result

before automorphism transform
0: EVAL: [1013651595554295762 690123443558830424 130323650780292307 1048276539879503589 513039758539482255 355282787011445632 842997710840536249 18133468774611369] modulus: 1152921504606846577
1: EVAL: [350064 365575 273736 59331 511933 378289 456374 226463] modulus: 524353
start automorphism transform
after automorphism transform
0: EVAL: [1048276539879503589 130323650780292307 1013651595554295762 690123443558830424 842997710840536249 18133468774611369 355282787011445632 513039758539482255] modulus: 1152921504606846577
1: EVAL: [59331 273736 350064 365575 456374 226463 378289 511933] modulus: 524353

(0.1, 0.2, 0.3, 0.4, ... ); Estimated precision: 40 bits

When I print the DCRTPoly (EVALUATION representation) before and after AutomorphismTransform,
I clearly see that the NTT values are permuted.

However, when printing the resulting Plaintext (CKKSPackedEncoding),
the slot values appear unchanged.

Is applying AutomorphismTransform directly on the underlying DCRTPoly
sufficient to achieve CKKS slot rotation for Plaintext?

Thank you.

Applying AutomorphismTransform to the underlying DCRTPoly is correct and does rotate the CKKS slots in the polynomial. The issue is that CKKSPackedEncoding keeps a separate cached slot vector (value), and printing / GetCKKSPackedValue() use that cache, not a fresh decode from the polynomial. You only changed the polynomial, so the cached vector stayed (0.1, 0.2, 0.3, 0.4).

So basically, you need to operate on this plaintext (under encryption) so that the vector value gets updated upon calling decrypt, then decode.

Try adding the following to your example, and will notice the plaintext does include what you are expecting:

    auto ct     = cc->Encrypt(keys.publicKey, plaintextDec);
    Plaintext result;
    cc->Decrypt(keys.secretKey, ct, &result);
    result->SetLength(encodedLength);
    std::cout << std::setprecision(15) << "After encrypt then decrypt: " << std::endl;
    std::cout << *result << std::endl;
1 Like