In the examples provided on github, in the comments, it’s said that levels needed for bootstrap is 1, but after testing it, seems like it requires 2 levels.
int main() {
// Setup CryptoContext
SecretKeyDist secretKeyDist = UNIFORM_TERNARY; // SPARSE_TERNARY or UNIFORM_TERNARY {-1, 0, +1}
SecurityLevel securityLevel = HEStd_NotSet; // If different from HEStd_NotSet, do not to set ring dimension
uint32_t ringDimension = 1 << 12; // Number of coefficients in the ring, minimum batchSize*2
CCParams<CryptoContextCKKSRNS> parameters;
parameters.SetSecretKeyDist(secretKeyDist);
parameters.SetSecurityLevel(securityLevel);
parameters.SetRingDim(ringDimension);
// Don't change, only expert users should modify
#if NATIVEINT == 128
ScalingTechnique rescaleTech = FIXEDAUTO;
usint dcrtBits = 78;
usint firstMod = 89;
#else
ScalingTechnique rescaleTech = FLEXIBLEAUTO;
usint dcrtBits = 59;
usint firstMod = 60;
#endif
parameters.SetScalingModSize(dcrtBits);
parameters.SetScalingTechnique(rescaleTech);
parameters.SetFirstModSize(firstMod);
/* Bootstrapping parameters.
* We set a budget for the number of levels we can consume in bootstrapping for encoding and decoding, respectively.
* Using larger numbers of levels reduces the complexity and number of rotation keys,
* but increases the depth required for bootstrapping.
* We must choose values smaller than ceil(log2(slots)). A level budget of {4, 4} is good for higher ring
* dimensions (65536 and higher).
*/
std::vector<uint32_t> levelBudget = {4, 4};
// Note that the actual number of levels avalailable after bootstrapping before next bootstrapping
// will be levelsAvailableAfterBootstrap - 1 because an additional level
// is used for scaling the ciphertext before next bootstrapping (in 64-bit CKKS bootstrapping)
uint32_t levelsAvailableAfterBootstrap = 10;
usint depth = levelsAvailableAfterBootstrap + FHECKKSRNS::GetBootstrapDepth(levelBudget, secretKeyDist);
parameters.SetMultiplicativeDepth(depth);
CryptoContext<DCRTPoly> cryptoContext = GenCryptoContext(parameters);
// Enable features
cryptoContext->Enable(PKE); // Enable public key encryption functionality
cryptoContext->Enable(KEYSWITCH); // Enable key switching (required for changing ciphertext keys or performing rotations)
cryptoContext->Enable(LEVELEDSHE); // Enable leveled SHE (Somewhat Homomorphic Encryption) operations
cryptoContext->Enable(ADVANCEDSHE); // Enable advanced SHE features like multiplication, rotations, and rescaling
cryptoContext->Enable(FHE); // Enable full FHE (bootstrapping) capabilities
uint32_t ringDim = cryptoContext->GetRingDimension();
// This is the maximum number of slots that can be used for full packing.
uint32_t numSlots = ringDim / 2;
std::cout << "CKKS scheme is using ring dimension " << ringDim << std::endl;
std::cout << "Number of slots: " << numSlots << std::endl;
std::cout << "CKKS scheme initialized with Bootstrapping support." << std::endl;
// Measure Key Generation Time
auto start = std::chrono::high_resolution_clock::now();
KeyPair<DCRTPoly> keyPair = cryptoContext->KeyGen();
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double, std::milli> durationKeyGen = end - start;
std::cout << "Key Generartion (Public/Private) took: " << durationKeyGen.count() << " ms" << std::endl;
// Measure EvalMult Key Generation Time
start = std::chrono::high_resolution_clock::now();
cryptoContext->EvalMultKeyGen(keyPair.secretKey);
end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double, std::milli> durationMultKeyGen = end - start;
std::cout << "EvalMult Key Generation took: " << durationMultKeyGen.count() << " ms" << std::endl;
// Measure EvalBootstrap Key Generation Time
start = std::chrono::high_resolution_clock::now();
cryptoContext->EvalBootstrapSetup(levelBudget);
cryptoContext->EvalBootstrapKeyGen(keyPair.secretKey, numSlots);
end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double, std::milli> durationBootstrapKeyGen = end - start;
std::cout << "EvalBootstrap Key Generation took: " << durationBootstrapKeyGen.count() << " ms" << std::endl;
std::vector<double> x;
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<> dis(0.0, 1.0);
for (size_t i = 0; i < numSlots; i++) {
x.push_back(dis(gen));
}
Plaintext ptxt = cryptoContext->MakeCKKSPackedPlaintext(x, 1, 0, nullptr, numSlots);
ptxt->SetLength(numSlots);
std::cout << "Input: " << ptxt << std::endl;
// Encrypt the encoded vectors
Ciphertext<DCRTPoly> ciph = cryptoContext->Encrypt(keyPair.publicKey, ptxt);
std::cout << "Initial number of levels remaining: " << depth - ciph->GetLevel() << std::endl;
double scalar = 3.5; // example scalar
// Create a vector filled with the scalar
std::vector<double> scalarVec(numSlots, scalar);
Plaintext scalarPtxt = cryptoContext->MakeCKKSPackedPlaintext(scalarVec);
Ciphertext<DCRTPoly> ciphScaled = cryptoContext->EvalMult(ciph, scalarPtxt);
Ciphertext<DCRTPoly> ciphScaled2 = cryptoContext->EvalMult(ciphScaled, 3.5);
cryptoContext->EvalMultInPlace(ciphScaled2, 3.5);
cryptoContext->EvalMultInPlace(ciphScaled2, 3.5);
cryptoContext->EvalMultInPlace(ciphScaled2, 3.5);
for (size_t i = 0; i < 26; ++i) {
cryptoContext->EvalMultInPlace(ciphScaled2, 3.5);
}
// Rescale explicitly
// cryptoContext->EvalRescaleInPlace(ciphScaled2);
std::cout << "Initial number of levels remaining after mult: " << depth - ciphScaled2->GetLevel() << std::endl;
// Step 5: Perform the bootstrapping operation. The goal is to increase the number of levels remaining
// for HE computation.
auto ciphertextAfter = cryptoContext->EvalBootstrap(ciphScaled2);
std::cout << "Number of levels remaining after bootstrapping: " << depth - ciphertextAfter->GetLevel() << std::endl
<< std::endl;
for (size_t i = 0; i < 10; ++i) {
cryptoContext->EvalMultInPlace(ciphertextAfter, 3.5);
}
std::cout << "Initial number of levels remaining after mult2: " << depth - ciphertextAfter->GetLevel() << std::endl;
auto ciphertextAfter2 = cryptoContext->EvalBootstrap(ciphertextAfter);
std::cout << "Number of levels remaining after bootstrapping2: " << depth - ciphertextAfter2->GetLevel() << std::endl
<< std::endl;
return 0;
}
Initial number of levels remaining: 32
Initial number of levels remaining after mult: 2
Number of levels remaining after bootstrapping: 11
Initial number of levels remaining after mult2: 1
terminate called after throwing an instance of 'lbcrypto::OpenFHEException'
what(): basic/openfhe-development/src/core/include/lattice/hal/default/dcrtpoly-impl.h:l.695:DropLastElement(): DropLastElement: Removing last element of DCRTPoly renders it invalid.
Aborted
make: *** [Makefile:73: fhe] Error 134
Seems like the EvalBootstrap can only be done with at least 2 levels. Can someone explain the conflicting docs?