Skip to content

Commit 01e2642

Browse files
authored
Merge pull request #1065 from openfheorg/dev
Updates to v1.4.1
2 parents aa8a86e + 2015f40 commit 01e2642

40 files changed

Lines changed: 379 additions & 249 deletions

CMakeLists.User.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ include_directories(${OpenFHE_INCLUDE}/pke)
4242
include_directories(${OpenFHE_INCLUDE}/binfhe)
4343
### add directories for other OpenFHE modules as needed for your project
4444

45+
if(UNIX AND NOT APPLE)
46+
add_link_options(-Wl,--no-as-needed)
47+
endif()
48+
4549
link_directories(${OpenFHE_LIBDIR})
4650
link_directories(${OPENMP_LIBRARIES})
4751
if(BUILD_STATIC)

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ project (OpenFHE C CXX)
2727

2828
set(OPENFHE_VERSION_MAJOR 1)
2929
set(OPENFHE_VERSION_MINOR 4)
30-
set(OPENFHE_VERSION_PATCH 0)
30+
set(OPENFHE_VERSION_PATCH 1)
3131
set(OPENFHE_VERSION ${OPENFHE_VERSION_MAJOR}.${OPENFHE_VERSION_MINOR}.${OPENFHE_VERSION_PATCH})
3232

3333
set(CMAKE_CXX_STANDARD 17)

docs/static_docs/Release_Notes.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
10/14/2025: OpenFHE 1.4.1 (stable) is released
2+
3+
* Changes `convert` to `ConvertRLWEToCKKS` and `ConvertCKKSToRLWE` + other small API updates for functional CKKS bootstrapping (#1047)
4+
* Removes the need for setting `LD_LIBRARY_PATH` when installing the library at a different location for user projects (#1050)
5+
* Adds a new API for `EvalFastRotation` that does not requre specifying the cyclotomic order (#1051)
6+
* Fixes several bugs
7+
8+
The detailed list of changes is available at https://github.com/openfheorg/openfhe-development/issues?q=is%3Aissue+milestone%3A%22Release+1.4.1%22
9+
110
08/18/2025: OpenFHE 1.4.0 (development) is released
211

312
* Adds general functional bootstrapping using CKKS proposed in https://eprint.iacr.org/2024/1623 (#954)
@@ -10,6 +19,7 @@ The detailed list of changes is available at https://github.com/openfheorg/openf
1019

1120
07/11/2025: OpenFHE 1.3.1 (stable) is released
1221

22+
* API change: Previously, const plaintexts were silently modified; the `const` qualifier now has to be removed in these cases; see #1046 for more details (#970)
1323
* Updates the noise estimation models for BGV and BFV, making them slightly more conservative (roughly 1 extra bit is added for each multiplicative level) (#1004)
1424
* Removes extra rotation indices in CKKS bootstrapping (#998)
1525
* Fixes a bug with CKKS bootstrapping for N=2^17 (#996)

scripts/uninstall_openfhe.sh

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,19 @@
1717
# ---------------------------------------------------------------------------------------------------------------------
1818

1919
function uninstall_unix() {
20-
sudo xargs rm -vf < install_manifest.txt || echo Nothing in install_manifest.txt to be uninstalled!
20+
xargs rm -vf < install_manifest.txt || echo Nothing in install_manifest.txt to be uninstalled!
2121

2222
# Parse out the include directory's full path from install_manifest.txt's by using bash parameter expansion
2323
_inc=$(cat install_manifest.txt|grep "/include/openfhe" | head -n 1)
2424
match="${_inc%openfhe*}openfhe"
2525
echo "Removing: ${match}"
26-
sudo rm -vr "${match}"
26+
rm -vr "${match}"
2727

2828
# Parse out the include directory's full path from install_manifest.txt's by using bash parameter expansion
2929
_lib=$(cat install_manifest.txt|grep "/lib/OpenFHE" | head -n 1)
3030
match="${_lib%OpenFHE*}"
3131
echo "Removing: ${match}"
32-
sudo rm -vr "${match}"
32+
rm -vr "${match}"
3333

3434
unset _inc _lib
3535
}
@@ -70,4 +70,4 @@ else # mingw differs over versions
7070
uninstall_mingw
7171
fi
7272

73-
unset osname
73+
unset osname

src/pke/examples/CKKS_FUNCTIONAL_BOOTSTRAPING.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ The desired number of levels that should remain after the function evaluation (f
6262
setup for the computation is called by `EvalFBTSetup` and the necessary keys are generated by calling `EvalBootstrapKeyGen`.
6363

6464
The RLWE input ciphertext needs to be converted to a CKKS ciphertext in order to commence the functional bootstrapping. This is done
65-
by calling `SchemeletRLWEMP::convert`. Then, `EvalFBT` is called to obtain a CKKS encrypting the coefficients of the function
66-
evaluation output. Finally, to return to the exact RLWE scheme, `SchemeletRLWEMP::convert` should be called again.
65+
by calling `SchemeletRLWEMP::ConvertRLWEToCKKS`. Then, `EvalFBT` is called to obtain a CKKS encrypting the coefficients of the function
66+
evaluation output. Finally, to return to the exact RLWE scheme, `SchemeletRLWEMP::ConvertCKKSToRLWE` should be called.
6767

6868
Internally, `EvalFBT` performs the following steps: modulus raising, coefficient to slots transform (equivalent to homomorphic
6969
encoding), complex exponential evaluation, computing powers of the complex exponential, power series evaluation of the

src/pke/examples/functional-bootstrapping-ckks.cpp

Lines changed: 51 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,13 @@ const BigInteger QBFVINIT(BigInteger(1) << 60);
4545
const BigInteger QBFVINITLARGE(BigInteger(1) << 80);
4646

4747
void ArbitraryLUT(BigInteger QBFVInit, BigInteger PInput, BigInteger POutput, BigInteger Q, BigInteger Bigq,
48-
uint32_t scaleTHI, size_t order, uint32_t numSlots, uint32_t ringDim,
48+
uint64_t scaleTHI, size_t order, uint32_t numSlots, uint32_t ringDim,
4949
std::function<int64_t(int64_t)> func);
5050
void MultiValueBootstrapping(BigInteger QBFVInit, BigInteger PInput, BigInteger POutput, BigInteger Q, BigInteger Bigq,
51-
uint32_t scaleTHI, size_t order, uint32_t numSlots, uint32_t ringDim,
51+
uint64_t scaleTHI, size_t order, uint32_t numSlots, uint32_t ringDim,
5252
uint32_t levelComputation);
5353
void MultiPrecisionSign(BigInteger QBFVInit, BigInteger PInput, BigInteger PDigit, BigInteger Q, BigInteger Bigq,
54-
uint32_t scaleTHI, uint32_t scaleStepTHI, size_t order, uint32_t numSlots, uint32_t ringDim);
54+
uint64_t scaleTHI, uint64_t scaleStepTHI, size_t order, uint32_t numSlots, uint32_t ringDim);
5555

5656
int main() {
5757
std::cerr << "\n*1.* Compute the function (x % PInput - POutput / 2) % POutput." << std::endl << std::endl;
@@ -97,7 +97,7 @@ int main() {
9797
}
9898

9999
void ArbitraryLUT(BigInteger QBFVInit, BigInteger PInput, BigInteger POutput, BigInteger Q, BigInteger Bigq,
100-
uint32_t scaleTHI, size_t order, uint32_t numSlots, uint32_t ringDim,
100+
uint64_t scaleTHI, size_t order, uint32_t numSlots, uint32_t ringDim,
101101
std::function<int64_t(int64_t)> func) {
102102
/* 1. Figure out whether sparse packing or full packing should be used.
103103
* numSlots represents the number of values to be encrypted in BFV.
@@ -163,12 +163,12 @@ void ArbitraryLUT(BigInteger QBFVInit, BigInteger PInput, BigInteger POutput, Bi
163163
parameters.SetNumLargeDigits(dnum);
164164
parameters.SetBatchSize(numSlotsCKKS);
165165
parameters.SetRingDim(ringDim);
166-
uint32_t depth = levelsAvailableAfterBootstrap + lvlb[0] + lvlb[1] + 2;
166+
uint32_t depth = levelsAvailableAfterBootstrap;
167167

168168
if (binaryLUT)
169-
depth += FHECKKSRNS::AdjustDepthFBT(coeffint, PInput, order, secretKeyDist);
169+
depth += FHECKKSRNS::GetFBTDepth(lvlb, coeffint, PInput, order, secretKeyDist);
170170
else
171-
depth += FHECKKSRNS::AdjustDepthFBT(coeffcomp, PInput, order, secretKeyDist);
171+
depth += FHECKKSRNS::GetFBTDepth(lvlb, coeffcomp, PInput, order, secretKeyDist);
172172

173173
parameters.SetMultiplicativeDepth(depth);
174174

@@ -209,8 +209,8 @@ void ArbitraryLUT(BigInteger QBFVInit, BigInteger PInput, BigInteger POutput, Bi
209209

210210
/* 7. Convert from the RLWE ciphertext to a CKKS ciphertext (both use the same secret key).
211211
*/
212-
auto ctxt = SchemeletRLWEMP::convert(*cc, ctxtBFV, keyPair.publicKey, Bigq, numSlotsCKKS,
213-
depth - (levelsAvailableBeforeBootstrap > 0));
212+
auto ctxt = SchemeletRLWEMP::ConvertRLWEToCKKS(*cc, ctxtBFV, keyPair.publicKey, Bigq, numSlotsCKKS,
213+
depth - (levelsAvailableBeforeBootstrap > 0));
214214

215215
/* 8. Apply the LUT over the ciphertext.
216216
*/
@@ -222,7 +222,7 @@ void ArbitraryLUT(BigInteger QBFVInit, BigInteger PInput, BigInteger POutput, Bi
222222

223223
/* 9. Convert the result back to RLWE.
224224
*/
225-
auto polys = SchemeletRLWEMP::convert(ctxtAfterFBT, Q);
225+
auto polys = SchemeletRLWEMP::ConvertCKKSToRLWE(ctxtAfterFBT, Q);
226226

227227
auto computed = SchemeletRLWEMP::DecryptCoeff(polys, Q, POutput, keyPair.secretKey, ep, numSlotsCKKS, numSlots);
228228

@@ -243,7 +243,7 @@ void ArbitraryLUT(BigInteger QBFVInit, BigInteger PInput, BigInteger POutput, Bi
243243
}
244244

245245
void MultiValueBootstrapping(BigInteger QBFVInit, BigInteger PInput, BigInteger POutput, BigInteger Q, BigInteger Bigq,
246-
uint32_t scaleTHI, size_t order, uint32_t numSlots, uint32_t ringDim,
246+
uint64_t scaleTHI, size_t order, uint32_t numSlots, uint32_t ringDim,
247247
uint32_t levelsComputation) {
248248
/* 1. Figure out whether sparse packing or full packing should be used.
249249
* numSlots represents the number of values to be encrypted in BFV.
@@ -321,12 +321,12 @@ void MultiValueBootstrapping(BigInteger QBFVInit, BigInteger PInput, BigInteger
321321
parameters.SetNumLargeDigits(dnum);
322322
parameters.SetBatchSize(numSlotsCKKS);
323323
parameters.SetRingDim(ringDim);
324-
uint32_t depth = levelsAvailableAfterBootstrap + lvlb[0] + lvlb[1] + 2 + levelsComputation;
324+
uint32_t depth = levelsAvailableAfterBootstrap + levelsComputation;
325325

326326
if (binaryLUT)
327-
depth += FHECKKSRNS::AdjustDepthFBT(coeffint1, PInput, order, secretKeyDist);
327+
depth += FHECKKSRNS::GetFBTDepth(lvlb, coeffint1, PInput, order, secretKeyDist);
328328
else
329-
depth += FHECKKSRNS::AdjustDepthFBT(coeffcomp1, PInput, order, secretKeyDist);
329+
depth += FHECKKSRNS::GetFBTDepth(lvlb, coeffcomp1, PInput, order, secretKeyDist);
330330

331331
parameters.SetMultiplicativeDepth(depth);
332332

@@ -384,8 +384,8 @@ void MultiValueBootstrapping(BigInteger QBFVInit, BigInteger PInput, BigInteger
384384

385385
/* 9. Convert from the RLWE ciphertext to a CKKS ciphertext (both use the same secret key).
386386
*/
387-
auto ctxt = SchemeletRLWEMP::convert(*cc, ctxtBFV, keyPair.publicKey, Bigq, numSlotsCKKS,
388-
depth - (levelsAvailableBeforeBootstrap > 0));
387+
auto ctxt = SchemeletRLWEMP::ConvertRLWEToCKKS(*cc, ctxtBFV, keyPair.publicKey, Bigq, numSlotsCKKS,
388+
depth - (levelsAvailableBeforeBootstrap > 0));
389389

390390
/* 10. Apply the LUTs over the ciphertext.
391391
* First, compute the complex exponential and its powers to reuse.
@@ -448,7 +448,7 @@ void MultiValueBootstrapping(BigInteger QBFVInit, BigInteger PInput, BigInteger
448448
ctxtAfterFBT2 = cc->EvalHomDecoding(ctxtAfterFBT2, scaleTHI, levelsComputation - 1);
449449
}
450450

451-
auto polys = SchemeletRLWEMP::convert(ctxtAfterFBT1, Q);
451+
auto polys = SchemeletRLWEMP::ConvertCKKSToRLWE(ctxtAfterFBT1, Q);
452452

453453
/* 11. Convert the results back to RLWE.
454454
*/
@@ -465,7 +465,7 @@ void MultiValueBootstrapping(BigInteger QBFVInit, BigInteger PInput, BigInteger
465465
auto max_error_it = std::max_element(exact.begin(), exact.end());
466466
std::cerr << "Max absolute error obtained in the first LUT: " << *max_error_it << std::endl << std::endl;
467467

468-
polys = SchemeletRLWEMP::convert(ctxtAfterFBT2, Q);
468+
polys = SchemeletRLWEMP::ConvertCKKSToRLWE(ctxtAfterFBT2, Q);
469469

470470
computed = SchemeletRLWEMP::DecryptCoeff(polys, Q, POutput, keyPair.secretKey, ep, numSlotsCKKS, numSlots, flagBR);
471471

@@ -481,7 +481,7 @@ void MultiValueBootstrapping(BigInteger QBFVInit, BigInteger PInput, BigInteger
481481
}
482482

483483
void MultiPrecisionSign(BigInteger QBFVInit, BigInteger PInput, BigInteger PDigit, BigInteger Q, BigInteger Bigq,
484-
uint32_t scaleTHI, uint32_t scaleStepTHI, size_t order, uint32_t numSlots, uint32_t ringDim) {
484+
uint64_t scaleTHI, uint64_t scaleStepTHI, size_t order, uint32_t numSlots, uint32_t ringDim) {
485485
/* 1. Figure out whether sparse packing or full packing should be used.
486486
* numSlots represents the number of values to be encrypted in BFV.
487487
* If this number is the same as the ring dimension, then the CKKS slots is half.
@@ -567,12 +567,12 @@ void MultiPrecisionSign(BigInteger QBFVInit, BigInteger PInput, BigInteger PDigi
567567
parameters.SetBatchSize(numSlotsCKKS);
568568
parameters.SetRingDim(ringDim);
569569

570-
uint32_t depth = levelsAvailableAfterBootstrap + lvlb[0] + lvlb[1] + 2;
570+
uint32_t depth = levelsAvailableAfterBootstrap;
571571

572572
if (binaryLUT)
573-
depth += FHECKKSRNS::AdjustDepthFBT(coeffintMod, PDigit, order, secretKeyDist);
573+
depth += FHECKKSRNS::GetFBTDepth(lvlb, coeffintMod, PDigit, order, secretKeyDist);
574574
else
575-
depth += FHECKKSRNS::AdjustDepthFBT(coeffcompMod, PDigit, order, secretKeyDist);
575+
depth += FHECKKSRNS::GetFBTDepth(lvlb, coeffcompMod, PDigit, order, secretKeyDist);
576576

577577
parameters.SetMultiplicativeDepth(depth);
578578

@@ -611,23 +611,27 @@ void MultiPrecisionSign(BigInteger QBFVInit, BigInteger PInput, BigInteger PDigi
611611
auto ctxtBFV = SchemeletRLWEMP::EncryptCoeff(x, QBFVInit, PInput, keyPair.secretKey, ep);
612612

613613
SchemeletRLWEMP::ModSwitch(ctxtBFV, Q, QBFVInit);
614+
uint32_t QBFVBits = Q.GetMSB() - 1;
614615

615616
/* 8. Set up the sign loop parameters. */
616-
double QBFVDouble = Q.ConvertToDouble();
617-
double pBFVDouble = PInput.ConvertToDouble();
618-
double pDigitDouble = PDigit.ConvertToDouble();
619-
double qDigitDouble = Bigq.ConvertToDouble();
620-
BigInteger pOrig = PInput;
621617
std::vector<int64_t> coeffint;
622618
std::vector<std::complex<double>> coeffcomp;
623619
if (binaryLUT)
624620
coeffint = coeffintMod;
625621
else
626622
coeffcomp = coeffcompMod;
627623

628-
bool step = false;
629-
bool go = QBFVDouble > qDigitDouble;
630-
size_t levelsToDrop = 0;
624+
const bool checkeq2 = PDigit.ConvertToInt() == 2;
625+
const bool checkgt2 = PDigit.ConvertToInt() > 2;
626+
const uint32_t pDigitBits = PDigit.GetMSB() - 1;
627+
628+
BigInteger QNew;
629+
BigInteger pOrig = PInput;
630+
631+
bool step = false;
632+
bool go = QBFVBits > dcrtBits;
633+
size_t levelsToDrop = 0;
634+
uint32_t postScalingBits = 0;
631635

632636
/* 9. Start the sign loop. For arbitrary digit size, pNew > 2, the last iteration needs
633637
* to evaluate step pNew not mod pNew.
@@ -640,50 +644,48 @@ void MultiPrecisionSign(BigInteger QBFVInit, BigInteger PInput, BigInteger PDigi
640644
encryptedDigit[0].SwitchModulus(Bigq, 1, 0, 0);
641645
encryptedDigit[1].SwitchModulus(Bigq, 1, 0, 0);
642646

643-
auto ctxt = SchemeletRLWEMP::convert(*cc, encryptedDigit, keyPair.publicKey, Bigq, numSlotsCKKS,
644-
depth - (levelsAvailableBeforeBootstrap > 0));
647+
auto ctxt = SchemeletRLWEMP::ConvertRLWEToCKKS(*cc, encryptedDigit, keyPair.publicKey, Bigq, numSlotsCKKS,
648+
depth - (levelsAvailableBeforeBootstrap > 0));
645649

646650
/* 9.2 Bootstrap the digit.*/
647651
Ciphertext<DCRTPoly> ctxtAfterFBT;
648652
if (binaryLUT)
649-
ctxtAfterFBT = cc->EvalFBT(ctxt, coeffint, PDigit.GetMSB() - 1, ep->GetModulus(),
650-
pOrig.ConvertToDouble() / pBFVDouble * scaleTHI, levelsToDrop, order);
653+
ctxtAfterFBT = cc->EvalFBT(ctxt, coeffint, pDigitBits, ep->GetModulus(), scaleTHI * (1 << postScalingBits),
654+
levelsToDrop, order);
651655
else
652-
ctxtAfterFBT = cc->EvalFBT(ctxt, coeffcomp, PDigit.GetMSB() - 1, ep->GetModulus(),
653-
pOrig.ConvertToDouble() / pBFVDouble * scaleTHI, levelsToDrop, order);
656+
ctxtAfterFBT = cc->EvalFBT(ctxt, coeffcomp, pDigitBits, ep->GetModulus(), scaleTHI * (1 << postScalingBits),
657+
levelsToDrop, order);
654658

655659
/* 9.3 Convert the result back to RLWE and update the
656660
* plaintext and ciphertext modulus of the ciphertext for the next iteration.
657661
*/
658-
auto polys = SchemeletRLWEMP::convert(ctxtAfterFBT, Q);
659-
660-
BigInteger QNew(BigInteger(1) << static_cast<uint32_t>(std::log2(QBFVDouble) - std::log2(pDigitDouble)));
661-
BigInteger PNew(BigInteger(1) << static_cast<uint32_t>(std::log2(pBFVDouble) - std::log2(pDigitDouble)));
662+
auto polys = SchemeletRLWEMP::ConvertCKKSToRLWE(ctxtAfterFBT, Q);
662663

663664
if (!step) {
664665
/* 9.4 If not in the last iteration, subtract the digit from the ciphertext. */
665666
ctxtBFV[0] = ctxtBFV[0] - polys[0];
666667
ctxtBFV[1] = ctxtBFV[1] - polys[1];
667668

668669
/* 9.5 Do modulus switching from Q to QNew for the RLWE ciphertext. */
670+
QNew = Q >> pDigitBits;
669671
ctxtBFV[0] = ctxtBFV[0].MultiplyAndRound(QNew, Q);
670672
ctxtBFV[0].SwitchModulus(QNew, 1, 0, 0);
671673
ctxtBFV[1] = ctxtBFV[1].MultiplyAndRound(QNew, Q);
672674
ctxtBFV[1].SwitchModulus(QNew, 1, 0, 0);
673-
674-
QBFVDouble /= pDigitDouble;
675-
pBFVDouble /= pDigitDouble;
676-
Q = QNew;
677-
PInput = PNew;
675+
Q >>= pDigitBits;
676+
PInput >>= pDigitBits;
677+
QBFVBits -= pDigitBits;
678+
postScalingBits += pDigitBits;
678679
}
679680
else {
680681
/* 9.6 If in the last iteration, return the digit. */
681-
ctxtBFV[0] = polys[0];
682-
ctxtBFV[1] = polys[1];
682+
ctxtBFV[0] = std::move(polys[0]);
683+
ctxtBFV[1] = std::move(polys[1]);
683684
}
684685

685686
/* 9.7 If in the last iteration, decrypt and assess correctness. */
686-
if ((PDigit.ConvertToInt() == 2 && QBFVDouble <= qDigitDouble) || step) {
687+
go = QBFVBits > dcrtBits;
688+
if (step || (checkeq2 && !go)) {
687689
auto computed =
688690
SchemeletRLWEMP::DecryptCoeff(ctxtBFV, Q, PInput, keyPair.secretKey, ep, numSlotsCKKS, numSlots);
689691

@@ -699,9 +701,7 @@ void MultiPrecisionSign(BigInteger QBFVInit, BigInteger PInput, BigInteger PDigi
699701
}
700702

701703
/* 9.8 Determine whether it is the last iteration and if not, update the parameters for the next iteration. */
702-
go = QBFVDouble > qDigitDouble;
703-
704-
if (PDigit.ConvertToInt() > 2 && !go && !step) {
704+
if (checkgt2 && !go && !step) {
705705
if (!binaryLUT)
706706
coeffcomp = coeffcompStep;
707707
scaleTHI = scaleStepTHI;

0 commit comments

Comments
 (0)