2021SC@SDUSC
catalogue
1. Enable TIC-TOC timing measurement
2. Define the functions and methods to be used
3. "EXACTRESCALE" and "APPROXRESCALE"
6. Manual readjustment demonstration
introduce
In this blog, we mainly introduce the general steps and some codes of homomorphic encryption and decryption of CKKS using the main API s of PALISADE. If you want to see the complete code, operation process and results, please check my next blog for specific operations.
General steps
1. Enable TIC-TOC timing measurement
Call PROFILE for timing measurement
#define PROFILE #include "palisade.h" using namespace lbcrypto;
2. Define the functions and methods to be used
void AutomaticRescaleDemo(RescalingTechnique rsTech); void ManualRescaleDemo(RescalingTechnique rsTech); void HybridKeySwitchingDemo1(); void HybridKeySwitchingDemo2(); void FastRotationsDemo1(); void FastRotationsDemo2();
3. "EXACTRESCALE" and "APPROXRESCALE"
Our CKKS implementation includes two variants called "EXACTRESCALE" and "APPROXRESCALE". Starting with version 1.10, we have added the APPROXAUTO variant, which works the same way as approxescale, but it can also be scaled automatically.
Before we start, we need to introduce scale operation, which is the core of CKKS. When we multiply the ciphertext c1 and c2 of two encrypted numbers m1*D and m2*D respectively, our result looks like m1*m2*D^2. Because the scale factor of this number is D^2, we say the depth of the result is 2. Obviously, a ciphertext with a depth of 2 cannot be added to a ciphertext with a depth of 1 because their scaling factors are different. Rescale the ciphertext with a depth of 2 and make it a depth of 1 by an operation that looks like dividing by D=2^p.
For efficiency reasons, our CKKS implementation works in RNS space, which means that we avoid dealing with large numbers and only deal with local integers. A complex problem is that we can only scale by dividing some prime numbers instead of D=2^p.
There are two ways to solve this problem. The first is to choose a prime number as close to 2^p as possible and assume that the scale factor remains unchanged. This inevitably leads to some approximation errors, which is why we call it the approxescale variant. The second way to deal with this problem is to track how the scale factor changes and try to adjust it. This is the EXACTRESCALE variant of CKKS. The cost of doing this is that the EXACTRESCALE calculation is usually slightly slower (according to our experience, the speed will be reduced by 5-35% according to the complexity of the calculation), because the value needs to be adjusted.
We designed EXACTRESCALE, so it hides all the nuances of tracking ciphertext depth and has to call the zoom operation. Therefore, EXACTRESCALE is more suitable for users who do not want to understand the details of underlying encryption and mathematics, or want to quickly build a prototype. In contrast, approxescale is better suited for expert optimized production applications.
The first two parts of this presentation introduce these two variants by using EXACTRESCALE and APPROXRESCALE to achieve the same calculation, that is, the function f(x) = x^18 + x^9 + 1.
AutomaticRescaleDemo(EXACTRESCALE); AutomaticRescaleDemo(APPROXAUTO); ManualRescaleDemo(APPROXRESCALE);
4.HYBRID switching technology
Our CKKS implementation supports three different key exchange algorithms, namely BV, GHS and HYBRID. Bv corresponds to a technology also known as digital decomposition (including RNS and linearization window based technology). GHS corresponds to ciphertext module doubling, and hyperid combines the characteristics of BV and GHS. For more details on different key switch technologies, please refer to the keyswitch bvgen, keyswitch ghsgen and keyyswitch HYBRID documents in scheme/ckks/ckks.h.
In most cases, HYBRID will be the most suitable and effective key switching technology, which is why we use the third and fourth parts of this demonstration for HYBRID key switching.
HybridKeySwitchingDemo1(); HybridKeySwitchingDemo2();
The last part of this presentation shows our implementation of an optimization technique called promotion. The idea is simple - when we want to perform multiple different rotations on the same ciphertext, we can calculate part of the rotation algorithm at one time and reuse it many times.
FastRotationsDemo1(); FastRotationsDemo2();
5. Zoom operation
EXACTRESCALE is a variant of CKKS. It has two main features: 1 - it automatically performs scaling before each multiplication. This is done to make it easier for users to write FHE calculations without worrying about the depth or scaling of the ciphertext. 2 - it tracks the exact scale factor of all ciphertexts. This means that the calculations in EXACTRESCALE will be more accurate than those in approxscale. Remember that this difference becomes apparent only when dealing with large multiplication depth calculations; This is because the larger multiplication depth means that we need to find more prime numbers close to D=2^p, which becomes more and more difficult as the multiplication depth increases.
if (rsTech == EXACTRESCALE) { std::cout << "\n\n\n ===== ExactRescaleDemo ============= " << std::endl; } else { std::cout << "\n\n\n ===== ApproxAutoDemo ============= " << std::endl; } uint32_t multDepth = 5; uint32_t scaleFactorBits = 50; uint32_t batchSize = 8; SecurityLevel securityLevel = HEStd_128_classic; uint32_t ringDimension = 0; CryptoContext<DCRTPoly> cc = CryptoContextFactory<DCRTPoly>::genCryptoContextCKKS( multDepth, scaleFactorBits, batchSize, securityLevel, ringDimension, rsTech); std::cout << "CKKS scheme is using ring dimension " << cc->GetRingDimension() << std::endl << std::endl; cc->Enable(ENCRYPTION); cc->Enable(SHE); cc->Enable(LEVELEDSHE); auto keys = cc->KeyGen(); cc->EvalMultKeyGen(keys.secretKey);
Enter
vector<double> x = {1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7}; Plaintext ptxt = cc->MakeCKKSPackedPlaintext(x); std::cout << "Input x: " << ptxt << std::endl; auto c = cc->Encrypt(keys.publicKey, ptxt);
Calculate f(x) = x^18 + x^9 + 1
auto c2 = cc->EvalMult(c, c); // x^2 auto c4 = cc->EvalMult(c2, c2); // x^4 auto c8 = cc->EvalMult(c4, c4); // x^8 auto c16 = cc->EvalMult(c8, c8); // x^16 auto c9 = cc->EvalMult(c8, c); // x^9 auto c18 = cc->EvalMult(c16, c2); // x^18 auto cRes = cc->EvalAdd(cc->EvalAdd(c18, c9), 1.0); // Final result Plaintext result; std::cout.precision(8); cc->Decrypt(keys.secretKey, cRes, &result); result->SetLength(batchSize); std::cout << "x^18 + x^9 + 1 = " << result << std::endl;
6. Manual readjustment demonstration
std::cout << "\n\n\n ===== ApproxRescaleDemo ============= " << std::endl; uint32_t multDepth = 5; uint32_t scaleFactorBits = 50; uint32_t batchSize = 8; SecurityLevel securityLevel = HEStd_128_classic; uint32_t ringDimension = 0; CryptoContext<DCRTPoly> cc = CryptoContextFactory<DCRTPoly>::genCryptoContextCKKS( multDepth, scaleFactorBits, batchSize, securityLevel, ringDimension, rsTech); std::cout << "CKKS scheme is using ring dimension " << cc->GetRingDimension() << std::endl << std::endl; cc->Enable(ENCRYPTION); cc->Enable(SHE); cc->Enable(LEVELEDSHE); auto keys = cc->KeyGen(); cc->EvalMultKeyGen(keys.secretKey);
Enter
vector<double> x = {1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7}; Plaintext ptxt = cc->MakeCKKSPackedPlaintext(x); std::cout << "Input x: " << ptxt << std::endl; auto c = cc->Encrypt(keys.publicKey, ptxt);
Calculate f(x) = x^18 + x^9 + 1
// x^2 auto c2_depth2 = cc->EvalMult(c, c); auto c2_depth1 = cc->Rescale(c2_depth2); // x^4 auto c4_depth2 = cc->EvalMult(c2_depth1, c2_depth1); auto c4_depth1 = cc->Rescale(c4_depth2); // x^8 auto c8_depth2 = cc->EvalMult(c4_depth1, c4_depth1); auto c8_depth1 = cc->Rescale(c8_depth2); // x^16 auto c16_depth2 = cc->EvalMult(c8_depth1, c8_depth1); auto c16_depth1 = cc->Rescale(c16_depth2); // x^9 auto c9_depth2 = cc->EvalMult(c8_depth1, c); // x^18 auto c18_depth2 = cc->EvalMult(c16_depth1, c2_depth1); // final result auto cRes_depth2 = cc->EvalAdd(cc->EvalAdd(c18_depth2, c9_depth2), 1.0); auto cRes_depth1 = cc->Rescale(cRes_depth2); Plaintext result; std::cout.precision(8); cc->Decrypt(keys.secretKey, cRes_depth1, &result); result->SetLength(batchSize); std::cout << "x^18 + x^9 + 1 = " << result << std::endl;