Skip to content

Commit f790011

Browse files
Composite Scaling CKKS Bootstrapping (#910 phase 3) (#931)
* Initial commit: adding composite scaling parameters and scaling tech mode. * Initial commit: adding composite scaling parameters and scaling tech mode. * Composite prime generation and adaptive scaling factors. * Propagating composite degree to mod reduce calls. * Handling overflow for scaling factors over 64 bits on composite scaling mode. * Bug fix of overflow handling for d > 2 and sc > 64. * Update impl of Rescale and Mod/LevelReduce methods with composite degree. * Minor bug fix: else case missing. * Update EvalPoly functions to use composite degree. * Update Compress function to raise error. * Update/Add new composite scaling examples. * Recalculate sizeP and update EstimateLogP interface. * Updated CKKS bootstrapping for composite scaling with examples. * Update error conditions. * Update poly evaluation example. * Tweak error condition regarding prime moduli size. * Update composite-prime generation function. * Remove comments from prime gen function. * Update composite scaling unittests and fix unittest runtime issue. * Some review changes. * Update composite scaling unittests and fix unittest runtime issue. * Disable composite scaling set methods for non-CKKS schemes. * Move COMPOSITESCALING support error handling. * Disable composite scaling set methods for non-CKKS schemes. * Removing additional constructor that accepts composite scaling parameters. * Review changes for PR 910 phase 2. * Review updates and fixes. * Review updates and fixes. * Review updates and fixes. * Update error message. * Update error message. * Code improvements * Avoid infinite loop in next prime sampling. * Fix decryption failed bug. * Replace std::round(std::log2()) by std::frexp to get bit size of qDouble. * Update error message. * Code improvements --------- Co-authored-by: Dmitriy Suponitskiy <dsuponitskiy@dualitytech.com>
1 parent df92ff3 commit f790011

7 files changed

+839
-183
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
//==================================================================================
2+
// BSD 2-Clause License
3+
//
4+
// Copyright (c) 2014-2022, NJIT, Duality Technologies Inc. and other contributors
5+
//
6+
// All rights reserved.
7+
//
8+
// Author TPOC: contact@openfhe.org
9+
//
10+
// Redistribution and use in source and binary forms, with or without
11+
// modification, are permitted provided that the following conditions are met:
12+
//
13+
// 1. Redistributions of source code must retain the above copyright notice, this
14+
// list of conditions and the following disclaimer.
15+
//
16+
// 2. Redistributions in binary form must reproduce the above copyright notice,
17+
// this list of conditions and the following disclaimer in the documentation
18+
// and/or other materials provided with the distribution.
19+
//
20+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21+
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22+
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23+
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24+
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25+
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26+
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27+
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28+
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29+
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30+
//==================================================================================
31+
32+
/*
33+
34+
Example for multiple iterations of CKKS bootstrapping to improve precision. Note that you need to run a
35+
single iteration of bootstrapping first, to measure the precision. Then, you can input the measured
36+
precision as a parameter to EvalBootstrap with multiple iterations. With 2 iterations, you can achieve
37+
double the precision of a single bootstrapping.
38+
39+
* Source: Bae Y., Cheon J., Cho W., Kim J., and Kim T. META-BTS: Bootstrapping Precision
40+
* Beyond the Limit. Cryptology ePrint Archive, Report
41+
* 2022/1167. (https://eprint.iacr.org/2022/1167.pdf)
42+
43+
*/
44+
45+
#define PROFILE
46+
47+
#include "openfhe.h"
48+
49+
#include <vector>
50+
#include <iostream>
51+
52+
using namespace lbcrypto;
53+
54+
void IterativeBootstrapExample();
55+
56+
int main(int argc, char* argv[]) {
57+
// We run the example with 8 slots and ring dimension 4096.
58+
IterativeBootstrapExample();
59+
}
60+
61+
// CalculateApproximationError() calculates the precision number (or approximation error).
62+
// The higher the precision, the less the error.
63+
double CalculateApproximationError(const std::vector<std::complex<double>>& result,
64+
const std::vector<std::complex<double>>& expectedResult) {
65+
if (result.size() != expectedResult.size())
66+
OPENFHE_THROW("Cannot compare vectors with different numbers of elements");
67+
68+
// using the infinity norm
69+
double maxError = 0;
70+
for (size_t i = 0; i < result.size(); ++i) {
71+
double error = std::abs(result[i].real() - expectedResult[i].real());
72+
if (maxError < error)
73+
maxError = error;
74+
}
75+
76+
return std::abs(std::log2(maxError));
77+
}
78+
79+
void IterativeBootstrapExample() {
80+
// Step 1: Set CryptoContext
81+
CCParams<CryptoContextCKKSRNS> parameters;
82+
SecretKeyDist secretKeyDist = UNIFORM_TERNARY;
83+
parameters.SetSecretKeyDist(secretKeyDist);
84+
parameters.SetSecurityLevel(HEStd_NotSet);
85+
parameters.SetRingDim(1 << 7);
86+
87+
// All modes are supported for 64-bit CKKS bootstrapping.
88+
ScalingTechnique rescaleTech = COMPOSITESCALINGAUTO;
89+
usint dcrtBits = 61;
90+
usint firstMod = 66;
91+
usint registerWordSize = 27;
92+
93+
parameters.SetScalingModSize(dcrtBits);
94+
parameters.SetScalingTechnique(rescaleTech);
95+
parameters.SetFirstModSize(firstMod);
96+
parameters.SetRegisterWordSize(registerWordSize);
97+
98+
// Here, we specify the number of iterations to run bootstrapping. Note that we currently only support 1 or 2 iterations.
99+
// Two iterations should give us approximately double the precision of one iteration.
100+
uint32_t numIterations = 2;
101+
102+
std::vector<uint32_t> levelBudget = {3, 3};
103+
// Each extra iteration on top of 1 requires an extra level to be consumed.
104+
uint32_t approxBootstrapDepth = 8 + (numIterations - 1);
105+
std::vector<uint32_t> bsgsDim = {0, 0};
106+
107+
uint32_t levelsAvailableAfterBootstrap = 10;
108+
// usint depth =
109+
// levelsAvailableAfterBootstrap + FHECKKSRNS::GetBootstrapDepth(levelBudget, secretKeyDist) + (numIterations - 1);
110+
usint depth =
111+
levelsAvailableAfterBootstrap + FHECKKSRNS::GetBootstrapDepth(approxBootstrapDepth, levelBudget, secretKeyDist);
112+
parameters.SetMultiplicativeDepth(depth);
113+
114+
// Generate crypto context.
115+
CryptoContext<DCRTPoly> cryptoContext = GenCryptoContext(parameters);
116+
117+
// Enable features that you wish to use. Note, we must enable FHE to use bootstrapping.
118+
cryptoContext->Enable(PKE);
119+
cryptoContext->Enable(KEYSWITCH);
120+
cryptoContext->Enable(LEVELEDSHE);
121+
cryptoContext->Enable(ADVANCEDSHE);
122+
cryptoContext->Enable(FHE);
123+
124+
usint ringDim = cryptoContext->GetRingDimension();
125+
std::cout << "CKKS scheme is using ring dimension " << ringDim << std::endl << std::endl;
126+
127+
const auto cryptoParamsCKKSRNS =
128+
std::dynamic_pointer_cast<CryptoParametersCKKSRNS>(cryptoContext->GetCryptoParameters());
129+
usint compositeDegree = cryptoParamsCKKSRNS->GetCompositeDegree();
130+
std::cout << "compositeDegree=" << cryptoParamsCKKSRNS->GetCompositeDegree()
131+
<< " modBitWidth=" << static_cast<float>(dcrtBits) / compositeDegree
132+
<< " targetHWArchWordSize=" << registerWordSize << std::endl;
133+
134+
// Step 2: Precomputations for bootstrapping
135+
// We use a sparse packing.
136+
// uint32_t numSlots = 8;
137+
// We use a full packing.
138+
uint32_t numSlots = cryptoContext->GetCyclotomicOrder() / 4;
139+
cryptoContext->EvalBootstrapSetup(levelBudget, bsgsDim, numSlots);
140+
141+
// Step 3: Key Generation
142+
auto keyPair = cryptoContext->KeyGen();
143+
cryptoContext->EvalMultKeyGen(keyPair.secretKey);
144+
// Generate bootstrapping keys.
145+
cryptoContext->EvalBootstrapKeyGen(keyPair.secretKey, numSlots);
146+
147+
// Step 4: Encoding and encryption of inputs
148+
// Generate random input
149+
std::vector<double> x;
150+
std::random_device rd;
151+
std::mt19937 gen(rd());
152+
std::uniform_real_distribution<> dis(0.0, 1.0);
153+
for (size_t i = 0; i < numSlots; i++) {
154+
x.push_back(dis(gen));
155+
}
156+
157+
// Encoding as plaintexts
158+
// We specify the number of slots as numSlots to achieve a performance improvement.
159+
// We use the other default values of depth 1, levels 0, and no params.
160+
// Alternatively, you can also set batch size as a parameter in the CryptoContext as follows:
161+
// parameters.SetBatchSize(numSlots);
162+
// Here, we assume all ciphertexts in the cryptoContext will have numSlots slots.
163+
// We start with a depleted ciphertext that has used up all of its levels.
164+
Plaintext ptxt = cryptoContext->MakeCKKSPackedPlaintext(x, 1, compositeDegree * (depth - 1), nullptr, numSlots);
165+
ptxt->SetLength(numSlots);
166+
std::cout << "Input: " << ptxt << std::endl;
167+
168+
// Encrypt the encoded vectors
169+
Ciphertext<DCRTPoly> ciph = cryptoContext->Encrypt(keyPair.publicKey, ptxt);
170+
171+
// Step 5: Measure the precision of a single bootstrapping operation.
172+
auto ciphertextAfter = cryptoContext->EvalBootstrap(ciph);
173+
174+
Plaintext result;
175+
cryptoContext->Decrypt(keyPair.secretKey, ciphertextAfter, &result);
176+
result->SetLength(numSlots);
177+
uint32_t precision =
178+
std::floor(CalculateApproximationError(result->GetCKKSPackedValue(), ptxt->GetCKKSPackedValue()));
179+
std::cout << "Bootstrapping precision after 1 iteration: " << precision << std::endl;
180+
181+
// Set precision equal to empirically measured value after many test runs.
182+
precision = 7;
183+
std::cout << "Precision input to algorithm: " << precision << std::endl;
184+
185+
// Step 6: Run bootstrapping with multiple iterations.
186+
auto ciphertextTwoIterations = cryptoContext->EvalBootstrap(ciph, numIterations, precision);
187+
188+
Plaintext resultTwoIterations;
189+
cryptoContext->Decrypt(keyPair.secretKey, ciphertextTwoIterations, &resultTwoIterations);
190+
result->SetLength(numSlots);
191+
auto actualResult = resultTwoIterations->GetCKKSPackedValue();
192+
193+
std::cout << "Output after two iterations of bootstrapping: " << actualResult << std::endl;
194+
double precisionMultipleIterations = CalculateApproximationError(actualResult, ptxt->GetCKKSPackedValue());
195+
196+
// Output the precision of bootstrapping after two iterations. It should be approximately double the original precision.
197+
std::cout << "Bootstrapping precision after 2 iterations: " << precisionMultipleIterations << std::endl;
198+
std::cout << "Number of levels remaining after 2 bootstrappings: "
199+
<< compositeDegree * depth - ciphertextTwoIterations->GetLevel() << std::endl;
200+
// << compositeDegree * depth - ciphertextTwoIterations->GetLevel() - (ciphertextTwoIterations->GetNoiseScaleDeg() - 1)
201+
// << std::endl;
202+
}

src/pke/examples/polynomial-evaluation-high-precision-composite-scaling.cpp

+9-8
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ int main(int argc, char* argv[]) {
110110
std::cout << "\n======EXAMPLE FOR EVALPOLY========\n" << std::endl;
111111

112112
uint32_t multDepth = 6;
113-
int argcCount = 0;
113+
int argcCount = 1;
114114
if (argc > 1) {
115115
while (argcCount < argc) {
116116
uint32_t paramValue = atoi(argv[argcCount]);
@@ -136,9 +136,9 @@ int main(int argc, char* argv[]) {
136136
break;
137137
}
138138
argcCount += 1;
139-
std::cout << "argcCount: " << argcCount << std::endl;
140139
}
141-
std::cout << "Complete !" << std::endl;
140+
141+
std::cout << "Completed reading input parameters!" << std::endl;
142142
}
143143
else {
144144
std::cout << "Using default parameters" << std::endl;
@@ -166,9 +166,12 @@ int main(int argc, char* argv[]) {
166166

167167
const auto cryptoParamsCKKSRNS = std::dynamic_pointer_cast<CryptoParametersCKKSRNS>(cc->GetCryptoParameters());
168168
uint32_t compositeDegree = cryptoParamsCKKSRNS->GetCompositeDegree();
169-
std::cout << "Composite Degree: " << compositeDegree << "\nPrime Moduli Bit Length: "
169+
170+
std::cout << "-----------------------------------------------------------------" << std::endl;
171+
std::cout << "Composite Degree: " << compositeDegree << "\nPrime Moduli Size: "
170172
<< static_cast<float>(scalingModSize) / cryptoParamsCKKSRNS->GetCompositeDegree()
171-
<< "\nTarget HW Arch Word Size: " << registerWordSize << std::endl;
173+
<< "\nRegister Word Size: " << registerWordSize << std::endl;
174+
std::cout << "-----------------------------------------------------------------" << std::endl;
172175

173176
std::vector<std::complex<double>> input({0.5, 0.7, 0.9, 0.95, 0.93});
174177

@@ -178,9 +181,7 @@ int main(int argc, char* argv[]) {
178181
std::vector<double> coefficients2({1, 2, 3, 4, 5, -1, -2, -3, -4, -5,
179182
0.1, 0.2, 0.3, 0.4, 0.5, -0.1, -0.2, -0.3, -0.4, -0.5,
180183
0.1, 0.2, 0.3, 0.4, 0.5, -0.1, -0.2, -0.3, -0.4, -0.5});
181-
// std::vector<double> coefficients2({0, 0, 0, 0, 0, -0, -0, -0, -0, -0,
182-
// 0., 0., 0., 0., 0., -0., -0., -0., -0., -0.,
183-
// 0., 0., 0., 0., 0., -0., -0., -0., -0., -0.});
184+
184185
Plaintext plaintext1 = cc->MakeCKKSPackedPlaintext(input);
185186

186187
auto keyPair = cc->KeyGen();

0 commit comments

Comments
 (0)