I’m using the CCKS algorithm. After each Evalmult, I have to rescale so that EvalAdd will work. But the plaintext I multiply may be a unit vector(only one element is 1). If I rescale every time after a plaintext-ciphertext multiplication, it seems to waste the multiplication depth.
In my opinion, homomorphic addition should work after homomorphic multiplication, especially for plaintext-ciphertext multiplication.
I want to know why EvalAdd doesn’t work after Evalmult. Thanks in advance!
Could you post the full example, including the parameters you are using? It is hard to understand what specifically you are asking about.
I apologize for my expression. This is an example.
uint32_t multDepth = 2;
uint32_t scaleModSize = 40;
uint32_t batchSize = 128;
ScalingTechnique rescaleTech = FIXEDMANUAL;
//
CCParams<CryptoContextCKKSRNS> parameters;
parameters.SetMultiplicativeDepth(multDepth);
parameters.SetScalingModSize(scaleModSize);
parameters.SetBatchSize(batchSize);
parameters.SetScalingTechnique(rescaleTech);
CryptoContext<DCRTPoly> cc = GenCryptoContext(parameters);
cc->Enable(PKE);
cc->Enable(KEYSWITCH);
cc->Enable(LEVELEDSHE);
auto keys = cc->KeyGen();
std::vector<double> x1 = {0.25, 0.5, 1, 2, 4};
std::vector<double> x2 = {4, 2.0, 1.0, 0.5, 0.25};
Plaintext ptxt1 = cc->MakeCKKSPackedPlaintext(x1);
Plaintext ptxt2 = cc->MakeCKKSPackedPlaintext(x2);
auto c1 = cc->Encrypt(keys.publicKey, ptxt1);
Ctx c3;
c3 = cc->EvalMult(c1, ptxt2);
Ptx ptxt3;
cc->Decrypt(keys.secretKey, c3, &ptxt3);
ptxt3->SetLength(8);
std::cout << ptxt3 <<std::endl;
c3 = cc->EvalAdd(c3, ptxt1);
cc->Decrypt(keys.secretKey, c3, &ptxt3);
ptxt3->SetLength(8);
std::cout << ptxt3 <<std::endl;
c3 = cc->EvalMult(c1, ptxt2);
cc->RescaleInPlace(c3);
cc->Decrypt(keys.secretKey, c3, &ptxt3);
ptxt3->SetLength(8);
std::cout << ptxt3 <<std::endl;
c3 = cc->EvalAdd(c3 ,ptxt1);
cc->Decrypt(keys.secretKey, c3, &ptxt3);
ptxt3->SetLength(8);
std::cout << ptxt3 <<std::endl;
I think the fourth and second outputs should be the same. But the output is
(1, 1, 1, 1, 1, -2.09141e-10, 4.27276e-10, -9.01037e-10, ... ); Estimated precision: 30 bits
(1, 1, 1, 1, 1, -5.85196e-10, -9.59139e-10, -3.23153e-10, ... ); Estimated precision: 30 bits
(1, 1, 1, 1, 1, 2.80713e-10, 3.63784e-10, -1.00066e-09, ... ); Estimated precision: 30 bits
(1.25, 1.5, 2, 3, 5, -8.32482e-10, 2.66915e-10, 8.11207e-10, ... ); Estimated precision: 30 bits
EvalAdd without rescaling did not work after EvalMult.
The Ctx is the Ciphertext and the Ptx is the Plaintext.
using Ctx = lbcrypto::Ciphertext<lbcrypto::DCRTPoly>;
using Ptx = lbcrypto::Plaintext;
The FIXEDMANUAL
mode does not do automated rescaling/modulus switching (see Page 6 of https://eprint.iacr.org/2022/915.pdf). This is why the second output is incorrect
(behavior by design). All other modes automatically call rescaling. If you want to do it without rescaling (in this example), MakeCKKSPackedPlaintext
should be called the noise scale degree of 2 (second parameter = 2 should be supplied).
Yes, the rescaling is not required after a plaintext-ciphertext multiplication (in contrast to ciphertext-ciphertext multiplication, where the approximation error grows very fast if no rescaling is applied). However, there is still an increase in the noise growth even for a ciphertext-plaintext multiplication. See the intro of Approximate Homomorphic Encryption with Reduced Approximation Error for more information.
Thanks for your explanation!
In simple terms, the plaintext that is evaluated with fresh ciphertext is \Delta\cdot m. And the plaintext that is evaluated with ciphertext after k times EvalMult is \Delta^{k+1} \cdot m.
Is there a problem with my understanding?
Yes, this is correct.