diff --git a/CMakeLists.txt b/CMakeLists.txt index bcc012f..749057f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,7 @@ if(APPLE) endif() find_package(OpenFHE 1.3.0 REQUIRED) +set(PYBIND11_FINDPYTHON ON) find_package(pybind11 REQUIRED) # "CMAKE_INTERPROCEDURAL_OPTIMIZATION ON" (ON is the default value) causes link failure. see diff --git a/examples/pke/interactive-bootstrapping.py b/examples/pke/interactive-bootstrapping.py new file mode 100644 index 0000000..f2acfb0 --- /dev/null +++ b/examples/pke/interactive-bootstrapping.py @@ -0,0 +1,245 @@ +from openfhe import * + + +def main(): + # the scaling technigue can be changed to FIXEDMANUAL, FIXEDAUTO, or FLEXIBLEAUTOEXT + ThresholdFHE(FLEXIBLEAUTO) + Chebyshev(FLEXIBLEAUTO) + +def ThresholdFHE(scaleTech): + # if scaleTech not in [FIXEDMANUAL, FIXEDAUTO, FLEXIBLEAUTOEXT]: + # errMsg = "ERROR: Scaling technique is not supported!" + # raise Exception(errMsg) + + print(f"Threshold FHE example with Scaling Technique {scaleTech}") + + parameters = CCParamsCKKSRNS() + # 1 extra level needs to be added for FIXED* modes (2 extra levels for FLEXIBLE* modes) to the multiplicative depth + # to support 2-party interactive bootstrapping + depth = 7 + parameters.SetMultiplicativeDepth(depth) + parameters.SetScalingModSize(50) + parameters.SetBatchSize(16) + parameters.SetScalingTechnique(scaleTech) + + cc = GenCryptoContext(parameters) + cc.Enable(PKE) + cc.Enable(LEVELEDSHE) + cc.Enable(ADVANCEDSHE) + cc.Enable(MULTIPARTY) + + ############################################################# + # Perform Key Generation Operation + ############################################################# + + print("Running key generation (used for source data)...") + print("Round 1 (party A) started.") + + kp1 = cc.KeyGen() + evalMultKey = cc.KeySwitchGen(kp1.secretKey, kp1.secretKey) + + print("Round 1 of key generation completed.") + ############################################################# + print("Round 2 (party B) started.") + print("Joint public key for (s_a + s_b) is generated...") + kp2 = cc.MultipartyKeyGen(kp1.publicKey) + + input = [-0.9, -0.8, -0.6, -0.4, -0.2, 0., 0.2, 0.4, 0.6, 0.8, 0.9] + + # This plaintext only has 3 RNS limbs, the minimum needed to perform 2-party interactive bootstrapping for FLEXIBLEAUTO + plaintext1 = cc.MakeCKKSPackedPlaintext(input, 1, depth - 2) + ciphertext1 = cc.Encrypt(kp2.publicKey, plaintext1) + + # INTERACTIVE BOOTSTRAPPING STARTS + + # under the hood it reduces to two towers + ciphertext1 = cc.IntBootAdjustScale(ciphertext1) + print("IntBootAdjustScale Succeeded") + + # masked decryption on the server: c0 = b + a*s0 + ciphertextOutput1 = cc.IntBootDecrypt(kp1.secretKey, ciphertext1) + print("IntBootDecrypt on Server Succeeded") + + ciphertext2 = ciphertext1.Clone() + ciphertext2.SetElements([ciphertext2.GetElements()[1]]) + + # masked decryption on the client: c1 = a*s1 + ciphertextOutput2 = cc.IntBootDecrypt(kp2.secretKey, ciphertext2) + print("IntBootDecrypt on Client Succeeded") + + # Encryption of masked decryption c1 = a*s1 + ciphertextOutput2 = cc.IntBootEncrypt(kp2.publicKey, ciphertextOutput2) + print("IntBootEncrypt on Client Succeeded") + + # Compute Enc(c1) + c0 + ciphertextOutput = cc.IntBootAdd(ciphertextOutput2, ciphertextOutput1) + print("IntBootAdd on Server Succeeded") + + # INTERACTIVE BOOTSTRAPPING ENDS + + # distributed decryption + ciphertextPartial1 = cc.MultipartyDecryptLead([ciphertextOutput], kp1.secretKey) + ciphertextPartial2 = cc.MultipartyDecryptMain([ciphertextOutput], kp2.secretKey) + + partialCiphertextVec = [ciphertextPartial1[0], ciphertextPartial2[0]] + plaintextMultiparty = cc.MultipartyDecryptFusion(partialCiphertextVec) + + plaintextMultiparty.SetLength(len(input)) + + print(f"Original plaintext \n\t {plaintext1.GetCKKSPackedValue()}") + print(f"Result after bootstrapping \n\t {plaintextMultiparty.GetCKKSPackedValue()}") + +def Chebyshev(scaleTech): +# if scaleTech not in [FIXEDMANUAL, FIXEDAUTO, FLEXIBLEAUTOEXT]: +# errMsg = "ERROR: Scaling technique is not supported!" +# raise Exception(errMsg) + + print(f"Threshold FHE example with Scaling Technique {scaleTech}") + + parameters = CCParamsCKKSRNS() + # 1 extra level needs to be added for FIXED* modes (2 extra levels for FLEXIBLE* modes) to the multiplicative depth + # to support 2-party interactive bootstrapping + parameters.SetMultiplicativeDepth(8) + parameters.SetScalingModSize(50) + parameters.SetBatchSize(16) + parameters.SetScalingTechnique(scaleTech) + + cc = GenCryptoContext(parameters) + # enable features that you wish to use + cc.Enable(PKE) + cc.Enable(LEVELEDSHE) + cc.Enable(ADVANCEDSHE) + cc.Enable(MULTIPARTY) + + ############################################################ + # Perform Key Generation Operation + ############################################################ + + print("Running key generation (used for source data)...") + print("Round 1 (party A) started.") + + kp1 = cc.KeyGen() + + evalMultKey = cc.KeySwitchGen(kp1.secretKey, kp1.secretKey) + cc.EvalSumKeyGen(kp1.secretKey) + evalSumKeys = cc.GetEvalSumKeyMap(kp1.secretKey.GetKeyTag()) + + print("Round 1 of key generation completed.") + ############################################################ + print("Round 2 (party B) started.") + print("Joint public key for (s_a + s_b) is generated...") + kp2 = cc.MultipartyKeyGen(kp1.publicKey) + + evalMultKey2 = cc.MultiKeySwitchGen(kp2.secretKey, kp2.secretKey, evalMultKey) + + print("Joint evaluation multiplication key for (s_a + s_b) is generated...") + evalMultAB = cc.MultiAddEvalKeys(evalMultKey, evalMultKey2, kp2.publicKey.GetKeyTag()) + + print("Joint evaluation multiplication key (s_a + s_b) is transformed into s_b*(s_a + s_b)...") + evalMultBAB = cc.MultiMultEvalKey(kp2.secretKey, evalMultAB, kp2.publicKey.GetKeyTag()) + + evalSumKeysB = cc.MultiEvalSumKeyGen(kp2.secretKey, evalSumKeys, kp2.publicKey.GetKeyTag()) + + print("Joint evaluation summation key for (s_a + s_b) is generated...") + evalSumKeysJoin = cc.MultiAddEvalSumKeys(evalSumKeys, evalSumKeysB, kp2.publicKey.GetKeyTag()) + + cc.InsertEvalSumKey(evalSumKeysJoin) + + print("Round 2 of key generation completed.") + + print("Round 3 (party A) started.") + print("Joint key (s_a + s_b) is transformed into s_a*(s_a + s_b)...") + evalMultAAB = cc.MultiMultEvalKey(kp1.secretKey, evalMultAB, kp2.publicKey.GetKeyTag()) + + print("Computing the final evaluation multiplication key for (s_a + s_b)*(s_a + s_b)...") + evalMultFinal = cc.MultiAddEvalMultKeys(evalMultAAB, evalMultBAB, evalMultAB.GetKeyTag()) + + cc.InsertEvalMultKey([evalMultFinal]) + + print("Round 3 of key generation completed.") + + input = [-4.0, -3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0, 4.0] + + coefficients = [1.0, 0.558971, 0.0, -0.0943712, 0.0, 0.0215023, 0.0, -0.00505348, 0.0, 0.00119324, + 0.0, -0.000281928, 0.0, 0.0000664347, 0.0, -0.0000148709] + + a = -4 + b = 4 + + plaintext1 = cc.MakeCKKSPackedPlaintext(input) + + ciphertext1 = cc.Encrypt(kp2.publicKey, plaintext1) + + # The Chebyshev series interpolation requires 6 levels + ciphertext1 = cc.EvalChebyshevSeries(ciphertext1, coefficients, a, b) + print("Ran Chebyshev interpolation") + + # INTERACTIVE BOOTSTRAPPING STARTS + + ciphertext1 = cc.IntBootAdjustScale(ciphertext1) + print("IntBootAdjustScale Succeeded") + + # masked decryption on the server: c0 = b + a*s0 + ciphertextOutput1 = cc.IntBootDecrypt(kp1.secretKey, ciphertext1) + print("IntBootDecrypt on Server Succeeded") + + ciphertext2 = ciphertext1.Clone() + ciphertext2.SetElements([ciphertext2.GetElements()[1]]) + + # masked decryption on the client: c1 = a*s1 + ciphertextOutput2 = cc.IntBootDecrypt(kp2.secretKey, ciphertext2) + print("IntBootDecrypt on Client Succeeded") + + # Encryption of masked decryption c1 = a*s1 + ciphertextOutput2 = cc.IntBootEncrypt(kp2.publicKey, ciphertextOutput2) + print("IntBootEncrypt on Client Succeeded") + + # Compute Enc(c1) + c0 + ciphertextOutput = cc.IntBootAdd(ciphertextOutput2, ciphertextOutput1) + print("IntBootAdd on Server Succeeded") + + # INTERACTIVE BOOTSTRAPPING ENDS + + # distributed decryption + + ciphertextPartial1 = cc.MultipartyDecryptLead([ciphertextOutput], kp1.secretKey) + + ciphertextPartial2 = cc.MultipartyDecryptMain([ciphertextOutput], kp2.secretKey) + + partialCiphertextVec = [ciphertextPartial1[0], ciphertextPartial2[0]] + plaintextMultiparty = cc.MultipartyDecryptFusion(partialCiphertextVec) + + plaintextMultiparty.SetLength(len(input)) + + print(f"\n Original Plaintext #1: \n {plaintext1}") + + print(f"\n Results of evaluating the polynomial with coefficients {coefficients} \n") + print(f"\n Ciphertext result: {plaintextMultiparty}") + + print("\n Plaintext result: ( 0.0179885, 0.0474289, 0.119205, 0.268936, 0.5, 0.731064, 0.880795, 0.952571, 0.982011 ) \n") + + print("\n Exact result: ( 0.0179862, 0.0474259, 0.119203, 0.268941, 0.5, 0.731059, 0.880797, 0.952574, 0.982014 ) \n") + + print("\n Another round of Chebyshev interpolation after interactive bootstrapping: \n") + + ciphertextOutput = cc.EvalChebyshevSeries(ciphertextOutput, coefficients, a, b) + print("Ran Chebyshev interpolation") + + # distributed decryption + + ciphertextPartial1 = cc.MultipartyDecryptLead([ciphertextOutput], kp1.secretKey) + + ciphertextPartial2 = cc.MultipartyDecryptMain([ciphertextOutput], kp2.secretKey) + + partialCiphertextVec = [ciphertextPartial1[0], ciphertextPartial2[0]] + plaintextMultiparty = cc.MultipartyDecryptFusion(partialCiphertextVec) + + plaintextMultiparty.SetLength(len(input)) + + print(f"\n Ciphertext result: {plaintextMultiparty}") + + print("\n Plaintext result: ( 0.504497, 0.511855, 0.529766, 0.566832, 0.622459, 0.675039, 0.706987, 0.721632, 0.727508 )") + + +if __name__ == "__main__": + main() diff --git a/src/lib/bindings.cpp b/src/lib/bindings.cpp index 2025c79..86ba7cf 100644 --- a/src/lib/bindings.cpp +++ b/src/lib/bindings.cpp @@ -61,6 +61,10 @@ inline std::shared_ptr GetParamsRNSChecked(const CryptoCont return ptr; } +void bind_DCRTPoly(py::module &m) { + py::class_(m, "DCRTPoly").def(py::init<>()); +} + template void bind_parameters(py::module &m,const std::string name) { @@ -1312,28 +1316,45 @@ void bind_encodings(py::module &m) void bind_ciphertext(py::module &m) { - py::class_, std::shared_ptr>>(m, "Ciphertext") - .def(py::init<>()) - .def("__add__", [](const Ciphertext &a, const Ciphertext &b) - {return a + b; },py::is_operator(),pybind11::keep_alive<0, 1>()) - // .def(py::self + py::self); - // .def("GetDepth", &CiphertextImpl::GetDepth) - // .def("SetDepth", &CiphertextImpl::SetDepth) - .def("GetLevel", &CiphertextImpl::GetLevel, - ctx_GetLevel_docs) - .def("SetLevel", &CiphertextImpl::SetLevel, - ctx_SetLevel_docs, - py::arg("level")) - .def("Clone", &CiphertextImpl::Clone) - .def("RemoveElement", &RemoveElementWrapper, cc_RemoveElement_docs) - // .def("GetHopLevel", &CiphertextImpl::GetHopLevel) - // .def("SetHopLevel", &CiphertextImpl::SetHopLevel) - // .def("GetScalingFactor", &CiphertextImpl::GetScalingFactor) - // .def("SetScalingFactor", &CiphertextImpl::SetScalingFactor) - .def("GetSlots", &CiphertextImpl::GetSlots) - .def("SetSlots", &CiphertextImpl::SetSlots) - .def("GetNoiseScaleDeg", &CiphertextImpl::GetNoiseScaleDeg) - .def("SetNoiseScaleDeg", &CiphertextImpl::SetNoiseScaleDeg); + py::class_, + std::shared_ptr>>(m, "Ciphertext") + .def(py::init<>()) + .def( + "__add__", + [](const Ciphertext &a, const Ciphertext &b) { + return a + b; + }, + py::is_operator(), pybind11::keep_alive<0, 1>()) + // .def(py::self + py::self); + // .def("GetDepth", &CiphertextImpl::GetDepth) + // .def("SetDepth", &CiphertextImpl::SetDepth) + .def("GetLevel", &CiphertextImpl::GetLevel, ctx_GetLevel_docs) + .def("SetLevel", &CiphertextImpl::SetLevel, ctx_SetLevel_docs, + py::arg("level")) + .def("Clone", &CiphertextImpl::Clone) + .def("RemoveElement", &RemoveElementWrapper, cc_RemoveElement_docs) + // .def("GetHopLevel", &CiphertextImpl::GetHopLevel) + // .def("SetHopLevel", &CiphertextImpl::SetHopLevel) + // .def("GetScalingFactor", &CiphertextImpl::GetScalingFactor) + // .def("SetScalingFactor", &CiphertextImpl::SetScalingFactor) + .def("GetSlots", &CiphertextImpl::GetSlots) + .def("SetSlots", &CiphertextImpl::SetSlots) + .def("GetNoiseScaleDeg", &CiphertextImpl::GetNoiseScaleDeg) + .def("SetNoiseScaleDeg", &CiphertextImpl::SetNoiseScaleDeg) + .def("GetElements", [](const CiphertextImpl& self) -> const std::vector & { + return self.GetElements(); + }, + py::return_value_policy::reference_internal) + .def("GetElementsMutable", [](CiphertextImpl& self) -> std::vector & { + return self.GetElements(); + }, + py::return_value_policy::reference_internal) + .def("SetElements", [](CiphertextImpl& self, const std::vector &elems) { + self.SetElements(elems); + }) + .def("SetElementsMove", [](CiphertextImpl& self, std::vector &&elems) { + self.SetElements(std::move(elems)); + }); } void bind_schemes(py::module &m){ @@ -1400,6 +1421,7 @@ PYBIND11_MODULE(openfhe, m) { m.doc() = "Open-Source Fully Homomorphic Encryption Library"; // binfhe library + bind_DCRTPoly(m); bind_binfhe_enums(m); bind_binfhe_context(m); bind_binfhe_keys(m);