From cad6582ddc9bf76ffa268a231f0be9d52a14dc73 Mon Sep 17 00:00:00 2001 From: Dmitriy Suponitskiy Date: Thu, 1 Oct 2026 04:38:36 -0400 Subject: [PATCH 01/16] Add Python bindings and examples for OpenFHE 1.6 - Port the missing PKE and BinFHE examples and expose the APIs they need, including functional bootstrapping, BinFHE public-key support, and serialization. - Move the functional-bootstrapping bindings into fbt.cpp and extend the example tests to check the complete v1.6 inventory. --- CMakeLists.txt | 16 +- README.md | 6 +- docs/binfhe.rst | 21 + examples/binfhe/boolean-multi-input.py | 114 ++++ ...n-serial-binary-dynamic-large-precision.py | 130 +++++ examples/binfhe/boolean-serial-binary.py | 106 ++++ ...ean-serial-json-dynamic-large-precision.py | 130 +++++ examples/binfhe/boolean-serial-json.py | 106 ++++ examples/binfhe/eval-decomp.py | 56 ++ examples/binfhe/eval-flooring.py | 37 ++ examples/binfhe/pke/boolean-ap-pke.py | 54 ++ examples/binfhe/pke/boolean-pke.py | 74 +++ .../binfhe/pke/boolean-serial-binary-pke.py | 124 +++++ .../binfhe/pke/boolean-serial-json-pke.py | 119 ++++ .../binfhe/pke/boolean-truth-tables-pke.py | 112 ++++ examples/binfhe/pke/eval-flooring-pke.py | 41 ++ examples/binfhe/pke/eval-function-pke.py | 45 ++ .../pke/FE-functional-bootstrapping-ckks.py | 250 +++++++++ examples/pke/advanced-ckks-bootstrapping.py | 145 +++++ examples/pke/ckks-bootstrap-keys-serial.py | 136 +++++ examples/pke/ckks-noise-flooding.py | 159 ++++++ examples/pke/ckks-release-memory.py | 83 +++ examples/pke/depth-bfvrns-behz.py | 210 ++++++++ examples/pke/depth-bfvrns.py | 210 ++++++++ examples/pke/depth-bgvrns.py | 221 ++++++++ examples/pke/functional-bootstrapping-ckks.py | 508 ++++++++++++++++++ examples/pke/inner-product.py | 94 ++++ ...ve-ckks-bootstrapping-composite-scaling.py | 139 +++++ examples/pke/linearwsum-evaluation.py | 75 +++ ...uation-high-precision-composite-scaling.py | 129 +++++ examples/pke/pre-hra-secure.py | 173 ++++++ examples/pke/rotation.py | 158 ++++++ examples/pke/scheme-switching-serial.py | 193 +++++++ ...le-ckks-bootstrapping-composite-scaling.py | 222 ++++++++ .../pke/simple-composite-scaling-manual.py | 205 +++++++ .../simple-real-numbers-composite-scaling.py | 13 +- ...interactive-mp-bootstrapping-Chebyshev.py} | 0 src/include/bindings.h | 12 + src/include/binfhe_bindings.h | 1 + src/lib/bindings.cpp | 27 +- src/lib/binfhe_bindings.cpp | 117 +++- src/lib/pke/fbt.cpp | 305 +++++++++++ .../pke/schemeswitching_data_serializer.cpp | 225 ++++++++ src/lib/pke/serialization.cpp | 45 ++ tests/test_examples.py | 118 +++- 45 files changed, 5439 insertions(+), 25 deletions(-) create mode 100644 examples/binfhe/boolean-multi-input.py create mode 100644 examples/binfhe/boolean-serial-binary-dynamic-large-precision.py create mode 100644 examples/binfhe/boolean-serial-binary.py create mode 100644 examples/binfhe/boolean-serial-json-dynamic-large-precision.py create mode 100644 examples/binfhe/boolean-serial-json.py create mode 100644 examples/binfhe/eval-decomp.py create mode 100644 examples/binfhe/eval-flooring.py create mode 100644 examples/binfhe/pke/boolean-ap-pke.py create mode 100644 examples/binfhe/pke/boolean-pke.py create mode 100644 examples/binfhe/pke/boolean-serial-binary-pke.py create mode 100644 examples/binfhe/pke/boolean-serial-json-pke.py create mode 100644 examples/binfhe/pke/boolean-truth-tables-pke.py create mode 100644 examples/binfhe/pke/eval-flooring-pke.py create mode 100644 examples/binfhe/pke/eval-function-pke.py create mode 100644 examples/pke/FE-functional-bootstrapping-ckks.py create mode 100644 examples/pke/advanced-ckks-bootstrapping.py create mode 100644 examples/pke/ckks-bootstrap-keys-serial.py create mode 100644 examples/pke/ckks-noise-flooding.py create mode 100644 examples/pke/ckks-release-memory.py create mode 100644 examples/pke/depth-bfvrns-behz.py create mode 100644 examples/pke/depth-bfvrns.py create mode 100644 examples/pke/depth-bgvrns.py create mode 100644 examples/pke/functional-bootstrapping-ckks.py create mode 100644 examples/pke/inner-product.py create mode 100644 examples/pke/iterative-ckks-bootstrapping-composite-scaling.py create mode 100644 examples/pke/linearwsum-evaluation.py create mode 100644 examples/pke/polynomial-evaluation-high-precision-composite-scaling.py create mode 100644 examples/pke/pre-hra-secure.py create mode 100644 examples/pke/rotation.py create mode 100644 examples/pke/scheme-switching-serial.py create mode 100644 examples/pke/simple-ckks-bootstrapping-composite-scaling.py create mode 100644 examples/pke/simple-composite-scaling-manual.py rename examples/pke/{tckks-interactive-mp-bootstrapping-Chebyschev.py => tckks-interactive-mp-bootstrapping-Chebyshev.py} (100%) create mode 100644 src/lib/pke/fbt.cpp create mode 100644 src/lib/pke/schemeswitching_data_serializer.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 64f98b5..03f5441 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,14 +3,14 @@ cmake_minimum_required (VERSION 3.16.3) project (OpenFHE-Python) set(OPENFHE_PYTHON_VERSION_MAJOR 1) -set(OPENFHE_PYTHON_VERSION_MINOR 5) -set(OPENFHE_PYTHON_VERSION_PATCH 1) +set(OPENFHE_PYTHON_VERSION_MINOR 6) +set(OPENFHE_PYTHON_VERSION_PATCH 0) set(OPENFHE_PYTHON_VERSION_TWEAK 0) set(OPENFHE_PYTHON_VERSION ${OPENFHE_PYTHON_VERSION_MAJOR}.${OPENFHE_PYTHON_VERSION_MINOR}.${OPENFHE_PYTHON_VERSION_PATCH}.${OPENFHE_PYTHON_VERSION_TWEAK}) -# OpenFHE version can be specified externally (-DOPENFHE_REQUIRED_VERSION=1.5.1) +# OpenFHE version can be specified externally (-DOPENFHE_REQUIRED_VERSION=1.6.0) if(NOT DEFINED OPENFHE_REQUIRED_VERSION) - set(OPENFHE_REQUIRED_VERSION "1.5.1" CACHE STRING "Required OpenFHE version") + set(OPENFHE_REQUIRED_VERSION "1.6.0" CACHE STRING "Required OpenFHE version") else() # User provided OPENFHE_REQUIRED_VERSION via -D message(STATUS "Using user-specified OpenFHE version: ${OPENFHE_REQUIRED_VERSION}") @@ -66,11 +66,13 @@ endif() ### add_executable( test demo-simple-example.cpp ) ### Pybind Modules -pybind11_add_module(openfhe - src/lib/bindings.cpp +pybind11_add_module(openfhe + src/lib/bindings.cpp src/lib/binfhe_bindings.cpp src/lib/binfhe/binfhecontext_wrapper.cpp - src/lib/pke/serialization.cpp + src/lib/pke/serialization.cpp + src/lib/pke/fbt.cpp + src/lib/pke/schemeswitching_data_serializer.cpp ) # The next line ensures that the installed openfhe module can find its shared library dependencies # in the 'lib/' subdirectory relative to itself without requiring LD_LIBRARY_PATH. diff --git a/README.md b/README.md index 5c2ad4d..68a179c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![License](https://img.shields.io/badge/License-BSD%202--Clause-blue.svg)](https://opensource.org/licenses/BSD-2-Clause) [![Python Versions](https://img.shields.io/badge/python-3.10%2B-blue)](https://www.python.org/) -[![OpenFHE Version](https://img.shields.io/badge/OpenFHE-1.5.1%2B-green)](https://github.com/openfheorg/openfhe-development) +[![OpenFHE Version](https://img.shields.io/badge/OpenFHE-1.6.0%2B-green)](https://github.com/openfheorg/openfhe-development) Python wrapper for the main cryptographic capabilities of the OpenFHE C++ library. `OpenFHE-Python` provides a more user-friendly interface for Python developers, while keeping the efficiency of C++ FHE operations. @@ -42,7 +42,7 @@ The legacy build instructions below are for other operating systems where [openf Before building, make sure you have the following dependencies installed: -- [OpenFHE 1.5.1+](https://github.com/openfheorg/openfhe-development) by following the instructions in [OpenFHE Documentation](https://openfhe-development.readthedocs.io/en/latest/sphinx_rsts/intro/installation/installation.html) +- [OpenFHE 1.6.0+](https://github.com/openfheorg/openfhe-development) by following the instructions in [OpenFHE Documentation](https://openfhe-development.readthedocs.io/en/latest/sphinx_rsts/intro/installation/installation.html) - [Python 3.10+](https://www.python.org/) - [pybind11](https://pybind11.readthedocs.io/en/stable/installing.html) @@ -152,7 +152,7 @@ To get familiar with the OpenFHE Python API, check out the examples: - [Code Example for BGV, BFV, and CKKS](examples/pke/threshold-fhe.py) - [2-party Interactive Bootstrapping Examples](examples/pke/interactive-bootstrapping.py) - [Simple n-party Interactive Bootstrapping Example](examples/pke/tckks-interactive-mp-bootstrapping.py) - - [n-party Interactive Bootstrapping after Chebyshev Approximation](examples/pke/tckks-interactive-mp-bootstrapping-Chebyschev.py) + - [n-party Interactive Bootstrapping after Chebyshev Approximation](examples/pke/tckks-interactive-mp-bootstrapping-Chebyshev.py) - [Code Example for BFV with 5 parties](examples/pke/threshold-fhe-5p.py) ## OpenFHE Python Wrapper Documentation diff --git a/docs/binfhe.rst b/docs/binfhe.rst index d9d0790..3f4a147 100644 --- a/docs/binfhe.rst +++ b/docs/binfhe.rst @@ -16,6 +16,27 @@ LWECiphertext LWEPrivateKey ---------------- .. autoclass:: openfhe.LWEPrivateKey + :members: + :undoc-members: + :show-inheritance: + +LWEPublicKey +---------------- +.. autoclass:: openfhe.LWEPublicKey + :members: + :undoc-members: + :show-inheritance: + +LWEKeyPair +---------------- +.. autoclass:: openfhe.LWEKeyPair + :members: + :undoc-members: + :show-inheritance: + +RingGSWBTKey +---------------- +.. autoclass:: openfhe.RingGSWBTKey :members: :undoc-members: :show-inheritance: \ No newline at end of file diff --git a/examples/binfhe/boolean-multi-input.py b/examples/binfhe/boolean-multi-input.py new file mode 100644 index 0000000..5665f2f --- /dev/null +++ b/examples/binfhe/boolean-multi-input.py @@ -0,0 +1,114 @@ +from openfhe import * + +## Sample Program: Step 1: Set CryptoContext + +cc = BinFHEContext() +cc.GenerateBinFHEContext(STD128_4, GINX) + +## Sample Program: Step 2: Key Generation +# Generate the secret key +sk = cc.KeyGen() + +print("Generating the bootstrapping keys...") + +# Generate the bootstrapping keys (refresh and switching keys) +cc.BTKeyGen(sk) + +print("Completed the key generation.") + +## Sample Program: Step 3: Encryption +# Encrypt several ciphertexts representing Boolean True (1) or False (0). +# plaintext modulus is set higher than 4 to 2 * num_of_inputs +p = 6 +ct1_3input = cc.Encrypt(sk, 1, SMALL_DIM, p) +ct2_3input = cc.Encrypt(sk, 1, SMALL_DIM, p) +ct3_3input = cc.Encrypt(sk, 0, SMALL_DIM, p) + +# 1, 1, 0 +ct123 = [ct1_3input, ct2_3input, ct3_3input] + +# 1, 1, 0 +ct_and3 = cc.EvalBinGate(AND3, ct123) + +# 1, 1, 0 +ct_or3 = cc.EvalBinGate(OR3, ct123) + +## Sample Program: Step 5: Decryption +result = cc.Decrypt(sk, ct_and3, p) +if result != 0: + raise Exception("Decryption failure") +print(f"Result of encrypted computation of AND(1, 1, 0) = {result}") + +result = cc.Decrypt(sk, ct_or3, p) +if result != 1: + raise Exception("Decryption failure") +print(f"Result of encrypted computation of OR(1, 1, 0) = {result}") + +# majority gate and cmux for 3 input does not need higher plaintext modulus +p = 4 +ct1_3input_p4 = cc.Encrypt(sk, 1, SMALL_DIM, p) +ct2_3input_p4 = cc.Encrypt(sk, 1, SMALL_DIM, p) +ct3_3input_p4 = cc.Encrypt(sk, 0, SMALL_DIM, p) +ct4_3input_p4 = cc.Encrypt(sk, 0, SMALL_DIM, p) + +# 1, 1, 0 +ct123_p4 = [ct1_3input_p4, ct2_3input_p4, ct3_3input_p4] + +# 1, 0, 0 +ct134_p4 = [ct1_3input_p4, ct3_3input_p4, ct4_3input_p4] + +# 1, 0, 1 +ct132_p4 = [ct1_3input_p4, ct3_3input_p4, ct2_3input_p4] + +# 1, 1, 0 +ct_majority = cc.EvalBinGate(MAJORITY, ct123_p4) + +# 1, 0, 1 +ct_cmux0 = cc.EvalBinGate(CMUX, ct132_p4) + +# 1, 0, 0 +ct_cmux1 = cc.EvalBinGate(CMUX, ct134_p4) + +result = cc.Decrypt(sk, ct_majority) +if result != 1: + raise Exception("Decryption failure") +print(f"Result of encrypted computation of Majority(1, 1, 0) = {result}") + +result = cc.Decrypt(sk, ct_cmux1) +if result != 1: + raise Exception("Decryption failure") +print(f"Result of encrypted computation of CMUX(1, 0, 0) = {result}") + +result = cc.Decrypt(sk, ct_cmux0) +if result != 0: + raise Exception("Decryption failure") +print(f"Result of encrypted computation of CMUX(1, 0, 1) = {result}") + +# for 4 input gates +p = 8 +ct1_4input = cc.Encrypt(sk, 1, SMALL_DIM, p) +ct2_4input = cc.Encrypt(sk, 0, SMALL_DIM, p) +ct3_4input = cc.Encrypt(sk, 0, SMALL_DIM, p) +ct4_4input = cc.Encrypt(sk, 0, SMALL_DIM, p) + +# 1, 0, 0, 0 +ct1234 = [ct1_4input, ct2_4input, ct3_4input, ct4_4input] + +## Sample Program: Step 4: Evaluation + +# 1, 0, 0, 0 +ct_and4 = cc.EvalBinGate(AND4, ct1234) + +# 1, 0, 0, 0 +ct_or4 = cc.EvalBinGate(OR4, ct1234) + +## Sample Program: Step 5: Decryption +result = cc.Decrypt(sk, ct_and4, p) +if result != 0: + raise Exception("Decryption failure") +print(f"Result of encrypted computation of AND(1, 0, 0, 0) = {result}") + +result = cc.Decrypt(sk, ct_or4, p) +if result != 1: + raise Exception("Decryption failure") +print(f"Result of encrypted computation of OR(1, 0, 0, 0) = {result}") diff --git a/examples/binfhe/boolean-serial-binary-dynamic-large-precision.py b/examples/binfhe/boolean-serial-binary-dynamic-large-precision.py new file mode 100644 index 0000000..64b5faa --- /dev/null +++ b/examples/binfhe/boolean-serial-binary-dynamic-large-precision.py @@ -0,0 +1,130 @@ +# +# Example for FHEW with binary serialization for large-precision sign evaluation +# with dynamic bootstrapping keys +# + +from openfhe import * +import math +import os +import tempfile + +# path where files will be written to +datafolder = "demoData" + + +def main_action(): + # Generating the crypto context + cc1 = BinFHEContext() + + logQ = 17 + cc1.GenerateBinFHEContext(TOY, False, logQ, 0, GINX, True) + Q = 1 << logQ + + q = 4096 # q + factor = 1 << int(logQ - math.log2(q)) # Q/q + p = cc1.GetMaxPlaintextSpace() * factor # Obtain the maximum plaintext space + + print("Generating keys.") + + # Generating the secret key + sk1 = cc1.KeyGen() + + # Generate the bootstrapping keys + cc1.BTKeyGen(sk1) + + print("Done generating all keys.") + + # Encryption for a ciphertext that will be serialized + ct1 = cc1.Encrypt(sk1, 1) + + # CODE FOR SERIALIZATION + + # Serializing key-independent crypto context + if not SerializeToFile(datafolder + "/cryptoContext.txt", cc1, BINARY): + raise Exception("Error serializing the cryptocontext") + print("The cryptocontext has been serialized.") + + # Serializing refreshing and key switching keys (needed for bootstrapping) + if not SerializeToFile(datafolder + "/btKey.txt", cc1.GetBTKey(), BINARY): + raise Exception("Error serializing the bootstrapping keys") + print("The bootstrapping keys have been serialized.") + + bt_key_map = cc1.GetBTKeyMap() + for index, thekey in bt_key_map.items(): + if not SerializeToFile(datafolder + "/" + str(index) + "btKey.txt", thekey, BINARY): + raise Exception("Error serializing the bootstrapping keys") + print(f"The BT map element for baseG = {index} has been serialized.") + + # Serializing private keys + if not SerializeToFile(datafolder + "/sk1.txt", sk1, BINARY): + raise Exception("Error serializing sk1") + print("The secret key sk1 key been serialized.") + + # Serializing a ciphertext + if not SerializeToFile(datafolder + "/ct1.txt", ct1, BINARY): + raise Exception("Error serializing ct1") + print("A ciphertext has been serialized.") + + # CODE FOR DESERIALIZATION + + # Deserializing the cryptocontext + cc, res = DeserializeBinFHECryptoContext(datafolder + "/cryptoContext.txt", BINARY) + if not res: + raise Exception("Could not deserialize the cryptocontext") + print("The cryptocontext has been deserialized.") + + # deserializing the refreshing and switching keys (for bootstrapping) + bt_key, res = DeserializeBinFHEBTKey(datafolder + "/btKey.txt", BINARY) + if not res: + raise Exception("Could not deserialize the bootstrapping keys") + print("The bootstrapping keys have been deserialized.") + + base_g_list = [1 << 14, 1 << 18, 1 << 27] + + for base_g in base_g_list: + map_key, res = DeserializeBinFHEBTKey(datafolder + "/" + str(base_g) + "btKey.txt", BINARY) + if not res: + raise Exception("Could not deserialize the bootstrapping keys") + print(f"The BT map element for baseG = {base_g} has been deserialized.") + + # Loading the keys in the cryptocontext + cc.BTKeyMapLoadSingleElement(base_g, map_key) + + # Loading the keys in the cryptocontext + cc.BTKeyLoad(bt_key) + + # Deserializing the secret key + sk, res = DeserializeLWEPrivateKey(datafolder + "/sk1.txt", BINARY) + if not res: + raise Exception("Could not deserialize the secret key") + print("The secret key has been deserialized.") + + # Deserializing a previously serialized ciphertext + ct, res = DeserializeLWECiphertext(datafolder + "/ct1.txt", BINARY) + if not res: + raise Exception("Could not deserialize the ciphertext") + print("The ciphertext has been deserialized.") + + # OPERATIONS WITH DESERIALIZED KEYS AND CIPHERTEXTS + + for i in range(8): + # We first encrypt with large Q + ct1 = cc.Encrypt(sk, p // 2 + i - 3, LARGE_DIM, p, Q) + + # Get the MSB + ct1 = cc.EvalSign(ct1) + + result = cc.Decrypt(sk, ct1, 2) + print(f"Input: {i}. Expected sign: {int(i >= 3)}. Evaluated Sign: {result}") + + +def main(): + global datafolder + with tempfile.TemporaryDirectory() as td: + datafolder = td + "/" + datafolder + os.mkdir(datafolder) + main_action() + + +if __name__ == "__main__": + main() diff --git a/examples/binfhe/boolean-serial-binary.py b/examples/binfhe/boolean-serial-binary.py new file mode 100644 index 0000000..948e8c6 --- /dev/null +++ b/examples/binfhe/boolean-serial-binary.py @@ -0,0 +1,106 @@ +# +# Example for FHEW with binary serialization +# + +from openfhe import * +import os +import tempfile + +# path where files will be written to +datafolder = "demoData" + + +def main_action(): + # Generating the crypto context + cc1 = BinFHEContext() + cc1.GenerateBinFHEContext(TOY) + + print("Generating keys.") + + # Generating the secret key + sk1 = cc1.KeyGen() + + # Generate the bootstrapping keys + cc1.BTKeyGen(sk1) + + print("Done generating all keys.") + + # Encryption for a ciphertext that will be serialized + ct1 = cc1.Encrypt(sk1, 1) + + # CODE FOR SERIALIZATION + + # Serializing key-independent crypto context + if not SerializeToFile(datafolder + "/cryptoContext.txt", cc1, BINARY): + raise Exception("Error serializing the cryptocontext") + print("The cryptocontext has been serialized.") + + # Serializing refreshing and key switching keys (needed for bootstrapping) + if not SerializeToFile(datafolder + "/btKey.txt", cc1.GetBTKey(), BINARY): + raise Exception("Error serializing the bootstrapping keys") + print("The bootstrapping keys have been serialized.") + + # Serializing private keys + if not SerializeToFile(datafolder + "/sk1.txt", sk1, BINARY): + raise Exception("Error serializing sk1") + print("The secret key sk1 key been serialized.") + + # Serializing a ciphertext + if not SerializeToFile(datafolder + "/ct1.txt", ct1, BINARY): + raise Exception("Error serializing ct1") + print("A ciphertext has been serialized.") + + # CODE FOR DESERIALIZATION + + # Deserializing the cryptocontext + cc, res = DeserializeBinFHECryptoContext(datafolder + "/cryptoContext.txt", BINARY) + if not res: + raise Exception("Could not deserialize the cryptocontext") + print("The cryptocontext has been deserialized.") + + # deserializing the refreshing and switching keys (for bootstrapping) + bt_key, res = DeserializeBinFHEBTKey(datafolder + "/btKey.txt", BINARY) + if not res: + raise Exception("Could not deserialize the bootstrapping keys") + print("The bootstrapping keys have been deserialized.") + + # Loading the keys in the cryptocontext + cc.BTKeyLoad(bt_key) + + # Deserializing the secret key + sk, res = DeserializeLWEPrivateKey(datafolder + "/sk1.txt", BINARY) + if not res: + raise Exception("Could not deserialize the secret key") + print("The secret key has been deserialized.") + + # Deserializing a previously serialized ciphertext + ct, res = DeserializeLWECiphertext(datafolder + "/ct1.txt", BINARY) + if not res: + raise Exception("Could not deserialize the ciphertext") + print("The ciphertext has been deserialized.") + + # OPERATIONS WITH DESERIALIZED KEYS AND CIPHERTEXTS + + ct2 = cc.Encrypt(sk, 1) + + print("Running the computation") + + ct_result = cc.EvalBinGate(AND, ct, ct2) + + print("The computation has completed") + + result = cc.Decrypt(sk, ct_result) + + print(f"result of 1 AND 1 = {result}") + + +def main(): + global datafolder + with tempfile.TemporaryDirectory() as td: + datafolder = td + "/" + datafolder + os.mkdir(datafolder) + main_action() + + +if __name__ == "__main__": + main() diff --git a/examples/binfhe/boolean-serial-json-dynamic-large-precision.py b/examples/binfhe/boolean-serial-json-dynamic-large-precision.py new file mode 100644 index 0000000..2684adc --- /dev/null +++ b/examples/binfhe/boolean-serial-json-dynamic-large-precision.py @@ -0,0 +1,130 @@ +# +# Example for FHEW with JSON serialization for large-precision sign evaluation +# with dynamic bootstrapping keys +# + +from openfhe import * +import math +import os +import tempfile + +# path where files will be written to +datafolder = "demoData" + + +def main_action(): + # Generating the crypto context + cc1 = BinFHEContext() + + logQ = 17 + cc1.GenerateBinFHEContext(TOY, False, logQ, 0, GINX, True) + Q = 1 << logQ + + q = 4096 # q + factor = 1 << int(logQ - math.log2(q)) # Q/q + p = cc1.GetMaxPlaintextSpace() * factor # Obtain the maximum plaintext space + + print("Generating keys.") + + # Generating the secret key + sk1 = cc1.KeyGen() + + # Generate the bootstrapping keys + cc1.BTKeyGen(sk1) + + print("Done generating all keys.") + + # Encryption for a ciphertext that will be serialized + ct1 = cc1.Encrypt(sk1, 1) + + # CODE FOR SERIALIZATION + + # Serializing key-independent crypto context + if not SerializeToFile(datafolder + "/cryptoContext.txt", cc1, JSON): + raise Exception("Error serializing the cryptocontext") + print("The cryptocontext has been serialized.") + + # Serializing refreshing and key switching keys (needed for bootstrapping) + if not SerializeToFile(datafolder + "/btKey.txt", cc1.GetBTKey(), JSON): + raise Exception("Error serializing the bootstrapping keys") + print("The bootstrapping keys have been serialized.") + + bt_key_map = cc1.GetBTKeyMap() + for index, thekey in bt_key_map.items(): + if not SerializeToFile(datafolder + "/" + str(index) + "btKey.txt", thekey, JSON): + raise Exception("Error serializing the bootstrapping keys") + print(f"The BT map element for baseG = {index} has been serialized.") + + # Serializing private keys + if not SerializeToFile(datafolder + "/sk1.txt", sk1, JSON): + raise Exception("Error serializing sk1") + print("The secret key sk1 key been serialized.") + + # Serializing a ciphertext + if not SerializeToFile(datafolder + "/ct1.txt", ct1, JSON): + raise Exception("Error serializing ct1") + print("A ciphertext has been serialized.") + + # CODE FOR DESERIALIZATION + + # Deserializing the cryptocontext + cc, res = DeserializeBinFHECryptoContext(datafolder + "/cryptoContext.txt", JSON) + if not res: + raise Exception("Could not deserialize the cryptocontext") + print("The cryptocontext has been deserialized.") + + # deserializing the refreshing and switching keys (for bootstrapping) + bt_key, res = DeserializeBinFHEBTKey(datafolder + "/btKey.txt", JSON) + if not res: + raise Exception("Could not deserialize the bootstrapping keys") + print("The bootstrapping keys have been deserialized.") + + base_g_list = [1 << 14, 1 << 18, 1 << 27] + + for base_g in base_g_list: + map_key, res = DeserializeBinFHEBTKey(datafolder + "/" + str(base_g) + "btKey.txt", JSON) + if not res: + raise Exception("Could not deserialize the bootstrapping keys") + print(f"The BT map element for baseG = {base_g} has been deserialized.") + + # Loading the keys in the cryptocontext + cc.BTKeyMapLoadSingleElement(base_g, map_key) + + # Loading the keys in the cryptocontext + cc.BTKeyLoad(bt_key) + + # Deserializing the secret key + sk, res = DeserializeLWEPrivateKey(datafolder + "/sk1.txt", JSON) + if not res: + raise Exception("Could not deserialize the secret key") + print("The secret key has been deserialized.") + + # Deserializing a previously serialized ciphertext + ct, res = DeserializeLWECiphertext(datafolder + "/ct1.txt", JSON) + if not res: + raise Exception("Could not deserialize the ciphertext") + print("The ciphertext has been deserialized.") + + # OPERATIONS WITH DESERIALIZED KEYS AND CIPHERTEXTS + + for i in range(8): + # We first encrypt with large Q + ct1 = cc.Encrypt(sk, p // 2 + i - 3, LARGE_DIM, p, Q) + + # Get the MSB + ct1 = cc.EvalSign(ct1) + + result = cc.Decrypt(sk, ct1, 2) + print(f"Input: {i}. Expected sign: {int(i >= 3)}. Evaluated Sign: {result}") + + +def main(): + global datafolder + with tempfile.TemporaryDirectory() as td: + datafolder = td + "/" + datafolder + os.mkdir(datafolder) + main_action() + + +if __name__ == "__main__": + main() diff --git a/examples/binfhe/boolean-serial-json.py b/examples/binfhe/boolean-serial-json.py new file mode 100644 index 0000000..697fe70 --- /dev/null +++ b/examples/binfhe/boolean-serial-json.py @@ -0,0 +1,106 @@ +# +# Example for FHEW with JSON serialization +# + +from openfhe import * +import os +import tempfile + +# path where files will be written to +datafolder = "demoData" + + +def main_action(): + # Generating the crypto context + cc1 = BinFHEContext() + cc1.GenerateBinFHEContext(TOY) + + print("Generating keys.") + + # Generating the secret key + sk1 = cc1.KeyGen() + + # Generate the bootstrapping keys + cc1.BTKeyGen(sk1) + + print("Done generating all keys.") + + # Encryption for a ciphertext that will be serialized + ct1 = cc1.Encrypt(sk1, 1) + + # CODE FOR SERIALIZATION + + # Serializing key-independent crypto context + if not SerializeToFile(datafolder + "/cryptoContext.txt", cc1, JSON): + raise Exception("Error serializing the cryptocontext") + print("The cryptocontext has been serialized.") + + # Serializing refreshing and key switching keys (needed for bootstrapping) + if not SerializeToFile(datafolder + "/btKey.txt", cc1.GetBTKey(), JSON): + raise Exception("Error serializing the bootstrapping keys") + print("The bootstrapping keys have been serialized.") + + # Serializing private keys + if not SerializeToFile(datafolder + "/sk1.txt", sk1, JSON): + raise Exception("Error serializing sk1") + print("The secret key sk1 key been serialized.") + + # Serializing a ciphertext + if not SerializeToFile(datafolder + "/ct1.txt", ct1, JSON): + raise Exception("Error serializing ct1") + print("A ciphertext has been serialized.") + + # CODE FOR DESERIALIZATION + + # Deserializing the cryptocontext + cc, res = DeserializeBinFHECryptoContext(datafolder + "/cryptoContext.txt", JSON) + if not res: + raise Exception("Could not deserialize the cryptocontext") + print("The cryptocontext has been deserialized.") + + # deserializing the refreshing and switching keys (for bootstrapping) + bt_key, res = DeserializeBinFHEBTKey(datafolder + "/btKey.txt", JSON) + if not res: + raise Exception("Could not deserialize the bootstrapping keys") + print("The bootstrapping keys have been deserialized.") + + # Loading the keys in the cryptocontext + cc.BTKeyLoad(bt_key) + + # Deserializing the secret key + sk, res = DeserializeLWEPrivateKey(datafolder + "/sk1.txt", JSON) + if not res: + raise Exception("Could not deserialize the secret key") + print("The secret key has been deserialized.") + + # Deserializing a previously serialized ciphertext + ct, res = DeserializeLWECiphertext(datafolder + "/ct1.txt", JSON) + if not res: + raise Exception("Could not deserialize the ciphertext") + print("The ciphertext has been deserialized.") + + # OPERATIONS WITH DESERIALIZED KEYS AND CIPHERTEXTS + + ct2 = cc.Encrypt(sk, 1) + + print("Running the computation") + + ct_result = cc.EvalBinGate(AND, ct, ct2) + + print("The computation has completed") + + result = cc.Decrypt(sk, ct_result) + + print(f"result of 1 AND 1 = {result}") + + +def main(): + global datafolder + with tempfile.TemporaryDirectory() as td: + datafolder = td + "/" + datafolder + os.mkdir(datafolder) + main_action() + + +if __name__ == "__main__": + main() diff --git a/examples/binfhe/eval-decomp.py b/examples/binfhe/eval-decomp.py new file mode 100644 index 0000000..b49b9d6 --- /dev/null +++ b/examples/binfhe/eval-decomp.py @@ -0,0 +1,56 @@ +from openfhe import * +import math + +## Sample Program: Step 1: Set CryptoContext + +cc = BinFHEContext() + +# Set the ciphertext modulus to be 1 << 23 +# Note that normally we do not use this way to obtain the input ciphertext. +# Instead, we assume that an LWE ciphertext with large ciphertext +# modulus is already provided (e.g., by extracting from a CKKS ciphertext). +# However, we do not provide such a step in this example. +# Therefore, we use a brute force way to create a large LWE ciphertext. +logQ = 23 +cc.GenerateBinFHEContext(STD128, False, logQ, 0, GINX, False) + +Q = 1 << logQ + +q = 4096 # q +factor = 1 << int(logQ - math.log2(q)) # Q/q +P = cc.GetMaxPlaintextSpace() * factor # Obtain the maximum plaintext space + +## Sample Program: Step 2: Key Generation +# Generate the secret key +sk = cc.KeyGen() + +print("Generating the bootstrapping keys...") + +# Generate the bootstrapping keys (refresh and switching keys) +cc.BTKeyGen(sk) + +print("Completed the key generation.") + +## Sample Program: Step 3: Encryption +ct1 = cc.Encrypt(sk, P // 2 + 1, LARGE_DIM, P, Q) +print(f"Encrypted value: {P // 2 + 1}") + +## Sample Program: Step 4: Evaluation +# Decompose the large ciphertext into small ciphertexts that fit in q +decomp = cc.EvalDecomp(ct1) + +## Sample Program: Step 5: Decryption +p = cc.GetMaxPlaintextSpace() +print("Decomposed value: ", end="") +for i in range(len(decomp)): + ct = decomp[i] + if i == len(decomp) - 1: + # after every evalfloor, the least significant digit is dropped so + # the last modulus is computed as log p = (log P) mod (log GetMaxPlaintextSpace) + logp = (P - 1).bit_length() % (p - 1).bit_length() + p = 1 << logp + result = cc.Decrypt(sk, ct, p) + print(f"({result} * {cc.GetMaxPlaintextSpace()}^{i})", end="") + if i != len(decomp) - 1: + print(" + ", end="") +print() diff --git a/examples/binfhe/eval-flooring.py b/examples/binfhe/eval-flooring.py new file mode 100644 index 0000000..18e770a --- /dev/null +++ b/examples/binfhe/eval-flooring.py @@ -0,0 +1,37 @@ +from openfhe import * + +## Sample Program: Step 1: Set CryptoContext + +cc = BinFHEContext() +cc.GenerateBinFHEContext(STD128, False) + +## Sample Program: Step 2: Key Generation +# Generate the secret key +sk = cc.KeyGen() + +print("Generating the bootstrapping keys...") + +# Generate the bootstrapping keys (refresh and switching keys) +cc.BTKeyGen(sk) + +print("Completed the key generation.") + +## Sample Program: Step 3: Encryption +# Obtain the maximum plaintext space +# With the default parameter, p = 8 +p = cc.GetMaxPlaintextSpace() + +# Number of bits to round down +bits = 1 +input = 6 +print(f"Homomorphically round down the input by {bits} bits.") + +ct1 = cc.Encrypt(sk, input % p, LARGE_DIM, p) + +## Sample Program: Step 4: Evaluation +ct_rounded = cc.EvalFloor(ct1, bits) + +## Sample Program: Step 5: Decryption +result = cc.Decrypt(sk, ct_rounded, p // (1 << bits)) + +print(f"Input: {input}. Expected: {input >> bits}. Evaluated = {result}") diff --git a/examples/binfhe/pke/boolean-ap-pke.py b/examples/binfhe/pke/boolean-ap-pke.py new file mode 100644 index 0000000..c9ca701 --- /dev/null +++ b/examples/binfhe/pke/boolean-ap-pke.py @@ -0,0 +1,54 @@ +from openfhe import * + +## Sample Program: Step 1: Set CryptoContext + +cc = BinFHEContext() + +# STD128 is the security level of 128 bits of security based on LWE Estimator +# and HE standard. Other common options are TOY, MEDIUM, STD192, and STD256. +# MEDIUM corresponds to the level of more than 100 bits for both quantum and +# classical computer attacks. The second argument is the bootstrapping method +# (AP or GINX). The default method is GINX. Here we explicitly set AP. GINX +# typically provides better performance: the bootstrapping key is much +# smaller in GINX (by 20x) while the runtime is roughly the same. +cc.GenerateBinFHEContext(STD128, AP) + +## Sample Program: Step 2: Key Generation + +# Generate the secret key +sk = cc.KeyGen() + +print("Generating the bootstrapping keys...") + +# Generate the bootstrapping keys (refresh, switching and public keys) +# Public keys are generated when the keygenMode is set to PUB_ENCRYPT +cc.BTKeyGen(sk, PUB_ENCRYPT) + +pk = cc.GetPublicKey() +print("Completed the key generation.") + +## Sample Program: Step 3: Encryption + +# Encrypt two ciphertexts representing Boolean True (1) +ct1 = cc.Encrypt(pk, 1) +ct2 = cc.Encrypt(pk, 1) + +## Sample Program: Step 4: Evaluation + +# Compute (1 AND 1) = 1; Other binary gate options are OR, NAND, and NOR +ct_and1 = cc.EvalBinGate(AND, ct1, ct2) + +# Compute (NOT 1) = 0 +ct2_not = cc.EvalNOT(ct2) + +# Compute (1 AND (NOT 1)) = 0 +ct_and2 = cc.EvalBinGate(AND, ct2_not, ct1) + +# Computes OR of the results in ct_and1 and ct_and2 = 1 +ct_result = cc.EvalBinGate(OR, ct_and1, ct_and2) + +## Sample Program: Step 5: Decryption + +result = cc.Decrypt(sk, ct_result) + +print(f"Result of encrypted computation of (1 AND 1) OR (1 AND (NOT 1)) = {result}") diff --git a/examples/binfhe/pke/boolean-pke.py b/examples/binfhe/pke/boolean-pke.py new file mode 100644 index 0000000..a04c5f0 --- /dev/null +++ b/examples/binfhe/pke/boolean-pke.py @@ -0,0 +1,74 @@ +from openfhe import * + +## Sample Program: Step 1: Set CryptoContext + +cc = BinFHEContext() + +# STD128 is the security level of 128 bits of security based on LWE Estimator +# and HE standard. Other common options are TOY, MEDIUM, STD192, and STD256. +# MEDIUM corresponds to the level of more than 100 bits for both quantum and +# classical computer attacks. +cc.GenerateBinFHEContext(STD128) + +# verifying public key encrypt and decrypt without bootstrap +# Generate the secret, public key pair +kp = cc.KeyGenPair() + +# LARGE_DIM specifies the dimension of the output ciphertext +ctp = cc.Encrypt(kp.publicKey, 1, LARGE_DIM) + +# decryption check before computation +result = cc.Decrypt(kp.secretKey, ctp) +print(f"Result of encrypted ciphertext of 1 = {result}") + +## Sample Program: Step 2: Key Generation + +# Generate the secret key +sk = cc.KeyGen() + +print("Generating the bootstrapping keys...") + +# Generate the bootstrapping keys (refresh, switching and public keys) +cc.BTKeyGen(sk, PUB_ENCRYPT) + +print("Completed the key generation.") + +## Sample Program: Step 3: Encryption + +# Encrypt two ciphertexts representing Boolean True (1). +# By default, freshly encrypted ciphertexts are bootstrapped. +# If you wish to get a fresh encryption without bootstrapping, write +# ct1 = cc.Encrypt(sk, 1, LARGE_DIM) +ct1 = cc.Encrypt(cc.GetPublicKey(), 1) +ct2 = cc.Encrypt(cc.GetPublicKey(), 1) + +# decryption check before computation +result = cc.Decrypt(sk, ct1) +print(f"Result of encrypted ciphertext of 1 = {result}") + +## Sample Program: Step 4: Evaluation + +# Compute (1 AND 1) = 1; Other binary gate options are OR, NAND, and NOR +ct_and1 = cc.EvalBinGate(AND, ct1, ct2) + +result1 = cc.Decrypt(sk, ct_and1) +print(f"Result of encrypted computation of (1 AND 1) = {result1}") + +# Compute (NOT 1) = 0 +ct2_not = cc.EvalNOT(ct2) + +result = cc.Decrypt(sk, ct2_not) +print(f"Result of encrypted computation of (NOT 1) = {result}") + +# Compute (1 AND (NOT 1)) = 0 +ct_and2 = cc.EvalBinGate(AND, ct2_not, ct1) + +result = cc.Decrypt(sk, ct_and2) +print(f"Result of encrypted computation of (1 AND (NOT 1)) = {result}") + +# Computes OR of the results in ct_and1 and ct_and2 = 1 +ct_result = cc.EvalBinGate(OR, ct_and1, ct_and2) + +## Sample Program: Step 5: Decryption +result = cc.Decrypt(sk, ct_result) +print(f"Result of encrypted computation of (1 AND 1) OR (1 AND (NOT 1)) = {result}") diff --git a/examples/binfhe/pke/boolean-serial-binary-pke.py b/examples/binfhe/pke/boolean-serial-binary-pke.py new file mode 100644 index 0000000..e9edc63 --- /dev/null +++ b/examples/binfhe/pke/boolean-serial-binary-pke.py @@ -0,0 +1,124 @@ +# +# Example for FHEW with binary serialization and public key encryption +# + +from openfhe import * +import os +import tempfile + +# path where files will be written to +datafolder = "demoData" + + +def main_action(): + # Generating the crypto context + cc1 = BinFHEContext() + cc1.GenerateBinFHEContext(TOY) + + print("Generating keys.") + + # Generating the secret key + sk1 = cc1.KeyGen() + + # Generate the bootstrapping keys and public key + cc1.BTKeyGen(sk1, PUB_ENCRYPT) + + print(f"Q: {cc1.GetQ()}") + print(f"N: {cc1.GetN()}") + print(f"q: {cc1.Getq()}") + print(f"n: {cc1.Getn()}") + + print("Done generating all keys.") + + pk1 = cc1.GetPublicKey() + + # Encryption for a ciphertext that will be serialized + ct1 = cc1.Encrypt(pk1, 1) + + # CODE FOR SERIALIZATION + + # Serializing key-independent crypto context + if not SerializeToFile(datafolder + "/cryptoContext.txt", cc1, BINARY): + raise Exception("Error serializing the cryptocontext") + print("The cryptocontext has been serialized.") + + # Serializing refreshing and key switching keys (needed for bootstrapping) + if not SerializeToFile(datafolder + "/btKey.txt", cc1.GetBTKey(), BINARY): + raise Exception("Error serializing the bootstrapping keys") + print("The bootstrapping keys have been serialized.") + + # Serializing secret key + if not SerializeToFile(datafolder + "/sk1.txt", sk1, BINARY): + raise Exception("Error serializing sk1") + print("The secret key sk1 key been serialized.") + + # Serializing public key + if not SerializeToFile(datafolder + "/pk1.txt", pk1, BINARY): + raise Exception("Error serializing pk1") + print("The public key pk1 key been serialized.") + + # Serializing a ciphertext + if not SerializeToFile(datafolder + "/ct1.txt", ct1, BINARY): + raise Exception("Error serializing ct1") + print("A ciphertext has been serialized.") + + # CODE FOR DESERIALIZATION + + # Deserializing the cryptocontext + cc, res = DeserializeBinFHECryptoContext(datafolder + "/cryptoContext.txt", BINARY) + if not res: + raise Exception("Could not deserialize the cryptocontext") + print("The cryptocontext has been deserialized.") + + # deserializing the refreshing and switching keys (for bootstrapping) + bt_key, res = DeserializeBinFHEBTKey(datafolder + "/btKey.txt", BINARY) + if not res: + raise Exception("Could not deserialize the bootstrapping keys") + print("The bootstrapping keys have been deserialized.") + + # Loading the keys in the cryptocontext + cc.BTKeyLoad(bt_key) + + # Deserializing the secret key + sk, res = DeserializeLWEPrivateKey(datafolder + "/sk1.txt", BINARY) + if not res: + raise Exception("Could not deserialize the secret key") + print("The secret key has been deserialized.") + + # Deserializing the public key + pk, res = DeserializeLWEPublicKey(datafolder + "/pk1.txt", BINARY) + if not res: + raise Exception("Could not deserialize the public key") + print("The public key has been deserialized.") + + # Deserializing a previously serialized ciphertext + ct, res = DeserializeLWECiphertext(datafolder + "/ct1.txt", BINARY) + if not res: + raise Exception("Could not deserialize the ciphertext") + print("The ciphertext has been deserialized.") + + # OPERATIONS WITH DESERIALIZED KEYS AND CIPHERTEXTS + + ct2 = cc.Encrypt(pk, 1) + + print("Running the computation") + + ct_result = cc.EvalBinGate(AND, ct, ct2) + + print("The computation has completed") + + result = cc.Decrypt(sk, ct_result) + + print(f"result of 1 AND 1 = {result}") + + +def main(): + global datafolder + with tempfile.TemporaryDirectory() as td: + datafolder = td + "/" + datafolder + os.mkdir(datafolder) + main_action() + + +if __name__ == "__main__": + main() diff --git a/examples/binfhe/pke/boolean-serial-json-pke.py b/examples/binfhe/pke/boolean-serial-json-pke.py new file mode 100644 index 0000000..621b4f2 --- /dev/null +++ b/examples/binfhe/pke/boolean-serial-json-pke.py @@ -0,0 +1,119 @@ +# +# Example for FHEW with JSON serialization and public key encryption +# + +from openfhe import * +import os +import tempfile + +# path where files will be written to +datafolder = "demoData" + + +def main_action(): + # Generating the crypto context + cc1 = BinFHEContext() + cc1.GenerateBinFHEContext(TOY) + + print("Generating keys.") + + # Generating the secret key + sk1 = cc1.KeyGen() + + # Generate the bootstrapping keys and public key + cc1.BTKeyGen(sk1, PUB_ENCRYPT) + + print("Done generating all keys.") + + pk1 = cc1.GetPublicKey() + + # Encryption for a ciphertext that will be serialized + ct1 = cc1.Encrypt(pk1, 1) + + # CODE FOR SERIALIZATION + + # Serializing key-independent crypto context + if not SerializeToFile(datafolder + "/cryptoContext.txt", cc1, JSON): + raise Exception("Error serializing the cryptocontext") + print("The cryptocontext has been serialized.") + + # Serializing refreshing and key switching keys (needed for bootstrapping) + if not SerializeToFile(datafolder + "/btKey.txt", cc1.GetBTKey(), JSON): + raise Exception("Error serializing the bootstrapping keys") + print("The bootstrapping keys have been serialized.") + + # Serializing secret key + if not SerializeToFile(datafolder + "/sk1.txt", sk1, JSON): + raise Exception("Error serializing sk1") + print("The secret key sk1 key been serialized.") + + # Serializing public key + if not SerializeToFile(datafolder + "/pk1.txt", pk1, JSON): + raise Exception("Error serializing pk1") + print("The public key pk1 key been serialized.") + + # Serializing a ciphertext + if not SerializeToFile(datafolder + "/ct1.txt", ct1, JSON): + raise Exception("Error serializing ct1") + print("A ciphertext has been serialized.") + + # CODE FOR DESERIALIZATION + + # Deserializing the cryptocontext + cc, res = DeserializeBinFHECryptoContext(datafolder + "/cryptoContext.txt", JSON) + if not res: + raise Exception("Could not deserialize the cryptocontext") + print("The cryptocontext has been deserialized.") + + # deserializing the refreshing and switching keys (for bootstrapping) + bt_key, res = DeserializeBinFHEBTKey(datafolder + "/btKey.txt", JSON) + if not res: + raise Exception("Could not deserialize the bootstrapping keys") + print("The bootstrapping keys have been deserialized.") + + # Loading the keys in the cryptocontext + cc.BTKeyLoad(bt_key) + + # Deserializing the secret key + sk, res = DeserializeLWEPrivateKey(datafolder + "/sk1.txt", JSON) + if not res: + raise Exception("Could not deserialize the secret key") + print("The secret key has been deserialized.") + + # Deserializing the public key + pk, res = DeserializeLWEPublicKey(datafolder + "/pk1.txt", JSON) + if not res: + raise Exception("Could not deserialize the public key") + print("The public key has been deserialized.") + + # Deserializing a previously serialized ciphertext + ct, res = DeserializeLWECiphertext(datafolder + "/ct1.txt", JSON) + if not res: + raise Exception("Could not deserialize the ciphertext") + print("The ciphertext has been deserialized.") + + # OPERATIONS WITH DESERIALIZED KEYS AND CIPHERTEXTS + + ct2 = cc.Encrypt(pk, 1) + + print("Running the computation") + + ct_result = cc.EvalBinGate(AND, ct, ct2) + + print("The computation has completed") + + result = cc.Decrypt(sk, ct_result) + + print(f"result of 1 AND 1 = {result}") + + +def main(): + global datafolder + with tempfile.TemporaryDirectory() as td: + datafolder = td + "/" + datafolder + os.mkdir(datafolder) + main_action() + + +if __name__ == "__main__": + main() diff --git a/examples/binfhe/pke/boolean-truth-tables-pke.py b/examples/binfhe/pke/boolean-truth-tables-pke.py new file mode 100644 index 0000000..1ae5974 --- /dev/null +++ b/examples/binfhe/pke/boolean-truth-tables-pke.py @@ -0,0 +1,112 @@ +from openfhe import * +import sys + +## Sample Program: Step 1: Set CryptoContext + +cc = BinFHEContext() + +print("Generate cryptocontext", file=sys.stderr) + +# STD128 is the security level of 128 bits of security based on LWE Estimator +# and HE standard. Other options are TOY, MEDIUM, STD192, and STD256. MEDIUM +# corresponds to the level of more than 100 bits for both quantum and +# classical computer attacks. +cc.GenerateBinFHEContext(STD128) + +print("Finished generating cryptocontext", file=sys.stderr) + +## Sample Program: Step 2: Key Generation + +# Generate the secret key +sk = cc.KeyGen() + +print("Generating the bootstrapping keys...") + +# Generate the bootstrapping keys (refresh, switching and public keys) +cc.BTKeyGen(sk, PUB_ENCRYPT) + +pk = cc.GetPublicKey() + +print("Completed the key generation.\n") + +## Sample Program: Step 3: Encryption + +# Encrypt two ciphertexts representing Boolean True (1) +ct10 = cc.Encrypt(pk, 1) +ct11 = cc.Encrypt(pk, 1) + +# Encrypt two ciphertexts representing Boolean False (0) +ct00 = cc.Encrypt(pk, 0) +ct01 = cc.Encrypt(pk, 0) + +## Sample Program: Step 4: Evaluation of NAND gates + +ct_nand1 = cc.EvalBinGate(NAND, ct10, ct11) +ct_nand2 = cc.EvalBinGate(NAND, ct10, ct01) +ct_nand3 = cc.EvalBinGate(NAND, ct00, ct01) +ct_nand4 = cc.EvalBinGate(NAND, ct00, ct11) + +print(f"1 NAND 1 = {cc.Decrypt(sk, ct_nand1)}") +print(f"1 NAND 0 = {cc.Decrypt(sk, ct_nand2)}") +print(f"0 NAND 0 = {cc.Decrypt(sk, ct_nand3)}") +print(f"0 NAND 1 = {cc.Decrypt(sk, ct_nand4)}\n") + +## Sample Program: Step 5: Evaluation of AND gates + +ct_and1 = cc.EvalBinGate(AND, ct10, ct11) +ct_and2 = cc.EvalBinGate(AND, ct10, ct01) +ct_and3 = cc.EvalBinGate(AND, ct00, ct01) +ct_and4 = cc.EvalBinGate(AND, ct00, ct11) + +print(f"1 AND 1 = {cc.Decrypt(sk, ct_and1)}") +print(f"1 AND 0 = {cc.Decrypt(sk, ct_and2)}") +print(f"0 AND 0 = {cc.Decrypt(sk, ct_and3)}") +print(f"0 AND 1 = {cc.Decrypt(sk, ct_and4)}\n") + +## Sample Program: Step 6: Evaluation of OR gates + +ct_or1 = cc.EvalBinGate(OR, ct10, ct11) +ct_or2 = cc.EvalBinGate(OR, ct10, ct01) +ct_or3 = cc.EvalBinGate(OR, ct00, ct01) +ct_or4 = cc.EvalBinGate(OR, ct00, ct11) + +print(f"1 OR 1 = {cc.Decrypt(sk, ct_or1)}") +print(f"1 OR 0 = {cc.Decrypt(sk, ct_or2)}") +print(f"0 OR 0 = {cc.Decrypt(sk, ct_or3)}") +print(f"0 OR 1 = {cc.Decrypt(sk, ct_or4)}\n") + +## Sample Program: Step 7: Evaluation of NOR gates + +ct_nor1 = cc.EvalBinGate(NOR, ct10, ct11) +ct_nor2 = cc.EvalBinGate(NOR, ct10, ct01) +ct_nor3 = cc.EvalBinGate(NOR, ct00, ct01) +ct_nor4 = cc.EvalBinGate(NOR, ct00, ct11) + +print(f"1 NOR 1 = {cc.Decrypt(sk, ct_nor1)}") +print(f"1 NOR 0 = {cc.Decrypt(sk, ct_nor2)}") +print(f"0 NOR 0 = {cc.Decrypt(sk, ct_nor3)}") +print(f"0 NOR 1 = {cc.Decrypt(sk, ct_nor4)}\n") + +## Sample Program: Step 8: Evaluation of XOR gates + +ct_xor1 = cc.EvalBinGate(XOR, ct10, ct11) +ct_xor2 = cc.EvalBinGate(XOR, ct10, ct01) +ct_xor3 = cc.EvalBinGate(XOR, ct00, ct01) +ct_xor4 = cc.EvalBinGate(XOR, ct00, ct11) + +print(f"1 XOR 1 = {cc.Decrypt(sk, ct_xor1)}") +print(f"1 XOR 0 = {cc.Decrypt(sk, ct_xor2)}") +print(f"0 XOR 0 = {cc.Decrypt(sk, ct_xor3)}") +print(f"0 XOR 1 = {cc.Decrypt(sk, ct_xor4)}\n") + +## Sample Program: Step 9: Evaluation of XNOR gates + +ct_xnor1 = cc.EvalBinGate(XNOR, ct10, ct11) +ct_xnor2 = cc.EvalBinGate(XNOR, ct10, ct01) +ct_xnor3 = cc.EvalBinGate(XNOR, ct00, ct01) +ct_xnor4 = cc.EvalBinGate(XNOR, ct00, ct11) + +print(f"1 XNOR 1 = {cc.Decrypt(sk, ct_xnor1)}") +print(f"1 XNOR 0 = {cc.Decrypt(sk, ct_xnor2)}") +print(f"0 XNOR 0 = {cc.Decrypt(sk, ct_xnor3)}") +print(f"0 XNOR 1 = {cc.Decrypt(sk, ct_xnor4)}\n") diff --git a/examples/binfhe/pke/eval-flooring-pke.py b/examples/binfhe/pke/eval-flooring-pke.py new file mode 100644 index 0000000..cb43acc --- /dev/null +++ b/examples/binfhe/pke/eval-flooring-pke.py @@ -0,0 +1,41 @@ +from openfhe import * + +## Sample Program: Step 1: Set CryptoContext + +cc = BinFHEContext() +cc.GenerateBinFHEContext(STD128, False) + +## Sample Program: Step 2: Key Generation + +# Generate the secret key +sk = cc.KeyGen() + +print("Generating the bootstrapping keys...") + +# Generate the bootstrapping keys (refresh, switching and public keys) +cc.BTKeyGen(sk, PUB_ENCRYPT) + +print("Completed the key generation.") + +## Sample Program: Step 3: Encryption + +# Obtain the maximum plaintext space +# With the default parameter, p = 8 +p = cc.GetMaxPlaintextSpace() + +# Number of bits to round down +bits = 1 +input = 6 +print(f"Homomorphically round down the input by {bits} bits.") + +pk = cc.GetPublicKey() + +ct1 = cc.Encrypt(pk, input % p, SMALL_DIM, p) + +## Sample Program: Step 4: Evaluation +ct_rounded = cc.EvalFloor(ct1, bits) + +## Sample Program: Step 5: Decryption +result = cc.Decrypt(sk, ct_rounded, p // (1 << bits)) + +print(f"Input: {input}. Expected: {input >> bits}. Evaluated = {result}") diff --git a/examples/binfhe/pke/eval-function-pke.py b/examples/binfhe/pke/eval-function-pke.py new file mode 100644 index 0000000..d0aff1d --- /dev/null +++ b/examples/binfhe/pke/eval-function-pke.py @@ -0,0 +1,45 @@ +from openfhe import * + +## Sample Program: Step 1: Set CryptoContext + +cc = BinFHEContext() +cc.GenerateBinFHEContext(STD128, True, 12) + +## Sample Program: Step 2: Key Generation + +# Generate the secret key +sk = cc.KeyGen() + +print("Generating the bootstrapping keys...") + +# Generate the bootstrapping keys (refresh, switching and public keys) +cc.BTKeyGen(sk, PUB_ENCRYPT) + +pk = cc.GetPublicKey() + +print("Completed the key generation.") + +## Sample Program: Step 3: Create the to-be-evaluated function and obtain its corresponding LUT +p = cc.GetMaxPlaintextSpace() # Obtain the maximum plaintext space + +# Initialize Function f(x) = x^3 % p +def fp(m, p1): + if m < p1: + return m**3 % p1 + else: + return (m - p1 // 2)**3 % p1 + +# Generate LUT from function f(x) +lut = cc.GenerateLUTviaFunction(fp, p) +print(f"Evaluate x^3%{p}.") + +## Sample Program: Step 4: evaluate f(x) homomorphically and decrypt +# Note that we check for all the possible plaintexts. +for i in range(p): + ct1 = cc.Encrypt(pk, i % p, SMALL_DIM, p) + + ct_cube = cc.EvalFunc(ct1, lut) + + result = cc.Decrypt(sk, ct_cube, p) + + print(f"Input: {i}. Expected: {fp(i, p)}. Evaluated = {result}") diff --git a/examples/pke/FE-functional-bootstrapping-ckks.py b/examples/pke/FE-functional-bootstrapping-ckks.py new file mode 100644 index 0000000..12ebf08 --- /dev/null +++ b/examples/pke/FE-functional-bootstrapping-ckks.py @@ -0,0 +1,250 @@ +# +# Example for FE (Fourier extension) functional bootstrapping in CKKS: the ciphertext is +# refreshed and a function is evaluated on it in one pass, by evaluating the function's +# Fourier series over the bootstrapped message. +# + +from openfhe import * +import math +import time + +# Fourier coefficients for the function math.exp(x) in [-2, 2] of degree 29 +COEFF_EXP_2_DOUBLE_29 = [ + complex(3.222093706013e+00, 0.000000000000e+00), + complex(-3.847497325992e+00, -3.036911315182e+00), + complex(1.613269565467e+00, 2.561562635536e+00), + complex(-7.142688329459e-01, -1.718044084361e+00), + complex(3.343375256712e-01, 1.087677253934e+00), + complex(-1.590083748525e-01, -6.591856314553e-01), + complex(7.444125399842e-02, 3.797240558554e-01), + complex(-3.347789249240e-02, -2.057416664390e-01), + complex(1.415639337190e-02, 1.036151378646e-01), + complex(-5.504078824289e-03, -4.782261609566e-02), + complex(1.912943397918e-03, 1.985252654576e-02), + complex(-5.690043709362e-04, -7.207032810727e-03), + complex(1.328669037822e-04, 2.177453328757e-03), + complex(-1.854630094550e-05, -4.890711403220e-04), + complex(-1.453596732859e-06, 5.053417952833e-05), + complex(1.572743922262e-06, 1.548627313317e-05), + complex(-2.838788709181e-07, -8.312159353832e-06), + complex(-7.562004956940e-08, 8.274522508133e-07), + complex(4.633880680387e-08, 6.598799631857e-07), + complex(-5.869467412101e-10, -2.528773346321e-07), + complex(-6.055031566421e-09, -3.204665662698e-08), + complex(1.190783888161e-09, 4.385916878681e-08), + complex(7.747582545133e-10, -3.358504294115e-09), + complex(-3.348892690935e-10, -7.088429287982e-09), + complex(-9.585399054352e-11, 1.863929871559e-09), + complex(8.204146390023e-11, 1.136804343802e-09), + complex(9.573941899682e-12, -5.803840255614e-10), + complex(-2.013818972690e-11, -1.764939479287e-10), + complex(1.533040187851e-13, 1.657178907988e-10), + complex(5.134323521894e-12, 2.323787581630e-11)] + +# Fourier coefficients for the function 1 / (1 + math.exp(-x)) in [-8, 8] of degree 34 +COEFF_SIGMOID_8_DOUBLE_34 = [ + complex(2.500000000000e-01, 0.000000000000e+00), + complex(1.276756478319e-15, -2.986114677117e-01), + complex(-2.711798613355e-15, -1.305833657358e-03), + complex(2.012279232133e-15, -6.069910606535e-02), + complex(-3.874233833051e-17, -3.496972005070e-03), + complex(-5.065392549852e-16, -1.436915534313e-02), + complex(-4.025501720156e-16, -3.090757902275e-03), + complex(6.661338147751e-16, -3.011048528323e-03), + complex(9.549219054383e-17, -1.395169741000e-03), + complex(-4.406197628981e-16, -7.358936474771e-04), + complex(-2.137022113088e-16, -4.185575122752e-04), + complex(4.614364446098e-16, -2.239585471946e-04), + complex(4.897883314203e-19, -1.182545527493e-04), + complex(-4.024558464266e-16, -6.447113701542e-05), + complex(-1.695114860110e-16, -3.521198006050e-05), + complex(3.903127820948e-16, -1.881494613389e-05), + complex(1.909467050622e-16, -1.006822281208e-05), + complex(-9.540979117872e-17, -5.490602888772e-06), + complex(-6.585471100731e-17, -2.983401096265e-06), + complex(1.040834085586e-16, -1.590510122180e-06), + complex(1.641151407480e-16, -8.526983863427e-07), + complex(-6.938893903907e-17, -4.673955051855e-07), + complex(-1.931861246494e-16, -2.539297722240e-07), + complex(3.035766082959e-16, -1.341034123884e-07), + complex(7.532874064103e-17, -7.180768169489e-08), + complex(-1.049507702966e-16, -4.002071006315e-08), + complex(-8.495889538759e-17, -2.179193493192e-08), + complex(1.587271980519e-16, -1.117364778916e-08), + complex(5.298577332100e-17, -5.959189144748e-09), + complex(-3.009745230820e-16, -3.501207830137e-09), + complex(-9.186228167035e-17, -1.915294608676e-09), + complex(2.055647319033e-16, -8.879601871852e-10), + complex(4.123369939761e-16, -4.698662414010e-10), + complex(-3.139849491518e-16, -3.309931637583e-10), + complex(5.506066522859e-17, -1.816820400721e-10)] + +# Fourier coefficients for the GELU approximation in [-8, 8] of degree 44 +COEFF_GELU_8_DOUBLE_44 = [ + complex(1.970461314322e+00, 0.000000000000e+00), + complex(-1.593875111273e+00, -2.423646025475e+00), + complex(-5.332570094236e-02, 1.043627310063e+00), + complex(-1.570391974449e-01, -5.402873054677e-01), + complex(-3.940694771457e-02, 2.819289867863e-01), + complex(-4.716127625363e-02, -1.391268773786e-01), + complex(-2.418730432251e-02, 6.233923043025e-02), + complex(-1.941167643365e-02, -2.432937320475e-02), + complex(-1.260556521786e-02, 7.763676253796e-03), + complex(-8.664960605021e-03, -1.754420766855e-03), + complex(-5.704614002479e-03, 1.292565051534e-04), + complex(-3.651093482904e-03, 8.792842531399e-05), + complex(-2.270560959509e-03, -3.329062052015e-05), + complex(-1.367226398367e-03, -2.302378982580e-06), + complex(-7.979823621962e-04, 4.854603433774e-06), + complex(-4.528446723700e-04, -5.500080713671e-07), + complex(-2.508556888546e-04, -7.491453056646e-07), + complex(-1.363438350097e-04, 2.239517241233e-07), + complex(-7.312414261638e-05, 1.280298946860e-07), + complex(-3.885704183314e-05, -6.935818435338e-08), + complex(-2.045210744808e-05, -2.372215028579e-08), + complex(-1.059289000752e-05, 2.148573735963e-08), + complex(-5.323541994507e-06, 4.532466957308e-09), + complex(-2.534606159357e-06, -7.005649868379e-09), + complex(-1.095525020071e-06, -8.007947707722e-10), + complex(-3.890401084408e-07, 2.430954434496e-09), + complex(-7.123273537061e-08, 8.161443049159e-11), + complex(5.044242802621e-08, -8.975149646921e-10), + complex(8.120772961289e-08, 3.455961905541e-11), + complex(7.542928368715e-08, 3.512492108152e-10), + complex(5.884712616061e-08, -3.699939660207e-11), + complex(4.212943410103e-08, -1.450199807245e-10), + complex(2.870135674255e-08, 2.446543662105e-11), + complex(1.893829504329e-08, 6.286847323116e-11), + complex(1.220565836477e-08, -1.443831859627e-11), + complex(7.712072154498e-09, -2.849185102259e-11), + complex(4.784403550098e-09, 8.229923686986e-12), + complex(2.915358732203e-09, 1.344066297062e-11), + complex(1.744138401955e-09, -5.307266236734e-12), + complex(1.034329697769e-09, -6.648169766598e-12), + complex(6.098917724102e-10, 3.421664931631e-12), + complex(3.589237189208e-10, 2.944192267787e-12), + complex(2.213863333275e-10, -2.779015756058e-12), + complex(1.478126284614e-10, -1.420742295235e-12), + complex(1.014093012815e-10, 4.286355138519e-13)] + + +def build_normalized_input(slots): + left = -0.5 + right = 0.5 + return [left + i * (right - left) / slots for i in range(slots)] + + +def compute_mean_precision_bits(expected, actual): + count = min(len(expected), len(actual)) + if count == 0: + return float("nan") + min_expected = min(expected[:count]) + max_expected = max(expected[:count]) + sum_error = sum(abs(expected[i] - actual[i]) for i in range(count)) + range_expected = max_expected - min_expected + if range_expected < 1e-15: + range_expected = 1.0 + mean_error = sum_error / count + return float("inf") if mean_error == 0.0 else -math.log2(mean_error / range_expected) + + +def print_values(values, count): + print(" ".join(f"{v:.10f}" for v in values[:count])) + + +def main(): + ring_dim = 1 << 16 + num_slots = 1 << 15 + scaling_technique = ScalingTechnique.FLEXIBLEAUTO + level_budget = [3, 2] + bsgs_dim = [0, 0] + dcrt_bits = 59 + first_mod = 60 + # SPARSE_ENCAPSULATED is recommended (probability of failure below 2^-128). UNIFORM_TERNARY (2^-79 for + # N = 2^16, 2^-33 for N = 2^17) needs a larger depth; SPARSE_TERNARY (about 2^-23 for N = 2^16) is discouraged. + skd = SecretKeyDist.SPARSE_ENCAPSULATED + # GetFEFBTDepth covers the functional bootstrapping itself, for the longest of the three series; the + # levels added on top of it are what is left to compute with on the refreshed ciphertext. + levels_after_bootstrapping = 6 + depth = max(FHECKKSRNS.GetFEFBTDepth(level_budget, COEFF_EXP_2_DOUBLE_29, skd), + FHECKKSRNS.GetFEFBTDepth(level_budget, COEFF_SIGMOID_8_DOUBLE_34, skd), + FHECKKSRNS.GetFEFBTDepth(level_budget, COEFF_GELU_8_DOUBLE_44, skd)) + levels_after_bootstrapping + + parameters = CCParamsCKKSRNS() + parameters.SetCKKSDataType(CKKSDataType.COMPLEX) + parameters.SetSecretKeyDist(skd) + parameters.SetSecurityLevel(SecurityLevel.HEStd_NotSet) + parameters.SetRingDim(ring_dim) + parameters.SetNumLargeDigits(3) + parameters.SetKeySwitchTechnique(KeySwitchTechnique.HYBRID) + parameters.SetScalingModSize(dcrt_bits) + parameters.SetScalingTechnique(scaling_technique) + parameters.SetFirstModSize(first_mod) + parameters.SetBatchSize(num_slots) + parameters.SetMultiplicativeDepth(depth) + + cc = GenCryptoContext(parameters) + cc.Enable(PKESchemeFeature.PKE) + cc.Enable(PKESchemeFeature.KEYSWITCH) + cc.Enable(PKESchemeFeature.LEVELEDSHE) + cc.Enable(PKESchemeFeature.ADVANCEDSHE) + cc.Enable(PKESchemeFeature.FHE) + cc.EvalFEFuncBootstrapSetup(level_budget, bsgs_dim, num_slots) + + key_pair = cc.KeyGen() + cc.EvalMultKeyGen(key_pair.secretKey) + cc.EvalBootstrapKeyGen(key_pair.secretKey, num_slots) + + normalized_input = build_normalized_input(num_slots) + plaintext = cc.MakeCKKSPackedPlaintext(normalized_input, 1, depth - (level_budget[1] + 1), None, num_slots) + input = cc.Encrypt(key_pair.publicKey, plaintext) + # total modulus bits (computed from the full modulus of the chain, unlike the C++ + # example which sums the MSBs of the individual prime moduli) + total_modulus_bits = SchemeletRLWEMP.GetElementParams(key_pair.secretKey, 0).GetModulus().bit_length() + level_before_boot = plaintext.GetLevel() + + # The refresh itself - SlotsToCoeffs, modulus raise, CoeffsToSlots and the complex exponential - does not + # depend on the target function, so it is run once here and shared by every function below. The powers + # are precomputed for the longest series of the family (gelu, degree 44); the shorter ones are evaluated + # against those same powers. + t = time.time() + shared_powers = cc.EvalFEFuncBootstrapPrecompute(input, COEFF_GELU_8_DOUBLE_44) + precompute_ms = (time.time() - t) * 1000 + + print(f"Shared bootstrapping and complex exponential: {round(precompute_ms)} ms " + f"(paid once for all functions below)") + + def run_one(title, radius, coeffs, target): + expected = [target(2.0 * radius * value) for value in normalized_input] + + t = time.time() + output = cc.EvalFEFuncBootstrapWithPrecomp(shared_powers, coeffs) + elapsed = (time.time() - t) * 1000 + + decrypted = cc.Decrypt(key_pair.secretKey, output) + decrypted.SetLength(len(expected)) + actual = decrypted.GetRealPackedValue() + + print(f"\n===== {title} =====") + print(f"CKKS total modulus: {total_modulus_bits} bits") + print(f"Level before bootstrapping: {level_before_boot}\n") + print(f"Level after bootstrapping: {output.GetLevel()}") + print(f"Series evaluation time: {round(elapsed)} ms") + print(f"Slots amortize time: {elapsed / num_slots:.6f} ms\n") + print("--- Sample Points Inspection (Total 10 points) ---") + print_values(normalized_input, 10) + print("----------- Expected Function Values: -----------") + print_values(expected, 10) + print("------- Functional Bootstrapping Results: -------") + print_values(actual, 10) + print("-------------------------------------------------") + print(f"Precision: {compute_mean_precision_bits(expected, actual):.4f} bits") + + run_one("exp[-2,2]", 2.0, COEFF_EXP_2_DOUBLE_29, lambda x: math.exp(x)) + run_one("sigmoid[-8,8]", 8.0, COEFF_SIGMOID_8_DOUBLE_34, lambda x: 1.0 / (1.0 + math.exp(-x))) + run_one("gelu_tanh[-8,8]", 8.0, COEFF_GELU_8_DOUBLE_44, + lambda x: 0.5 * x * (1.0 + math.tanh(math.sqrt(2.0 / math.pi) * (x + 0.044715 * x**3)))) + + +if __name__ == "__main__": + main() diff --git a/examples/pke/advanced-ckks-bootstrapping.py b/examples/pke/advanced-ckks-bootstrapping.py new file mode 100644 index 0000000..b80b1c0 --- /dev/null +++ b/examples/pke/advanced-ckks-bootstrapping.py @@ -0,0 +1,145 @@ +# +# Example for CKKS bootstrapping with sparse packing +# + +from openfhe import * +import random + + +def main(): + # We run the example with 8 slots and ring dimension 4096 to illustrate how to run bootstrapping with a sparse plaintext. + # Using a sparse plaintext and specifying the smaller number of slots gives a performance improvement (typically up to 3x). + bootstrap_example(8) + + +def bootstrap_example(num_slots): + # Step 1: Set CryptoContext + parameters = CCParamsCKKSRNS() + + # A. Specify main parameters + # A1) Secret key distribution + # SPARSE_ENCAPSULATED is recommended for CKKS bootstrapping (probability of failure below 2^-128). + # UNIFORM_TERNARY, used here, is the distribution of the homomorphic encryption security guidelines; + # its probability of failure is 2^-67 for N = 2^16 and 2^-27 for N = 2^17 with full packing. + # SPARSE_TERNARY (original CKKS paper) is discouraged: about 2^-23 for N = 2^16. + secret_key_dist = SecretKeyDist.UNIFORM_TERNARY + parameters.SetSecretKeyDist(secret_key_dist) + + # A2) Desired security level based on FHE standards. + # In this example, we use the "NotSet" option, so the example can run more quickly with + # a smaller ring dimension. Note that this should be used only in + # non-production environments, or by experts who understand the security + # implications of their choices. In production-like environments, we recommend using + # HEStd_128_classic, HEStd_192_classic, or HEStd_256_classic for 128-bit, 192-bit, + # or 256-bit security, respectively. If you choose one of these as your security level, + # you do not need to set the ring dimension. + parameters.SetSecurityLevel(SecurityLevel.HEStd_NotSet) + parameters.SetRingDim(1 << 12) + + # A3) Key switching parameters. + # By default, we use HYBRID key switching with a digit size of 3. + # Choosing a larger digit size can reduce complexity, but the size of keys will increase. + # Note that you can leave these lines of code out completely, since these are the default values. + parameters.SetNumLargeDigits(3) + parameters.SetKeySwitchTechnique(KeySwitchTechnique.HYBRID) + + # A4) Scaling parameters. + # By default, we set the modulus sizes and rescaling technique to the following values + # to obtain a good precision and performance tradeoff. We recommend keeping the parameters + # below unless you are an FHE expert. + if get_native_int() == 128: + # Currently, only FIXEDMANUAL and FIXEDAUTO modes are supported for 128-bit CKKS bootstrapping. + rescale_tech = ScalingTechnique.FIXEDAUTO + dcrt_bits = 78 + first_mod = 89 + else: + # All modes are supported for 64-bit CKKS bootstrapping. + rescale_tech = ScalingTechnique.FLEXIBLEAUTO + dcrt_bits = 59 + first_mod = 60 + + parameters.SetScalingModSize(dcrt_bits) + parameters.SetScalingTechnique(rescale_tech) + parameters.SetFirstModSize(first_mod) + + # A4) 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). + level_budget = [3, 3] + + # We give the user the option of configuring values for an optimization algorithm in bootstrapping. + # Here, we specify the giant step for the baby-step-giant-step algorithm in linear transforms + # for encoding and decoding, respectively. Either choose this to be a power of 2 + # or an exact divisor of the number of slots. Setting it to have the default value of {0, 0} allows OpenFHE to choose + # the values automatically. + bsgs_dim = [0, 0] + + # A5) Multiplicative depth. + # The goal of bootstrapping is to increase the number of available levels we have, or in other words, + # to dynamically increase the multiplicative depth. However, the bootstrapping procedure itself + # needs to consume a few levels to run. We compute the number of bootstrapping levels required + # using GetBootstrapDepth, and add it to levelsAvailableAfterBootstrap to set our initial multiplicative + # depth. + levels_available_after_bootstrap = 10 + depth = levels_available_after_bootstrap + FHECKKSRNS.GetBootstrapDepth(level_budget, secret_key_dist) + parameters.SetMultiplicativeDepth(depth) + + # Generate crypto context. + cryptocontext = GenCryptoContext(parameters) + + # Enable features that you wish to use. Note, we must enable FHE to use bootstrapping. + cryptocontext.Enable(PKESchemeFeature.PKE) + cryptocontext.Enable(PKESchemeFeature.KEYSWITCH) + cryptocontext.Enable(PKESchemeFeature.LEVELEDSHE) + cryptocontext.Enable(PKESchemeFeature.ADVANCEDSHE) + cryptocontext.Enable(PKESchemeFeature.FHE) + + ring_dim = cryptocontext.GetRingDimension() + print(f"CKKS scheme is using ring dimension {ring_dim}\n") + + # Step 2: Precomputations for bootstrapping + cryptocontext.EvalBootstrapSetup(level_budget, bsgs_dim, num_slots) + + # Step 3: Key Generation + key_pair = cryptocontext.KeyGen() + cryptocontext.EvalMultKeyGen(key_pair.secretKey) + # Generate bootstrapping keys. + cryptocontext.EvalBootstrapKeyGen(key_pair.secretKey, num_slots) + + # Step 4: Encoding and encryption of inputs + # Generate random input + x = [random.uniform(0.0, 1.0) for _ in range(num_slots)] + + # Encoding as plaintexts + # We specify the number of slots as numSlots to achieve a performance improvement. + # We use the other default values of depth 1, levels 0, and no params. + # Alternatively, you can also set batch size as a parameter in the CryptoContext as follows: + # parameters.SetBatchSize(numSlots); + # Here, we assume all ciphertexts in the cryptoContext will have numSlots slots. + # We start with a depleted ciphertext that has used up all of its levels. + ptxt = cryptocontext.MakeCKKSPackedPlaintext(x, 1, depth - 1, None, num_slots) + ptxt.SetLength(num_slots) + print(f"Input: {ptxt}") + + # Encrypt the encoded vectors + ciph = cryptocontext.Encrypt(key_pair.publicKey, ptxt) + + print(f"Initial number of levels remaining: {depth - ciph.GetLevel()}") + + # Step 5: Perform the bootstrapping operation. The goal is to increase the number of levels remaining + # for HE computation. + ciphertext_after = cryptocontext.EvalBootstrap(ciph) + + print(f"Number of levels remaining after bootstrapping: {depth - ciphertext_after.GetLevel()}\n") + + # Step 7: Decryption and output + result = cryptocontext.Decrypt(key_pair.secretKey, ciphertext_after) + result.SetLength(num_slots) + print(f"Output after bootstrapping \n\t{result}") + + +if __name__ == "__main__": + main() diff --git a/examples/pke/ckks-bootstrap-keys-serial.py b/examples/pke/ckks-bootstrap-keys-serial.py new file mode 100644 index 0000000..3491120 --- /dev/null +++ b/examples/pke/ckks-bootstrap-keys-serial.py @@ -0,0 +1,136 @@ +# +# Example for serializing and deserializing CKKS bootstrap evaluation keys. +# + +from openfhe import * +import os +import tempfile + +datafolder = "demoData" +cc_location = "/bootstrap-cryptocontext.txt" +public_key_location = "/bootstrap-public-key.txt" +secret_key_location = "/bootstrap-secret-key.txt" +ciphertext_location = "/bootstrap-ciphertext.txt" +mult_key_location = "/bootstrap-eval-mult-keys.txt" +bootstrap_key_location = "/bootstrap-eval-keys.txt" + + +def error_check(condition, message): + if not condition: + raise Exception(message) + + +def main_action(): + parameters = CCParamsCKKSRNS() + # SPARSE_ENCAPSULATED is recommended for CKKS bootstrapping (probability of failure below 2^-128). UNIFORM_TERNARY, + # used here, is the distribution of the homomorphic encryption security guidelines; its probability of failure is + # 2^-67 for N = 2^16 and 2^-27 for N = 2^17 with full packing. + secret_key_dist = SecretKeyDist.UNIFORM_TERNARY + parameters.SetSecretKeyDist(secret_key_dist) + parameters.SetSecurityLevel(SecurityLevel.HEStd_NotSet) + parameters.SetRingDim(1 << 12) + + if get_native_int() == 128: + rescale_tech = ScalingTechnique.FIXEDAUTO + dcrt_bits = 78 + first_mod = 89 + else: + rescale_tech = ScalingTechnique.FLEXIBLEAUTO + dcrt_bits = 59 + first_mod = 60 + + parameters.SetScalingModSize(dcrt_bits) + parameters.SetScalingTechnique(rescale_tech) + parameters.SetFirstModSize(first_mod) + + level_budget = [4, 4] + levels_available_after_bootstrap = 10 + depth = levels_available_after_bootstrap + FHECKKSRNS.GetBootstrapDepth(level_budget, secret_key_dist) + parameters.SetMultiplicativeDepth(depth) + + server_cc = GenCryptoContext(parameters) + server_cc.Enable(PKESchemeFeature.PKE) + server_cc.Enable(PKESchemeFeature.KEYSWITCH) + server_cc.Enable(PKESchemeFeature.LEVELEDSHE) + server_cc.Enable(PKESchemeFeature.ADVANCEDSHE) + server_cc.Enable(PKESchemeFeature.FHE) + + num_slots = server_cc.GetRingDimension() // 2 + server_cc.EvalBootstrapSetup(level_budget, [0, 0], num_slots) + + key_pair = server_cc.KeyGen() + server_cc.EvalMultKeyGen(key_pair.secretKey) + server_cc.EvalBootstrapKeyGen(key_pair.secretKey, num_slots) + + x = [0.25, 0.5, 0.75, 1.0] + plaintext = server_cc.MakeCKKSPackedPlaintext(x, 1, depth - 1) + plaintext.SetLength(len(x)) + ciphertext = server_cc.Encrypt(key_pair.publicKey, plaintext) + + error_check(SerializeToFile(datafolder + cc_location, server_cc, BINARY), + "Error serializing crypto context") + error_check(SerializeToFile(datafolder + public_key_location, key_pair.publicKey, BINARY), + "Error serializing public key") + error_check(SerializeToFile(datafolder + secret_key_location, key_pair.secretKey, BINARY), + "Error serializing secret key") + error_check(SerializeToFile(datafolder + ciphertext_location, ciphertext, BINARY), + "Error serializing ciphertext") + + error_check(server_cc.SerializeEvalMultKey(datafolder + mult_key_location, BINARY, ""), + "Error serializing eval-mult keys") + + error_check(SerializeEvalBootstrapKey(datafolder + bootstrap_key_location, BINARY, server_cc, + key_pair.secretKey.GetKeyTag(), num_slots), + "Error serializing bootstrap eval keys") + + ClearEvalMultKeys() + server_cc.ClearEvalAutomorphismKeys() + ReleaseAllContexts() + + client_cc, res = DeserializeCryptoContext(datafolder + cc_location, BINARY) + error_check(res, "Error deserializing crypto context") + public_key, res = DeserializePublicKey(datafolder + public_key_location, BINARY) + error_check(res, "Error deserializing public key") + secret_key, res = DeserializePrivateKey(datafolder + secret_key_location, BINARY) + error_check(res, "Error deserializing secret key") + client_ciphertext, res = DeserializeCiphertext(datafolder + ciphertext_location, BINARY) + error_check(res, "Error deserializing ciphertext") + + client_cc.EvalBootstrapSetup(level_budget, [0, 0], num_slots) + + error_check(client_cc.DeserializeEvalMultKey(datafolder + mult_key_location, BINARY), + "Error deserializing eval-mult keys") + + error_check(DeserializeEvalBootstrapKey(datafolder + bootstrap_key_location, BINARY, client_cc, + secret_key.GetKeyTag(), num_slots), + "Error deserializing bootstrap eval keys") + + # The bootstrap keys can also be deserialized by an explicit list of automorphism indices. + # Here we take the indices of the keys just loaded and load the same file again through the + # by-index overload. + bootstrap_key_indices = client_cc.GetExistingEvalAutomorphismKeyIndices(secret_key.GetKeyTag()) + error_check(DeserializeEvalBootstrapKey(datafolder + bootstrap_key_location, BINARY, + secret_key.GetKeyTag(), bootstrap_key_indices), + "Error deserializing bootstrap eval keys by index list") + + ciphertext_after = client_cc.EvalBootstrap(client_ciphertext) + + result = client_cc.Decrypt(secret_key, ciphertext_after) + result.SetLength(len(x)) + + print(f"Input: {plaintext}") + print(f"Output after deserialized-key bootstrapping: {result}") + + client_cc.ClearStaticMapsAndVectors() + + +def main(): + global datafolder + with tempfile.TemporaryDirectory() as td: + datafolder = td + "/" + datafolder + os.mkdir(datafolder) + main_action() + + +if __name__ == "__main__": + main() diff --git a/examples/pke/ckks-noise-flooding.py b/examples/pke/ckks-noise-flooding.py new file mode 100644 index 0000000..6992683 --- /dev/null +++ b/examples/pke/ckks-noise-flooding.py @@ -0,0 +1,159 @@ +# +# Please see CKKS_NOISE_FLOODING.md for technical details on CKKS noise flooding for the INDCPA^D scenario. +# +# Example for using CKKS with the experimental NOISE_FLOODING_DECRYPT mode. We do not recommend +# this mode for production yet. This experimental mode gives us equivalent security levels to +# BGV and BFV, but it requires the user to run all encrypted operations twice. The first iteration +# is a preliminary run to measure noise, and the second iteration is the actual run, which +# will input the noise as a parameter. We use the noise to enhance security within decryption. +# +# Note that a user can choose to run the first computation with NATIVE_SIZE = 64 to estimate noise, +# and the second computation with NATIVE_SIZE = 128, if they wish. This would require a +# different set of binaries: first, with NATIVE_SIZE = 64 and the second one with NATIVE_SIZE = 128. +# It can be considered as an optimization for the case when we need NATIVE_SIZE = 128. +# +# For NATIVE_SIZE=128, we automatically choose the scaling mod size and first mod size in the second iteration +# based on the input noise estimate. This means that we currently do not support bootstrapping in the +# NOISE_FLOODING_DECRYPT mode, since the scaling mod size and first mod size affect the noise estimate for +# bootstrapping. We plan to add support for bootstrapping in NOISE_FLOODING_DECRYPT mode in a future release. +# + +from openfhe import * + + +def ckks_noise_flooding_demo(): + # ----------------------- Setup first CryptoContext ----------------------------- + # Phase 1 will be for noise estimation. + # ------------------------------------------------------------------------------- + print("---------------------------------- PHASE 1: NOISE ESTIMATION ----------------------------------") + parameters_noise_estimation = CCParamsCKKSRNS() + # EXEC_NOISE_ESTIMATION indicates that the resulting plaintext will estimate the amount of noise in the computation. + parameters_noise_estimation.SetExecutionMode(ExecutionMode.EXEC_NOISE_ESTIMATION) + + crypto_context_noise_estimation = get_crypto_context(parameters_noise_estimation) + + ring_dim = crypto_context_noise_estimation.GetRingDimension() + print(f"CKKS scheme is using ring dimension {ring_dim}\n") + + # Key Generation + key_pair_noise_estimation = crypto_context_noise_estimation.KeyGen() + crypto_context_noise_estimation.EvalMultKeyGen(key_pair_noise_estimation.secretKey) + + # We run the encrypted computation the first time. + noise_ciphertext = encrypted_computation(crypto_context_noise_estimation, key_pair_noise_estimation.publicKey) + + # Decrypt noise + noise_plaintext = crypto_context_noise_estimation.Decrypt(key_pair_noise_estimation.secretKey, noise_ciphertext) + noise = noise_plaintext.GetLogError() + print(f"Noise \n\t{noise}") + + # ----------------------- Setup second CryptoContext ----------------------------- + # Phase 2 will be for the actual evaluation. + # IMPORTANT: We must use a different public/private key pair here to achieve the + # security guarantees for noise flooding. + # ------------------------------------------------------------------------------- + print("---------------------------------- PHASE 2: EVALUATION ----------------------------------") + parameters_evaluation = CCParamsCKKSRNS() + # EXEC_EVALUATION indicates that we are in phase 2 of computation, and will obtain the actual result. + parameters_evaluation.SetExecutionMode(ExecutionMode.EXEC_EVALUATION) + # Here, we set the noise of our previous computation + parameters_evaluation.SetNoiseEstimate(noise) + + # We can set our desired precision for 128-bit CKKS only. For NATIVE_SIZE=64, we ignore this parameter. + parameters_evaluation.SetDesiredPrecision(25) + + # We can set the statistical security and number of adversarial queries, but we can also + # leave these lines out, as we are setting them to the default values here. + parameters_evaluation.SetStatisticalSecurity(30) + parameters_evaluation.SetNumAdversarialQueries(1) + + # The remaining parameters must be the same as the first CryptoContext. Note that we can choose to run the + # first computation with NATIVEINT = 64 to estimate noise, and the second computation with NATIVEINT = 128, + # or vice versa, if we wish. + crypto_context_evaluation = get_crypto_context(parameters_evaluation) + + # IMPORTANT: Generate new keys + key_pair_evaluation = crypto_context_evaluation.KeyGen() + crypto_context_evaluation.EvalMultKeyGen(key_pair_evaluation.secretKey) + + # We run the encrypted computation the second time. + ciphertext_result = encrypted_computation(crypto_context_evaluation, key_pair_evaluation.publicKey) + + # Decrypt final result + result = crypto_context_evaluation.Decrypt(key_pair_evaluation.secretKey, ciphertext_result) + vec_size = 8 + result.SetLength(vec_size) + print(f"Final output \n\t{result.GetCKKSPackedValue()}") + + expected_result = [1.01, 1.04, 0, 0, 1.25, 0, 0, 1.64] + print(f"Expected result\n\t {expected_result}") + + +def get_crypto_context(parameters): + # We recommend putting part of the CryptoContext inside a function because + # you must make sure all parameters are the same, except EXECUTION_MODE and NOISE_ESTIMATE. + + # This demo is to illustrate how to use the security mode NOISE_FLOODING_DECRYPT to achieve enhanced security. + parameters.SetDecryptionNoiseMode(DecryptionNoiseMode.NOISE_FLOODING_DECRYPT) + + # Specify main parameters + parameters.SetSecretKeyDist(SecretKeyDist.UNIFORM_TERNARY) + + # Desired security level based on FHE standards. Note that this is different than NoiseDecryptionMode, + # which also gives us enhanced security in CKKS when using NOISE_FLOODING_DECRYPT. + # We must always use the same ring dimension in both iterations, so we set the security level to HEStd_NotSet, + # and manually set the ring dimension. + parameters.SetSecurityLevel(SecurityLevel.HEStd_NotSet) + parameters.SetRingDim(1 << 16) + + rescale_tech = ScalingTechnique.FIXEDAUTO + dcrt_bits = 59 + first_mod = 60 + + parameters.SetScalingTechnique(rescale_tech) + parameters.SetScalingModSize(dcrt_bits) + parameters.SetFirstModSize(first_mod) + + # In this example, we perform two multiplications and an addition. + parameters.SetMultiplicativeDepth(2) + + # Generate crypto context. + crypto_context = GenCryptoContext(parameters) + + # Enable features that you wish to use. + crypto_context.Enable(PKESchemeFeature.PKE) + crypto_context.Enable(PKESchemeFeature.LEVELEDSHE) + + return crypto_context + + +def encrypted_computation(crypto_context, public_key): + # We recommend putting the encrypted computation you wish to perform inside a function because + # you have to perform it twice. In this example, we perform two multiplications and an addition. + # The first iteration will return a ciphertext that contains a noise measurement. + # The second iteration will return the actual encrypted computation. + + # Encoding and encryption of inputs + # Generate random input + vec1 = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8] + vec2 = [1, 1, 0, 0, 1, 0, 0, 1] + + # Encoding as plaintexts and encrypt + ptxt1 = crypto_context.MakeCKKSPackedPlaintext(vec1) + ptxt2 = crypto_context.MakeCKKSPackedPlaintext(vec2) + ciph1 = crypto_context.Encrypt(public_key, ptxt1) + ciph2 = crypto_context.Encrypt(public_key, ptxt2) + + ciph_mult = crypto_context.EvalMult(ciph1, ciph2) + ciph_mult2 = crypto_context.EvalMult(ciph_mult, ciph1) + ciph_result = crypto_context.EvalAdd(ciph_mult2, ciph2) + + return ciph_result + + +def main(): + ckks_noise_flooding_demo() + + +if __name__ == "__main__": + main() diff --git a/examples/pke/ckks-release-memory.py b/examples/pke/ckks-release-memory.py new file mode 100644 index 0000000..a6cd2dd --- /dev/null +++ b/examples/pke/ckks-release-memory.py @@ -0,0 +1,83 @@ +# +# Example for checking CKKS bootstrap memory cleanup. +# +# EvalBootstrapSetup() caches precomputed data in the CKKS scheme. This example +# compares ClearBootstrapPrecom() with ReleaseAllContexts() while keeping the +# local CryptoContext handle alive. +# +# Note: unlike the C++ version, which probes the allocator directly (mallinfo2), +# this Python version uses the process resident set size (VmRSS on Linux) as the +# memory probe, so the absolute numbers differ, but the effect of +# ClearBootstrapPrecom()/ReleaseAllContexts() is still visible. +# + +from openfhe import * +import sys + + +def heap_in_use_bytes(): + try: + with open("/proc/self/status") as f: + for line in f: + if line.startswith("VmRSS:"): + return int(line.split()[1]) * 1024 + except OSError: + pass + raise Exception("memory probe unavailable on this platform") + + +def build_bootstrap_context(): + parameters = CCParamsCKKSRNS() + # SPARSE_ENCAPSULATED is recommended for CKKS bootstrapping (probability of failure below 2^-128). UNIFORM_TERNARY, + # used here, is the distribution of the homomorphic encryption security guidelines; its probability of failure is + # 2^-67 for N = 2^16 and 2^-27 for N = 2^17 with full packing. + sk_dist = SecretKeyDist.UNIFORM_TERNARY + parameters.SetSecretKeyDist(sk_dist) + parameters.SetSecurityLevel(SecurityLevel.HEStd_NotSet) + parameters.SetRingDim(1 << 12) + + parameters.SetScalingModSize(59) + parameters.SetFirstModSize(60) + parameters.SetScalingTechnique(ScalingTechnique.FLEXIBLEAUTO) + + level_budget = [4, 4] + depth = 10 + FHECKKSRNS.GetBootstrapDepth(level_budget, sk_dist) + parameters.SetMultiplicativeDepth(depth) + + cc = GenCryptoContext(parameters) + cc.Enable(PKESchemeFeature.PKE) + cc.Enable(PKESchemeFeature.KEYSWITCH) + cc.Enable(PKESchemeFeature.LEVELEDSHE) + cc.Enable(PKESchemeFeature.ADVANCEDSHE) + cc.Enable(PKESchemeFeature.FHE) + return cc + + +def main(): + cc = build_bootstrap_context() + ring_dim = cc.GetRingDimension() + num_slots = ring_dim // 2 + + print(f"ring dim = {ring_dim}") + + before = heap_in_use_bytes() + cc.EvalBootstrapSetup([4, 4], [0, 0], num_slots) + after = heap_in_use_bytes() + cc.ClearBootstrapPrecom() + after_cleanup = heap_in_use_bytes() + print(f"EvalBootstrapSetup(): before: {before}; after: {after}; " + f"after ClearBootstrapPrecom(): {after_cleanup}", file=sys.stderr) + + before = heap_in_use_bytes() + cc.EvalBootstrapSetup([4, 4], [0, 0], num_slots) + after = heap_in_use_bytes() + cc.ClearBootstrapPrecom() + after_cleanup = heap_in_use_bytes() + ReleaseAllContexts() + final = heap_in_use_bytes() + print(f"EvalBootstrapSetup(): before: {before}; after: {after}; " + f"after ClearBootstrapPrecom(): {after_cleanup}; after ReleaseAllContexts(): {final}", file=sys.stderr) + + +if __name__ == "__main__": + main() diff --git a/examples/pke/depth-bfvrns-behz.py b/examples/pke/depth-bfvrns-behz.py new file mode 100644 index 0000000..63d6b75 --- /dev/null +++ b/examples/pke/depth-bfvrns-behz.py @@ -0,0 +1,210 @@ +# +# Example of a computation circuit of depth 3 +# + +from openfhe import * +import math +import time + + +def main(): + ############################################################ + # Set-up of parameters + ############################################################ + + print("\nThis code demonstrates the use of the BEHZ scheme for homomorphic multiplication. ") + print("This code shows how to auto-generate parameters during run-time based on desired plaintext moduli and security levels. ") + print("In this demonstration we use three input plaintext and show how to both add them together and multiply them together. ") + + parameters = CCParamsBFVRNS() + parameters.SetPlaintextModulus(536903681) + parameters.SetMultiplicativeDepth(3) + parameters.SetMaxRelinSkDeg(3) + parameters.SetScalingModSize(60) + parameters.SetMultiplicationTechnique(MultiplicationTechnique.BEHZ) + + crypto_context = GenCryptoContext(parameters) + # enable features that you wish to use + crypto_context.Enable(PKESchemeFeature.PKE) + crypto_context.Enable(PKESchemeFeature.KEYSWITCH) + crypto_context.Enable(PKESchemeFeature.LEVELEDSHE) + crypto_context.Enable(PKESchemeFeature.ADVANCEDSHE) + + print(f"\np = {crypto_context.GetPlaintextModulus()}") + print(f"n = {crypto_context.GetCyclotomicOrder() // 2}") + print(f"log2 q = {math.log2(crypto_context.GetModulus())}") + + ############################################################ + # Perform Key Generation Operation + ############################################################ + + print("\nRunning key generation (used for source data)...") + + t = time.time() + key_pair = crypto_context.KeyGen() + processing_time = (time.time() - t) * 1000 + print(f"Key generation time: {processing_time}ms") + + print("Running key generation for homomorphic multiplication evaluation keys...") + + t = time.time() + crypto_context.EvalMultKeysGen(key_pair.secretKey) + processing_time = (time.time() - t) * 1000 + print(f"Key generation time for homomorphic multiplication evaluation keys: {processing_time}ms") + + ############################################################ + # Encode source data + ############################################################ + + vector_of_ints1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] + plaintext1 = crypto_context.MakePackedPlaintext(vector_of_ints1) + + vector_of_ints2 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] + plaintext2 = crypto_context.MakePackedPlaintext(vector_of_ints2) + + vector_of_ints3 = [2, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] + plaintext3 = crypto_context.MakePackedPlaintext(vector_of_ints3) + + vector_of_ints4 = [2, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] + plaintext4 = crypto_context.MakePackedPlaintext(vector_of_ints4) + + vector_of_ints5 = [3, 2, 1, 4, 5, 6, 7, 8, 9, 10, 11, 12] + plaintext5 = crypto_context.MakePackedPlaintext(vector_of_ints5) + + vector_of_ints6 = [3, 2, 1, 4, 5, 6, 7, 8, 9, 10, 11, 12] + plaintext6 = crypto_context.MakePackedPlaintext(vector_of_ints6) + + vector_of_ints7 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] + plaintext7 = crypto_context.MakePackedPlaintext(vector_of_ints7) + + print(f"\nOriginal Plaintext #1: \n{plaintext1}") + print(f"\nOriginal Plaintext #2: \n{plaintext2}") + print(f"\nOriginal Plaintext #3: \n{plaintext3}") + print(f"\nOriginal Plaintext #4: \n{plaintext4}") + print(f"\nOriginal Plaintext #5: \n{plaintext5}") + print(f"\nOriginal Plaintext #6: \n{plaintext6}") + print(f"\nOriginal Plaintext #7: \n{plaintext7}") + + ############################################################ + # Encryption + ############################################################ + + print("\nRunning encryption of all plaintexts... ", end="") + + ciphertexts = [] + + t = time.time() + ciphertexts.append(crypto_context.Encrypt(key_pair.publicKey, plaintext1)) + ciphertexts.append(crypto_context.Encrypt(key_pair.publicKey, plaintext2)) + ciphertexts.append(crypto_context.Encrypt(key_pair.publicKey, plaintext3)) + ciphertexts.append(crypto_context.Encrypt(key_pair.publicKey, plaintext4)) + ciphertexts.append(crypto_context.Encrypt(key_pair.publicKey, plaintext5)) + ciphertexts.append(crypto_context.Encrypt(key_pair.publicKey, plaintext6)) + ciphertexts.append(crypto_context.Encrypt(key_pair.publicKey, plaintext7)) + processing_time = (time.time() - t) * 1000 + + print("Completed\n") + print(f"\nAverage encryption time: {processing_time / 7}ms") + + ############################################################ + # Homomorphic multiplication of 2 ciphertexts + ############################################################ + + t = time.time() + ciphertext_mult = crypto_context.EvalMult(ciphertexts[0], ciphertexts[1]) + processing_time = (time.time() - t) * 1000 + print(f"\nTotal time of multiplying 2 ciphertexts using EvalMult w/ relinearization: {processing_time}ms") + + t = time.time() + plaintext_dec_mult = crypto_context.Decrypt(key_pair.secretKey, ciphertext_mult) + processing_time = (time.time() - t) * 1000 + print(f"\nDecryption time: {processing_time}ms") + + plaintext_dec_mult.SetLength(plaintext1.GetLength()) + + print("\nResult of homomorphic multiplication of ciphertexts #1 and #2: ") + print(plaintext_dec_mult) + + ############################################################ + # Homomorphic multiplication of 7 ciphertexts + ############################################################ + + print("\nRunning a binary-tree multiplication of 7 ciphertexts...", end="") + + t = time.time() + ciphertext_mult7 = crypto_context.EvalMultMany(ciphertexts) + processing_time = (time.time() - t) * 1000 + + print("Completed\n") + print(f"\nTotal time of multiplying 7 ciphertexts using EvalMultMany: {processing_time}ms") + + plaintext_dec_mult7 = crypto_context.Decrypt(key_pair.secretKey, ciphertext_mult7) + plaintext_dec_mult7.SetLength(plaintext1.GetLength()) + + print("\nResult of 6 homomorphic multiplications: ") + print(plaintext_dec_mult7) + + ############################################################ + # Homomorphic multiplication of 3 ciphertexts where relinearization is done + # at the end + ############################################################ + + print("\nRunning a depth-3 multiplication w/o relinearization until the very end...", end="") + + t = time.time() + ciphertext_mult12 = crypto_context.EvalMultNoRelin(ciphertexts[0], ciphertexts[1]) + processing_time = (time.time() - t) * 1000 + + print("Completed\n") + print(f"Time of multiplying 2 ciphertexts w/o relinearization: {processing_time}ms") + + ciphertext_mult123 = crypto_context.EvalMultAndRelinearize(ciphertext_mult12, ciphertexts[2]) + + plaintext_dec_mult123 = crypto_context.Decrypt(key_pair.secretKey, ciphertext_mult123) + plaintext_dec_mult123.SetLength(plaintext1.GetLength()) + + print("\nResult of 3 homomorphic multiplications: ") + print(plaintext_dec_mult123) + + ############################################################ + # Homomorphic multiplication of 3 ciphertexts w/o any relinearization + ############################################################ + + print("\nRunning a depth-3 multiplication w/o relinearization...", end="") + + ciphertext_mult12 = crypto_context.EvalMultNoRelin(ciphertexts[0], ciphertexts[1]) + ciphertext_mult123 = crypto_context.EvalMultNoRelin(ciphertext_mult12, ciphertexts[2]) + + print("Completed\n") + + plaintext_dec_mult123 = crypto_context.Decrypt(key_pair.secretKey, ciphertext_mult123) + plaintext_dec_mult123.SetLength(plaintext1.GetLength()) + + print("\nResult of 3 homomorphic multiplications: ") + print(plaintext_dec_mult123) + + ############################################################ + # Homomorphic multiplication of 3 ciphertexts w/ relinearization after each + # multiplication + ############################################################ + + print("\nRunning a depth-3 multiplication w/ relinearization after each multiplication...", end="") + + t = time.time() + ciphertext_mult12 = crypto_context.EvalMult(ciphertexts[0], ciphertexts[1]) + processing_time = (time.time() - t) * 1000 + + print("Completed\n") + print(f"Time of multiplying 2 ciphertexts w/ relinearization: {processing_time}ms") + + ciphertext_mult123 = crypto_context.EvalMult(ciphertext_mult12, ciphertexts[2]) + + plaintext_dec_mult123 = crypto_context.Decrypt(key_pair.secretKey, ciphertext_mult123) + plaintext_dec_mult123.SetLength(plaintext1.GetLength()) + + print("\nResult of 3 homomorphic multiplications: ") + print(plaintext_dec_mult123) + + +if __name__ == "__main__": + main() diff --git a/examples/pke/depth-bfvrns.py b/examples/pke/depth-bfvrns.py new file mode 100644 index 0000000..aac5e1c --- /dev/null +++ b/examples/pke/depth-bfvrns.py @@ -0,0 +1,210 @@ +# +# Example of a computation circuit of depth 3. +# BFVrns demo for a homomorphic multiplication of depth 6 and three different +# approaches for depth-3 multiplications +# + +from openfhe import * +import math +import time + + +def main(): + ############################################################ + # Set-up of parameters + ############################################################ + + print("\nThis code demonstrates the use of the BFVrns scheme for homomorphic multiplication. ") + print("This code shows how to auto-generate parameters during run-time based on desired plaintext moduli and security levels. ") + print("In this demonstration we use three input plaintext and show how to both add them together and multiply them together. ") + + parameters = CCParamsBFVRNS() + parameters.SetPlaintextModulus(536903681) + parameters.SetMultiplicativeDepth(3) + parameters.SetMaxRelinSkDeg(3) + + crypto_context = GenCryptoContext(parameters) + # enable features that you wish to use + crypto_context.Enable(PKESchemeFeature.PKE) + crypto_context.Enable(PKESchemeFeature.KEYSWITCH) + crypto_context.Enable(PKESchemeFeature.LEVELEDSHE) + crypto_context.Enable(PKESchemeFeature.ADVANCEDSHE) + + print(f"\np = {crypto_context.GetPlaintextModulus()}") + print(f"n = {crypto_context.GetCyclotomicOrder() // 2}") + print(f"log2 q = {math.log2(crypto_context.GetModulus())}") + + ############################################################ + # Perform Key Generation Operation + ############################################################ + + print("\nRunning key generation (used for source data)...") + + t = time.time() + key_pair = crypto_context.KeyGen() + processing_time = (time.time() - t) * 1000 + print(f"Key generation time: {processing_time}ms") + + print("Running key generation for homomorphic multiplication evaluation keys...") + + t = time.time() + crypto_context.EvalMultKeysGen(key_pair.secretKey) + processing_time = (time.time() - t) * 1000 + print(f"Key generation time for homomorphic multiplication evaluation keys: {processing_time}ms") + + ############################################################ + # Encode source data + ############################################################ + + vector_of_ints1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] + plaintext1 = crypto_context.MakePackedPlaintext(vector_of_ints1) + + vector_of_ints2 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] + plaintext2 = crypto_context.MakePackedPlaintext(vector_of_ints2) + + vector_of_ints3 = [2, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] + plaintext3 = crypto_context.MakePackedPlaintext(vector_of_ints3) + + vector_of_ints4 = [2, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] + plaintext4 = crypto_context.MakePackedPlaintext(vector_of_ints4) + + vector_of_ints5 = [3, 2, 1, 4, 5, 6, 7, 8, 9, 10, 11, 12] + plaintext5 = crypto_context.MakePackedPlaintext(vector_of_ints5) + + vector_of_ints6 = [3, 2, 1, 4, 5, 6, 7, 8, 9, 10, 11, 12] + plaintext6 = crypto_context.MakePackedPlaintext(vector_of_ints6) + + vector_of_ints7 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] + plaintext7 = crypto_context.MakePackedPlaintext(vector_of_ints7) + + print(f"\nOriginal Plaintext #1: \n{plaintext1}") + print(f"\nOriginal Plaintext #2: \n{plaintext2}") + print(f"\nOriginal Plaintext #3: \n{plaintext3}") + print(f"\nOriginal Plaintext #4: \n{plaintext4}") + print(f"\nOriginal Plaintext #5: \n{plaintext5}") + print(f"\nOriginal Plaintext #6: \n{plaintext6}") + print(f"\nOriginal Plaintext #7: \n{plaintext7}") + + ############################################################ + # Encryption + ############################################################ + + print("\nRunning encryption of all plaintexts... ", end="") + + ciphertexts = [] + + t = time.time() + ciphertexts.append(crypto_context.Encrypt(key_pair.publicKey, plaintext1)) + ciphertexts.append(crypto_context.Encrypt(key_pair.publicKey, plaintext2)) + ciphertexts.append(crypto_context.Encrypt(key_pair.publicKey, plaintext3)) + ciphertexts.append(crypto_context.Encrypt(key_pair.publicKey, plaintext4)) + ciphertexts.append(crypto_context.Encrypt(key_pair.publicKey, plaintext5)) + ciphertexts.append(crypto_context.Encrypt(key_pair.publicKey, plaintext6)) + ciphertexts.append(crypto_context.Encrypt(key_pair.publicKey, plaintext7)) + processing_time = (time.time() - t) * 1000 + + print("Completed\n") + print(f"\nAverage encryption time: {processing_time / 7}ms") + + ############################################################ + # Homomorphic multiplication of 2 ciphertexts + ############################################################ + + t = time.time() + ciphertext_mult = crypto_context.EvalMult(ciphertexts[0], ciphertexts[1]) + processing_time = (time.time() - t) * 1000 + print(f"\nTotal time of multiplying 2 ciphertexts using EvalMult w/ relinearization: {processing_time}ms") + + t = time.time() + plaintext_dec_mult = crypto_context.Decrypt(key_pair.secretKey, ciphertext_mult) + processing_time = (time.time() - t) * 1000 + print(f"\nDecryption time: {processing_time}ms") + + plaintext_dec_mult.SetLength(plaintext1.GetLength()) + + print("\nResult of homomorphic multiplication of ciphertexts #1 and #2: ") + print(plaintext_dec_mult) + + ############################################################ + # Homomorphic multiplication of 7 ciphertexts + ############################################################ + + print("\nRunning a binary-tree multiplication of 7 ciphertexts...", end="") + + t = time.time() + ciphertext_mult7 = crypto_context.EvalMultMany(ciphertexts) + processing_time = (time.time() - t) * 1000 + + print("Completed\n") + print(f"\nTotal time of multiplying 7 ciphertexts using EvalMultMany: {processing_time}ms") + + plaintext_dec_mult7 = crypto_context.Decrypt(key_pair.secretKey, ciphertext_mult7) + plaintext_dec_mult7.SetLength(plaintext1.GetLength()) + + print("\nResult of 6 homomorphic multiplications: ") + print(plaintext_dec_mult7) + + ############################################################ + # Homomorphic multiplication of 3 ciphertexts where relinearization is done + # at the end + ############################################################ + + print("\nRunning a depth-3 multiplication w/o relinearization until the very end...", end="") + + t = time.time() + ciphertext_mult12 = crypto_context.EvalMultNoRelin(ciphertexts[0], ciphertexts[1]) + processing_time = (time.time() - t) * 1000 + + print("Completed\n") + print(f"Time of multiplying 2 ciphertexts w/o relinearization: {processing_time}ms") + + ciphertext_mult123 = crypto_context.EvalMultAndRelinearize(ciphertext_mult12, ciphertexts[2]) + + plaintext_dec_mult123 = crypto_context.Decrypt(key_pair.secretKey, ciphertext_mult123) + plaintext_dec_mult123.SetLength(plaintext1.GetLength()) + + print("\nResult of 3 homomorphic multiplications: ") + print(plaintext_dec_mult123) + + ############################################################ + # Homomorphic multiplication of 3 ciphertexts w/o any relinearization + ############################################################ + + print("\nRunning a depth-3 multiplication w/o relinearization...", end="") + + ciphertext_mult12 = crypto_context.EvalMultNoRelin(ciphertexts[0], ciphertexts[1]) + ciphertext_mult123 = crypto_context.EvalMultNoRelin(ciphertext_mult12, ciphertexts[2]) + + print("Completed\n") + + plaintext_dec_mult123 = crypto_context.Decrypt(key_pair.secretKey, ciphertext_mult123) + plaintext_dec_mult123.SetLength(plaintext1.GetLength()) + + print("\nResult of 3 homomorphic multiplications: ") + print(plaintext_dec_mult123) + + ############################################################ + # Homomorphic multiplication of 3 ciphertexts w/ relinearization after each + # multiplication + ############################################################ + + print("\nRunning a depth-3 multiplication w/ relinearization after each multiplication...", end="") + + t = time.time() + ciphertext_mult12 = crypto_context.EvalMult(ciphertexts[0], ciphertexts[1]) + processing_time = (time.time() - t) * 1000 + + print("Completed\n") + print(f"Time of multiplying 2 ciphertexts w/ relinearization: {processing_time}ms") + + ciphertext_mult123 = crypto_context.EvalMult(ciphertext_mult12, ciphertexts[2]) + + plaintext_dec_mult123 = crypto_context.Decrypt(key_pair.secretKey, ciphertext_mult123) + plaintext_dec_mult123.SetLength(plaintext1.GetLength()) + + print("\nResult of 3 homomorphic multiplications: ") + print(plaintext_dec_mult123) + + +if __name__ == "__main__": + main() diff --git a/examples/pke/depth-bgvrns.py b/examples/pke/depth-bgvrns.py new file mode 100644 index 0000000..3659d8b --- /dev/null +++ b/examples/pke/depth-bgvrns.py @@ -0,0 +1,221 @@ +# +# Example of a computation circuit of depth 3 +# BGVrns demo for a homomorphic multiplication of depth 6 and three different +# approaches for depth-3 multiplications +# + +from openfhe import * +import math +import time + + +def main(): + ############################################################ + # Set-up of parameters + ############################################################ + + print("\nThis code demonstrates the use of the BGVrns scheme for homomorphic multiplication. ") + print("This code shows how to auto-generate parameters during run-time based on desired plaintext moduli and security levels. ") + print("In this demonstration we use three input plaintexts and show how to both add them together and multiply them together.\n") + + # Crypto Parameters + # # of evalMults = 3 (first 3) is used to support the multiplication of 7 + # ciphertexts, i.e., ceiling{log2{7}} Max depth is set to 3 (second 3) to + # generate homomorphic evaluation multiplication keys for s^2 and s^3 + parameters = CCParamsBGVRNS() + parameters.SetMultiplicativeDepth(3) + parameters.SetPlaintextModulus(536903681) + parameters.SetMaxRelinSkDeg(3) + + crypto_context = GenCryptoContext(parameters) + # enable features that you wish to use + crypto_context.Enable(PKESchemeFeature.PKE) + crypto_context.Enable(PKESchemeFeature.KEYSWITCH) + crypto_context.Enable(PKESchemeFeature.LEVELEDSHE) + crypto_context.Enable(PKESchemeFeature.ADVANCEDSHE) + + print(f"\np = {crypto_context.GetPlaintextModulus()}") + print(f"n = {crypto_context.GetCyclotomicOrder() // 2}") + print(f"log2 q = {math.log2(crypto_context.GetModulus())}") + + ############################################################ + # Perform Key Generation Operation + ############################################################ + + print("\nRunning key generation (used for source data)...") + + t = time.time() + key_pair = crypto_context.KeyGen() + processing_time = (time.time() - t) * 1000 + print(f"Key generation time: {processing_time}ms") + + print("Running key generation for homomorphic multiplication evaluation keys...") + + t = time.time() + crypto_context.EvalMultKeysGen(key_pair.secretKey) + processing_time = (time.time() - t) * 1000 + print(f"Key generation time for homomorphic multiplication evaluation keys: {processing_time}ms") + + ############################################################ + # Encode source data + ############################################################ + + vector_of_ints1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] + plaintext1 = crypto_context.MakePackedPlaintext(vector_of_ints1) + + vector_of_ints2 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] + plaintext2 = crypto_context.MakePackedPlaintext(vector_of_ints2) + + vector_of_ints3 = [2, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] + plaintext3 = crypto_context.MakePackedPlaintext(vector_of_ints3) + + vector_of_ints4 = [2, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] + plaintext4 = crypto_context.MakePackedPlaintext(vector_of_ints4) + + vector_of_ints5 = [3, 2, 1, 4, 5, 6, 7, 8, 9, 10, 11, 12] + plaintext5 = crypto_context.MakePackedPlaintext(vector_of_ints5) + + vector_of_ints6 = [3, 2, 1, 4, 5, 6, 7, 8, 9, 10, 11, 12] + plaintext6 = crypto_context.MakePackedPlaintext(vector_of_ints6) + + vector_of_ints7 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] + plaintext7 = crypto_context.MakePackedPlaintext(vector_of_ints7) + + print(f"\nOriginal Plaintext #1: \n{plaintext1}") + print(f"\nOriginal Plaintext #2: \n{plaintext2}") + print(f"\nOriginal Plaintext #3: \n{plaintext3}") + print(f"\nOriginal Plaintext #4: \n{plaintext4}") + print(f"\nOriginal Plaintext #5: \n{plaintext5}") + print(f"\nOriginal Plaintext #6: \n{plaintext6}") + print(f"\nOriginal Plaintext #7: \n{plaintext7}") + + ############################################################ + # Encryption + ############################################################ + + print("\nRunning encryption of all plaintexts... ", end="") + + ciphertexts = [] + + t = time.time() + ciphertexts.append(crypto_context.Encrypt(key_pair.publicKey, plaintext1)) + ciphertexts.append(crypto_context.Encrypt(key_pair.publicKey, plaintext2)) + ciphertexts.append(crypto_context.Encrypt(key_pair.publicKey, plaintext3)) + ciphertexts.append(crypto_context.Encrypt(key_pair.publicKey, plaintext4)) + ciphertexts.append(crypto_context.Encrypt(key_pair.publicKey, plaintext5)) + ciphertexts.append(crypto_context.Encrypt(key_pair.publicKey, plaintext6)) + ciphertexts.append(crypto_context.Encrypt(key_pair.publicKey, plaintext7)) + processing_time = (time.time() - t) * 1000 + + print("Completed\n") + print(f"\nAverage encryption time: {processing_time / 7}ms") + + ############################################################ + # Homomorphic multiplication of 2 ciphertexts + ############################################################ + + t = time.time() + ciphertext_mult = crypto_context.EvalMult(ciphertexts[0], ciphertexts[1]) + processing_time = (time.time() - t) * 1000 + print(f"\nTotal time of multiplying 2 ciphertexts using EvalMult w/ relinearization: {processing_time}ms") + + t = time.time() + plaintext_dec_mult = crypto_context.Decrypt(key_pair.secretKey, ciphertext_mult) + processing_time = (time.time() - t) * 1000 + print(f"\nDecryption time: {processing_time}ms") + + plaintext_dec_mult.SetLength(plaintext1.GetLength()) + + print("\nResult of homomorphic multiplication of ciphertexts #1 and #2: ") + print(plaintext_dec_mult) + + ############################################################ + # Homomorphic multiplication of 7 ciphertexts + ############################################################ + + print("\nRunning a binary-tree multiplication of 7 ciphertexts...", end="") + + t = time.time() + ciphertext_mult7 = crypto_context.EvalMultMany(ciphertexts) + processing_time = (time.time() - t) * 1000 + + print("Completed\n") + print(f"\nTotal time of multiplying 7 ciphertexts using EvalMultMany: {processing_time}ms") + + plaintext_dec_mult7 = crypto_context.Decrypt(key_pair.secretKey, ciphertext_mult7) + plaintext_dec_mult7.SetLength(plaintext1.GetLength()) + + print("\nResult of 6 homomorphic multiplications: ") + print(plaintext_dec_mult7) + + ############################################################ + # Homomorphic multiplication of 3 ciphertexts where relinearization is done + # at the end + ############################################################ + + print("\nRunning a depth-3 multiplication w/o relinearization until the very end...", end="") + + t = time.time() + ciphertext_mult12 = crypto_context.EvalMultNoRelin(ciphertexts[0], ciphertexts[1]) + processing_time = (time.time() - t) * 1000 + + print("Completed\n") + print(f"Time of multiplying 2 ciphertexts w/o relinearization: {processing_time}ms") + + crypto_context.ModReduceInPlace(ciphertext_mult12) + + ciphertexts2 = crypto_context.ModReduce(ciphertexts[2]) + + ciphertext_mult123 = crypto_context.EvalMultAndRelinearize(ciphertext_mult12, ciphertexts2) + + plaintext_dec_mult123 = crypto_context.Decrypt(key_pair.secretKey, ciphertext_mult123) + plaintext_dec_mult123.SetLength(plaintext1.GetLength()) + + print("\nResult of 2 homomorphic multiplications: ") + print(plaintext_dec_mult123) + + ############################################################ + # Homomorphic multiplication of 3 ciphertexts w/o any relinearization + ############################################################ + + print("\nRunning a depth-3 multiplication w/o relinearization...", end="") + + ciphertext_mult12 = crypto_context.EvalMultNoRelin(ciphertexts[0], ciphertexts[1]) + crypto_context.ModReduceInPlace(ciphertext_mult12) + ciphertext_mult123 = crypto_context.EvalMultNoRelin(ciphertext_mult12, ciphertexts2) + ciphertext_mult123 = crypto_context.ModReduce(ciphertext_mult123) + + print("Completed\n") + + plaintext_dec_mult123 = crypto_context.Decrypt(key_pair.secretKey, ciphertext_mult123) + plaintext_dec_mult123.SetLength(plaintext1.GetLength()) + + print("\nResult of 3 homomorphic multiplications: ") + print(plaintext_dec_mult123) + + ############################################################ + # Homomorphic multiplication of 3 ciphertexts w/ relinearization after each + # multiplication + ############################################################ + + print("\nRunning a depth-3 multiplication w/ relinearization after each multiplication...", end="") + + t = time.time() + ciphertext_mult12 = crypto_context.EvalMult(ciphertexts[0], ciphertexts[1]) + crypto_context.ModReduceInPlace(ciphertext_mult12) + processing_time = (time.time() - t) * 1000 + + print("Completed\n") + print(f"Time for multiplying 3 ciphertexts w/ relinearization: {processing_time}ms") + + ciphertext_mult123 = crypto_context.EvalMult(ciphertext_mult12, ciphertexts2) + + plaintext_dec_mult123 = crypto_context.Decrypt(key_pair.secretKey, ciphertext_mult123) + plaintext_dec_mult123.SetLength(plaintext1.GetLength()) + + print("\nResult of 3 homomorphic multiplications: ") + print(plaintext_dec_mult123) + + +if __name__ == "__main__": + main() diff --git a/examples/pke/functional-bootstrapping-ckks.py b/examples/pke/functional-bootstrapping-ckks.py new file mode 100644 index 0000000..caa2a01 --- /dev/null +++ b/examples/pke/functional-bootstrapping-ckks.py @@ -0,0 +1,508 @@ +# +# Examples for functional bootstrapping for RLWE ciphertexts using CKKS. +# + +from openfhe import * + +QBFV_INIT = 1 << 60 +QBFV_INIT_LARGE = 1 << 80 + + +def fill(a, slots): + # Clones the current vector up to the size indicated by the 'slots' variable + return [a[i % len(a)] for i in range(slots)] + + +def rotate(a, index): + # Rotates the vector by the index amount (positive - left, negative - right) + slots = len(a) + index = index % slots + if index == 0: + return list(a) + return list(a[index:]) + list(a[:index]) + + +def rotate_two_halves(a, index): + # Rotates each half of the vector by the index amount, mimicking the BFV subring rotations + slots = len(a) + slots_half = slots >> 1 + index = index % slots_half + if index == 0: + return list(a) + result = [0] * slots + for i in range(slots_half - index): + result[i] = a[i + index] + for i in range(slots_half - index, slots_half): + result[i] = a[i + index - slots_half] + for i in range(slots_half, slots - index): + result[i] = a[i + index] + for i in range(slots - index, slots): + result[i] = a[i + index - slots_half] + return result + + +def arbitrary_lut(qbfv_init, p_input, p_output, Q, bigq, scale_thi, order, num_slots, ring_dim, func): + # 1. Figure out whether sparse packing or full packing should be used. + # numSlots represents the number of values to be encrypted in BFV. + # If this number is the same as the ring dimension, then the CKKS slots is half. + flag_sp = num_slots <= ring_dim // 2 # sparse packing + num_slots_ckks = num_slots if flag_sp else num_slots // 2 + + # 2. Input + x = [p_input // 2, p_input // 2 + 1, 0, 3, 16, 33, 64, p_input - 1] + print(f"First 8 elements of the input (repeated) up to size {num_slots}:") + print(x) + if len(x) < num_slots: + x = fill(x, num_slots) + + # 3. The case of Boolean LUTs using the first order Trigonometric Hermite Interpolation + # supports an optimized implementation. + # In particular, it supports real coefficients as opposed to complex coefficients. + # Therefore, we separate between this case and the general case. + # There is no need to scale the coefficients in the Boolean case. + # However, in the general case, it is recommended to scale down the Hermite + # coefficients in order to bring their magnitude close to one. This scaling + # is reverted later. + binary_lut = (p_input == 2) and (order == 1) + + if binary_lut: + # those are coefficients for [1, cos^2(pi x)], not [1, cos(2pi x)] as in the general case. + coeff = [func(1), func(0) - func(1)] + else: + coeff = GetHermiteTrigCoefficients(func, p_input, order, scale_thi) # divided by 2 + + # 4. Set up the cryptoparameters. + # The scaling factor in CKKS should have the same bit length as the RLWE ciphertext modulus. + # The number of levels to be reserved before and after the LUT evaluation should be specified. + # The secret key distribution for CKKS should be SPARSE_ENCAPSULATED (recommended), UNIFORM_TERNARY, + # or SPARSE_TERNARY (discouraged). + dcrt_bits = bigq.bit_length() - 1 + first_mod = bigq.bit_length() - 1 + levels_available_after_bootstrap = 0 + levels_available_before_bootstrap = 0 + dnum = 3 + secret_key_dist = SecretKeyDist.SPARSE_ENCAPSULATED + scal_tech = ScalingTechnique.FLEXIBLEAUTO + lvlb = [3, 3] + + parameters = CCParamsCKKSRNS() + parameters.SetSecretKeyDist(secret_key_dist) + parameters.SetSecurityLevel(SecurityLevel.HEStd_NotSet) + parameters.SetScalingModSize(dcrt_bits) + parameters.SetScalingTechnique(scal_tech) + parameters.SetFirstModSize(first_mod) + parameters.SetNumLargeDigits(dnum) + parameters.SetBatchSize(num_slots_ckks) + parameters.SetRingDim(ring_dim) + + depth = levels_available_after_bootstrap + FHECKKSRNS.GetFBTDepth(lvlb, coeff, p_input, order, secret_key_dist) + parameters.SetMultiplicativeDepth(depth) + + cc = GenCryptoContext(parameters) + cc.Enable(PKESchemeFeature.PKE) + cc.Enable(PKESchemeFeature.KEYSWITCH) + cc.Enable(PKESchemeFeature.LEVELEDSHE) + cc.Enable(PKESchemeFeature.ADVANCEDSHE) + cc.Enable(PKESchemeFeature.FHE) + + print(f"CKKS scheme is using ring dimension {cc.GetRingDimension()}, a multiplicative depth of {depth} " + f"and the {scal_tech} rescaling technique\n") + + # 5. Compute various moduli and scaling sizes, used for scheme conversions. + # Then generate the setup parameters and necessary keys. + key_pair = cc.KeyGen() + + cc.EvalFBTSetup(coeff, num_slots_ckks, p_input, p_output, bigq, key_pair.publicKey, [0, 0], lvlb, + levels_available_after_bootstrap, 0, order) + + cc.EvalBootstrapKeyGen(key_pair.secretKey, num_slots_ckks) + cc.EvalMultKeyGen(key_pair.secretKey) + + # 6. Perform encryption in the RLWE scheme, using a larger initial ciphertext modulus. + # Switching the modulus to a smaller ciphertext modulus helps offset the encryption error. + ep = SchemeletRLWEMP.GetElementParams(key_pair.secretKey, depth - (levels_available_before_bootstrap > 0)) + + ctxt_bfv = SchemeletRLWEMP.EncryptCoeff(x, qbfv_init, p_input, key_pair.secretKey, ep) + + SchemeletRLWEMP.ModSwitch(ctxt_bfv, Q, qbfv_init) + + # 7. Convert from the RLWE ciphertext to a CKKS ciphertext (both use the same secret key). + ctxt = SchemeletRLWEMP.ConvertRLWEToCKKS(cc, ctxt_bfv, key_pair.publicKey, bigq, num_slots_ckks, + depth - (levels_available_before_bootstrap > 0)) + + # 8. Apply the LUT over the ciphertext. + ctxt_after_fbt = cc.EvalFBT(ctxt, coeff, p_input.bit_length() - 1, ep.GetModulus(), scale_thi, 0, order) + + # 9. Convert the result back to RLWE. + polys = SchemeletRLWEMP.ConvertCKKSToRLWE(ctxt_after_fbt, Q) + + computed = SchemeletRLWEMP.DecryptCoeff(polys, Q, p_output, key_pair.secretKey, ep, num_slots_ckks, num_slots) + + print(f"First 8 elements of the obtained output % POutput: {computed[:8]}") + + exact = [func(elem) - p_output if func(elem) > p_output / 2.0 else func(elem) for elem in x] + + errors = [abs(e - c) % p_output for e, c in zip(exact, computed)] + print(f"Max absolute error obtained: {max(errors)}\n") + + +def multi_value_bootstrapping(qbfv_init, p_input, p_output, Q, bigq, scale_thi, order, num_slots, ring_dim, + levels_computation): + # 1. Figure out whether sparse packing or full packing should be used. + flag_sp = num_slots <= ring_dim // 2 # sparse packing + num_slots_ckks = num_slots if flag_sp else num_slots // 2 + + # 2. Distinct functions to compute over the same input. + a = p_input + b = p_output + + def func1(x): + return (x % a - a // 2) % b + + def func2(x): + return (x % a) % b + + # 3. Input + x = [p_input // 2, p_input // 2 + 1, 0, 3, 16, 33, 64, p_input - 1] + print(f"First 8 elements of the input (repeated) up to size {num_slots}:") + print(x) + if len(x) < num_slots: + x = fill(x, num_slots) + + # 4. Separate the optimized Boolean LUT case from the general case (see arbitrary_lut). + binary_lut = (p_input == 2) and (order == 1) + + if binary_lut: + coeff1 = [func1(1), func1(0) - func1(1)] + coeff2 = [func2(1), func2(0) - func2(1)] + else: + coeff1 = GetHermiteTrigCoefficients(func1, p_input, order, scale_thi) + coeff2 = GetHermiteTrigCoefficients(func2, p_input, order, scale_thi) + + # 5. Set up the cryptoparameters. + dcrt_bits = bigq.bit_length() - 1 + first_mod = bigq.bit_length() - 1 + levels_available_after_bootstrap = 0 + levels_available_before_bootstrap = 0 + dnum = 3 + secret_key_dist = SecretKeyDist.SPARSE_ENCAPSULATED + scal_tech = ScalingTechnique.FIXEDMANUAL + lvlb = [3, 3] + + parameters = CCParamsCKKSRNS() + parameters.SetSecretKeyDist(secret_key_dist) + parameters.SetSecurityLevel(SecurityLevel.HEStd_NotSet) + parameters.SetScalingModSize(dcrt_bits) + parameters.SetScalingTechnique(scal_tech) + parameters.SetFirstModSize(first_mod) + parameters.SetNumLargeDigits(dnum) + parameters.SetBatchSize(num_slots_ckks) + parameters.SetRingDim(ring_dim) + + depth = levels_available_after_bootstrap + levels_computation + \ + FHECKKSRNS.GetFBTDepth(lvlb, coeff1, p_input, order, secret_key_dist) + parameters.SetMultiplicativeDepth(depth) + + cc = GenCryptoContext(parameters) + cc.Enable(PKESchemeFeature.PKE) + cc.Enable(PKESchemeFeature.KEYSWITCH) + cc.Enable(PKESchemeFeature.LEVELEDSHE) + cc.Enable(PKESchemeFeature.ADVANCEDSHE) + cc.Enable(PKESchemeFeature.FHE) + + print(f"CKKS scheme is using ring dimension {cc.GetRingDimension()}, a multiplicative depth of {depth} " + f"and the {scal_tech} rescaling technique\n") + + # 6. Generate the setup parameters and necessary keys. + key_pair = cc.KeyGen() + + cc.EvalFBTSetup(coeff1, num_slots_ckks, p_input, p_output, bigq, key_pair.publicKey, [0, 0], lvlb, + levels_available_after_bootstrap, levels_computation, order) + + cc.EvalBootstrapKeyGen(key_pair.secretKey, num_slots_ckks) + cc.EvalMultKeyGen(key_pair.secretKey) + cc.EvalAtIndexKeyGen(key_pair.secretKey, [-2]) + + mask_real = fill([1, 1, 1, 1, 0, 0, 0, 0], num_slots) + + # The mask level is counted on the full modulus chain, which has an extra modulus for FLEXIBLEAUTOEXT + ext_off = 1 if scal_tech == ScalingTechnique.FLEXIBLEAUTOEXT else 0 + + # Note that the corresponding plaintext mask for full packing can be just real, as real times complex + # multiplies both real and imaginary parts + ptxt_mask = cc.MakeCKKSPackedPlaintext( + fill([1, 1, 1, 1, 0, 0, 0, 0], num_slots_ckks), 1, + depth + ext_off - lvlb[1] - levels_available_after_bootstrap - levels_computation, None, num_slots_ckks) + + # 7. When leveled computations (multiplications, rotations) are desired to be performed while in + # slot-packed CKKS (before returning to RLWE coefficient packing), and the FFT method is used + # for the homomorphic encoding and decoding during functional bootstrapping, the inputs in RLWE + # should be encoded in a bit reversed order. This bit reverse order will be cancelled during + # the homomorphic encoding, therefore the slots in CKKS will be in natural order. + # Both the RLWE encryption and RLWE decryption should specify this flag. + flag_br = lvlb[0] != 1 or lvlb[1] != 1 + + # 8. Perform encryption in the RLWE scheme, using a larger initial ciphertext modulus. + ep = SchemeletRLWEMP.GetElementParams(key_pair.secretKey, depth - (levels_available_before_bootstrap > 0)) + + ctxt_bfv = SchemeletRLWEMP.EncryptCoeff(x, qbfv_init, p_input, key_pair.secretKey, ep, flag_br) + + SchemeletRLWEMP.ModSwitch(ctxt_bfv, Q, qbfv_init) + + # 9. Convert from the RLWE ciphertext to a CKKS ciphertext (both use the same secret key). + ctxt = SchemeletRLWEMP.ConvertRLWEToCKKS(cc, ctxt_bfv, key_pair.publicKey, bigq, num_slots_ckks, + depth - (levels_available_before_bootstrap > 0)) + + # 10. Apply the LUTs over the ciphertext. + # First, compute the complex exponential and its powers to reuse. + # Second, apply multiple LUTs over these powers. All LUTs which reuse the precomputations must be + # interpolated with the same shape (same PInput and order) as the coefficients used for the precomputation. + exact = [func1(elem) - p_output if func1(elem) > p_output / 2.0 else func1(elem) for elem in x] + exact2 = [func2(elem) - p_output if func2(elem) > p_output / 2.0 else func2(elem) for elem in x] + + complex_exp_powers = cc.EvalMVBPrecompute(ctxt, coeff1, p_input.bit_length() - 1, ep.GetModulus(), order) + + ctxt_after_fbt1 = cc.EvalMVB(complex_exp_powers, coeff1, p_input.bit_length() - 1, scale_thi, + levels_computation, order) + + ctxt_after_fbt2 = cc.EvalMVBNoDecoding(complex_exp_powers, coeff2, p_input.bit_length() - 1, order) + + # Apply a rotation + ctxt_after_fbt2 = cc.EvalRotate(ctxt_after_fbt2, -2) + exact2 = rotate(exact2, -2) if flag_sp else rotate_two_halves(exact2, -2) + + # Apply a multiplicative mask + ctxt_after_fbt2 = cc.EvalMult(ctxt_after_fbt2, ptxt_mask) + cc.ModReduceInPlace(ctxt_after_fbt2) + + exact2 = [e * m for e, m in zip(exact2, mask_real)] + + # Back to coefficient encoding + ctxt_after_fbt2 = cc.EvalHomDecoding(ctxt_after_fbt2, scale_thi, levels_computation - 1) + + # 11. Convert the results back to RLWE. + polys = SchemeletRLWEMP.ConvertCKKSToRLWE(ctxt_after_fbt1, Q) + + computed = SchemeletRLWEMP.DecryptCoeff(polys, Q, p_output, key_pair.secretKey, ep, num_slots_ckks, num_slots, + flag_br) + + print(f"First 8 elements of the obtained output = (input % PInput - POutput / 2) % POutput: {computed[:8]}") + + errors = [abs(e - c) % p_output for e, c in zip(exact, computed)] + print(f"Max absolute error obtained in the first LUT: {max(errors)}\n") + + polys = SchemeletRLWEMP.ConvertCKKSToRLWE(ctxt_after_fbt2, Q) + + computed = SchemeletRLWEMP.DecryptCoeff(polys, Q, p_output, key_pair.secretKey, ep, num_slots_ckks, num_slots, + flag_br) + + print(f"First 8 elements of the obtained output = (input % PInput) % POutput, rotated by -2 and masked: " + f"{computed[:8]}") + + errors = [abs(int(e) - c) % p_output for e, c in zip(exact2, computed)] + print(f"Max absolute error obtained in the second LUT: {max(errors)}\n") + + +def multi_precision_sign(qbfv_init, p_input, p_digit, Q, bigq, scale_thi, scale_step_thi, order, num_slots, ring_dim): + # 1. Figure out whether sparse packing or full packing should be used. + flag_sp = num_slots <= ring_dim // 2 # sparse packing + num_slots_ckks = num_slots if flag_sp else num_slots // 2 + + # 2. Functions necessary for the sign evaluation. + a = p_input + b = p_digit + + def func_mod(x): + return x % b + + def func_step(x): + return int((x % a) >= (b // 2)) + + # 3. Input. + x = [p_input // 2, p_input // 2 + 1, 0, 3, 16, 33, 64, p_input - 1] + print(f"First 8 elements of the input (repeated) up to size {num_slots}:") + print(x) + if len(x) < num_slots: + x = fill(x, num_slots) + + exact = [int(elem >= p_input / 2.0) for elem in x] + + # 4. Separate the optimized Boolean LUT case from the general case (see arbitrary_lut). + binary_lut = (p_digit == 2) and (order == 1) + + coeffcomp_mod = [] + coeffcomp_step = [] + if binary_lut: + coeffint_mod = [func_mod(1), func_mod(0) - func_mod(1)] + else: + coeffcomp_mod = GetHermiteTrigCoefficients(func_mod, p_digit, order, scale_thi) # divided by 2 + coeffcomp_step = GetHermiteTrigCoefficients(func_step, p_digit, order, scale_step_thi) # divided by 2 + + # 5. Set up the cryptoparameters. + # The scaling factor in CKKS should have the same bit length as the RLWE ciphertext modulus + # corresponding to the digit. + dcrt_bits = bigq.bit_length() - 1 + first_mod = bigq.bit_length() - 1 + levels_available_after_bootstrap = 0 + levels_available_before_bootstrap = 0 + dnum = 3 + secret_key_dist = SecretKeyDist.SPARSE_ENCAPSULATED + scal_tech = ScalingTechnique.FIXEDMANUAL + lvlb = [3, 3] + + parameters = CCParamsCKKSRNS() + parameters.SetSecretKeyDist(secret_key_dist) + parameters.SetSecurityLevel(SecurityLevel.HEStd_NotSet) + parameters.SetScalingModSize(dcrt_bits) + parameters.SetScalingTechnique(scal_tech) + parameters.SetFirstModSize(first_mod) + parameters.SetNumLargeDigits(dnum) + parameters.SetBatchSize(num_slots_ckks) + parameters.SetRingDim(ring_dim) + + if binary_lut: + depth = levels_available_after_bootstrap + \ + FHECKKSRNS.GetFBTDepth(lvlb, coeffint_mod, p_digit, order, secret_key_dist) + else: + depth = levels_available_after_bootstrap + \ + FHECKKSRNS.GetFBTDepth(lvlb, coeffcomp_mod, p_digit, order, secret_key_dist) + parameters.SetMultiplicativeDepth(depth) + + cc = GenCryptoContext(parameters) + cc.Enable(PKESchemeFeature.PKE) + cc.Enable(PKESchemeFeature.KEYSWITCH) + cc.Enable(PKESchemeFeature.LEVELEDSHE) + cc.Enable(PKESchemeFeature.ADVANCEDSHE) + cc.Enable(PKESchemeFeature.FHE) + + key_pair = cc.KeyGen() + + print(f"CKKS scheme is using ring dimension {cc.GetRingDimension()}, a multiplicative depth of {depth} " + f"and the {scal_tech} rescaling technique\n") + + # 6. Generate the setup parameters and necessary keys. + cc.EvalMultKeyGen(key_pair.secretKey) + + if binary_lut: + cc.EvalFBTSetup(coeffint_mod, num_slots_ckks, p_digit, p_input, bigq, key_pair.publicKey, [0, 0], lvlb, + levels_available_after_bootstrap, 0, order) + else: + cc.EvalFBTSetup(coeffcomp_mod, num_slots_ckks, p_digit, p_input, bigq, key_pair.publicKey, [0, 0], lvlb, + levels_available_after_bootstrap, 0, order) + + cc.EvalBootstrapKeyGen(key_pair.secretKey, num_slots_ckks) + + # 7. Perform encryption in the RLWE scheme, using a larger initial ciphertext modulus. + ep = SchemeletRLWEMP.GetElementParams(key_pair.secretKey, depth - (levels_available_before_bootstrap > 0)) + + ctxt_bfv = SchemeletRLWEMP.EncryptCoeff(x, qbfv_init, p_input, key_pair.secretKey, ep) + + SchemeletRLWEMP.ModSwitch(ctxt_bfv, Q, qbfv_init) + qbfv_bits = Q.bit_length() - 1 + + # 8. Set up the sign loop parameters. + if binary_lut: + coeff = coeffint_mod + else: + coeff = coeffcomp_mod + + checkeq2 = p_digit == 2 + checkgt2 = p_digit > 2 + p_digit_bits = p_digit.bit_length() - 1 + + p_orig = p_input + + step = False + go = qbfv_bits > dcrt_bits + levels_to_drop = 0 + post_scaling_bits = 0 + + # 9. Start the sign loop. For arbitrary digit size, pNew > 2, the last iteration needs + # to evaluate step pNew not mod pNew. + # Currently this only works when log(pNew) divides log(p). + while go: + # 9.1. Apply mod Bigq to extract the digit and convert it from RLWE to CKKS. + encrypted_digit = ctxt_bfv.Clone() + encrypted_digit.SwitchModulus(bigq) + + ctxt = SchemeletRLWEMP.ConvertRLWEToCKKS(cc, encrypted_digit, key_pair.publicKey, bigq, num_slots_ckks, + depth - (levels_available_before_bootstrap > 0)) + + # 9.2 Bootstrap the digit. + ctxt_after_fbt = cc.EvalFBT(ctxt, coeff, p_digit_bits, ep.GetModulus(), + scale_thi * (1 << post_scaling_bits), levels_to_drop, order) + + # 9.3 Convert the result back to RLWE and update the + # plaintext and ciphertext modulus of the ciphertext for the next iteration. + polys = SchemeletRLWEMP.ConvertCKKSToRLWE(ctxt_after_fbt, Q) + + if not step: + # 9.4 If not in the last iteration, subtract the digit from the ciphertext. + ctxt_bfv = ctxt_bfv - polys + + # 9.5 Do modulus switching from Q to QNew for the RLWE ciphertext. + q_new = Q >> p_digit_bits + SchemeletRLWEMP.ModSwitch(ctxt_bfv, q_new, Q) + Q >>= p_digit_bits + p_input >>= p_digit_bits + qbfv_bits -= p_digit_bits + post_scaling_bits += p_digit_bits + else: + # 9.6 If in the last iteration, return the digit. + ctxt_bfv = polys + + # 9.7 If in the last iteration, decrypt and assess correctness. + go = qbfv_bits > dcrt_bits + if step or (checkeq2 and not go): + computed = SchemeletRLWEMP.DecryptCoeff(ctxt_bfv, Q, p_input, key_pair.secretKey, ep, num_slots_ckks, + num_slots) + + print(f"First 8 elements of the obtained sign: {computed[:8]}") + + errors = [abs(e - c) % p_orig for e, c in zip(exact, computed)] + print(f"\nMax absolute error obtained: {max(errors)}\n") + + # 9.8 Determine whether it is the last iteration and if not, update the parameters for the next iteration. + if checkgt2 and not go and not step: + if not binary_lut: + coeff = coeffcomp_step + scale_thi = scale_step_thi + step = True + go = True + if len(coeffcomp_mod) > 4 and GetMultiplicativeDepthByCoeffVector(coeffcomp_mod, True) > \ + GetMultiplicativeDepthByCoeffVector(coeffcomp_step, True): + levels_to_drop = GetMultiplicativeDepthByCoeffVector(coeffcomp_mod, True) - \ + GetMultiplicativeDepthByCoeffVector(coeffcomp_step, True) + + +def main(): + print("\n*1.* Compute the function (x % PInput - POutput / 2) % POutput.\n") + # Boolean LUT + print("=====Boolean LUT order 1 sparsely packed=====\n") + arbitrary_lut(QBFV_INIT, 2, 2, 1 << 33, 1 << 33, 1, 1, 8, 4096, lambda x: (x % 2 - 2 // 2) % 2) + print("=====Boolean LUT order 2 sparsely packed=====\n") + arbitrary_lut(QBFV_INIT, 2, 2, 1 << 33, 1 << 33, 1, 2, 8, 4096, lambda x: (x % 2 - 2 // 2) % 2) + print("=====Boolean LUT order 1 fully packed=====\n") + arbitrary_lut(QBFV_INIT, 2, 2, 1 << 33, 1 << 33, 1, 1, 1024, 4096, lambda x: (x % 2 - 2 // 2) % 2) + # LUT with 8-bit input and 4-bit output + print("=====8-to-4 bit LUT order 1 sparsely packed=====\n") + arbitrary_lut(QBFV_INIT, 256, 16, 1 << 47, 1 << 47, 32, 1, 8, 4096, lambda x: (x % 256 - 16 // 2) % 16) + + print("\n\n*2.* Compute multiple functions over the same ciphertext.\n") + # Two LUTs with 8-bit input and 8-bit output and intermediate leveled computations + print("=====Multivalue bootstrapping for two 8-to-8 bit LUTs order 1 fully packed=====\n") + multi_value_bootstrapping(QBFV_INIT, 256, 256, 1 << 47, 1 << 47, 32, 1, 256, 2048, 1) + + print("\n\n*3.* Homomorphically evaluate the sign.\n") + # Compute the sign of a 12-bit input using 1-bit and 4-bit digits + # The following needs to hold true: log2(PInput) - log2(PDigit) = log2(Q) - log2(Bigq) + print("=====Sign evaluation of a 12-bit input using 1-bit digits order 1 sparsely packed=====\n") + multi_precision_sign(QBFV_INIT, 4096, 2, 1 << 46, 1 << 35, 1, 1, 1, 32, 2048) + print("=====Sign evaluation of a 12-bit input using 4-bit digits order 1 fully packed=====\n") + multi_precision_sign(QBFV_INIT, 4096, 16, 1 << 48, 1 << 40, 32, 8, 1, 64, 2048) + print("=====Sign evaluation of a 32-bit input using 8-bit digits order 1 fully packed=====\n") + multi_precision_sign(QBFV_INIT_LARGE, 1 << 32, 256, 1 << 71, 1 << 47, 256, 32, 1, 64, 2048) + + +if __name__ == "__main__": + main() diff --git a/examples/pke/inner-product.py b/examples/pke/inner-product.py new file mode 100644 index 0000000..b9ad955 --- /dev/null +++ b/examples/pke/inner-product.py @@ -0,0 +1,94 @@ +# +# Simple example for BFV and CKKS for inner product. +# + +from openfhe import * + + +def plain_inner_product(vec): + return sum(el * el for el in vec) + + +def inner_product_bfv(incoming_vector): + expected_result = plain_inner_product(incoming_vector) + + # Crypto CryptoParams + parameters = CCParamsBFVRNS() + parameters.SetPlaintextModulus(65537) + parameters.SetMultiplicativeDepth(20) + parameters.SetSecurityLevel(SecurityLevel.HEStd_NotSet) + parameters.SetRingDim(1 << 7) + batch_size = parameters.GetRingDim() // 2 + + # Set crypto params and create context + cc = GenCryptoContext(parameters) + + # Enable the features that you wish to use. + cc.Enable(PKESchemeFeature.PKE) + cc.Enable(PKESchemeFeature.LEVELEDSHE) + cc.Enable(PKESchemeFeature.ADVANCEDSHE) + + keys = cc.KeyGen() + cc.EvalMultKeyGen(keys.secretKey) + cc.EvalSumKeyGen(keys.secretKey) + + plaintext1 = cc.MakePackedPlaintext(incoming_vector) + ct1 = cc.Encrypt(keys.publicKey, plaintext1) + final_result = cc.EvalInnerProduct(ct1, ct1, batch_size) + res = cc.Decrypt(keys.secretKey, final_result) + final = res.GetPackedValue()[0] + + print(f"Expected Result: {expected_result} Inner Product Result: {final}") + return expected_result == final + + +def inner_product_ckks(incoming_vector): + expected_result = plain_inner_product(incoming_vector) + + security_level = SecurityLevel.HEStd_NotSet + dcrt_bits = 59 + ring_dim = 1 << 8 + batch_size = ring_dim // 2 + mult_depth = 10 + + parameters = CCParamsCKKSRNS() + parameters.SetMultiplicativeDepth(mult_depth) + parameters.SetScalingModSize(dcrt_bits) + parameters.SetBatchSize(batch_size) + parameters.SetSecurityLevel(security_level) + parameters.SetRingDim(ring_dim) + + cc = GenCryptoContext(parameters) + + cc.Enable(PKESchemeFeature.PKE) + cc.Enable(PKESchemeFeature.LEVELEDSHE) + cc.Enable(PKESchemeFeature.ADVANCEDSHE) + + keys = cc.KeyGen() + cc.EvalMultKeyGen(keys.secretKey) + cc.EvalSumKeyGen(keys.secretKey) + + plaintext1 = cc.MakeCKKSPackedPlaintext(incoming_vector) + ct1 = cc.Encrypt(keys.publicKey, plaintext1) + final_result = cc.EvalInnerProduct(ct1, ct1, batch_size) + res = cc.Decrypt(keys.secretKey, final_result) + res.SetLength(len(incoming_vector)) + final = res.GetCKKSPackedValue()[0].real + + print(f"Expected Result: {expected_result} Inner Product Result: {final}") + return abs(expected_result - final) <= 0.0001 + + +def main(): + vec = [1, 2, 3, 4, 5] + bfv_res = inner_product_bfv(vec) + print(f"BFV Inner Product Correct? {bfv_res}") + + print("********************************************************************") + as_double = [el + el / 100.0 for el in vec] + ckks_res = inner_product_ckks(as_double) + print(f"CKKS Inner Product Correct? {ckks_res}") + + +if __name__ == "__main__": + main() diff --git a/examples/pke/iterative-ckks-bootstrapping-composite-scaling.py b/examples/pke/iterative-ckks-bootstrapping-composite-scaling.py new file mode 100644 index 0000000..cd64ed5 --- /dev/null +++ b/examples/pke/iterative-ckks-bootstrapping-composite-scaling.py @@ -0,0 +1,139 @@ +# +# Example for multiple iterations of CKKS bootstrapping with composite scaling +# to improve precision +# + +from openfhe import * +import math +import random + + +def main(): + iterative_bootstrap_example() + + +def calculate_approximation_error(result, expected_result): + # CalculateApproximationError() calculates the precision number (or approximation error). + # The higher the precision, the less the error. + # As recommended in footnote 23 of Security Guidelines for Implementing Homomorphic Encryption + # (https://cic.iacr.org/p/1/4/26/pdf), precision bits are evaluated as the negative + # base 2 logarithm of the average L1 norm between results from standard (cleartext) calculation + # and those computed homomorphically. + if len(result) != len(expected_result): + raise Exception("Cannot compare vectors with different numbers of elements") + + # using the average + acc_error = 0 + for i in range(len(result)): + acc_error += abs(result[i] - expected_result[i]) + return abs(math.log2(acc_error / len(result))) + + +def iterative_bootstrap_example(): + # Step 1: Set CryptoContext + parameters = CCParamsCKKSRNS() + secret_key_dist = SecretKeyDist.UNIFORM_TERNARY + parameters.SetSecretKeyDist(secret_key_dist) + parameters.SetSecurityLevel(SecurityLevel.HEStd_NotSet) + parameters.SetRingDim(1 << 7) + + # All modes are supported for 64-bit CKKS bootstrapping. + # For this configuration, 3 words per level will be used + rescale_tech = ScalingTechnique.COMPOSITESCALINGAUTO + dcrt_bits = 61 + first_mod = 66 + register_word_size = 27 + + parameters.SetScalingModSize(dcrt_bits) + parameters.SetScalingTechnique(rescale_tech) + parameters.SetFirstModSize(first_mod) + parameters.SetRegisterWordSize(register_word_size) + + # Here, we specify the number of iterations to run bootstrapping. Note that we currently only support 1 or 2 iterations. + # Two iterations should give us approximately double the precision of one iteration. + num_iterations = 2 + + level_budget = [3, 3] + bsgs_dim = [0, 0] + + levels_available_after_bootstrap = 10 + # Each extra iteration on top of 1 requires an extra level to be consumed. + depth = levels_available_after_bootstrap + FHECKKSRNS.GetBootstrapDepth(level_budget, secret_key_dist) + (num_iterations - 1) + parameters.SetMultiplicativeDepth(depth) + + # Generate crypto context. + cryptocontext = GenCryptoContext(parameters) + + # Enable features that you wish to use. Note, we must enable FHE to use bootstrapping. + cryptocontext.Enable(PKESchemeFeature.PKE) + cryptocontext.Enable(PKESchemeFeature.KEYSWITCH) + cryptocontext.Enable(PKESchemeFeature.LEVELEDSHE) + cryptocontext.Enable(PKESchemeFeature.ADVANCEDSHE) + cryptocontext.Enable(PKESchemeFeature.FHE) + + ring_dim = cryptocontext.GetRingDimension() + print(f"CKKS scheme is using ring dimension {ring_dim}\n") + + composite_degree = cryptocontext.GetCompositeDegree() + print(f"compositeDegree={composite_degree} modBitWidth={dcrt_bits / composite_degree} " + f"targetHWArchWordSize={register_word_size}") + + # Step 2: Precomputations for bootstrapping + # We use a full packing. + num_slots = cryptocontext.GetCyclotomicOrder() // 4 + cryptocontext.EvalBootstrapSetup(level_budget, bsgs_dim, num_slots) + + # Step 3: Key Generation + key_pair = cryptocontext.KeyGen() + cryptocontext.EvalMultKeyGen(key_pair.secretKey) + # Generate bootstrapping keys. + cryptocontext.EvalBootstrapKeyGen(key_pair.secretKey, num_slots) + + # Step 4: Encoding and encryption of inputs + # Generate random input + x = [random.uniform(0.0, 1.0) for _ in range(num_slots)] + + # Encoding as plaintexts + # We specify the number of slots as numSlots to achieve a performance improvement. + # We use the other default values of depth 1, levels 0, and no params. + # Alternatively, you can also set batch size as a parameter in the CryptoContext as follows: + # parameters.SetBatchSize(numSlots); + # Here, we assume all ciphertexts in the cryptoContext will have numSlots slots. + # We start with a depleted ciphertext that has used up all of its levels. + ptxt = cryptocontext.MakeCKKSPackedPlaintext(x, 1, composite_degree * (depth - 1), None, num_slots) + ptxt.SetLength(num_slots) + print(f"Input: {ptxt}") + + # Encrypt the encoded vectors + ciph = cryptocontext.Encrypt(key_pair.publicKey, ptxt) + + # Step 5: Measure the precision of a single bootstrapping operation. + ciphertext_after = cryptocontext.EvalBootstrap(ciph) + + result = cryptocontext.Decrypt(key_pair.secretKey, ciphertext_after) + result.SetLength(num_slots) + precision = math.floor(calculate_approximation_error(result.GetCKKSPackedValue(), ptxt.GetCKKSPackedValue())) + print(f"Bootstrapping precision after 1 iteration: {precision}") + + # Set precision equal to empirically measured value after many test runs. + precision = 19 + print(f"Precision input to algorithm: {precision}") + + # Step 6: Run bootstrapping with multiple iterations. + ciphertext_two_iterations = cryptocontext.EvalBootstrap(ciph, num_iterations, precision) + + result_two_iterations = cryptocontext.Decrypt(key_pair.secretKey, ciphertext_two_iterations) + result.SetLength(num_slots) + actual_result = result_two_iterations.GetCKKSPackedValue() + + print(f"\nOutput after two iterations of bootstrapping: {actual_result}") + precision_multiple_iterations = calculate_approximation_error(actual_result, ptxt.GetCKKSPackedValue()) + + # Output the precision of bootstrapping after two iterations. It should be approximately double the original precision. + print(f"\nBootstrapping precision after 2 iterations: {precision_multiple_iterations}") + print(f"Number of levels remaining after 2 bootstrappings: " + f"{depth - ciphertext_two_iterations.GetLevel() // composite_degree - (ciphertext_two_iterations.GetNoiseScaleDeg() - 1)}\n") + + +if __name__ == "__main__": + main() diff --git a/examples/pke/linearwsum-evaluation.py b/examples/pke/linearwsum-evaluation.py new file mode 100644 index 0000000..efeae7b --- /dev/null +++ b/examples/pke/linearwsum-evaluation.py @@ -0,0 +1,75 @@ +# +# Example of linear weighted sum evaluation using CKKS. +# + +from openfhe import * +import time + + +def main(): + print("\n======EXAMPLE FOR EVAL LINEAR WEIGHTED SUM========\n") + + parameters = CCParamsCKKSRNS() + parameters.SetMultiplicativeDepth(1) + parameters.SetScalingModSize(50) + parameters.SetBatchSize(8) + parameters.SetSecurityLevel(SecurityLevel.HEStd_NotSet) + parameters.SetRingDim(2048) + parameters.SetScalingTechnique(ScalingTechnique.FLEXIBLEAUTO) + parameters.SetFirstModSize(60) + + cc = GenCryptoContext(parameters) + cc.Enable(PKESchemeFeature.PKE) + cc.Enable(PKESchemeFeature.KEYSWITCH) + cc.Enable(PKESchemeFeature.LEVELEDSHE) + cc.Enable(PKESchemeFeature.ADVANCEDSHE) + + input = [ + [0.5, 0.7, 0.9, 0.95, 0.93, 1.3], + [1.2, 1.7, -0.9, 0.85, -0.63, 2], + [0.5, 0, 1.9, 2.95, -3.93, 3.3], + [1.5, 0.7, 1.9, 2.95, -3.78, 3.3], + [0.5, 2.7, 1.9, 0.0, -3.43, 1.3], + [0.5, 0.7, -1.9, 2.95, 1.96, 0.0], + [0.0, 0.0, 1.0, 0.0, 0.0, 0.0], + ] + + encoded_length = len(input) + + coefficients = [0.15, 0.75, 1.25, 1, 0, 0.5, 0.5] + + key_pair = cc.KeyGen() + + print("Generating evaluation key for homomorphic multiplication...", end="") + cc.EvalMultKeyGen(key_pair.secretKey) + print("Completed.") + + ciphertext_vec = [] + for i in range(encoded_length): + plaintext = cc.MakeCKKSPackedPlaintext(input[i]) + ciphertext_vec.append(cc.Encrypt(key_pair.publicKey, plaintext)) + + t = time.time() + result = cc.EvalLinearWSum(ciphertext_vec, coefficients) + time_eval_linear_wsum = (time.time() - t) * 1000 + + unenc_ip = [] + for i in range(len(input[0])): + x = 0 + for j in range(encoded_length): + x += input[j][i] * coefficients[j] + unenc_ip.append(x) + + plaintext_dec = cc.Decrypt(key_pair.secretKey, result) + plaintext_dec.SetLength(encoded_length) + + print(f"\n Result of evaluating a linear weighted sum with coefficients {coefficients} \n") + print(plaintext_dec) + + print(f"\n Expected result: {unenc_ip}") + + print(f"\n Evaluation time: {time_eval_linear_wsum:.4f} ms") + + +if __name__ == "__main__": + main() diff --git a/examples/pke/polynomial-evaluation-high-precision-composite-scaling.py b/examples/pke/polynomial-evaluation-high-precision-composite-scaling.py new file mode 100644 index 0000000..a40b550 --- /dev/null +++ b/examples/pke/polynomial-evaluation-high-precision-composite-scaling.py @@ -0,0 +1,129 @@ +# +# Example of polynomial evaluation using CKKS with composite scaling. +# +# Note: unlike the C++ version of this example, the moduli chain of the public key +# and the average scale approximation error are not printed here because the Python +# bindings do not expose the DCRTPoly element parameters (GetPublicElements, +# GetNumOfElements, etc.). +# + +from openfhe import * +import sys +import time + + +def main(args=[]): + # Parameters for d=4 + # first_mod_size = 106 + # scaling_mod_size = 104 + # register_word_size = 32 + # Parameters for d=3 + first_mod_size = 96 + scaling_mod_size = 80 + register_word_size = 32 + + print("\n======EXAMPLE FOR EVALPOLY========\n") + + mult_depth = 6 + + if len(args) > 0: + argc_count = 0 + while argc_count < len(args): + param_value = int(args[argc_count]) + if argc_count == 0: + first_mod_size = param_value + print(f"Setting First Mod Size: {first_mod_size}") + elif argc_count == 1: + scaling_mod_size = param_value + print(f"Setting Scaling Mod Size: {scaling_mod_size}") + elif argc_count == 2: + register_word_size = param_value + print(f"Setting Register Word Size: {register_word_size}") + elif argc_count == 3: + mult_depth = param_value + print(f"Setting Multiplicative Depth: {mult_depth}") + else: + print("Invalid option") + argc_count += 1 + + print("Completed reading input parameters!") + else: + print("Using default parameters") + print(f"First Mod Size: {first_mod_size}") + print(f"Scaling Mod Size: {scaling_mod_size}") + print(f"Register Word Size: {register_word_size}") + print(f"Multiplicative Depth: {mult_depth}") + print(f"Usage: {sys.argv[0]} [firstModSize] [scalingModSize] [registerWordSize] [multDepth]") + + parameters = CCParamsCKKSRNS() + parameters.SetMultiplicativeDepth(mult_depth) + parameters.SetFirstModSize(first_mod_size) + parameters.SetScalingModSize(scaling_mod_size) + + parameters.SetRegisterWordSize(register_word_size) + parameters.SetScalingTechnique(ScalingTechnique.COMPOSITESCALINGAUTO) + + cc = GenCryptoContext(parameters) + cc.Enable(PKESchemeFeature.PKE) + cc.Enable(PKESchemeFeature.KEYSWITCH) + cc.Enable(PKESchemeFeature.LEVELEDSHE) + cc.Enable(PKESchemeFeature.ADVANCEDSHE) + + composite_degree = cc.GetCompositeDegree() + + print("-----------------------------------------------------------------") + print(f"Composite Degree: {composite_degree}") + print(f"Prime Moduli Size: {scaling_mod_size / composite_degree}") + print(f"Register Word Size: {register_word_size}") + print("-----------------------------------------------------------------") + + input = [0.5, 0.7, 0.9, 0.95, 0.93] + + encoded_length = len(input) + + coefficients1 = [0.15, 0.75, 0, 1.25, 0, 0, 1, 0, 1, 2, 0, 1, 0, 0, 0, 0, 1] + coefficients2 = [1, 2, 3, 4, 5, -1, -2, -3, -4, -5, + 0.1, 0.2, 0.3, 0.4, 0.5, -0.1, -0.2, -0.3, -0.4, -0.5, + 0.1, 0.2, 0.3, 0.4, 0.5, -0.1, -0.2, -0.3, -0.4, -0.5] + + plaintext1 = cc.MakeCKKSPackedPlaintext(input) + + key_pair = cc.KeyGen() + + print("Generating evaluation key for homomorphic multiplication...", end="") + cc.EvalMultKeyGen(key_pair.secretKey) + print("Completed.") + + ciphertext1 = cc.Encrypt(key_pair.publicKey, plaintext1) + + t = time.time() + result = cc.EvalPoly(ciphertext1, coefficients1) + time_eval_poly1 = (time.time() - t) * 1000 + + t = time.time() + result2 = cc.EvalPoly(ciphertext1, coefficients2) + time_eval_poly2 = (time.time() - t) * 1000 + + plaintext_dec = cc.Decrypt(key_pair.secretKey, result) + plaintext_dec.SetLength(encoded_length) + + plaintext_dec2 = cc.Decrypt(key_pair.secretKey, result2) + plaintext_dec2.SetLength(encoded_length) + + print(f"\n Original Plaintext #1: \n{plaintext1}") + + print(f"\n Result of evaluating a polynomial with coefficients {coefficients1} \n{plaintext_dec}") + + print("\n Expected result: (0.70519107, 1.38285078, 3.97211180, 5.60215665, 4.86357575) ") + + print(f"\n Evaluation time: {time_eval_poly1:.4f} ms") + + print(f"\n Result of evaluating a polynomial with coefficients {coefficients2} \n{plaintext_dec2}") + + print("\n Expected result: (3.4515092326, 5.3752765397, 4.8993108833, 3.2495023573, 4.0485229982) ") + + print(f"\n Evaluation time: {time_eval_poly2:.4f} ms") + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/examples/pke/pre-hra-secure.py b/examples/pke/pre-hra-secure.py new file mode 100644 index 0000000..e69eac9 --- /dev/null +++ b/examples/pke/pre-hra-secure.py @@ -0,0 +1,173 @@ +# +# Example of HRA-secure Proxy Re-Encryption with 13 hops. +# + +from openfhe import * +import math +import random +import time + + +def run_demo_pre(): + # Generate parameters. + print("setting up the HRA-secure BGV PRE cryptosystem") + t = time.time() + + plaintext_modulus = 2 # can encode shorts + + num_hops = 13 + + parameters = CCParamsBGVRNS() + parameters.SetPlaintextModulus(plaintext_modulus) + parameters.SetScalingTechnique(ScalingTechnique.FIXEDMANUAL) + parameters.SetPRENumHops(num_hops) + parameters.SetStatisticalSecurity(40) + parameters.SetNumAdversarialQueries(1048576) + parameters.SetRingDim(32768) + parameters.SetPREMode(ProxyReEncryptionMode.NOISE_FLOODING_HRA) + parameters.SetKeySwitchTechnique(KeySwitchTechnique.HYBRID) + parameters.SetMultiplicativeDepth(0) + + cc = GenCryptoContext(parameters) + print(f"\nParam generation time: \t{(time.time() - t) * 1000} ms") + + # Turn on features + cc.Enable(PKESchemeFeature.PKE) + cc.Enable(PKESchemeFeature.KEYSWITCH) + cc.Enable(PKESchemeFeature.LEVELEDSHE) + cc.Enable(PKESchemeFeature.PRE) + + print(f"p = {cc.GetPlaintextModulus()}") + print(f"n = {cc.GetCyclotomicOrder() // 2}") + print(f"log2 q = {math.log2(cc.GetModulus())}") + + ringsize = cc.GetRingDimension() + print(f"Alice can encrypt {ringsize // 8} bytes of data") + + ############################################################ + # Perform Key Generation Operation + ############################################################ + + print("\nRunning Alice key generation (used for source data)...") + + t = time.time() + key_pair1 = cc.KeyGen() + print(f"Key generation time: \t{(time.time() - t) * 1000} ms") + + ############################################################ + # Encode source data + ############################################################ + + nshort = ringsize + + v_shorts = [random.randint(0, plaintext_modulus - 1) for _ in range(nshort)] + + pt = cc.MakeCoefPackedPlaintext(v_shorts) + + ############################################################ + # Encryption + ############################################################ + + t = time.time() + ct1 = cc.Encrypt(key_pair1.publicKey, pt) + print(f"Encryption time: \t{(time.time() - t) * 1000} ms") + + ############################################################ + # Decryption of Ciphertext + ############################################################ + + t = time.time() + pt_dec1 = cc.Decrypt(key_pair1.secretKey, ct1) + print(f"Decryption time: \t{(time.time() - t) * 1000} ms") + + pt_dec1.SetLength(pt.GetLength()) + + ############################################################ + # Perform Key Generation Operation + ############################################################ + + key_pair_vector = [] + reencryption_key_vector = [] + + print(f"Generating keys for {num_hops} parties") + + for i in range(num_hops): + t = time.time() + key_pair_vector.append(cc.KeyGen()) + t1 = (time.time() - t) * 1000 + if i == 1: + print(f"Key generation time: \t{t1} ms") + + ############################################################ + # Perform the proxy re-encryption key generation operation. + # This generates the keys which are used to perform the key switching. + ############################################################ + if i == 0: + reencryption_key_vector.append(cc.ReKeyGen(key_pair1.secretKey, key_pair_vector[i].publicKey)) + else: + t = time.time() + reencryption_key_vector.append(cc.ReKeyGen(key_pair_vector[i - 1].secretKey, key_pair_vector[i].publicKey)) + t1 = (time.time() - t) * 1000 + if i == 1: + print(f"Re-encryption key generation time: \t{t1} ms") + + ############################################################ + # Re-Encryption + ############################################################ + good = True + for i in range(num_hops): + t = time.time() + ct1 = cc.ReEncrypt(ct1, reencryption_key_vector[i]) + t1 = (time.time() - t) * 1000 + print(f"Re-Encryption time at hop {i + 1}\t{t1} ms") + + if i < num_hops - 1: + cc.ModReduceInPlace(ct1) + + ############################################################ + # Decryption of Ciphertext + ############################################################ + + t = time.time() + pt_dec2 = cc.Decrypt(key_pair_vector[i].secretKey, ct1) + t1 = (time.time() - t) * 1000 + print(f"Decryption time: \t{t1} ms") + + pt_dec2.SetLength(pt.GetLength()) + + unpacked0 = pt.GetCoefPackedValue() + unpacked1 = pt_dec1.GetCoefPackedValue() + unpacked2 = pt_dec2.GetCoefPackedValue() + + # note that OpenFHE assumes that plaintext is in the range of -p/2..p/2 + # to recover 0...q simply add q if the unpacked value is negative + unpacked1 = [v + plaintext_modulus if v < 0 else v for v in unpacked1] + unpacked2 = [v + plaintext_modulus if v < 0 else v for v in unpacked2] + + # compare all the results for correctness + for j in range(pt.GetLength()): + if unpacked0[j] != unpacked1[j] or unpacked0[j] != unpacked2[j]: + good = False + + if good: + print("PRE passes") + else: + print("PRE fails") + + ############################################################ + # Done + ############################################################ + + print("Execution Completed.") + + return good + + +def main(): + passed = run_demo_pre() + if not passed: # there could be an error + raise Exception("PRE failed") + + +if __name__ == "__main__": + main() diff --git a/examples/pke/rotation.py b/examples/pke/rotation.py new file mode 100644 index 0000000..ca402c8 --- /dev/null +++ b/examples/pke/rotation.py @@ -0,0 +1,158 @@ +# +# Example of vector rotation. +# This code shows how the EvalRotate and EvalMerge operations work +# + +from openfhe import * + + +def main(): + print("\nThis code shows how the EvalRotate and EvalMerge operations work " + "for different cyclotomic rings (both power-of-two and cyclic).\n") + + print("\n========== BFVrns.EvalRotate - Power-of-Two Cyclotomics ===========") + bfvrns_eval_rotate_2n() + + print("\n========== CKKS.EvalRotate - Power-of-Two Cyclotomics ===========") + ckks_eval_rotate_2n() + + print("\n========== BFVrns.EvalMerge - Power-of-Two Cyclotomics ===========") + bfvrns_eval_merge_2n() + + +def bfvrns_eval_rotate_2n(): + parameters = CCParamsBFVRNS() + parameters.SetPlaintextModulus(65537) + parameters.SetMaxRelinSkDeg(3) + + cc = GenCryptoContext(parameters) + # enable features that you wish to use + cc.Enable(PKESchemeFeature.PKE) + cc.Enable(PKESchemeFeature.KEYSWITCH) + cc.Enable(PKESchemeFeature.LEVELEDSHE) + + n = cc.GetCyclotomicOrder() // 2 + + # Initialize the public key containers. + kp = cc.KeyGen() + + index_list = [2, 3, 4, 5, 6, 7, 8, 9, 10, -n + 2, -n + 3, n - 1, n - 2, -1, -2, -3, -4, -5] + + cc.EvalRotateKeyGen(kp.secretKey, index_list) + + vector_of_ints = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + vector_of_ints += [0] * (n - len(vector_of_ints)) + vector_of_ints[n - 1] = n + vector_of_ints[n - 2] = n - 1 + vector_of_ints[n - 3] = n - 2 + + int_array = cc.MakePackedPlaintext(vector_of_ints) + + ciphertext = cc.Encrypt(kp.publicKey, int_array) + + for i in range(18): + permuted_ciphertext = cc.EvalRotate(ciphertext, index_list[i]) + + int_array_new = cc.Decrypt(kp.secretKey, permuted_ciphertext) + int_array_new.SetLength(10) + + print(f"Automorphed array - at index {index_list[i]}: {int_array_new}") + + +def ckks_eval_rotate_2n(): + parameters = CCParamsCKKSRNS() + parameters.SetMultiplicativeDepth(2) + parameters.SetScalingModSize(40) + + cc = GenCryptoContext(parameters) + cc.Enable(PKESchemeFeature.PKE) + cc.Enable(PKESchemeFeature.KEYSWITCH) + cc.Enable(PKESchemeFeature.LEVELEDSHE) + + cycl_order = cc.GetCyclotomicOrder() + + # Initialize the public key containers. + kp = cc.KeyGen() + + n = cycl_order // 4 + index_list = [2, 3, 4, 5, 6, 7, 8, 9, 10, -n + 2, -n + 3, n - 1, n - 2, -1, -2, -3, -4, -5] + + cc.EvalRotateKeyGen(kp.secretKey, index_list) + + vector_of_ints = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + vector_of_ints += [0] * (n - len(vector_of_ints)) + vector_of_ints[n - 1] = n + vector_of_ints[n - 2] = n - 1 + vector_of_ints[n - 3] = n - 2 + + int_array = cc.MakeCKKSPackedPlaintext(vector_of_ints) + + ciphertext = cc.Encrypt(kp.publicKey, int_array) + + for i in range(18): + permuted_ciphertext = cc.EvalRotate(ciphertext, index_list[i]) + + int_array_new = cc.Decrypt(kp.secretKey, permuted_ciphertext) + int_array_new.SetLength(10) + + print(f"Automorphed array - at index {index_list[i]}: {int_array_new}") + + +def bfvrns_eval_merge_2n(): + parameters = CCParamsBFVRNS() + parameters.SetPlaintextModulus(65537) + parameters.SetMultiplicativeDepth(2) + parameters.SetMaxRelinSkDeg(3) + + cc = GenCryptoContext(parameters) + # enable features that you wish to use + cc.Enable(PKESchemeFeature.PKE) + cc.Enable(PKESchemeFeature.KEYSWITCH) + cc.Enable(PKESchemeFeature.LEVELEDSHE) + cc.Enable(PKESchemeFeature.ADVANCEDSHE) + + # Initialize the public key containers. + kp = cc.KeyGen() + + index_list = [-1, -2, -3, -4, -5] + + cc.EvalRotateKeyGen(kp.secretKey, index_list) + + ciphertexts = [] + + vector_of_ints1 = [32, 2, 3, 4, 5, 6, 7, 8, 9, 10] + int_array1 = cc.MakePackedPlaintext(vector_of_ints1) + ciphertexts.append(cc.Encrypt(kp.publicKey, int_array1)) + + vector_of_ints2 = [2, 2, 3, 4, 5, 6, 7, 8, 9, 10] + int_array2 = cc.MakePackedPlaintext(vector_of_ints2) + ciphertexts.append(cc.Encrypt(kp.publicKey, int_array2)) + + vector_of_ints3 = [4, 2, 3, 4, 5, 6, 7, 8, 9, 10] + int_array3 = cc.MakePackedPlaintext(vector_of_ints3) + ciphertexts.append(cc.Encrypt(kp.publicKey, int_array3)) + + vector_of_ints4 = [8, 2, 3, 4, 5, 6, 7, 8, 9, 10] + int_array4 = cc.MakePackedPlaintext(vector_of_ints4) + ciphertexts.append(cc.Encrypt(kp.publicKey, int_array4)) + + vector_of_ints5 = [16, 2, 3, 4, 5, 6, 7, 8, 9, 10] + int_array5 = cc.MakePackedPlaintext(vector_of_ints5) + ciphertexts.append(cc.Encrypt(kp.publicKey, int_array5)) + + print(f"Input ciphertext {int_array1}") + print(f"Input ciphertext {int_array2}") + print(f"Input ciphertext {int_array3}") + print(f"Input ciphertext {int_array4}") + print(f"Input ciphertext {int_array5}") + + merged_ciphertext = cc.EvalMerge(ciphertexts) + + int_array_new = cc.Decrypt(kp.secretKey, merged_ciphertext) + int_array_new.SetLength(10) + + print(f"\nMerged ciphertext {int_array_new}") + + +if __name__ == "__main__": + main() diff --git a/examples/pke/scheme-switching-serial.py b/examples/pke/scheme-switching-serial.py new file mode 100644 index 0000000..b35830c --- /dev/null +++ b/examples/pke/scheme-switching-serial.py @@ -0,0 +1,193 @@ +# +# Scheme switching serialization in a simple context. The goal of this is to show a simple setup for scheme switching +# serialization before progressing into the next logical step - serialization and communication across +# 2 separate entities +# + +from openfhe import * +import math +import os +import tempfile + +# Save-Load locations for keys +datafolder = "demoData" + +# Save-load locations for evaluated ciphertext +cipher_argmin_location = "/ciphertextArgmin.txt" + + +def demarcate(msg): + # Visual separator between the sections of code + print("*" * 49 + "\n") + print(msg) + print("*" * 49 + "\n") + + +def server_verification(cc, kp, vector_size): + # - deserialize data from the client. + # - Verify that the results are as we expect + server_ciphertext_from_client_argmin, res = DeserializeCiphertext(datafolder + cipher_argmin_location, BINARY) + print("Deserialized all data from client on server\n") + + demarcate("Part 5: Correctness verification") + + server_plaintext_from_client_argmin = cc.Decrypt(kp.secretKey, server_ciphertext_from_client_argmin) + server_plaintext_from_client_argmin.SetLength(vector_size) + + return server_plaintext_from_client_argmin + + +def server_setup_and_write(ring_dim, batch_size, mult_depth, scale_mod_size, first_mod_size, log_q_lwe, one_hot): + # - simulates a server at startup where we generate a cryptocontext and keys. + # - then, we generate some data (akin to loading raw data on an enclave) + # before encrypting the data + sl = SecurityLevel.HEStd_NotSet + sl_bin = TOY + + parameters = CCParamsCKKSRNS() + parameters.SetMultiplicativeDepth(mult_depth) + parameters.SetSecurityLevel(sl) + parameters.SetRingDim(ring_dim) + parameters.SetBatchSize(batch_size) + parameters.SetScalingModSize(scale_mod_size) + parameters.SetFirstModSize(first_mod_size) + parameters.SetScalingTechnique(ScalingTechnique.FLEXIBLEAUTO) + + server_cc = GenCryptoContext(parameters) + + # Enable the features that you wish to use + server_cc.Enable(PKESchemeFeature.PKE) + server_cc.Enable(PKESchemeFeature.KEYSWITCH) + server_cc.Enable(PKESchemeFeature.LEVELEDSHE) + server_cc.Enable(PKESchemeFeature.ADVANCEDSHE) + server_cc.Enable(PKESchemeFeature.FHE) + server_cc.Enable(PKESchemeFeature.SCHEMESWITCH) + + print("Cryptocontext generated") + + server_kp = server_cc.KeyGen() + print("Keypair generated") + + params = SchSwchParams() + params.SetSecurityLevelCKKS(sl) + params.SetSecurityLevelFHEW(sl_bin) + params.SetCtxtModSizeFHEWLargePrec(log_q_lwe) + params.SetNumSlotsCKKS(batch_size) + params.SetNumValues(batch_size) + params.SetComputeArgmin(True) + params.SetOneHotEncoding(one_hot) + private_key_fhew = server_cc.EvalSchemeSwitchingSetup(params) + + server_cc.EvalSchemeSwitchingKeyGen(server_kp, private_key_fhew) + + vec = [1.0, 2.0, 3.0, 4.0] + print(f"\nDisplaying data vector: {vec}\n") + + server_p = server_cc.MakeCKKSPackedPlaintext(vec) + + print(f"Plaintext version of vector: {server_p}") + print("Plaintexts have been generated from complex-double vectors") + + server_c = server_cc.Encrypt(server_kp.publicKey, server_p) + + print("Ciphertext have been generated from Plaintext") + + # Part 2: + # We serialize the following: + # Cryptocontext + # Public key + # relinearization (eval mult keys) + # rotation keys + # binfhe cryptocontext + # binfhe bootstrapping keys + # Some of the ciphertext + # + # We serialize all of them to files + demarcate("Scheme Switching Part 2: Data Serialization (server)") + + serializer = SchemeSwitchingDataSerializer(server_cc, server_kp.publicKey, server_c) + serializer.SetDataDirectory(datafolder) + serializer.Serialize() + + return server_cc, server_kp, len(vec) + + +def client_process(modulus_lwe): + # - deserialize data from a file which simulates receiving data from a server + # after making a request + # - we then process the data + ClearEvalMultKeys() + ClearEvalSumKeys() + CryptoContext.ClearEvalAutomorphismKeys() + ReleaseAllContexts() + + deserializer = SchemeSwitchingDataDeserializer() + deserializer.SetDataDirectory(datafolder) + deserializer.Deserialize() + + client_cc = deserializer.getCryptoContext() + client_public_key = deserializer.getPublicKey() + client_bin_cc = client_cc.GetBinCCForSchemeSwitch() + client_c = deserializer.getRAWCiphertext() + + # Scale the inputs to ensure their difference is correctly represented after switching to FHEW + scale_sign = 512.0 + beta = client_bin_cc.GetBeta() + p_lwe = modulus_lwe // (2 * beta) # Large precision + + client_cc.EvalCompareSwitchPrecompute(p_lwe, scale_sign, False) + + print("Done with precomputations\n") + + # Compute on the ciphertext + client_ciphertext_argmin = client_cc.EvalMinSchemeSwitching(client_c, client_public_key, client_c.GetSlots(), + client_c.GetSlots(), 0, 1) + + print("Done with argmin computation\n") + + # Now, we want to simulate a client who is encrypting data for the server to + # decrypt. E.g weights of a machine learning algorithm + demarcate("Part 3.5: Client Serialization of data that has been operated on") + + SerializeToFile(datafolder + cipher_argmin_location, client_ciphertext_argmin[1], BINARY) + + print("Serialized ciphertext from client\n") + + +def main_action(): + # Set main params + ring_dim = 64 + batch_size = 4 + mult_depth = 13 + int(math.log2(batch_size)) + log_q_cc_lwe = 25 + one_hot = True + scale_mod_size = 50 + first_mod_size = 60 + + demarcate("Scheme switching Part 1: Cryptocontext generation, key generation, data encryption (server)") + + cc, kp, vector_size = server_setup_and_write(ring_dim, batch_size, mult_depth, scale_mod_size, first_mod_size, + log_q_cc_lwe, one_hot) + + demarcate("Scheme switching Part 3: Client deserialize all data") + + client_process(1 << log_q_cc_lwe) + + demarcate("Scheme switching Part 4: Server deserialization of data from client. ") + + argmin_res = server_verification(cc, kp, vector_size) + + # vec1: {1,2,3,4} + print(argmin_res) # EXPECT: 1.0, 0.0, 0.0, 0.0 + + +def main(): + global datafolder + with tempfile.TemporaryDirectory() as td: + datafolder = td + "/" + datafolder + os.mkdir(datafolder) + main_action() + + +if __name__ == "__main__": + main() diff --git a/examples/pke/simple-ckks-bootstrapping-composite-scaling.py b/examples/pke/simple-ckks-bootstrapping-composite-scaling.py new file mode 100644 index 0000000..87ddadb --- /dev/null +++ b/examples/pke/simple-ckks-bootstrapping-composite-scaling.py @@ -0,0 +1,222 @@ +# +# Simple example for CKKS bootstrapping with composite scaling +# + +from openfhe import * +import math + + +def main(): + simple_bootstrap_example() + simple_bootstrap_stc_first_example() + + +def calculate_approximation_error(result, expected_result): + # CalculateApproximationError() calculates the precision number (or approximation error). + # The higher the precision, the less the error. + # As recommended in footnote 23 of Security Guidelines for Implementing Homomorphic Encryption + # (https://cic.iacr.org/p/1/4/26/pdf), precision bits are evaluated as the negative + # base 2 logarithm of the average L1 norm between results from standard (cleartext) calculation + # and those computed homomorphically. + if len(result) != len(expected_result): + raise Exception("Cannot compare vectors with different numbers of elements") + + # using the average + acc_error = 0 + for i in range(len(result)): + acc_error += abs(result[i] - expected_result[i]) + return abs(math.log2(acc_error / len(result))) + + +def simple_bootstrap_example(): + parameters = CCParamsCKKSRNS() + + # A. Specify main parameters + # A1) Secret key distribution + # SPARSE_ENCAPSULATED is recommended for CKKS bootstrapping (probability of failure below 2^-128). + # UNIFORM_TERNARY, used here, is the distribution of the homomorphic encryption security guidelines; + # its probability of failure is 2^-67 for N = 2^16 and 2^-27 for N = 2^17 with full packing. + # SPARSE_TERNARY (original CKKS paper) is discouraged: about 2^-23 for N = 2^16. + secret_key_dist = SecretKeyDist.UNIFORM_TERNARY + parameters.SetSecretKeyDist(secret_key_dist) + + rescale_tech = ScalingTechnique.COMPOSITESCALINGAUTO + dcrt_bits = 98 + first_mod = 100 + + parameters.SetScalingModSize(dcrt_bits) + parameters.SetScalingTechnique(rescale_tech) + parameters.SetFirstModSize(first_mod) + + parameters.SetSecurityLevel(SecurityLevel.HEStd_NotSet) + parameters.SetRingDim(1 << 12) + + register_word_size = 64 + parameters.SetRegisterWordSize(register_word_size) + + level_budget = [4, 4] + + levels_available_after_bootstrap = 10 + + depth = levels_available_after_bootstrap + FHECKKSRNS.GetBootstrapDepth(level_budget, secret_key_dist) + + print(f"levelBudget[0] = {level_budget[0]}") + print(f"levelBudget[1] = {level_budget[1]}") + print(f"secretKeyDist = {secret_key_dist}") + print(f"depth = {depth}") + + parameters.SetMultiplicativeDepth(depth) + + cryptocontext = GenCryptoContext(parameters) + cryptocontext.Enable(PKESchemeFeature.PKE) + cryptocontext.Enable(PKESchemeFeature.KEYSWITCH) + cryptocontext.Enable(PKESchemeFeature.LEVELEDSHE) + cryptocontext.Enable(PKESchemeFeature.ADVANCEDSHE) + cryptocontext.Enable(PKESchemeFeature.FHE) + + ring_dim = cryptocontext.GetRingDimension() + # This is the maximum number of slots that can be used for full packing. + num_slots = ring_dim // 2 + print(f"CKKS scheme is using ring dimension {ring_dim}\n") + + cryptocontext.EvalBootstrapSetup(level_budget) + + key_pair = cryptocontext.KeyGen() + cryptocontext.EvalMultKeyGen(key_pair.secretKey) + cryptocontext.EvalBootstrapKeyGen(key_pair.secretKey, num_slots) + + x = [0.25, 0.5, 0.75, 1.0, 2.0, 3.0, 4.0, 5.0] + encoded_length = len(x) + + composite_degree = cryptocontext.GetCompositeDegree() + + ptxt = cryptocontext.MakeCKKSPackedPlaintext(x, 1, composite_degree * (depth - 1)) + + print(f"Composite degree: {composite_degree} Bit length: {dcrt_bits / composite_degree} " + f"Register size: {register_word_size}") + + ptxt.SetLength(encoded_length) + print(f"Input: {ptxt}") + + ciph = cryptocontext.Encrypt(key_pair.publicKey, ptxt) + + print(f"Initial number of levels remaining: {depth - ciph.GetLevel() // composite_degree}") + + # Perform the bootstrapping operation. The goal is to increase the number of levels remaining + # for HE computation. + ciphertext_after = cryptocontext.EvalBootstrap(ciph, 1) + + print(f"Number of levels remaining after bootstrapping: " + f"{depth - ciphertext_after.GetLevel() // composite_degree - (ciphertext_after.GetNoiseScaleDeg() - 1)}\n") + + print(f"Scaling factor after bootstrapping: {ciphertext_after.GetScalingFactor()}") + + print(f"Composite degree: {cryptocontext.GetCompositeDegree()}") + print(f"Modulus bit length: {dcrt_bits / cryptocontext.GetCompositeDegree()}") + print(f"Word register size: {register_word_size}") + + result = cryptocontext.Decrypt(ciphertext_after, key_pair.secretKey) + result.SetLength(encoded_length) + print(f"Output after bootstrapping \n\t{result}") + + actual_result = result.GetCKKSPackedValue() + precision = calculate_approximation_error(actual_result, ptxt.GetCKKSPackedValue()) + print(f"Estimated precision: {precision}") + + +def simple_bootstrap_stc_first_example(): + parameters = CCParamsCKKSRNS() + + secret_key_dist = SecretKeyDist.UNIFORM_TERNARY + parameters.SetSecretKeyDist(secret_key_dist) + + rescale_tech = ScalingTechnique.COMPOSITESCALINGAUTO + dcrt_bits = 98 + first_mod = 100 + + parameters.SetScalingModSize(dcrt_bits) + parameters.SetScalingTechnique(rescale_tech) + parameters.SetFirstModSize(first_mod) + + parameters.SetSecurityLevel(SecurityLevel.HEStd_NotSet) + parameters.SetRingDim(1 << 12) + + register_word_size = 64 + parameters.SetRegisterWordSize(register_word_size) + + level_budget = [4, 4] + + # Note that the number of levels available after bootstrapping in the next bootstrapping call + # will be levelsAvailableAfterBootstrap - 1 because an additional level + # is used for scaling the ciphertext before next bootstrapping (in 64-bit CKKS bootstrapping) + levels_available_after_bootstrap = 10 + level_budget[1] + + depth = levels_available_after_bootstrap + FHECKKSRNS.GetBootstrapDepth([level_budget[0], 0], secret_key_dist) + + print(f"levelBudget[0] = {level_budget[0]}") + print(f"levelBudget[1] = {level_budget[1]}") + print(f"secretKeyDist = {secret_key_dist}") + print(f"depth = {depth}") + + parameters.SetMultiplicativeDepth(depth) + + cryptocontext = GenCryptoContext(parameters) + cryptocontext.Enable(PKESchemeFeature.PKE) + cryptocontext.Enable(PKESchemeFeature.KEYSWITCH) + cryptocontext.Enable(PKESchemeFeature.LEVELEDSHE) + cryptocontext.Enable(PKESchemeFeature.ADVANCEDSHE) + cryptocontext.Enable(PKESchemeFeature.FHE) + + ring_dim = cryptocontext.GetRingDimension() + # This is the maximum number of slots that can be used for full packing. + num_slots = ring_dim // 2 + print(f"CKKS scheme is using ring dimension {ring_dim}\n") + + cryptocontext.EvalBootstrapSetup(level_budget, [0, 0], num_slots, 0, True, True) + + key_pair = cryptocontext.KeyGen() + cryptocontext.EvalMultKeyGen(key_pair.secretKey) + cryptocontext.EvalBootstrapKeyGen(key_pair.secretKey, num_slots) + + x = [0.25, 0.5, 0.75, 1.0, 2.0, 3.0, 4.0, 5.0] + encoded_length = len(x) + + composite_degree = cryptocontext.GetCompositeDegree() + + # We start with a depleted ciphertext that has used up all of its levels. + ptxt = cryptocontext.MakeCKKSPackedPlaintext(x, 1, composite_degree * (depth - 1 - level_budget[1])) + + print(f"Composite degree: {composite_degree} Bit length: {dcrt_bits / composite_degree} " + f"Register size: {register_word_size}") + + ptxt.SetLength(encoded_length) + print(f"Input: {ptxt}") + + ciph = cryptocontext.Encrypt(key_pair.publicKey, ptxt) + + print(f"Initial number of levels remaining: {depth - ciph.GetLevel() // composite_degree}") + + # Perform the bootstrapping operation. The goal is to increase the number of levels remaining + # for HE computation. + ciphertext_after = cryptocontext.EvalBootstrap(ciph) + + print(f"Number of levels remaining after bootstrapping: " + f"{depth - ciphertext_after.GetLevel() // composite_degree - (ciphertext_after.GetNoiseScaleDeg() - 1)}\n") + + print(f"Scaling factor after bootstrapping: {ciphertext_after.GetScalingFactor()}") + + print(f"Composite degree: {cryptocontext.GetCompositeDegree()}") + print(f"Modulus bit length: {dcrt_bits / cryptocontext.GetCompositeDegree()}") + print(f"Word register size: {register_word_size}") + + result = cryptocontext.Decrypt(ciphertext_after, key_pair.secretKey) + result.SetLength(encoded_length) + print(f"Output after bootstrapping \n\t{result}") + + actual_result = result.GetCKKSPackedValue() + precision = calculate_approximation_error(actual_result, ptxt.GetCKKSPackedValue()) + print(f"Estimated precision: {precision}") + + +if __name__ == "__main__": + main() diff --git a/examples/pke/simple-composite-scaling-manual.py b/examples/pke/simple-composite-scaling-manual.py new file mode 100644 index 0000000..46bb1f0 --- /dev/null +++ b/examples/pke/simple-composite-scaling-manual.py @@ -0,0 +1,205 @@ +# +# Simple examples for CKKS in the COMPOSITESCALINGMANUAL mode, with the register +# word size and composite degree set explicitly +# + +from openfhe import * +import sys + + +def main(args=[]): + # Step 1: Setup CryptoContext + + # A. Specify main parameters + # A1) Multiplicative depth: the maximum possible depth of a given + # multiplication, but not the total number of multiplications supported by + # the scheme. + multDepth = 2 + + # A2) Bit-length of scaling factor. + firstModSize = 96 + scaleModSize = 95 + + # A3) Number of plaintext slots used in the ciphertext. + batchSize = 8 + + # The word size in bits of the target hardware architecture. + registerWordSize = 27 + + # This example features the usage of COMPOSITESCALINGMANUAL scaling technique usage. + # Internally, it works the same way as COMPOSITESCALINGAUTO. The only difference is that + # it allows the user/developer to manually set the composite degree value d without any restrictions. + # Use it in practice to explore different combinations of parameters (registerWordSize, scalingModSize, + # and compositeDegree) that might be suitable to the target application/workload and hardware but it is + # typically assumed to be a faulty selection in COMPOSITESCALINGAUTO. Note that there is no guarantee it + # will work. It solely increases the degrees of freedom in COMPOSITESCALING usage that can be manipulated + # by the user/developer. + scalTech = ScalingTechnique.COMPOSITESCALINGMANUAL + + # Desired and manually-set composite degree d in [2,3,4]. + compositeDegree = 4 + + # Parse CLI args like the C++ demo: + # script.py [firstModSize] [scalingModSize] [registerWordSize] [compositeDegree] [multDepth] + if len(args) > 0: + argcCount = 0 + while argcCount < len(args): + paramValue = int(args[argcCount]) + if argcCount == 0: + firstModSize = paramValue + print(f"Setting First Mod Size: {firstModSize}") + elif argcCount == 1: + scaleModSize = paramValue + print(f"Setting Scaling Mod Size: {scaleModSize}") + elif argcCount == 2: + registerWordSize = paramValue + print(f"Setting Register Word Size: {registerWordSize}") + elif argcCount == 3: + compositeDegree = paramValue + print(f"Setting Composite Degree: {compositeDegree}") + elif argcCount == 4: + multDepth = paramValue + print(f"Setting Multiplicative Depth: {multDepth}") + else: + print("Invalid option") + argcCount += 1 + print(f"argcCount: {argcCount + 1}") + print("Complete !") + else: + print("Using default parameters") + print(f"First Mod Size: {firstModSize}") + print(f"Scaling Mod Size: {scaleModSize}") + print(f"Register Word Size: {registerWordSize}") + print(f"Composite Degree: {compositeDegree}") + print(f"Multiplicative Depth: {multDepth}") + print(f"Usage: {sys.argv[0]} [firstModSize] [scalingModSize] [registerWordSize] [compositeDegree] [multDepth]") + + # A4) Desired security level based on FHE standards. + parameters = CCParamsCKKSRNS() + parameters.SetMultiplicativeDepth(multDepth) + parameters.SetFirstModSize(firstModSize) + parameters.SetScalingModSize(scaleModSize) + parameters.SetBatchSize(batchSize) + parameters.SetSecurityLevel(SecurityLevel.HEStd_NotSet) + parameters.SetRingDim(1 << 12) + + parameters.SetScalingTechnique(scalTech) + parameters.SetRegisterWordSize(registerWordSize) + parameters.SetCompositeDegree(compositeDegree) + + cc = GenCryptoContext(parameters) + print(f"Composite Degree: {cc.GetCompositeDegree()}") + print(f"Prime Modulus Size: {scaleModSize / cc.GetCompositeDegree()}") + print(f"Register Word Size: {registerWordSize}") + + # Enable the features that you wish to use + cc.Enable(PKESchemeFeature.PKE) + cc.Enable(PKESchemeFeature.KEYSWITCH) + cc.Enable(PKESchemeFeature.LEVELEDSHE) + print(f"CKKS scheme is using ring dimension {cc.GetRingDimension()}\n") + + # B. Step 2: Key Generation + # B1) Generate encryption keys. + keys = cc.KeyGen() + + # B2) Generate the relinearization key + cc.EvalMultKeyGen(keys.secretKey) + + # B3) Generate the rotation keys + cc.EvalRotateKeyGen(keys.secretKey, [1, -2]) + + # Step 3: Encoding and encryption of inputs + + # Inputs + x1 = [0.25, 0.5, 0.75, 1.0, 2.0, 3.0, 4.0, 5.0] + x2 = [5.0, 4.0, 3.0, 2.0, 1.0, 0.75, 0.5, 0.25] + + # Encoding as plaintexts + ptxt1 = cc.MakeCKKSPackedPlaintext(x1) + ptxt2 = cc.MakeCKKSPackedPlaintext(x2) + + print(f"Input x1: {ptxt1}") + print(f"Input x2: {ptxt2}") + + # Encrypt the encoded vectors + c1 = cc.Encrypt(keys.publicKey, ptxt1) + c2 = cc.Encrypt(keys.publicKey, ptxt2) + + # Step 4: Evaluation + + # Homomorphic addition + cAdd = cc.EvalAdd(c1, c2) + + # Homomorphic subtraction + cSub = cc.EvalSub(c1, c2) + + # Homomorphic scalar multiplication + cScalar = cc.EvalMult(c1, 4.0) + + # Homomorphic multiplication + cMul = cc.EvalMult(c1, c2) + + # Homomorphic rotations + cRot1 = cc.EvalRotate(c1, 1) + cRot2 = cc.EvalRotate(c1, -2) + + # Step 5: Decryption and output + print("\nResults of homomorphic computations: ") + + result = cc.Decrypt(keys.secretKey, c1) + result.SetLength(batchSize) + print(f"x1 = {result}", end="") + print(f"Estimated precision in bits: {result.GetLogPrecision()}") + + # Decrypt the result of addition + result = cc.Decrypt(keys.secretKey, cAdd) + result.SetLength(batchSize) + print(f"x1 + x2 = {result}", end="") + print(f"Estimated precision in bits: {result.GetLogPrecision()}") + + # Decrypt the result of subtraction + result = cc.Decrypt(keys.secretKey, cSub) + result.SetLength(batchSize) + print(f"x1 - x2 = {result}") + + # Decrypt the result of scalar multiplication + result = cc.Decrypt(keys.secretKey, cScalar) + result.SetLength(batchSize) + print(f"4 * x1 = {result}") + + # Decrypt the result of multiplication + result = cc.Decrypt(keys.secretKey, cMul) + result.SetLength(batchSize) + print(f"x1 * x2 = {result}") + + # Decrypt the result of rotations + result = cc.Decrypt(keys.secretKey, cRot1) + result.SetLength(batchSize) + print("\nIn rotations, very small outputs (~10^-10 here) correspond to 0's:") + print(f"x1 rotate by 1 = {result}") + + result = cc.Decrypt(keys.secretKey, cRot2) + result.SetLength(batchSize) + print(f"x1 rotate by -2 = {result}") + + # Testing EvalSub ciphertext - double + cSubDouble = cc.EvalSub(c1, 0.5) + print(f"c1 noise degree = {c1.GetNoiseScaleDeg()}") + print(f"c1 scaling factor = {c1.GetScalingFactor()}") + + # Testing EvalAdd ciphertext + negative double + cAddNegDouble = cc.EvalAdd(c1, -0.5) + + # Decrypt the result of subtraction + result = cc.Decrypt(keys.secretKey, cSubDouble) + result.SetLength(batchSize) + print(f"x1 - 0.5 = {result}") + + # Decrypt the result of addition + result = cc.Decrypt(keys.secretKey, cAddNegDouble) + result.SetLength(batchSize) + print(f"x1 + (-0.5) = {result}") + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/examples/pke/simple-real-numbers-composite-scaling.py b/examples/pke/simple-real-numbers-composite-scaling.py index 2b14dbd..36d16a1 100644 --- a/examples/pke/simple-real-numbers-composite-scaling.py +++ b/examples/pke/simple-real-numbers-composite-scaling.py @@ -2,7 +2,7 @@ import sys -def main(): +def main(args=()): # Defaults (same as C++) multDepth = 2 firstModSize = 90 @@ -12,11 +12,10 @@ def main(): # Parse CLI args like the C++ demo: # script.py [firstModSize] [scalingModSize] [registerWordSize] [multDepth] - argv = sys.argv - if len(argv) > 1: + if args: argcCount = 1 - while argcCount < len(argv): - paramValue = int(argv[argcCount]) + while argcCount <= len(args): + paramValue = int(args[argcCount - 1]) if argcCount == 1: firstModSize = paramValue print(f"Setting First Mod Size: {firstModSize}") @@ -40,7 +39,7 @@ def main(): print(f"Scaling Mod Size: {scaleModSize}") print(f"Register Word Size: {registerWordSize}") print(f"Multiplicative Depth: {multDepth}") - print(f"Usage: {argv[0]} [firstModSize] [scalingModSize] [registerWordSize] [multDepth]") + print(f"Usage: {sys.argv[0]} [firstModSize] [scalingModSize] [registerWordSize] [multDepth]") # Step 1: Setup CryptoContext (CKKS) parameters = CCParamsCKKSRNS() @@ -146,4 +145,4 @@ def main(): if __name__ == "__main__": - main() + main(sys.argv[1:]) diff --git a/examples/pke/tckks-interactive-mp-bootstrapping-Chebyschev.py b/examples/pke/tckks-interactive-mp-bootstrapping-Chebyshev.py similarity index 100% rename from examples/pke/tckks-interactive-mp-bootstrapping-Chebyschev.py rename to examples/pke/tckks-interactive-mp-bootstrapping-Chebyshev.py diff --git a/src/include/bindings.h b/src/include/bindings.h index 1f06ba6..a1021ab 100644 --- a/src/include/bindings.h +++ b/src/include/bindings.h @@ -31,8 +31,17 @@ #ifndef __BINDINGS_H__ #define __BINDINGS_H__ +#include "openfhe.h" + #include +#include + +using DCRTCryptoContextClass = pybind11::class_< + lbcrypto::CryptoContextImpl, + std::shared_ptr>>; +using FHECKKSRNSClass = pybind11::class_; + void bind_parameters(pybind11::module &m); void bind_crypto_context(pybind11::module &m); void bind_enums_and_constants(pybind11::module &m); @@ -42,5 +51,8 @@ void bind_ciphertext(pybind11::module &m); void bind_serialization(pybind11::module &m); void bind_schemes(pybind11::module &m); void bind_sch_swch_params(pybind11::module &m); +void bind_fbt_crypto_context(DCRTCryptoContextClass &cls); +void bind_fbt_scheme(FHECKKSRNSClass &cls); +void bind_fbt(pybind11::module &m); #endif // __BINDINGS_H__ diff --git a/src/include/binfhe_bindings.h b/src/include/binfhe_bindings.h index e2f49ef..4f576b9 100644 --- a/src/include/binfhe_bindings.h +++ b/src/include/binfhe_bindings.h @@ -37,4 +37,5 @@ void bind_binfhe_enums(pybind11::module &m); void bind_binfhe_context(pybind11::module &m); void bind_binfhe_keys(pybind11::module &m); void bind_binfhe_ciphertext(pybind11::module &m); +void bind_binfhe_serialization(pybind11::module &m); #endif // __BINFHE_BINDINGS_H__ \ No newline at end of file diff --git a/src/lib/bindings.cpp b/src/lib/bindings.cpp index b5727e7..79ecd40 100644 --- a/src/lib/bindings.cpp +++ b/src/lib/bindings.cpp @@ -62,6 +62,12 @@ inline std::shared_ptr GetParamsRNSChecked(const CryptoCont return ptr; } +// Converts BigInteger to an arbitrary-size Python integer using its decimal +// string representation, as BigInteger may not fit in any native integer type. +inline py::int_ BigIntegerToPyInt(const BigInteger& value) { + return py::reinterpret_steal(PyLong_FromString(value.ToString().c_str(), nullptr, 10)); +} + void bind_DCRTPoly(py::module &m) { py::class_(m, "DCRTPoly").def(py::init<>()); } @@ -386,7 +392,11 @@ void bind_crypto_context(py::module &m) { // TODO (Oliveira): If we expose Poly's and ParmType, this block will go somewhere else using ParmType = typename DCRTPoly::Params; using ParmTypePtr = std::shared_ptr; - py::class_(m, "ParmType"); + py::class_(m, "ParmType") + .def("GetModulus", + [](const ParmType& self) { return BigIntegerToPyInt(self.GetModulus()); }) + .def("GetRingDimension", &ParmType::GetRingDimension) + .def("GetCyclotomicOrder", &ParmType::GetCyclotomicOrder); auto cc_class = py::class_, std::shared_ptr>>(m, "CryptoContext"); @@ -989,6 +999,7 @@ void bind_crypto_context(py::module &m) { py::arg("numIterations") = 1, py::arg("precision") = 0, py::doc(cc_EvalBootstrap_docs)) + .def_static("ClearStaticMapsAndVectors", &CryptoContextImpl::ClearStaticMapsAndVectors) .def("EvalCKKStoFHEWSetup", &CryptoContextImpl::EvalCKKStoFHEWSetup, py::arg("schswchparams"), py::doc(cc_EvalCKKStoFHEWSetup_docs)) @@ -1244,6 +1255,8 @@ void bind_crypto_context(py::module &m) { py::arg("sertype"), py::doc(cc_DeserializeEvalAutomorphismKey_docs)); + bind_fbt_crypto_context(cc_class); + bind_crypto_context_templates(cc_class); bind_crypto_context_templates(cc_class); bind_crypto_context_templates>(cc_class); @@ -1284,6 +1297,7 @@ void bind_crypto_context(py::module &m) { m.def("ReleaseAllContexts", &CryptoContextFactory::ReleaseAllContexts); m.def("ClearEvalMultKeys", static_cast(&CryptoContextImpl::ClearEvalMultKeys)); + m.def("ClearEvalSumKeys", static_cast(&CryptoContextImpl::ClearEvalSumKeys)); } int get_native_int() { @@ -1665,7 +1679,9 @@ void bind_ciphertext(py::module &m) { void bind_schemes(py::module &m) { // Bind schemes specific functionalities like bootstrapping functions and multiparty - py::class_(m, "FHECKKSRNS") + auto fheckks_class = py::class_(m, "FHECKKSRNS"); + + fheckks_class .def(py::init<>()) .def_static("GetBootstrapDepth", py::overload_cast&, SecretKeyDist>(&FHECKKSRNS::GetBootstrapDepth), @@ -1675,8 +1691,9 @@ void bind_schemes(py::module &m) { .def_static("GetBootstrapDepth", py::overload_cast&, SecretKeyDist>(&FHECKKSRNS::GetBootstrapDepth), py::arg("levelBudget"), - py::arg("keyDist")) - ; + py::arg("keyDist")); + + bind_fbt_scheme(fheckks_class); } void bind_sch_swch_params(py::module &m) { @@ -1757,6 +1774,7 @@ PYBIND11_MODULE(openfhe, m) { bind_binfhe_ciphertext(m); bind_binfhe_keys(m); bind_binfhe_context(m); + bind_binfhe_serialization(m); // pke library bind_enums_and_constants(m); bind_parameters(m,"CCParamsBFVRNS"); @@ -1769,6 +1787,7 @@ PYBIND11_MODULE(openfhe, m) { bind_serialization(m); bind_schemes(m); bind_sch_swch_params(m); + bind_fbt(m); bind_utils(m); bind_free_functions(m); } diff --git a/src/lib/binfhe_bindings.cpp b/src/lib/binfhe_bindings.cpp index eb141b5..149639e 100644 --- a/src/lib/binfhe_bindings.cpp +++ b/src/lib/binfhe_bindings.cpp @@ -32,6 +32,7 @@ #include "openfhe.h" #include "binfhecontext.h" +#include "binfhecontext-ser.h" #include "binfhecontext_docs.h" #include "binfhecontext_wrapper.h" @@ -40,6 +41,10 @@ #include "cereal/archives/binary.hpp" +#include +#include +#include + using namespace lbcrypto; namespace py = pybind11; @@ -130,7 +135,13 @@ void bind_binfhe_enums(py::module &m) { .value("XOR_FAST", BINGATE::XOR_FAST) .value("XNOR_FAST", BINGATE::XNOR_FAST) .value("XOR", BINGATE::XOR) - .value("XNOR", BINGATE::XNOR); + .value("XNOR", BINGATE::XNOR) + .value("MAJORITY", BINGATE::MAJORITY) + .value("AND3", BINGATE::AND3) + .value("OR3", BINGATE::OR3) + .value("AND4", BINGATE::AND4) + .value("OR4", BINGATE::OR4) + .value("CMUX", BINGATE::CMUX); m.attr("OR") = py::cast(BINGATE::OR); m.attr("AND") = py::cast(BINGATE::AND); m.attr("NOR") = py::cast(BINGATE::NOR); @@ -139,6 +150,12 @@ void bind_binfhe_enums(py::module &m) { m.attr("XNOR_FAST") = py::cast(BINGATE::XNOR_FAST); m.attr("XOR") = py::cast(BINGATE::XOR); m.attr("XNOR") = py::cast(BINGATE::XNOR); + m.attr("MAJORITY") = py::cast(BINGATE::MAJORITY); + m.attr("AND3") = py::cast(BINGATE::AND3); + m.attr("OR3") = py::cast(BINGATE::OR3); + m.attr("AND4") = py::cast(BINGATE::AND4); + m.attr("OR4") = py::cast(BINGATE::OR4); + m.attr("CMUX") = py::cast(BINGATE::CMUX); } void bind_binfhe_keys(py::module &m) { @@ -147,6 +164,22 @@ void bind_binfhe_keys(py::module &m) { .def("GetLength", &LWEPrivateKeyImpl::GetLength) .def(py::self == py::self) .def(py::self != py::self); + + py::class_>(m, "LWEPublicKey") + .def(py::init<>()) + .def("GetLength", &LWEPublicKeyImpl::GetLength) + .def(py::self == py::self) + .def(py::self != py::self); + + py::class_>(m, "LWEKeyPair") + .def_readonly("publicKey", &LWEKeyPairImpl::publicKey) + .def_readonly("secretKey", &LWEKeyPairImpl::secretKey) + .def("good", &LWEKeyPairImpl::good); + + // struct holding the refreshing, key switching and public keys generated by BTKeyGen + // (treated as an opaque object in Python: generate, serialize and load it) + py::class_(m, "RingGSWBTKey") + .def(py::init<>()); } void bind_binfhe_ciphertext(py::module &m) { py::class_>(m, "LWECiphertext") @@ -186,7 +219,8 @@ void bind_binfhe_context(py::module &m) { .def("BTKeyGen", &BinFHEContext::BTKeyGen, binfhe_BTKeyGen_docs, py::arg("sk"), - py::arg("keygenMode") = SYM_ENCRYPT) + py::arg("keygenMode") = SYM_ENCRYPT, + py::arg("internal32") = true) .def("Encrypt", [](BinFHEContext& self, ConstLWEPrivateKey sk, const LWEPlaintext& m, BINFHE_OUTPUT output, LWEPlaintextModulus p, uint64_t mod) { return self.Encrypt(sk, m, output, p, NativeInteger(mod)); @@ -197,6 +231,16 @@ void bind_binfhe_context(py::module &m) { py::arg("p") = 4, py::arg("mod") = 0, py::doc(binfhe_Encrypt_docs)) + .def("Encrypt", + [](BinFHEContext& self, ConstLWEPublicKey pk, const LWEPlaintext& m, BINFHE_OUTPUT output, LWEPlaintextModulus p, uint64_t mod) { + return self.Encrypt(pk, m, output, p, NativeInteger(mod)); + }, + py::arg("pk"), + py::arg("m"), + py::arg("output") = SMALL_DIM, + py::arg("p") = 4, + py::arg("mod") = 0, + py::doc(binfhe_Encrypt_docs)) .def("Decrypt", [](BinFHEContext& self, ConstLWEPrivateKey sk, ConstLWECiphertext ct, LWEPlaintextModulus p) { LWEPlaintext result; @@ -283,5 +327,72 @@ void bind_binfhe_context(py::module &m) { .def("GetRefreshKey", &BinFHEContext::GetRefreshKey) .def("GetBinFHEScheme", &BinFHEContext::GetBinFHEScheme) .def("GetLWEScheme", &BinFHEContext::GetLWEScheme) - .def("GetParams", &BinFHEContext::GetParams); + .def("GetParams", &BinFHEContext::GetParams) + .def("GetN", + [](BinFHEContext& self) { + return self.GetParams()->GetLWEParams()->GetN(); + }) + .def("GetQ", + [](BinFHEContext& self) { + return self.GetParams()->GetLWEParams()->GetQ().ConvertToInt(); + }) + .def("GetBTKey", &BinFHEContext::GetBTKey) + .def("BTKeyLoad", &BinFHEContext::BTKeyLoad, + py::arg("key"), + py::arg("internal32") = true) + .def("GetBTKeyMap", + [](BinFHEContext& self) { + return *self.GetBTKeyMap(); + }) + .def("BTKeyMapLoadSingleElement", &BinFHEContext::BTKeyMapLoadSingleElement, + py::arg("baseG"), + py::arg("key")); +} + +// binfhe serialization helpers, following the same patterns as src/lib/pke/serialization.cpp + +template +static std::tuple BinFHEDeserializeFromFileWrapper(const std::string& filename, const ST& sertype) { + T newob; + bool result = Serial::DeserializeFromFile(filename, newob, sertype); + return std::make_tuple(newob, result); +} + +template +static void bind_binfhe_serialization_for_sertype(py::module &m) { + m.def("SerializeToFile", + static_cast( + &Serial::SerializeToFile), + py::arg("filename"), py::arg("obj"), py::arg("sertype")); + m.def("DeserializeBinFHECryptoContext", &BinFHEDeserializeFromFileWrapper, + py::arg("filename"), py::arg("sertype")); + m.def("SerializeToFile", + static_cast( + &Serial::SerializeToFile), + py::arg("filename"), py::arg("obj"), py::arg("sertype")); + m.def("DeserializeLWECiphertext", &BinFHEDeserializeFromFileWrapper, + py::arg("filename"), py::arg("sertype")); + m.def("SerializeToFile", + static_cast( + &Serial::SerializeToFile), + py::arg("filename"), py::arg("obj"), py::arg("sertype")); + m.def("DeserializeLWEPrivateKey", &BinFHEDeserializeFromFileWrapper, + py::arg("filename"), py::arg("sertype")); + m.def("SerializeToFile", + static_cast( + &Serial::SerializeToFile), + py::arg("filename"), py::arg("obj"), py::arg("sertype")); + m.def("DeserializeLWEPublicKey", &BinFHEDeserializeFromFileWrapper, + py::arg("filename"), py::arg("sertype")); + m.def("SerializeToFile", + static_cast( + &Serial::SerializeToFile), + py::arg("filename"), py::arg("obj"), py::arg("sertype")); + m.def("DeserializeBinFHEBTKey", &BinFHEDeserializeFromFileWrapper, + py::arg("filename"), py::arg("sertype")); +} + +void bind_binfhe_serialization(py::module &m) { + bind_binfhe_serialization_for_sertype(m); + bind_binfhe_serialization_for_sertype(m); } diff --git a/src/lib/pke/fbt.cpp b/src/lib/pke/fbt.cpp new file mode 100644 index 0000000..0a2c988 --- /dev/null +++ b/src/lib/pke/fbt.cpp @@ -0,0 +1,305 @@ +//================================================================================== +// BSD 2-Clause License +// +// Copyright (c) 2023-2026, Duality Technologies Inc. and other contributors +// +// All rights reserved. +// +// Author TPOC: contact@openfhe.org +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +//================================================================================== + +// Bindings for CKKS functional bootstrapping helpers: the RLWE schemelet +// (SchemeletRLWEMP), trigonometric Hermite interpolation coefficients and +// the scheme-switching data (de)serializer. + +#include "bindings.h" + +#include "openfhe.h" +#include "math/hermite.h" +#include "schemelet/rlwe-mp.h" +#include "scheme/ckksrns/ckksrns-utils.h" +#include "unittest/utils/schemeswitching-data-serializer.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace lbcrypto; +using CC = CryptoContextImpl; +namespace py = pybind11; + +// disable the PYBIND11 template-based conversion for this type: a vector of Poly is +// treated as an opaque RLWE ciphertext produced/consumed by SchemeletRLWEMP +PYBIND11_MAKE_OPAQUE(std::vector); + +using ElementParams = ILDCRTParams; + +// Converts an arbitrary-size Python integer to BigInteger (and back) using its decimal +// string representation, as BigInteger may not fit in any native integer type. +static BigInteger FBTPyIntToBigInteger(const py::int_& value) { + auto valueStr = py::reinterpret_steal(PyObject_Str(value.ptr())); + return BigInteger(valueStr.cast()); +} +static py::int_ FBTBigIntegerToPyInt(const BigInteger& value) { + return py::reinterpret_steal(PyLong_FromString(value.ToString().c_str(), nullptr, 10)); +} + +void bind_fbt_crypto_context(DCRTCryptoContextClass &cls) { + // CKKS functional bootstrapping of a look-up table. The overloads taking a list of ints + // correspond to the optimized Boolean case (integer Hermite coefficients); the overloads + // taking a list of complex numbers cover the general case. Lambdas convert + // arbitrary-size Python ints to BigInteger. + cls.def("EvalFBTSetup", + [](CC& self, const std::vector& coeffs, uint32_t numSlots, const py::int_& PIn, + const py::int_& POut, const py::int_& Bigq, const PublicKey& pubKey, + const std::vector& dim1, const std::vector& levelBudget, uint32_t lvlsAfterBoot, + uint32_t depthLeveledComputation, size_t order) { + self.EvalFBTSetup(coeffs, numSlots, FBTPyIntToBigInteger(PIn), FBTPyIntToBigInteger(POut), + FBTPyIntToBigInteger(Bigq), pubKey, dim1, levelBudget, lvlsAfterBoot, + depthLeveledComputation, order); + }, + py::arg("coeffs"), py::arg("numSlots"), py::arg("PIn"), py::arg("POut"), py::arg("Bigq"), + py::arg("pubKey"), py::arg("dim1"), py::arg("levelBudget"), py::arg("lvlsAfterBoot") = 0, + py::arg("depthLeveledComputation") = 0, py::arg("order") = (size_t)1) + .def("EvalFBTSetup", + [](CC& self, const std::vector>& coeffs, uint32_t numSlots, const py::int_& PIn, + const py::int_& POut, const py::int_& Bigq, const PublicKey& pubKey, + const std::vector& dim1, const std::vector& levelBudget, uint32_t lvlsAfterBoot, + uint32_t depthLeveledComputation, size_t order) { + self.EvalFBTSetup(coeffs, numSlots, FBTPyIntToBigInteger(PIn), FBTPyIntToBigInteger(POut), + FBTPyIntToBigInteger(Bigq), pubKey, dim1, levelBudget, lvlsAfterBoot, + depthLeveledComputation, order); + }, + py::arg("coeffs"), py::arg("numSlots"), py::arg("PIn"), py::arg("POut"), py::arg("Bigq"), + py::arg("pubKey"), py::arg("dim1"), py::arg("levelBudget"), py::arg("lvlsAfterBoot") = 0, + py::arg("depthLeveledComputation") = 0, py::arg("order") = (size_t)1) + .def("EvalFBT", + [](CC& self, ConstCiphertext ciphertext, const std::vector& coeffs, + uint32_t digitBitSize, const py::int_& initialScaling, uint64_t postScaling, uint32_t levelToReduce, + size_t order) { + return self.EvalFBT(ciphertext, coeffs, digitBitSize, FBTPyIntToBigInteger(initialScaling), postScaling, + levelToReduce, order); + }, + py::arg("ciphertext"), py::arg("coeffs"), py::arg("digitBitSize"), py::arg("initialScaling"), + py::arg("postScaling"), py::arg("levelToReduce") = 0, py::arg("order") = (size_t)1) + .def("EvalFBT", + [](CC& self, ConstCiphertext ciphertext, const std::vector>& coeffs, + uint32_t digitBitSize, const py::int_& initialScaling, uint64_t postScaling, uint32_t levelToReduce, + size_t order) { + return self.EvalFBT(ciphertext, coeffs, digitBitSize, FBTPyIntToBigInteger(initialScaling), postScaling, + levelToReduce, order); + }, + py::arg("ciphertext"), py::arg("coeffs"), py::arg("digitBitSize"), py::arg("initialScaling"), + py::arg("postScaling"), py::arg("levelToReduce") = 0, py::arg("order") = (size_t)1) + .def("EvalFBTNoDecoding", + [](CC& self, ConstCiphertext ciphertext, const std::vector& coeffs, + uint32_t digitBitSize, const py::int_& initialScaling, size_t order) { + return self.EvalFBTNoDecoding(ciphertext, coeffs, digitBitSize, FBTPyIntToBigInteger(initialScaling), + order); + }, + py::arg("ciphertext"), py::arg("coeffs"), py::arg("digitBitSize"), py::arg("initialScaling"), + py::arg("order") = (size_t)1) + .def("EvalFBTNoDecoding", + [](CC& self, ConstCiphertext ciphertext, const std::vector>& coeffs, + uint32_t digitBitSize, const py::int_& initialScaling, size_t order) { + return self.EvalFBTNoDecoding(ciphertext, coeffs, digitBitSize, FBTPyIntToBigInteger(initialScaling), + order); + }, + py::arg("ciphertext"), py::arg("coeffs"), py::arg("digitBitSize"), py::arg("initialScaling"), + py::arg("order") = (size_t)1) + .def("EvalHomDecoding", &CryptoContextImpl::EvalHomDecoding, + py::arg("ciphertext"), py::arg("postScaling"), py::arg("levelToReduce") = 0) + .def("EvalMVBPrecompute", + [](CC& self, ConstCiphertext ciphertext, const std::vector& coeffs, + uint32_t digitBitSize, const py::int_& initialScaling, size_t order) { + return self.EvalMVBPrecompute(ciphertext, coeffs, digitBitSize, FBTPyIntToBigInteger(initialScaling), + order); + }, + py::arg("ciphertext"), py::arg("coeffs"), py::arg("digitBitSize"), py::arg("initialScaling"), + py::arg("order") = (size_t)1) + .def("EvalMVBPrecompute", + [](CC& self, ConstCiphertext ciphertext, const std::vector>& coeffs, + uint32_t digitBitSize, const py::int_& initialScaling, size_t order) { + return self.EvalMVBPrecompute(ciphertext, coeffs, digitBitSize, FBTPyIntToBigInteger(initialScaling), + order); + }, + py::arg("ciphertext"), py::arg("coeffs"), py::arg("digitBitSize"), py::arg("initialScaling"), + py::arg("order") = (size_t)1) + .def("EvalMVB", &CryptoContextImpl::EvalMVB, + py::arg("ciphertexts"), py::arg("coeffs"), py::arg("digitBitSize"), py::arg("postScaling"), + py::arg("levelToReduce") = 0, py::arg("order") = (size_t)1) + .def("EvalMVB", &CryptoContextImpl::EvalMVB>, + py::arg("ciphertexts"), py::arg("coeffs"), py::arg("digitBitSize"), py::arg("postScaling"), + py::arg("levelToReduce") = 0, py::arg("order") = (size_t)1) + .def("EvalMVBNoDecoding", &CryptoContextImpl::EvalMVBNoDecoding, + py::arg("ciphertexts"), py::arg("coeffs"), py::arg("digitBitSize"), py::arg("order") = (size_t)1) + .def("EvalMVBNoDecoding", &CryptoContextImpl::EvalMVBNoDecoding>, + py::arg("ciphertexts"), py::arg("coeffs"), py::arg("digitBitSize"), py::arg("order") = (size_t)1) + // FE functional bootstrapping + .def("EvalFEFuncBootstrapSetup", &CryptoContextImpl::EvalFEFuncBootstrapSetup, + py::arg("levelBudget") = std::vector({5, 4}), + py::arg("dim1") = std::vector({0, 0}), + py::arg("slots") = 0) + .def("EvalFEFuncBootstrap", &CryptoContextImpl::EvalFEFuncBootstrap, + py::arg("ciphertext"), py::arg("coefficients")) + .def("EvalFEFuncBootstrapPrecompute", &CryptoContextImpl::EvalFEFuncBootstrapPrecompute, + py::arg("ciphertext"), py::arg("coefficients")) + .def("EvalFEFuncBootstrapWithPrecomp", &CryptoContextImpl::EvalFEFuncBootstrapWithPrecomp, + py::arg("powers"), py::arg("coefficients")) + .def("ClearBootstrapPrecom", &CryptoContextImpl::ClearBootstrapPrecom); +} + +void bind_fbt_scheme(FHECKKSRNSClass &cls) { + // GetFBTDepth uses lambdas to convert arbitrary-size Python ints to BigInteger. + cls.def_static("GetFBTDepth", + [](const std::vector& levelBudget, const std::vector& coefficients, + const py::int_& PInput, size_t order, SecretKeyDist skd, uint32_t firstModSize) { + return FHECKKSRNS::GetFBTDepth(levelBudget, coefficients, FBTPyIntToBigInteger(PInput), order, skd, + firstModSize); + }, + py::arg("levelBudget"), py::arg("coefficients"), py::arg("PInput"), py::arg("order"), py::arg("skd"), + py::arg("firstModSize") = 60) + .def_static("GetFBTDepth", + [](const std::vector& levelBudget, const std::vector>& coefficients, + const py::int_& PInput, size_t order, SecretKeyDist skd, uint32_t firstModSize) { + return FHECKKSRNS::GetFBTDepth(levelBudget, coefficients, FBTPyIntToBigInteger(PInput), order, skd, + firstModSize); + }, + py::arg("levelBudget"), py::arg("coefficients"), py::arg("PInput"), py::arg("order"), py::arg("skd"), + py::arg("firstModSize") = 60) + .def_static("GetFEFBTDepth", &FHECKKSRNS::GetFEFBTDepth>, + py::arg("levelBudget"), py::arg("coefficients"), py::arg("skd") = SPARSE_TERNARY, + py::arg("firstModSize") = 60); +} + +void bind_fbt(py::module &m) { + // opaque holder for the powers of the complex exponential precomputed by + // EvalMVBPrecompute/EvalFEFuncBootstrapPrecompute + py::class_, std::shared_ptr>>(m, "SeriesPowers"); + + // opaque holder for an RLWE ciphertext (a vector of Poly) used by SchemeletRLWEMP + py::class_>(m, "PolyVector") + .def(py::init<>()) + .def("__len__", [](const std::vector& self) { return self.size(); }) + .def("Clone", [](const std::vector& self) { return std::vector(self); }) + // switches every element of the RLWE ciphertext to the given modulus + .def("SwitchModulus", + [](std::vector& self, const py::int_& modulus) { + auto mod = FBTPyIntToBigInteger(modulus); + for (auto& poly : self) + poly.SwitchModulus(mod, 1, 0, 0); + }, + py::arg("modulus")) + // element-wise subtraction of two RLWE ciphertexts + .def("__sub__", + [](const std::vector& self, const std::vector& other) { + if (self.size() != other.size()) + OPENFHE_THROW("PolyVector sizes do not match"); + std::vector result(self); + for (size_t i = 0; i < result.size(); ++i) + result[i] = result[i] - other[i]; + return result; + }); + + // Multiplicative depth needed to evaluate the series with the given coefficients + m.def("GetMultiplicativeDepthByCoeffVector", &GetMultiplicativeDepthByCoeffVector, + py::arg("vec"), py::arg("isNormalized") = false); + m.def("GetMultiplicativeDepthByCoeffVector", &GetMultiplicativeDepthByCoeffVector>, + py::arg("vec"), py::arg("isNormalized") = false); + + // note: the element parameters type (ILDCRTParams) returned by + // SchemeletRLWEMP::GetElementParams is already registered as "ParmType" in bind_crypto_context + + // Trigonometric Hermite interpolation coefficients of a look-up table given as a Python function + m.def("GetHermiteTrigCoefficients", &GetHermiteTrigCoefficients, + py::arg("func"), py::arg("p"), py::arg("order"), py::arg("scale")); + + // Conversions between an RLWE scheme and CKKS sharing the same secret key + py::class_(m, "SchemeletRLWEMP") + .def_static("GetElementParams", &SchemeletRLWEMP::GetElementParams, + py::arg("privateKey"), py::arg("level") = 0) + .def_static("EncryptCoeff", + [](const std::vector& input, const py::int_& Q, const py::int_& p, + const PrivateKey& privateKey, const std::shared_ptr& elementParams, + bool bitReverse) { + return SchemeletRLWEMP::EncryptCoeff(input, FBTPyIntToBigInteger(Q), FBTPyIntToBigInteger(p), + privateKey, elementParams, bitReverse); + }, + py::arg("input"), py::arg("Q"), py::arg("p"), py::arg("privateKey"), py::arg("elementParams"), + py::arg("bitReverse") = false) + .def_static("DecryptCoeff", + [](const std::vector& input, const py::int_& Q, const py::int_& p, + const PrivateKey& privateKey, const std::shared_ptr& elementParams, + uint32_t numSlots, uint32_t length, bool bitReverse) { + return SchemeletRLWEMP::DecryptCoeff(input, FBTPyIntToBigInteger(Q), FBTPyIntToBigInteger(p), + privateKey, elementParams, numSlots, length, bitReverse); + }, + py::arg("input"), py::arg("Q"), py::arg("p"), py::arg("privateKey"), py::arg("elementParams"), + py::arg("numSlots"), py::arg("length") = 0, py::arg("bitReverse") = false) + .def_static("ModSwitch", + [](std::vector& input, const py::int_& Q1, const py::int_& Q2) { + SchemeletRLWEMP::ModSwitch(input, FBTPyIntToBigInteger(Q1), FBTPyIntToBigInteger(Q2)); + }, + py::arg("input"), py::arg("Q1"), py::arg("Q2")) + .def_static("ConvertRLWEToCKKS", + [](const CryptoContext& cc, const std::vector& coeffs, const PublicKey& pubKey, + const py::int_& Bigq, uint32_t slots, uint32_t level) { + return SchemeletRLWEMP::ConvertRLWEToCKKS(*cc, coeffs, pubKey, FBTPyIntToBigInteger(Bigq), slots, + level); + }, + py::arg("cc"), py::arg("coeffs"), py::arg("pubKey"), py::arg("Bigq"), py::arg("slots"), + py::arg("level") = 0) + .def_static("ConvertCKKSToRLWE", + [](ConstCiphertext ctxt, const py::int_& Q) { + return SchemeletRLWEMP::ConvertCKKSToRLWE(ctxt, FBTPyIntToBigInteger(Q)); + }, + py::arg("ctxt"), py::arg("Q")) + .def_static("GetQPrime", + [](const PublicKey& pubKey, uint32_t lvls) { + return FBTBigIntegerToPyInt(SchemeletRLWEMP::GetQPrime(pubKey, lvls)); + }, + py::arg("pubKey"), py::arg("lvls")); + + // Serializer/deserializer for all the data needed by CKKS <-> FHEW scheme switching + py::class_(m, "SchemeSwitchingDataSerializer") + .def(py::init, PublicKey, Ciphertext>(), + py::arg("cryptoContext"), py::arg("publicKey"), py::arg("RAWCiphertext")) + .def("SetDataDirectory", &SchemeSwitchingDataSerializer::SetDataDirectory, py::arg("dir")) + .def("Serialize", &SchemeSwitchingDataSerializer::Serialize); + + py::class_(m, "SchemeSwitchingDataDeserializer") + .def(py::init<>()) + .def("SetDataDirectory", &SchemeSwitchingDataDeserializer::SetDataDirectory, py::arg("dir")) + .def("getCryptoContext", &SchemeSwitchingDataDeserializer::getCryptoContext) + .def("getPublicKey", &SchemeSwitchingDataDeserializer::getPublicKey) + .def("getRAWCiphertext", &SchemeSwitchingDataDeserializer::getRAWCiphertext) + .def("Deserialize", &SchemeSwitchingDataDeserializer::Deserialize); +} diff --git a/src/lib/pke/schemeswitching_data_serializer.cpp b/src/lib/pke/schemeswitching_data_serializer.cpp new file mode 100644 index 0000000..f47bd99 --- /dev/null +++ b/src/lib/pke/schemeswitching_data_serializer.cpp @@ -0,0 +1,225 @@ +//================================================================================== +// BSD 2-Clause License +// +// Copyright (c) 2023-2026, Duality Technologies Inc. and other contributors +// +// All rights reserved. +// +// Author TPOC: contact@openfhe.org +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +//================================================================================== + +// Implementation of SchemeSwitchingDataSerializer/SchemeSwitchingDataDeserializer, +// adapted from openfhe-development/src/pke/unittest/utils/schemeswitching-data-serializer.cpp. +// The class is declared in an installed OpenFHE header (unittest/utils/...), but its +// implementation is compiled only into the OpenFHE unit tests, so it is provided here. + +#include "unittest/utils/schemeswitching-data-serializer.h" + +#include +#include +#include +#include + +#include "ciphertext-ser.h" +#include "cryptocontext-ser.h" +#include "key/key-ser.h" +#include "scheme/ckksrns/ckksrns-ser.h" +#include "binfhecontext-ser.h" + +namespace lbcrypto { + +// Macros to be used in this source file only +#define THROW_SERIALIZATION_ERROR OPENFHE_THROW(std::string("Error serializing to ") + outFile) +#define THROW_DESERIALIZATION_ERROR OPENFHE_THROW(std::string("Error deserializing from ") + outFile) +#define THROW_CAN_NOT_OPEN_FILE OPENFHE_THROW(std::string("Can not open ") + outFile) +#define SERTYPE SerType::BINARY + +void SchemeSwitchingDataSerializer::Serialize() { + // check if all 5 data members to be serialized are valid (not NULL) + if (nullptr == cryptoContext) + OPENFHE_THROW("cryptoContext is nullptr"); + else if (nullptr == publicKey) + OPENFHE_THROW("publicKey is nullptr"); + else if (nullptr == binFHECryptoContext) + OPENFHE_THROW("binFHECryptoContext is nullptr"); + else if (nullptr == FHEWtoCKKSSwitchKey) + OPENFHE_THROW("FHEWtoCKKSSwitchKey is nullptr"); + else if (nullptr == RAWCiphertext) + OPENFHE_THROW("RAWCiphertext is nullptr"); + + std::string outFile; + //============================================================================================================= + outFile = dataDirectory + "/" + cryptoContextFile; + if (!Serial::SerializeToFile(outFile, cryptoContext, SERTYPE)) { + THROW_SERIALIZATION_ERROR; + } + //============================================================================================================= + outFile = dataDirectory + "/" + pubKeyFile; + if (!Serial::SerializeToFile(outFile, publicKey, SERTYPE)) { + THROW_SERIALIZATION_ERROR; + } + //============================================================================================================= + outFile = dataDirectory + "/" + multKeyFile; + std::ofstream multKeyFileStream(outFile, std::ios::out | std::ios::binary); + if (multKeyFileStream.is_open()) { + if (!cryptoContext->SerializeEvalMultKey(multKeyFileStream, SERTYPE)) { + THROW_SERIALIZATION_ERROR; + } + multKeyFileStream.close(); + } else { + THROW_CAN_NOT_OPEN_FILE; + } + //============================================================================================================= + outFile = dataDirectory + "/" + rotKeyFile; + std::ofstream rotationKeyFile(outFile, std::ios::out | std::ios::binary); + if (rotationKeyFile.is_open()) { + if (!cryptoContext->SerializeEvalAutomorphismKey(rotationKeyFile, SERTYPE)) { + THROW_SERIALIZATION_ERROR; + } + rotationKeyFile.close(); + } else { + THROW_CAN_NOT_OPEN_FILE; + } + //============================================================================================================= + outFile = dataDirectory + "/" + FHEWtoCKKSSwitchKeyFile; + if (!Serial::SerializeToFile(outFile, FHEWtoCKKSSwitchKey, SERTYPE)) { + THROW_SERIALIZATION_ERROR; + } + //============================================================================================================= + outFile = dataDirectory + "/" + ciphertextFile; + if (!Serial::SerializeToFile(outFile, RAWCiphertext, SERTYPE)) { + THROW_SERIALIZATION_ERROR; + } + //============================================================================================================= + outFile = dataDirectory + "/" + binFHECryptoContextFile; + if (!Serial::SerializeToFile(outFile, binFHECryptoContext, SERTYPE)) { + THROW_SERIALIZATION_ERROR; + } + //============================================================================================================= + outFile = dataDirectory + "/" + binFHEBootKeyFile; + if (!Serial::SerializeToFile(outFile, binFHECryptoContext->GetBTKey(), SERTYPE)) { + THROW_SERIALIZATION_ERROR; + } + //============================================================================================================= + std::vector indices; + auto BTKeyMap = binFHECryptoContext->GetBTKeyMap(); + for (auto it = BTKeyMap->begin(); it != BTKeyMap->end(); ++it) { + uint32_t index = it->first; + RingGSWBTKey thekey = it->second; + + outFile = createMapFileName(index, baseBTKeyFile); + if (!Serial::SerializeToFile(outFile, thekey, SERTYPE)) { + THROW_SERIALIZATION_ERROR; + } + + indices.push_back(index); + } + outFile = dataDirectory + "/" + keyIndexFile; + if (!Serial::SerializeToFile(outFile, indices, SERTYPE)) { + THROW_SERIALIZATION_ERROR; + } + //============================================================================================================= +} + +void SchemeSwitchingDataDeserializer::Deserialize() { + std::string outFile; + //============================================================================================================= + outFile = dataDirectory + "/" + cryptoContextFile; + if (!Serial::DeserializeFromFile(outFile, cryptoContext, SERTYPE)) { + THROW_DESERIALIZATION_ERROR; + } + //============================================================================================================= + outFile = dataDirectory + "/" + pubKeyFile; + if (!Serial::DeserializeFromFile(outFile, publicKey, SERTYPE)) { + THROW_DESERIALIZATION_ERROR; + } + //============================================================================================================= + outFile = dataDirectory + "/" + multKeyFile; + std::ifstream multKeyFileStream(outFile, std::ios::in | std::ios::binary); + if (multKeyFileStream.is_open()) { + if (!cryptoContext->DeserializeEvalMultKey(multKeyFileStream, SERTYPE)) { + THROW_DESERIALIZATION_ERROR; + } + multKeyFileStream.close(); + } else { + THROW_CAN_NOT_OPEN_FILE; + } + //============================================================================================================= + outFile = dataDirectory + "/" + rotKeyFile; + std::ifstream rotationKeyFile(outFile, std::ios::in | std::ios::binary); + if (rotationKeyFile.is_open()) { + if (!cryptoContext->DeserializeEvalAutomorphismKey(rotationKeyFile, SERTYPE)) { + THROW_DESERIALIZATION_ERROR; + } + rotationKeyFile.close(); + } else { + THROW_CAN_NOT_OPEN_FILE; + } + //============================================================================================================= + outFile = dataDirectory + "/" + FHEWtoCKKSSwitchKeyFile; + if (!Serial::DeserializeFromFile(outFile, FHEWtoCKKSSwitchKey, SERTYPE)) { + THROW_DESERIALIZATION_ERROR; + } + cryptoContext->SetSwkFC(FHEWtoCKKSSwitchKey); + //============================================================================================================= + outFile = dataDirectory + "/" + ciphertextFile; + if (!Serial::DeserializeFromFile(outFile, RAWCiphertext, SERTYPE)) { + THROW_DESERIALIZATION_ERROR; + } + //============================================================================================================= + outFile = dataDirectory + "/" + binFHECryptoContextFile; + if (!Serial::DeserializeFromFile(outFile, binFHECryptoContext, SERTYPE)) { + THROW_DESERIALIZATION_ERROR; + } + //============================================================================================================= + RingGSWBTKey BTKey; + outFile = dataDirectory + "/" + binFHEBootKeyFile; + if (!Serial::DeserializeFromFile(outFile, BTKey, SERTYPE)) { + THROW_DESERIALIZATION_ERROR; + } + binFHECryptoContext->BTKeyLoad(BTKey); + //============================================================================================================= + std::vector indices; + outFile = dataDirectory + "/" + keyIndexFile; + if (!Serial::DeserializeFromFile(outFile, indices, SERTYPE)) { + THROW_DESERIALIZATION_ERROR; + } else if (!indices.size()) { + std::string errMsg(std::string("Error deserializing from ") + outFile + ". No indices found."); + OPENFHE_THROW(errMsg); + } + for (uint32_t index : indices) { + RingGSWBTKey thekey; + outFile = createMapFileName(index, baseBTKeyFile); + if (!Serial::DeserializeFromFile(outFile, thekey, SERTYPE)) { + THROW_DESERIALIZATION_ERROR; + } + + // add single keys to the map + binFHECryptoContext->BTKeyMapLoadSingleElement(index, thekey); + } + cryptoContext->SetBinCCForSchemeSwitch(binFHECryptoContext); + //============================================================================================================= +} + +} // namespace lbcrypto diff --git a/src/lib/pke/serialization.cpp b/src/lib/pke/serialization.cpp index b574103..210c63c 100644 --- a/src/lib/pke/serialization.cpp +++ b/src/lib/pke/serialization.cpp @@ -73,6 +73,37 @@ bool DeserializeEvalMultKeyWrapper(const std::string& filename, const ST& sertyp return res; } +template +bool SerializeEvalBootstrapKeyWrapper(const std::string& filename, const ST& sertype, const CryptoContextDCRT& cc, + const std::string& keyTag, uint32_t slots) { + std::ofstream outfile(filename, std::ios::out | std::ios::binary); + bool res = CryptoContextImpl::SerializeEvalBootstrapKey(outfile, sertype, cc, keyTag, slots); + outfile.close(); + return res; +} + +template +bool DeserializeEvalBootstrapKeyWrapper(const std::string& filename, const ST& sertype, const CryptoContextDCRT& cc, + const std::string& keyTag, uint32_t slots) { + std::ifstream keyfile(filename, std::ios::in | std::ios::binary); + if (!keyfile.is_open()) { + std::cerr << "I cannot read serialization from " << filename << std::endl; + return false; + } + return CryptoContextImpl::DeserializeEvalBootstrapKey(keyfile, sertype, cc, keyTag, slots); +} + +template +bool DeserializeEvalBootstrapKeyByIndexWrapper(const std::string& filename, const ST& sertype, + const std::string& keyTag, const std::vector& indexList) { + std::ifstream keyfile(filename, std::ios::in | std::ios::binary); + if (!keyfile.is_open()) { + std::cerr << "I cannot read serialization from " << filename << std::endl; + return false; + } + return CryptoContextImpl::DeserializeEvalBootstrapKey(keyfile, sertype, keyTag, indexList); +} + template std::tuple DeserializeFromFileWrapper(const std::string& filename, const ST& sertype) { T newob; @@ -229,6 +260,20 @@ void DeserializeEvalAutomorphismKeyFromBytesWrapper(const py::bytes& bytes, cons } void bind_serialization(pybind11::module &m) { + // CKKS bootstrapping key (de)serialization + m.def("SerializeEvalBootstrapKey", &SerializeEvalBootstrapKeyWrapper, + py::arg("filename"), py::arg("sertype"), py::arg("cc"), py::arg("keyTag"), py::arg("slots")); + m.def("SerializeEvalBootstrapKey", &SerializeEvalBootstrapKeyWrapper, + py::arg("filename"), py::arg("sertype"), py::arg("cc"), py::arg("keyTag"), py::arg("slots")); + m.def("DeserializeEvalBootstrapKey", &DeserializeEvalBootstrapKeyWrapper, + py::arg("filename"), py::arg("sertype"), py::arg("cc"), py::arg("keyTag"), py::arg("slots")); + m.def("DeserializeEvalBootstrapKey", &DeserializeEvalBootstrapKeyWrapper, + py::arg("filename"), py::arg("sertype"), py::arg("cc"), py::arg("keyTag"), py::arg("slots")); + m.def("DeserializeEvalBootstrapKey", &DeserializeEvalBootstrapKeyByIndexWrapper, + py::arg("filename"), py::arg("sertype"), py::arg("keyTag"), py::arg("indexList")); + m.def("DeserializeEvalBootstrapKey", &DeserializeEvalBootstrapKeyByIndexWrapper, + py::arg("filename"), py::arg("sertype"), py::arg("keyTag"), py::arg("indexList")); + // Json Serialization m.def("SerializeToFile", static_cast(&Serial::SerializeToFile), py::arg("filename"), py::arg("obj"), py::arg("sertype")); diff --git a/tests/test_examples.py b/tests/test_examples.py index 8d8093a..51f0102 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -39,10 +39,29 @@ def importhelper(path, modulename): "simple-integers.py", "simple-real-numbers-serial.py", "iterative-ckks-bootstrapping.py", - "tckks-interactive-mp-bootstrapping-Chebyschev.py", + "tckks-interactive-mp-bootstrapping-Chebyshev.py", "simple-real-numbers.py", "threshold-fhe.py", "pre-buffer.py", + "advanced-ckks-bootstrapping.py", + "ckks-noise-flooding.py", + "depth-bfvrns.py", + "depth-bfvrns-behz.py", + "depth-bgvrns.py", + "inner-product.py", + "interactive-bootstrapping.py", + "iterative-ckks-bootstrapping-composite-scaling.py", + "linearwsum-evaluation.py", + "pre-hra-secure.py", + "rotation.py", + "simple-ckks-bootstrapping-composite-scaling.py", + "simple-complex-numbers.py", + "simple-real-numbers-composite-scaling.py", + "ckks-bootstrap-keys-serial.py", + "ckks-release-memory.py", + "scheme-switching-serial.py", + "polynomial-evaluation-high-precision-composite-scaling.py", + "simple-composite-scaling-manual.py", ], ) def test_run_scripts(raw_modulename): @@ -58,3 +77,100 @@ def test_run_scripts(raw_modulename): print(f"-*- running module {modulename} -*-") module = importhelper(td, modulename) module.main() + + +# The functional bootstrapping examples take too long for the regular CI runs +# (FE-functional-bootstrapping-ckks uses ring dimension 2^16 with 2^15 slots), +# so they are run only when RUN_SLOW_EXAMPLES is set in the environment. +@pytest.mark.skipif( + not os.environ.get("RUN_SLOW_EXAMPLES"), + reason="set RUN_SLOW_EXAMPLES=1 to run the slow functional bootstrapping examples", +) +@pytest.mark.parametrize( + "raw_modulename", + [ + "functional-bootstrapping-ckks.py", + "FE-functional-bootstrapping-ckks.py", + ], +) +def test_run_slow_scripts(raw_modulename): + with tempfile.TemporaryDirectory() as td: + os.mkdir(td + "/demoData") + modulename_py = raw_modulename.replace("-", "_") + shutil.copyfile( + os.path.join(EXAMPLES_SCRIPTS_PATH, raw_modulename), + os.path.join(td, modulename_py), + ) + sys.path.insert(0, td) + modulename = modulename_py.split(".")[0] + print(f"-*- running module {modulename} -*-") + module = importhelper(td, modulename) + module.main() + + +BINFHE_EXAMPLES_SCRIPTS_PATH = os.path.join(Path(__file__).parent.parent, "examples", "binfhe") + + +@pytest.mark.parametrize( + "raw_modulename", + [ + "boolean.py", + "boolean-ap.py", + "boolean-lmkcdey.py", + "boolean-multi-input.py", + "boolean-serial-binary.py", + "boolean-serial-binary-dynamic-large-precision.py", + "boolean-serial-json.py", + "boolean-serial-json-dynamic-large-precision.py", + "boolean-truth-tables.py", + "eval-decomp.py", + "eval-flooring.py", + "eval-function.py", + "eval-sign.py", + "pke/boolean-pke.py", + "pke/boolean-ap-pke.py", + "pke/boolean-serial-binary-pke.py", + "pke/boolean-serial-json-pke.py", + "pke/boolean-truth-tables-pke.py", + "pke/eval-flooring-pke.py", + "pke/eval-function-pke.py", + ], +) +def test_run_binfhe_scripts(raw_modulename): + with tempfile.TemporaryDirectory() as td: + os.mkdir(td + "/demoData") + modulename_py = os.path.basename(raw_modulename).replace("-", "_") + shutil.copyfile( + os.path.join(BINFHE_EXAMPLES_SCRIPTS_PATH, raw_modulename), + os.path.join(td, modulename_py), + ) + sys.path.insert(0, td) + modulename = modulename_py.split(".")[0] + print(f"-*- running module {modulename} -*-") + # most binfhe examples run at import time; the serialization ones define main() + module = importhelper(td, modulename) + if hasattr(module, "main"): + module.main() + + +def _parametrized_example_names(test_function): + for mark in test_function.pytestmark: + if mark.name == "parametrize" and mark.args[0] == "raw_modulename": + return set(mark.args[1]) + raise AssertionError(f"{test_function.__name__} has no raw_modulename parametrization") + + +def test_example_inventory_is_complete(): + pke_examples = { + path.relative_to(EXAMPLES_SCRIPTS_PATH).as_posix() + for path in Path(EXAMPLES_SCRIPTS_PATH).rglob("*.py") + } + tested_pke_examples = _parametrized_example_names(test_run_scripts) + tested_pke_examples |= _parametrized_example_names(test_run_slow_scripts) + assert tested_pke_examples == pke_examples + + binfhe_examples = { + path.relative_to(BINFHE_EXAMPLES_SCRIPTS_PATH).as_posix() + for path in Path(BINFHE_EXAMPLES_SCRIPTS_PATH).rglob("*.py") + } + assert _parametrized_example_names(test_run_binfhe_scripts) == binfhe_examples From 0ece47a315b161b5a050171fccc18088f7cad77c Mon Sep 17 00:00:00 2001 From: Dmitriy Suponitskiy Date: Thu, 1 Oct 2026 09:18:15 -0400 Subject: [PATCH 02/16] Fix plaintext modulus value in depth-bgvrns.py --- examples/pke/depth-bgvrns.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/pke/depth-bgvrns.py b/examples/pke/depth-bgvrns.py index 3659d8b..2950d13 100644 --- a/examples/pke/depth-bgvrns.py +++ b/examples/pke/depth-bgvrns.py @@ -24,7 +24,9 @@ def main(): # generate homomorphic evaluation multiplication keys for s^2 and s^3 parameters = CCParamsBGVRNS() parameters.SetMultiplicativeDepth(3) - parameters.SetPlaintextModulus(536903681) + # This prime supports packed encoding for the m = 65536 cyclotomic order + # selected by the 128-bit backend, as well as the smaller backends. + parameters.SetPlaintextModulus(537133057) parameters.SetMaxRelinSkDeg(3) crypto_context = GenCryptoContext(parameters) From d4774171b528433dde90f037c97b703924f05ba6 Mon Sep 17 00:00:00 2001 From: Dmitriy Suponitskiy Date: Thu, 1 Oct 2026 15:02:06 -0400 Subject: [PATCH 03/16] Run each example test in a subprocess to avoid "out of memory" error The binfhe examples keep their cryptocontexts and bootstrapping keys in module-level globals (1-2.5 GB each), so running all of them in one pytest process accumulates tens of GB and gets pytest killed on the runner. Running every example in its own interpreter returns the memory to the OS after each test. --- tests/test_examples.py | 77 ++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 47 deletions(-) diff --git a/tests/test_examples.py b/tests/test_examples.py index 51f0102..79ec64f 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -1,7 +1,7 @@ import os +import subprocess import sys from pathlib import Path -import importlib.util import pytest import tempfile import shutil @@ -12,14 +12,32 @@ EXAMPLES_SCRIPTS_PATH = os.path.join(Path(__file__).parent.parent, "examples", "pke") -def importhelper(path, modulename): - spec = importlib.util.spec_from_file_location( - modulename, os.path.join(path, modulename + ".py") - ) - module = importlib.util.module_from_spec(spec) - sys.modules[modulename] = module - spec.loader.exec_module(module) - return module +def run_example(scripts_path, raw_modulename, require_main): + """ + Run one example in its own interpreter so the memory it allocates is + returned to the OS when the example finishes. The binfhe examples keep + their cryptocontexts and bootstrapping keys in module-level globals + (~1-2.5 GB each); running every example inside the pytest process + accumulates tens of GB and gets pytest OOM-killed on CI. + """ + with tempfile.TemporaryDirectory() as td: + os.mkdir(os.path.join(td, "demoData")) + modulename_py = os.path.basename(raw_modulename).replace("-", "_") + shutil.copyfile( + os.path.join(scripts_path, raw_modulename), + os.path.join(td, modulename_py), + ) + modulename = modulename_py.split(".")[0] + print(f"-*- running module {modulename} -*-") + if require_main: + # pke examples must define main() and must not run at import time + code = f"import {modulename}; {modulename}.main()" + else: + # most binfhe examples run at import time; the serialization ones define main() + code = f"import {modulename}; getattr({modulename}, 'main', lambda: None)()" + env = dict(os.environ) + env["PYTHONPATH"] = td + os.pathsep + env.get("PYTHONPATH", "") + subprocess.run([sys.executable, "-c", code], cwd=td, env=env, check=True) @pytest.mark.parametrize( @@ -65,18 +83,7 @@ def importhelper(path, modulename): ], ) def test_run_scripts(raw_modulename): - with tempfile.TemporaryDirectory() as td: - os.mkdir(td + "/demoData") - modulename_py = raw_modulename.replace("-", "_") - shutil.copyfile( - os.path.join(EXAMPLES_SCRIPTS_PATH, raw_modulename), - os.path.join(td, modulename_py), - ) - sys.path.insert(0, td) - modulename = modulename_py.split(".")[0] - print(f"-*- running module {modulename} -*-") - module = importhelper(td, modulename) - module.main() + run_example(EXAMPLES_SCRIPTS_PATH, raw_modulename, require_main=True) # The functional bootstrapping examples take too long for the regular CI runs @@ -94,18 +101,7 @@ def test_run_scripts(raw_modulename): ], ) def test_run_slow_scripts(raw_modulename): - with tempfile.TemporaryDirectory() as td: - os.mkdir(td + "/demoData") - modulename_py = raw_modulename.replace("-", "_") - shutil.copyfile( - os.path.join(EXAMPLES_SCRIPTS_PATH, raw_modulename), - os.path.join(td, modulename_py), - ) - sys.path.insert(0, td) - modulename = modulename_py.split(".")[0] - print(f"-*- running module {modulename} -*-") - module = importhelper(td, modulename) - module.main() + run_example(EXAMPLES_SCRIPTS_PATH, raw_modulename, require_main=True) BINFHE_EXAMPLES_SCRIPTS_PATH = os.path.join(Path(__file__).parent.parent, "examples", "binfhe") @@ -137,20 +133,7 @@ def test_run_slow_scripts(raw_modulename): ], ) def test_run_binfhe_scripts(raw_modulename): - with tempfile.TemporaryDirectory() as td: - os.mkdir(td + "/demoData") - modulename_py = os.path.basename(raw_modulename).replace("-", "_") - shutil.copyfile( - os.path.join(BINFHE_EXAMPLES_SCRIPTS_PATH, raw_modulename), - os.path.join(td, modulename_py), - ) - sys.path.insert(0, td) - modulename = modulename_py.split(".")[0] - print(f"-*- running module {modulename} -*-") - # most binfhe examples run at import time; the serialization ones define main() - module = importhelper(td, modulename) - if hasattr(module, "main"): - module.main() + run_example(BINFHE_EXAMPLES_SCRIPTS_PATH, raw_modulename, require_main=False) def _parametrized_example_names(test_function): From 8f9e3d0537001ebc1f0151460a3b445bbb7e235e Mon Sep 17 00:00:00 2001 From: Dmitriy Suponitskiy Date: Thu, 1 Oct 2026 18:47:50 -0400 Subject: [PATCH 04/16] Add 128-bit guards to CKKS examples The examples added for 1.6.0 hard-coded FLEXIBLEAUTO scaling, which 128-bit CKKS rejects. Pick FIXEDAUTO and matching modulus sizes when get_native_int() is 128, the same way the older examples do, and skip the composite scaling and functional bootstrapping examples on 128-bit builds since those techniques only exist for 64-bit. --- examples/pke/ckks-release-memory.py | 11 ++++++++--- examples/pke/interactive-bootstrapping.py | 6 ++++-- examples/pke/linearwsum-evaluation.py | 5 ++++- examples/pke/scheme-switching-serial.py | 4 +++- examples/pke/simple-complex-numbers.py | 24 +++++++++++++++-------- tests/test_examples.py | 15 ++++++++++++++ 6 files changed, 50 insertions(+), 15 deletions(-) diff --git a/examples/pke/ckks-release-memory.py b/examples/pke/ckks-release-memory.py index a6cd2dd..ec15a06 100644 --- a/examples/pke/ckks-release-memory.py +++ b/examples/pke/ckks-release-memory.py @@ -36,9 +36,14 @@ def build_bootstrap_context(): parameters.SetSecurityLevel(SecurityLevel.HEStd_NotSet) parameters.SetRingDim(1 << 12) - parameters.SetScalingModSize(59) - parameters.SetFirstModSize(60) - parameters.SetScalingTechnique(ScalingTechnique.FLEXIBLEAUTO) + if get_native_int() == 128: + parameters.SetScalingModSize(78) + parameters.SetFirstModSize(89) + parameters.SetScalingTechnique(ScalingTechnique.FIXEDAUTO) + else: + parameters.SetScalingModSize(59) + parameters.SetFirstModSize(60) + parameters.SetScalingTechnique(ScalingTechnique.FLEXIBLEAUTO) level_budget = [4, 4] depth = 10 + FHECKKSRNS.GetBootstrapDepth(level_budget, sk_dist) diff --git a/examples/pke/interactive-bootstrapping.py b/examples/pke/interactive-bootstrapping.py index f2acfb0..304e98e 100644 --- a/examples/pke/interactive-bootstrapping.py +++ b/examples/pke/interactive-bootstrapping.py @@ -3,8 +3,10 @@ def main(): # the scaling technigue can be changed to FIXEDMANUAL, FIXEDAUTO, or FLEXIBLEAUTOEXT - ThresholdFHE(FLEXIBLEAUTO) - Chebyshev(FLEXIBLEAUTO) + # (128-bit CKKS supports only the FIXED* scaling techniques) + scaleTech = FIXEDAUTO if get_native_int() == 128 else FLEXIBLEAUTO + ThresholdFHE(scaleTech) + Chebyshev(scaleTech) def ThresholdFHE(scaleTech): # if scaleTech not in [FIXEDMANUAL, FIXEDAUTO, FLEXIBLEAUTOEXT]: diff --git a/examples/pke/linearwsum-evaluation.py b/examples/pke/linearwsum-evaluation.py index efeae7b..c667c3e 100644 --- a/examples/pke/linearwsum-evaluation.py +++ b/examples/pke/linearwsum-evaluation.py @@ -15,7 +15,10 @@ def main(): parameters.SetBatchSize(8) parameters.SetSecurityLevel(SecurityLevel.HEStd_NotSet) parameters.SetRingDim(2048) - parameters.SetScalingTechnique(ScalingTechnique.FLEXIBLEAUTO) + if get_native_int() == 128: + parameters.SetScalingTechnique(ScalingTechnique.FIXEDAUTO) + else: + parameters.SetScalingTechnique(ScalingTechnique.FLEXIBLEAUTO) parameters.SetFirstModSize(60) cc = GenCryptoContext(parameters) diff --git a/examples/pke/scheme-switching-serial.py b/examples/pke/scheme-switching-serial.py index b35830c..c6c73bb 100644 --- a/examples/pke/scheme-switching-serial.py +++ b/examples/pke/scheme-switching-serial.py @@ -51,7 +51,9 @@ def server_setup_and_write(ring_dim, batch_size, mult_depth, scale_mod_size, fir parameters.SetBatchSize(batch_size) parameters.SetScalingModSize(scale_mod_size) parameters.SetFirstModSize(first_mod_size) - parameters.SetScalingTechnique(ScalingTechnique.FLEXIBLEAUTO) + # 128-bit CKKS supports only the FIXED* scaling techniques; keep the library default there + if get_native_int() != 128: + parameters.SetScalingTechnique(ScalingTechnique.FLEXIBLEAUTO) server_cc = GenCryptoContext(parameters) diff --git a/examples/pke/simple-complex-numbers.py b/examples/pke/simple-complex-numbers.py index 2defbd2..f36034c 100644 --- a/examples/pke/simple-complex-numbers.py +++ b/examples/pke/simple-complex-numbers.py @@ -146,10 +146,14 @@ def SimpleBootstrappingComplex(): ringDim = 1 << 6 parameters.SetRingDim(ringDim) - # In your C++ demo this depends on NATIVEINT. Here we pick the common 64-bit path. - rescaleTech = ScalingTechnique.FLEXIBLEAUTO - dcrtBits = 59 - firstMod = 60 + if get_native_int() == 128: + rescaleTech = ScalingTechnique.FIXEDAUTO + dcrtBits = 78 + firstMod = 89 + else: + rescaleTech = ScalingTechnique.FLEXIBLEAUTO + dcrtBits = 59 + firstMod = 60 parameters.SetScalingModSize(dcrtBits) parameters.SetScalingTechnique(rescaleTech) @@ -216,10 +220,14 @@ def SimpleBootstrappingStCFirstComplex(): ringDim = 1 << 6 parameters.SetRingDim(ringDim) - # Common 64-bit path - rescaleTech = ScalingTechnique.FLEXIBLEAUTO - dcrtBits = 59 - firstMod = 60 + if get_native_int() == 128: + rescaleTech = ScalingTechnique.FIXEDAUTO + dcrtBits = 78 + firstMod = 89 + else: + rescaleTech = ScalingTechnique.FLEXIBLEAUTO + dcrtBits = 59 + firstMod = 60 parameters.SetScalingModSize(dcrtBits) parameters.SetScalingTechnique(rescaleTech) diff --git a/tests/test_examples.py b/tests/test_examples.py index 79ec64f..47ba3c3 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -11,6 +11,19 @@ EXAMPLES_SCRIPTS_PATH = os.path.join(Path(__file__).parent.parent, "examples", "pke") +# 128-bit CKKS rejects the COMPOSITESCALING* and FLEXIBLE* scaling techniques these +# examples demonstrate (their C++ counterparts have no 128-bit variant either), +# so they only run on 64-bit builds. +EXAMPLES_UNSUPPORTED_ON_128 = { + "iterative-ckks-bootstrapping-composite-scaling.py", + "simple-ckks-bootstrapping-composite-scaling.py", + "simple-real-numbers-composite-scaling.py", + "polynomial-evaluation-high-precision-composite-scaling.py", + "simple-composite-scaling-manual.py", + "functional-bootstrapping-ckks.py", + "FE-functional-bootstrapping-ckks.py", +} + def run_example(scripts_path, raw_modulename, require_main): """ @@ -20,6 +33,8 @@ def run_example(scripts_path, raw_modulename, require_main): (~1-2.5 GB each); running every example inside the pytest process accumulates tens of GB and gets pytest OOM-killed on CI. """ + if fhe.get_native_int() == 128 and raw_modulename in EXAMPLES_UNSUPPORTED_ON_128: + pytest.skip("this example's scaling technique is not supported for NATIVE_SIZE=128") with tempfile.TemporaryDirectory() as td: os.mkdir(os.path.join(td, "demoData")) modulename_py = os.path.basename(raw_modulename).replace("-", "_") From 029684000ce584c6b0ba6f24ec4991beb28d2692 Mon Sep 17 00:00:00 2001 From: Dmitriy Suponitskiy Date: Tue, 6 Oct 2026 00:39:49 -0400 Subject: [PATCH 05/16] Move CKKS depth APIs to CryptoContext and do not expose the FHECKKSRNS class to Python --- .../pke/FE-functional-bootstrapping-ckks.py | 6 +- examples/pke/advanced-ckks-bootstrapping.py | 2 +- examples/pke/ckks-bootstrap-keys-serial.py | 2 +- examples/pke/ckks-release-memory.py | 2 +- examples/pke/functional-bootstrapping-ckks.py | 8 +-- ...ve-ckks-bootstrapping-composite-scaling.py | 2 +- examples/pke/iterative-ckks-bootstrapping.py | 6 +- ...le-ckks-bootstrapping-composite-scaling.py | 4 +- examples/pke/simple-ckks-bootstrapping.py | 6 +- examples/pke/simple-complex-numbers.py | 4 +- src/include/bindings.h | 3 - src/lib/bindings.cpp | 20 ------- src/lib/pke/fbt.cpp | 58 ++++++++++--------- tests/test_ckks.py | 11 +++- 14 files changed, 61 insertions(+), 73 deletions(-) diff --git a/examples/pke/FE-functional-bootstrapping-ckks.py b/examples/pke/FE-functional-bootstrapping-ckks.py index 12ebf08..1284d09 100644 --- a/examples/pke/FE-functional-bootstrapping-ckks.py +++ b/examples/pke/FE-functional-bootstrapping-ckks.py @@ -166,9 +166,9 @@ def main(): # GetFEFBTDepth covers the functional bootstrapping itself, for the longest of the three series; the # levels added on top of it are what is left to compute with on the refreshed ciphertext. levels_after_bootstrapping = 6 - depth = max(FHECKKSRNS.GetFEFBTDepth(level_budget, COEFF_EXP_2_DOUBLE_29, skd), - FHECKKSRNS.GetFEFBTDepth(level_budget, COEFF_SIGMOID_8_DOUBLE_34, skd), - FHECKKSRNS.GetFEFBTDepth(level_budget, COEFF_GELU_8_DOUBLE_44, skd)) + levels_after_bootstrapping + depth = max(CryptoContext.GetFEFBTDepth(level_budget, COEFF_EXP_2_DOUBLE_29, skd), + CryptoContext.GetFEFBTDepth(level_budget, COEFF_SIGMOID_8_DOUBLE_34, skd), + CryptoContext.GetFEFBTDepth(level_budget, COEFF_GELU_8_DOUBLE_44, skd)) + levels_after_bootstrapping parameters = CCParamsCKKSRNS() parameters.SetCKKSDataType(CKKSDataType.COMPLEX) diff --git a/examples/pke/advanced-ckks-bootstrapping.py b/examples/pke/advanced-ckks-bootstrapping.py index b80b1c0..a51b918 100644 --- a/examples/pke/advanced-ckks-bootstrapping.py +++ b/examples/pke/advanced-ckks-bootstrapping.py @@ -84,7 +84,7 @@ def bootstrap_example(num_slots): # using GetBootstrapDepth, and add it to levelsAvailableAfterBootstrap to set our initial multiplicative # depth. levels_available_after_bootstrap = 10 - depth = levels_available_after_bootstrap + FHECKKSRNS.GetBootstrapDepth(level_budget, secret_key_dist) + depth = levels_available_after_bootstrap + CryptoContext.GetBootstrapDepth(level_budget, secret_key_dist) parameters.SetMultiplicativeDepth(depth) # Generate crypto context. diff --git a/examples/pke/ckks-bootstrap-keys-serial.py b/examples/pke/ckks-bootstrap-keys-serial.py index 3491120..eceac1c 100644 --- a/examples/pke/ckks-bootstrap-keys-serial.py +++ b/examples/pke/ckks-bootstrap-keys-serial.py @@ -45,7 +45,7 @@ def main_action(): level_budget = [4, 4] levels_available_after_bootstrap = 10 - depth = levels_available_after_bootstrap + FHECKKSRNS.GetBootstrapDepth(level_budget, secret_key_dist) + depth = levels_available_after_bootstrap + CryptoContext.GetBootstrapDepth(level_budget, secret_key_dist) parameters.SetMultiplicativeDepth(depth) server_cc = GenCryptoContext(parameters) diff --git a/examples/pke/ckks-release-memory.py b/examples/pke/ckks-release-memory.py index ec15a06..0e404e2 100644 --- a/examples/pke/ckks-release-memory.py +++ b/examples/pke/ckks-release-memory.py @@ -46,7 +46,7 @@ def build_bootstrap_context(): parameters.SetScalingTechnique(ScalingTechnique.FLEXIBLEAUTO) level_budget = [4, 4] - depth = 10 + FHECKKSRNS.GetBootstrapDepth(level_budget, sk_dist) + depth = 10 + CryptoContext.GetBootstrapDepth(level_budget, sk_dist) parameters.SetMultiplicativeDepth(depth) cc = GenCryptoContext(parameters) diff --git a/examples/pke/functional-bootstrapping-ckks.py b/examples/pke/functional-bootstrapping-ckks.py index caa2a01..e0e0856 100644 --- a/examples/pke/functional-bootstrapping-ckks.py +++ b/examples/pke/functional-bootstrapping-ckks.py @@ -95,7 +95,7 @@ def arbitrary_lut(qbfv_init, p_input, p_output, Q, bigq, scale_thi, order, num_s parameters.SetBatchSize(num_slots_ckks) parameters.SetRingDim(ring_dim) - depth = levels_available_after_bootstrap + FHECKKSRNS.GetFBTDepth(lvlb, coeff, p_input, order, secret_key_dist) + depth = levels_available_after_bootstrap + CryptoContext.GetFBTDepth(lvlb, coeff, p_input, order, secret_key_dist) parameters.SetMultiplicativeDepth(depth) cc = GenCryptoContext(parameters) @@ -200,7 +200,7 @@ def func2(x): parameters.SetRingDim(ring_dim) depth = levels_available_after_bootstrap + levels_computation + \ - FHECKKSRNS.GetFBTDepth(lvlb, coeff1, p_input, order, secret_key_dist) + CryptoContext.GetFBTDepth(lvlb, coeff1, p_input, order, secret_key_dist) parameters.SetMultiplicativeDepth(depth) cc = GenCryptoContext(parameters) @@ -362,10 +362,10 @@ def func_step(x): if binary_lut: depth = levels_available_after_bootstrap + \ - FHECKKSRNS.GetFBTDepth(lvlb, coeffint_mod, p_digit, order, secret_key_dist) + CryptoContext.GetFBTDepth(lvlb, coeffint_mod, p_digit, order, secret_key_dist) else: depth = levels_available_after_bootstrap + \ - FHECKKSRNS.GetFBTDepth(lvlb, coeffcomp_mod, p_digit, order, secret_key_dist) + CryptoContext.GetFBTDepth(lvlb, coeffcomp_mod, p_digit, order, secret_key_dist) parameters.SetMultiplicativeDepth(depth) cc = GenCryptoContext(parameters) diff --git a/examples/pke/iterative-ckks-bootstrapping-composite-scaling.py b/examples/pke/iterative-ckks-bootstrapping-composite-scaling.py index cd64ed5..7593b18 100644 --- a/examples/pke/iterative-ckks-bootstrapping-composite-scaling.py +++ b/examples/pke/iterative-ckks-bootstrapping-composite-scaling.py @@ -58,7 +58,7 @@ def iterative_bootstrap_example(): levels_available_after_bootstrap = 10 # Each extra iteration on top of 1 requires an extra level to be consumed. - depth = levels_available_after_bootstrap + FHECKKSRNS.GetBootstrapDepth(level_budget, secret_key_dist) + (num_iterations - 1) + depth = levels_available_after_bootstrap + CryptoContext.GetBootstrapDepth(level_budget, secret_key_dist) + (num_iterations - 1) parameters.SetMultiplicativeDepth(depth) # Generate crypto context. diff --git a/examples/pke/iterative-ckks-bootstrapping.py b/examples/pke/iterative-ckks-bootstrapping.py index f25aa03..c8016e0 100644 --- a/examples/pke/iterative-ckks-bootstrapping.py +++ b/examples/pke/iterative-ckks-bootstrapping.py @@ -45,7 +45,7 @@ def iterative_bootstrap_example(): bsgs_dim = [0,0] levels_available_after_bootstrap = 10 - depth = levels_available_after_bootstrap = 10 + FHECKKSRNS.GetBootstrapDepth(level_budget, secret_key_dist) + (num_iterations - 1) + depth = levels_available_after_bootstrap = 10 + CryptoContext.GetBootstrapDepth(level_budget, secret_key_dist) + (num_iterations - 1) parameters.SetMultiplicativeDepth(depth) # Generate crypto context @@ -146,7 +146,7 @@ def iterative_bootstrap_stc_example(): bsgs_dim = [0,0] levels_available_after_bootstrap = 10 + level_budget[1] - depth = levels_available_after_bootstrap + FHECKKSRNS.GetBootstrapDepth(9, level_budget, secret_key_dist) + depth = levels_available_after_bootstrap + CryptoContext.GetBootstrapDepth(9, level_budget, secret_key_dist) parameters.SetMultiplicativeDepth(depth) # Generate crypto context @@ -251,4 +251,4 @@ def iterative_bootstrap_stc_example(): f"{depth - ciphertext_two_iterations.GetLevel() - (ciphertext_two_iterations.GetNoiseScaleDeg() - 1)}\n\n") if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/examples/pke/simple-ckks-bootstrapping-composite-scaling.py b/examples/pke/simple-ckks-bootstrapping-composite-scaling.py index 87ddadb..4c18800 100644 --- a/examples/pke/simple-ckks-bootstrapping-composite-scaling.py +++ b/examples/pke/simple-ckks-bootstrapping-composite-scaling.py @@ -58,7 +58,7 @@ def simple_bootstrap_example(): levels_available_after_bootstrap = 10 - depth = levels_available_after_bootstrap + FHECKKSRNS.GetBootstrapDepth(level_budget, secret_key_dist) + depth = levels_available_after_bootstrap + CryptoContext.GetBootstrapDepth(level_budget, secret_key_dist) print(f"levelBudget[0] = {level_budget[0]}") print(f"levelBudget[1] = {level_budget[1]}") @@ -151,7 +151,7 @@ def simple_bootstrap_stc_first_example(): # is used for scaling the ciphertext before next bootstrapping (in 64-bit CKKS bootstrapping) levels_available_after_bootstrap = 10 + level_budget[1] - depth = levels_available_after_bootstrap + FHECKKSRNS.GetBootstrapDepth([level_budget[0], 0], secret_key_dist) + depth = levels_available_after_bootstrap + CryptoContext.GetBootstrapDepth([level_budget[0], 0], secret_key_dist) print(f"levelBudget[0] = {level_budget[0]}") print(f"levelBudget[1] = {level_budget[1]}") diff --git a/examples/pke/simple-ckks-bootstrapping.py b/examples/pke/simple-ckks-bootstrapping.py index 56ceba2..7dc06e3 100644 --- a/examples/pke/simple-ckks-bootstrapping.py +++ b/examples/pke/simple-ckks-bootstrapping.py @@ -29,7 +29,7 @@ def simple_bootstrap_example(): levels_available_after_bootstrap = 10 - depth = levels_available_after_bootstrap + FHECKKSRNS.GetBootstrapDepth(level_budget, secret_key_dist) + depth = levels_available_after_bootstrap + CryptoContext.GetBootstrapDepth(level_budget, secret_key_dist) parameters.SetMultiplicativeDepth(depth) @@ -101,7 +101,7 @@ def simple_bootstrap_stc_example(): levels_available_after_bootstrap = 10 + level_budget[1] - depth = levels_available_after_bootstrap + FHECKKSRNS.GetBootstrapDepth({level_budget[0], 0}, secret_key_dist) + depth = levels_available_after_bootstrap + CryptoContext.GetBootstrapDepth({level_budget[0], 0}, secret_key_dist) parameters.SetMultiplicativeDepth(depth) @@ -144,4 +144,4 @@ def simple_bootstrap_stc_example(): print(f"Output after bootstrapping: {result}") if __name__ == '__main__': - main() \ No newline at end of file + main() diff --git a/examples/pke/simple-complex-numbers.py b/examples/pke/simple-complex-numbers.py index f36034c..e3fb9a8 100644 --- a/examples/pke/simple-complex-numbers.py +++ b/examples/pke/simple-complex-numbers.py @@ -166,7 +166,7 @@ def SimpleBootstrappingComplex(): levelBudget = [2, 2] levelsAvailableAfterBootstrap = 10 - depth = levelsAvailableAfterBootstrap + FHECKKSRNS.GetBootstrapDepth(levelBudget, secretKeyDist) + depth = levelsAvailableAfterBootstrap + CryptoContext.GetBootstrapDepth(levelBudget, secretKeyDist) parameters.SetMultiplicativeDepth(depth) cryptoContext = GenCryptoContext(parameters) @@ -241,7 +241,7 @@ def SimpleBootstrappingStCFirstComplex(): levelBudget = [2, 2] levelsAvailableAfterBootstrap = 10 + levelBudget[1] - depth = levelsAvailableAfterBootstrap + FHECKKSRNS.GetBootstrapDepth([levelBudget[0], 0], secretKeyDist) + depth = levelsAvailableAfterBootstrap + CryptoContext.GetBootstrapDepth([levelBudget[0], 0], secretKeyDist) parameters.SetMultiplicativeDepth(depth) cryptoContext = GenCryptoContext(parameters) diff --git a/src/include/bindings.h b/src/include/bindings.h index a1021ab..afe886f 100644 --- a/src/include/bindings.h +++ b/src/include/bindings.h @@ -40,7 +40,6 @@ using DCRTCryptoContextClass = pybind11::class_< lbcrypto::CryptoContextImpl, std::shared_ptr>>; -using FHECKKSRNSClass = pybind11::class_; void bind_parameters(pybind11::module &m); void bind_crypto_context(pybind11::module &m); @@ -49,10 +48,8 @@ void bind_keys(pybind11::module &m); void bind_encodings(pybind11::module &m); void bind_ciphertext(pybind11::module &m); void bind_serialization(pybind11::module &m); -void bind_schemes(pybind11::module &m); void bind_sch_swch_params(pybind11::module &m); void bind_fbt_crypto_context(DCRTCryptoContextClass &cls); -void bind_fbt_scheme(FHECKKSRNSClass &cls); void bind_fbt(pybind11::module &m); #endif // __BINDINGS_H__ diff --git a/src/lib/bindings.cpp b/src/lib/bindings.cpp index 79ecd40..3b51758 100644 --- a/src/lib/bindings.cpp +++ b/src/lib/bindings.cpp @@ -1677,25 +1677,6 @@ void bind_ciphertext(py::module &m) { }); } -void bind_schemes(py::module &m) { - // Bind schemes specific functionalities like bootstrapping functions and multiparty - auto fheckks_class = py::class_(m, "FHECKKSRNS"); - - fheckks_class - .def(py::init<>()) - .def_static("GetBootstrapDepth", - py::overload_cast&, SecretKeyDist>(&FHECKKSRNS::GetBootstrapDepth), - py::arg("depth"), - py::arg("levelBudget"), - py::arg("keyDist")) - .def_static("GetBootstrapDepth", - py::overload_cast&, SecretKeyDist>(&FHECKKSRNS::GetBootstrapDepth), - py::arg("levelBudget"), - py::arg("keyDist")); - - bind_fbt_scheme(fheckks_class); -} - void bind_sch_swch_params(py::module &m) { py::class_(m, "SchSwchParams") .def(py::init<>()) @@ -1785,7 +1766,6 @@ PYBIND11_MODULE(openfhe, m) { bind_keys(m); bind_crypto_context(m); bind_serialization(m); - bind_schemes(m); bind_sch_swch_params(m); bind_fbt(m); bind_utils(m); diff --git a/src/lib/pke/fbt.cpp b/src/lib/pke/fbt.cpp index 0a2c988..fff39a5 100644 --- a/src/lib/pke/fbt.cpp +++ b/src/lib/pke/fbt.cpp @@ -73,11 +73,36 @@ static py::int_ FBTBigIntegerToPyInt(const BigInteger& value) { } void bind_fbt_crypto_context(DCRTCryptoContextClass &cls) { - // CKKS functional bootstrapping of a look-up table. The overloads taking a list of ints - // correspond to the optimized Boolean case (integer Hermite coefficients); the overloads - // taking a list of complex numbers cover the general case. Lambdas convert - // arbitrary-size Python ints to BigInteger. - cls.def("EvalFBTSetup", + // Keep CKKS depth helpers on CryptoContext without exposing the scheme implementation class. + // For functional bootstrapping, overloads taking a list of ints correspond to the optimized + // Boolean case; overloads taking a list of complex numbers cover the general case. Lambdas + // convert arbitrary-size Python ints to BigInteger. + cls.def_static("GetBootstrapDepth", + py::overload_cast&, SecretKeyDist>(&FHECKKSRNS::GetBootstrapDepth), + py::arg("depth"), py::arg("levelBudget"), py::arg("keyDist")) + .def_static("GetBootstrapDepth", + py::overload_cast&, SecretKeyDist>(&FHECKKSRNS::GetBootstrapDepth), + py::arg("levelBudget"), py::arg("keyDist")) + .def_static("GetFBTDepth", + [](const std::vector& levelBudget, const std::vector& coefficients, + const py::int_& PInput, size_t order, SecretKeyDist skd, uint32_t firstModSize) { + return FHECKKSRNS::GetFBTDepth(levelBudget, coefficients, FBTPyIntToBigInteger(PInput), order, skd, + firstModSize); + }, + py::arg("levelBudget"), py::arg("coefficients"), py::arg("PInput"), py::arg("order"), py::arg("skd"), + py::arg("firstModSize") = 60) + .def_static("GetFBTDepth", + [](const std::vector& levelBudget, const std::vector>& coefficients, + const py::int_& PInput, size_t order, SecretKeyDist skd, uint32_t firstModSize) { + return FHECKKSRNS::GetFBTDepth(levelBudget, coefficients, FBTPyIntToBigInteger(PInput), order, skd, + firstModSize); + }, + py::arg("levelBudget"), py::arg("coefficients"), py::arg("PInput"), py::arg("order"), py::arg("skd"), + py::arg("firstModSize") = 60) + .def_static("GetFEFBTDepth", &FHECKKSRNS::GetFEFBTDepth>, + py::arg("levelBudget"), py::arg("coefficients"), py::arg("skd") = SPARSE_TERNARY, + py::arg("firstModSize") = 60) + .def("EvalFBTSetup", [](CC& self, const std::vector& coeffs, uint32_t numSlots, const py::int_& PIn, const py::int_& POut, const py::int_& Bigq, const PublicKey& pubKey, const std::vector& dim1, const std::vector& levelBudget, uint32_t lvlsAfterBoot, @@ -177,29 +202,6 @@ void bind_fbt_crypto_context(DCRTCryptoContextClass &cls) { .def("ClearBootstrapPrecom", &CryptoContextImpl::ClearBootstrapPrecom); } -void bind_fbt_scheme(FHECKKSRNSClass &cls) { - // GetFBTDepth uses lambdas to convert arbitrary-size Python ints to BigInteger. - cls.def_static("GetFBTDepth", - [](const std::vector& levelBudget, const std::vector& coefficients, - const py::int_& PInput, size_t order, SecretKeyDist skd, uint32_t firstModSize) { - return FHECKKSRNS::GetFBTDepth(levelBudget, coefficients, FBTPyIntToBigInteger(PInput), order, skd, - firstModSize); - }, - py::arg("levelBudget"), py::arg("coefficients"), py::arg("PInput"), py::arg("order"), py::arg("skd"), - py::arg("firstModSize") = 60) - .def_static("GetFBTDepth", - [](const std::vector& levelBudget, const std::vector>& coefficients, - const py::int_& PInput, size_t order, SecretKeyDist skd, uint32_t firstModSize) { - return FHECKKSRNS::GetFBTDepth(levelBudget, coefficients, FBTPyIntToBigInteger(PInput), order, skd, - firstModSize); - }, - py::arg("levelBudget"), py::arg("coefficients"), py::arg("PInput"), py::arg("order"), py::arg("skd"), - py::arg("firstModSize") = 60) - .def_static("GetFEFBTDepth", &FHECKKSRNS::GetFEFBTDepth>, - py::arg("levelBudget"), py::arg("coefficients"), py::arg("skd") = SPARSE_TERNARY, - py::arg("firstModSize") = 60); -} - void bind_fbt(py::module &m) { // opaque holder for the powers of the complex exponential precomputed by // EvalMVBPrecompute/EvalFEFuncBootstrapPrecompute diff --git a/tests/test_ckks.py b/tests/test_ckks.py index 26fd747..b808b6f 100644 --- a/tests/test_ckks.py +++ b/tests/test_ckks.py @@ -5,6 +5,15 @@ pytestmark = pytest.mark.skipif(fhe.get_native_int() == 32, reason="Doesn't work for NATIVE_INT=32") + +def test_ckks_depth_helpers_are_on_crypto_context(): + assert fhe.CryptoContext.GetBootstrapDepth([2, 2], fhe.UNIFORM_TERNARY) > 0 + assert fhe.CryptoContext.GetBootstrapDepth(9, [2, 2], fhe.UNIFORM_TERNARY) > 0 + assert fhe.CryptoContext.GetFBTDepth([3, 3], [1, -1], 2, 1, fhe.SPARSE_ENCAPSULATED) > 0 + assert fhe.CryptoContext.GetFBTDepth([3, 3], [1 + 0j, -1 + 0j], 4, 1, fhe.SPARSE_ENCAPSULATED) > 0 + assert fhe.CryptoContext.GetFEFBTDepth([3, 2], [1 + 0j, -1 + 0j], fhe.SPARSE_ENCAPSULATED) > 0 + + @pytest.fixture(scope="module") def ckks_context(): """ @@ -108,7 +117,7 @@ def test_existing_bootstrap_key_indices(): parameters.SetRingDim(512) parameters.SetSecretKeyDist(fhe.UNIFORM_TERNARY) level_budget = [2, 2] - depth = fhe.FHECKKSRNS.GetBootstrapDepth(level_budget, fhe.UNIFORM_TERNARY) + depth = fhe.CryptoContext.GetBootstrapDepth(level_budget, fhe.UNIFORM_TERNARY) parameters.SetMultiplicativeDepth(depth + 2) parameters.SetScalingTechnique(fhe.FIXEDAUTO) parameters.SetScalingModSize(78 if fhe.get_native_int() == 128 else 59) From cfce50e96b3d64b664ec5ae049041b166eeaf913 Mon Sep 17 00:00:00 2001 From: Dmitriy Suponitskiy Date: Tue, 6 Oct 2026 20:14:54 -0400 Subject: [PATCH 06/16] Expose the API-coverage backlog to Python - Fix bind_eval_multinplace_scalar_overloads, which registered EvalAddInPlace instead of EvalMultInPlace. The scalar EvalAddInPlace overloads are bound elsewhere, so nothing is lost. - Bind PrivateKey(cryptoContext). RecoverSharedKey() dereferences sk->GetCryptoContext(), so a default-constructed key crashed it. - Add CryptoContext.GetElementParams(), needed to call GetPlaintextForDecrypt(). - Add the CryptoContext overloads of SerializeEvalSumKey and check is_open(), so an unusable path returns False instead of a cereal error. - Add tests/test_api_coverage.py, which checks decrypted values rather than method presence. --- src/include/docstrings/cryptocontext_docs.h | 19 ++ src/lib/bindings.cpp | 221 +++++++++++++- src/lib/pke/fbt.cpp | 6 + tests/test_api_coverage.py | 310 ++++++++++++++++++++ 4 files changed, 550 insertions(+), 6 deletions(-) create mode 100644 tests/test_api_coverage.py diff --git a/src/include/docstrings/cryptocontext_docs.h b/src/include/docstrings/cryptocontext_docs.h index b7f1e71..d077b9b 100644 --- a/src/include/docstrings/cryptocontext_docs.h +++ b/src/include/docstrings/cryptocontext_docs.h @@ -76,6 +76,25 @@ const char* cc_GetCyclotomicOrder_docs = R"pbdoc( :rtype: int )pbdoc"; +const char* cc_GetElementParams_docs = R"pbdoc( + Getter for the element (ring) parameters of the crypto context. + + Supplies the elementParams argument of GetPlaintextForDecrypt(). + + :return: element parameters + :rtype: ParmType +)pbdoc"; + +const char* cc_GetRootOfUnity_docs = R"pbdoc( + Getter for the root of unity of the element parameters. + + DCRTPoly keeps a root of unity per RNS tower rather than one for the whole + ciphertext modulus, so this returns 0 for every DCRTPoly context. + + :return: root of unity, 0 for DCRTPoly + :rtype: int +)pbdoc"; + const char* cc_GetModulus_docs = R"pbdoc( Getter for ciphertext modulus diff --git a/src/lib/bindings.cpp b/src/lib/bindings.cpp index 3b51758..2b70e4a 100644 --- a/src/lib/bindings.cpp +++ b/src/lib/bindings.cpp @@ -68,6 +68,15 @@ inline py::int_ BigIntegerToPyInt(const BigInteger& value) { return py::reinterpret_steal(PyLong_FromString(value.ToString().c_str(), nullptr, 10)); } +inline py::int_ NativeIntegerToPyInt(const NativeInteger& value) { + return py::reinterpret_steal(PyLong_FromString(value.ToString().c_str(), nullptr, 10)); +} + +inline NativeInteger PyIntToNativeInteger(const py::int_& value) { + auto valueStr = py::reinterpret_steal(PyObject_Str(value.ptr())); + return NativeInteger(valueStr.cast()); +} + void bind_DCRTPoly(py::module &m) { py::class_(m, "DCRTPoly").def(py::init<>()); } @@ -91,7 +100,7 @@ void bind_parameters(py::module &m, const std::string name) { .def("GetDesiredPrecision", &CCParams::GetDesiredPrecision) .def("GetStatisticalSecurity", &CCParams::GetStatisticalSecurity) .def("GetNumAdversarialQueries", &CCParams::GetNumAdversarialQueries) - //.def("GetThresholdNumOfParties", &CCParams::GetThresholdNumOfParties) + .def("GetThresholdNumOfParties", &CCParams::GetThresholdNumOfParties) .def("GetKeySwitchTechnique", &CCParams::GetKeySwitchTechnique) .def("GetScalingTechnique", &CCParams::GetScalingTechnique) .def("GetBatchSize", &CCParams::GetBatchSize) @@ -228,7 +237,26 @@ void bind_crypto_context_templates(py::class_>& cls) { py::arg("ciphertext"), py::arg("coefficients"), py::doc(cc_EvalPolyPS_docs)) - ; + .def("EvalChebyPolys", + static_cast> (CC::*)( + ConstCiphertext&, const std::vector&, double, double + ) const>(&CC::EvalChebyPolys), + py::arg("ciphertext"), py::arg("coefficients"), py::arg("a"), py::arg("b")) + .def("EvalChebyshevSeriesWithPrecomp", + static_cast (CC::*)( + std::shared_ptr>, const std::vector& + ) const>(&CC::EvalChebyshevSeriesWithPrecomp), + py::arg("polys"), py::arg("coefficients")) + .def("EvalPolyWithPrecomp", + static_cast (CC::*)( + std::shared_ptr>, const std::vector& + ) const>(&CC::EvalPolyWithPrecomp), + py::arg("powers"), py::arg("coefficients")) + .def("EvalPowers", + static_cast> (CC::*)( + ConstCiphertext&, const std::vector& + ) const>(&CC::EvalPowers), + py::arg("ciphertext"), py::arg("coefficients")); } void bind_eval_add_family(py::class_>& cls) { @@ -377,11 +405,11 @@ void bind_eval_mult_scalar_overloads(py::class_>& cls) { // } template void bind_eval_multinplace_scalar_overloads(py::class_>& cls) { - cls.def("EvalAddInPlace", static_cast&, ScalarT) const>(&CC::EvalAddInPlace), + cls.def("EvalMultInPlace", static_cast&, ScalarT) const>(&CC::EvalMultInPlace), py::arg("ciphertext"), py::arg("scalar"), py::doc("")); - cls.def("EvalAddInPlace", static_cast&) const>(&CC::EvalAddInPlace), + cls.def("EvalMultInPlace", static_cast&) const>(&CC::EvalMultInPlace), py::arg("scalar"), py::arg("ciphertext"), py::doc("")); // TODO (dsuponit): replace this with an actual docstring @@ -398,9 +426,15 @@ void bind_crypto_context(py::module &m) { .def("GetRingDimension", &ParmType::GetRingDimension) .def("GetCyclotomicOrder", &ParmType::GetCyclotomicOrder); + py::class_, std::shared_ptr>>(m, "SchemeBase"); + auto cc_class = py::class_, std::shared_ptr>>(m, "CryptoContext"); cc_class.def(py::init<>()) + .def("ClearAllCKKSCaches", &CC::ClearAllCKKSCaches) + .def("ClearSchemeSwitchPrecom", &CC::ClearSchemeSwitchPrecom) + .def("ComposedEvalMult", &CC::ComposedEvalMult, + py::arg("ciphertext1"), py::arg("ciphertext2")) .def("GetKeyGenLevel", &CryptoContextImpl::GetKeyGenLevel, cc_GetKeyGenLevel_docs) .def("SetKeyGenLevel", &CryptoContextImpl::SetKeyGenLevel, py::arg("level"), @@ -453,6 +487,19 @@ void bind_crypto_context(py::module &m) { return self->GetCryptoParameters()->GetDigitSize(); }) .def("GetCyclotomicOrder", &CryptoContextImpl::GetCyclotomicOrder, cc_GetCyclotomicOrder_docs) + .def("GetEncodingParams", &CC::GetEncodingParams) + .def("GetElementParams", + [](const CC& self) { + return self.GetCryptoParameters()->GetElementParams(); + }, + py::doc(cc_GetElementParams_docs)) + .def("GetRootOfUnity", [](const CC& self) { + return BigIntegerToPyInt(self.GetRootOfUnity()); + }, + py::doc(cc_GetRootOfUnity_docs)) + .def("GetScheme", &CC::GetScheme) + .def("getSchemeId", &CC::getSchemeId) + .def("setSchemeId", &CC::setSchemeId, py::arg("schemeTag")) .def("GetCKKSDataType", &CryptoContextImpl::GetCKKSDataType) .def("GetCKKSBootCorrectionFactor", &CC::GetCKKSBootCorrectionFactor) .def("SetCKKSBootCorrectionFactor", &CC::SetCKKSBootCorrectionFactor, py::arg("cf")) @@ -504,6 +551,7 @@ void bind_crypto_context(py::module &m) { py::arg("feature"), py::doc(cc_Enable_docs)) .def("KeyGen", &CryptoContextImpl::KeyGen, cc_KeyGen_docs) + .def("SparseKeyGen", &CC::SparseKeyGen) .def("EvalMultKeyGen", &CryptoContextImpl::EvalMultKeyGen, py::arg("privateKey"), py::doc(cc_EvalMultKeyGen_docs)) @@ -651,6 +699,11 @@ void bind_crypto_context(py::module &m) { py::arg("oldPrivateKey"), py::arg("newPrivateKey"), py::doc(cc_KeySwitchGen_docs)) + .def("KeySwitch", &CC::KeySwitch, py::arg("ciphertext"), py::arg("evalKey")) + .def("KeySwitchInPlace", &CC::KeySwitchInPlace, py::arg("ciphertext"), py::arg("evalKey")) + .def("KeySwitchDown", &CC::KeySwitchDown, py::arg("ciphertext")) + .def("KeySwitchDownFirstElement", &CC::KeySwitchDownFirstElement, py::arg("ciphertext")) + .def("KeySwitchExt", &CC::KeySwitchExt, py::arg("ciphertext"), py::arg("addFirst")) .def("EvalAddMutable", py::overload_cast&, Ciphertext&>(&CryptoContextImpl::EvalAddMutable, py::const_), py::arg("ciphertext1"), @@ -720,6 +773,13 @@ void bind_crypto_context(py::module &m) { py::arg("ciphertext1"), py::arg("ciphertext2"), py::doc(cc_EvalMultNoRelin_docs)) + .def("EvalMultNoRelinNoCheck", &CC::EvalMultNoRelinNoCheck, + py::arg("ciphertext1"), py::arg("ciphertext2")) + .def("EvalMultNoCheck", [](const CC& self, ConstCiphertext& ciphertext, const py::int_& scalar) { + return self.EvalMultNoCheck(ciphertext, PyIntToNativeInteger(scalar)); + }, py::arg("ciphertext"), py::arg("scalar")) + .def("EvalAddInPlaceNoCheck", &CC::EvalAddInPlaceNoCheck, + py::arg("ciphertext1"), py::arg("ciphertext2")) .def("Relinearize", &CryptoContextImpl::Relinearize, py::arg("ciphertext"), py::doc(cc_Relinearize_docs)) @@ -730,6 +790,10 @@ void bind_crypto_context(py::module &m) { py::arg("ciphertext1"), py::arg("ciphertext2"), py::doc(cc_EvalMultAndRelinearize_docs)) + .def("LevelReduce", &CC::LevelReduce, + py::arg("ciphertext"), py::arg("evalKey"), py::arg("levels") = 1) + .def("LevelReduceInPlace", &CC::LevelReduceInPlace, + py::arg("ciphertext"), py::arg("evalKey"), py::arg("levels") = 1) .def("EvalNegate", &CryptoContextImpl::EvalNegate, py::arg("ciphertext"), py::doc(cc_EvalNegate_docs)) @@ -851,6 +915,10 @@ void bind_crypto_context(py::module &m) { }, py::arg("partialCiphertextVec"), py::doc(cc_MultipartyDecryptFusion_docs)) + .def("ShareKeys", &CC::ShareKeys, + py::arg("privateKey"), py::arg("N"), py::arg("threshold"), py::arg("index"), py::arg("shareType")) + .def("RecoverSharedKey", &CC::RecoverSharedKey, + py::arg("privateKey"), py::arg("shares"), py::arg("N"), py::arg("threshold"), py::arg("shareType")) .def("MultiKeySwitchGen", &CryptoContextImpl::MultiKeySwitchGen, py::arg("originalPrivateKey"), py::arg("newPrivateKey"), @@ -869,6 +937,8 @@ void bind_crypto_context(py::module &m) { py::arg("indexList"), py::arg("keyTag") = "", py::doc(cc_MultiEvalAtIndexKeyGen_docs)) + .def("MultiEvalAutomorphismKeyGen", &CC::MultiEvalAutomorphismKeyGen, + py::arg("privateKey"), py::arg("evalKeyMap"), py::arg("indexList"), py::arg("keyTag") = "") .def("MultiEvalSumKeyGen", &CryptoContextImpl::MultiEvalSumKeyGen, py::arg("privateKey"), py::arg("evalKeyMap"), @@ -994,11 +1064,14 @@ void bind_crypto_context(py::module &m) { py::arg("privateKey"), py::arg("slots"), py::doc(cc_EvalBootstrapKeyGen_docs)) + .def("EvalBootstrapPrecompute", &CC::EvalBootstrapPrecompute, py::arg("slots") = 0) .def("EvalBootstrap", &CryptoContextImpl::EvalBootstrap, py::arg("ciphertext"), py::arg("numIterations") = 1, py::arg("precision") = 0, py::doc(cc_EvalBootstrap_docs)) + .def("EvalBootstrapStCFirst", &CC::EvalBootstrapStCFirst, + py::arg("ciphertext"), py::arg("numIterations") = 1, py::arg("precision") = 0) .def_static("ClearStaticMapsAndVectors", &CryptoContextImpl::ClearStaticMapsAndVectors) .def("EvalCKKStoFHEWSetup", &CryptoContextImpl::EvalCKKStoFHEWSetup, py::arg("schswchparams"), @@ -1039,6 +1112,10 @@ void bind_crypto_context(py::module &m) { .def("EvalSchemeSwitchingSetup", &CryptoContextImpl::EvalSchemeSwitchingSetup, py::arg("schswchparams"), py::doc(cc_EvalSchemeSwitchingSetup_docs)) + .def("SetBinCCForSchemeSwitch", &CC::SetBinCCForSchemeSwitch, py::arg("binFHEContext")) + .def("SetParamsFromCKKSCryptocontext", &CC::SetParamsFromCKKSCryptocontext, py::arg("params")) + .def("GetSwkFC", &CC::GetSwkFC) + .def("SetSwkFC", &CC::SetSwkFC, py::arg("switchingKey")) //void EvalSchemeSwitchingKeyGen(const KeyPair &keyPair, ConstLWEPrivateKey &lwesk, uint32_t numValues = 0, bool oneHot = true, bool alt = false, uint32_t dim1CF = 0, uint32_t dim1FC = 0, uint32_t LCF = 1, uint32_t LFC = 0) .def("EvalSchemeSwitchingKeyGen", &CryptoContextImpl::EvalSchemeSwitchingKeyGen, py::arg("keyPair"), @@ -1170,6 +1247,78 @@ void bind_crypto_context(py::module &m) { }, py::arg("keyTag") = "", py::doc(cc_GetExistingEvalAutomorphismKeyIndices_docs)) + .def_static("GetAllEvalAutomorphismKeys", []() { + py::dict result; + for (const auto& [keyTag, keys] : CC::GetAllEvalAutomorphismKeys()) + result[py::str(keyTag)] = py::cast(keys); + return result; + }) + .def_static("GetAllEvalMultKeys", []() { + py::dict result; + for (const auto& [keyTag, keys] : CC::GetAllEvalMultKeys()) + result[py::str(keyTag)] = py::cast(keys); + return result; + }) + .def_static("GetAllEvalSumKeys", []() { + py::dict result; + for (const auto& [keyTag, keys] : CC::GetAllEvalSumKeys()) + result[py::str(keyTag)] = py::cast(keys); + return result; + }) + .def_static("GetEvalAutomorphismNoKeyIndices", &CC::GetEvalAutomorphismNoKeyIndices, + py::arg("keyTag"), py::arg("indices")) + .def_static("GetUniqueValues", &CC::GetUniqueValues, + py::arg("oldValues"), py::arg("newValues")) + .def_static("GetPlaintextForDecrypt", &CC::GetPlaintextForDecrypt, + py::arg("encodingType"), py::arg("elementParams"), py::arg("encodingParams"), + py::arg("ckksDataType") = CKKSDataType::REAL) + .def("SerializedObjectName", &CC::SerializedObjectName) + .def_static("SerializedVersion", &CC::SerializedVersion) + .def_static("SerializeEvalSumKey", [](const std::string& filename, const SerType::SERBINARY& sertype, + const std::string& keyTag) { + std::ofstream outfile(filename, std::ios::out | std::ios::binary); + if (!outfile.is_open()) { + std::cerr << "I cannot write serialization to " << filename << std::endl; + return false; + } + bool res = CC::SerializeEvalSumKey(outfile, sertype, keyTag); + outfile.close(); + return res; + }, py::arg("filename"), py::arg("sertype"), py::arg("keyTag") = "") + .def_static("SerializeEvalSumKey", [](const std::string& filename, const SerType::SERJSON& sertype, + const std::string& keyTag) { + std::ofstream outfile(filename, std::ios::out | std::ios::binary); + if (!outfile.is_open()) { + std::cerr << "I cannot write serialization to " << filename << std::endl; + return false; + } + bool res = CC::SerializeEvalSumKey(outfile, sertype, keyTag); + outfile.close(); + return res; + }, py::arg("filename"), py::arg("sertype"), py::arg("keyTag") = "") + // the CryptoContext overloads serialize only the keys belonging to the given context + .def_static("SerializeEvalSumKey", [](const std::string& filename, const SerType::SERBINARY& sertype, + const CryptoContext cryptoContext) { + std::ofstream outfile(filename, std::ios::out | std::ios::binary); + if (!outfile.is_open()) { + std::cerr << "I cannot write serialization to " << filename << std::endl; + return false; + } + bool res = CC::SerializeEvalSumKey(outfile, sertype, cryptoContext); + outfile.close(); + return res; + }, py::arg("filename"), py::arg("sertype"), py::arg("cryptoContext")) + .def_static("SerializeEvalSumKey", [](const std::string& filename, const SerType::SERJSON& sertype, + const CryptoContext cryptoContext) { + std::ofstream outfile(filename, std::ios::out | std::ios::binary); + if (!outfile.is_open()) { + std::cerr << "I cannot write serialization to " << filename << std::endl; + return false; + } + bool res = CC::SerializeEvalSumKey(outfile, sertype, cryptoContext); + outfile.close(); + return res; + }, py::arg("filename"), py::arg("sertype"), py::arg("cryptoContext")) .def_static("SerializeEvalMultKey", [](const std::string &filename, const SerType::SERBINARY &sertype, std::string keyTag = "") { std::ofstream outfile(filename, std::ios::out | std::ios::binary); bool res = CryptoContextImpl::SerializeEvalMultKey(outfile, sertype, keyTag); @@ -1210,6 +1359,22 @@ void bind_crypto_context(py::module &m) { py::arg("sertype"), py::arg("keyTag") = "", py::doc(cc_SerializeEvalAutomorphismKey_docs)) + .def_static("DeserializeEvalSumKey", [](const std::string& filename, const SerType::SERBINARY& sertype) { + std::ifstream infile(filename, std::ios::in | std::ios::binary); + if (!infile.is_open()) { + std::cerr << "I cannot read serialization from " << filename << std::endl; + return false; + } + return CC::DeserializeEvalSumKey(infile, sertype); + }, py::arg("filename"), py::arg("sertype")) + .def_static("DeserializeEvalSumKey", [](const std::string& filename, const SerType::SERJSON& sertype) { + std::ifstream infile(filename, std::ios::in | std::ios::binary); + if (!infile.is_open()) { + std::cerr << "I cannot read serialization from " << filename << std::endl; + return false; + } + return CC::DeserializeEvalSumKey(infile, sertype); + }, py::arg("filename"), py::arg("sertype")) .def_static("DeserializeEvalMultKey", [](const std::string &filename, const SerType::SERBINARY &sertype) { std::ifstream emkeys(filename, std::ios::in | std::ios::binary); if (!emkeys.is_open()) { @@ -1348,6 +1513,17 @@ void bind_enums_and_constants(py::module &m) { .value("COEFFICIENT", Format::COEFFICIENT); m.attr("EVALUATION") = py::cast(Format::EVALUATION); m.attr("COEFFICIENT") = py::cast(Format::COEFFICIENT); + py::enum_(m, "PlaintextEncodings") + .value("INVALID_ENCODING", PlaintextEncodings::INVALID_ENCODING) + .value("COEF_PACKED_ENCODING", PlaintextEncodings::COEF_PACKED_ENCODING) + .value("PACKED_ENCODING", PlaintextEncodings::PACKED_ENCODING) + .value("STRING_ENCODING", PlaintextEncodings::STRING_ENCODING) + .value("CKKS_PACKED_ENCODING", PlaintextEncodings::CKKS_PACKED_ENCODING); + m.attr("INVALID_ENCODING") = py::cast(PlaintextEncodings::INVALID_ENCODING); + m.attr("COEF_PACKED_ENCODING") = py::cast(PlaintextEncodings::COEF_PACKED_ENCODING); + m.attr("PACKED_ENCODING") = py::cast(PlaintextEncodings::PACKED_ENCODING); + m.attr("STRING_ENCODING") = py::cast(PlaintextEncodings::STRING_ENCODING); + m.attr("CKKS_PACKED_ENCODING") = py::cast(PlaintextEncodings::CKKS_PACKED_ENCODING); // Serialization Types py::class_(m, "SERJSON"); py::class_(m, "SERBINARY"); @@ -1483,10 +1659,13 @@ void bind_enums_and_constants(py::module &m) { void bind_keys(py::module &m) { py::class_, std::shared_ptr>>(m, "PublicKey") .def(py::init<>()) + .def("GetCryptoContext", &PublicKeyImpl::GetCryptoContext) .def("GetKeyTag", &PublicKeyImpl::GetKeyTag) .def("SetKeyTag", &PublicKeyImpl::SetKeyTag); py::class_, std::shared_ptr>>(m, "PrivateKey") .def(py::init<>()) + // a key created without a CryptoContext cannot be passed to RecoverSharedKey() + .def(py::init&>(), py::arg("cryptoContext")) .def("GetCryptoContext", &PrivateKeyImpl::GetCryptoContext) .def("GetKeyTag", &PrivateKeyImpl::GetKeyTag) .def("SetKeyTag", &PrivateKeyImpl::SetKeyTag); @@ -1581,12 +1760,31 @@ class PlaintextImpl_helper : public PlaintextImpl { }; void bind_encodings(py::module &m) { + py::class_(m, "EncodingParams") + .def("GetPlaintextModulus", &EncodingParamsImpl::GetPlaintextModulus) + .def("GetBatchSize", &EncodingParamsImpl::GetBatchSize) + .def("GetPlaintextGenerator", &EncodingParamsImpl::GetPlaintextGenerator); + py::class_, PlaintextImpl_helper>(m, "Plaintext") .def("GetScalingFactor", &PlaintextImpl::GetScalingFactor, ptx_GetScalingFactor_docs) + .def("GetScalingFactorInt", [](const PlaintextImpl& self) { + return NativeIntegerToPyInt(self.GetScalingFactorInt()); + }) + .def("SetScalingFactorInt", [](PlaintextImpl& self, const py::int_& sf) { + self.SetScalingFactorInt(PyIntToNativeInteger(sf)); + }, py::arg("sf")) .def("SetScalingFactor", &PlaintextImpl::SetScalingFactor, py::arg("sf"), py::doc(ptx_SetScalingFactor_docs)) .def("GetSchemeID", &PlaintextImpl::GetSchemeID, ptx_GetSchemeID_docs) + .def("GetEncodingType", &PlaintextImpl::GetEncodingType) + .def("GetEncodingParams", &PlaintextImpl::GetEncodingParams) + .def("GetCKKSDataType", &PlaintextImpl::GetCKKSDataType) + .def("SetCKKSDataType", &PlaintextImpl::SetCKKSDataType, py::arg("dataType")) + .def("GetElementRingDimension", &PlaintextImpl::GetElementRingDimension) + .def("GetElementModulus", [](const PlaintextImpl& self) { + return BigIntegerToPyInt(self.GetElementModulus()); + }) .def("GetLength", &PlaintextImpl::GetLength, ptx_GetLength_docs) .def("SetLength", &PlaintextImpl::SetLength, py::arg("newSize"), @@ -1644,16 +1842,24 @@ void bind_ciphertext(py::module &m) { py::arg("level"), py::doc(ctx_SetLevel_docs)) .def("Clone", &CiphertextImpl::Clone) + .def("CloneEmpty", &CiphertextImpl::CloneEmpty) + .def("NumberCiphertextElements", &CiphertextImpl::NumberCiphertextElements) .def("RemoveElement", [](Ciphertext& self, uint32_t index) { self->GetElements().erase(self->GetElements().begin() + index); }, py::arg("index"), py::doc(cc_RemoveElement_docs)) - // .def("GetHopLevel", &CiphertextImpl::GetHopLevel) - // .def("SetHopLevel", &CiphertextImpl::SetHopLevel) + .def("GetHopLevel", &CiphertextImpl::GetHopLevel) + .def("SetHopLevel", &CiphertextImpl::SetHopLevel, py::arg("hopLevel")) .def("GetScalingFactor", &CiphertextImpl::GetScalingFactor) .def("SetScalingFactor", &CiphertextImpl::SetScalingFactor) + .def("GetScalingFactorInt", [](const CiphertextImpl& self) { + return NativeIntegerToPyInt(self.GetScalingFactorInt()); + }) + .def("SetScalingFactorInt", [](CiphertextImpl& self, const py::int_& sf) { + self.SetScalingFactorInt(PyIntToNativeInteger(sf)); + }, py::arg("sf")) .def("GetSlots", &CiphertextImpl::GetSlots) .def("SetSlots", &CiphertextImpl::SetSlots) .def("GetNoiseScaleDeg", &CiphertextImpl::GetNoiseScaleDeg) @@ -1661,6 +1867,8 @@ void bind_ciphertext(py::module &m) { .def("GetCryptoContext", &CiphertextImpl::GetCryptoContext) .def("GetKeyTag", &CiphertextImpl::GetKeyTag) .def("GetEncodingType", &CiphertextImpl::GetEncodingType) + .def("SetEncodingType", &CiphertextImpl::SetEncodingType, py::arg("encodingType")) + .def("SetKeyTag", &CiphertextImpl::SetKeyTag, py::arg("keyTag")) .def("GetElements", [](const CiphertextImpl& self) -> const std::vector& { return self.GetElements(); }, @@ -1718,6 +1926,7 @@ void bind_sch_swch_params(py::module &m) { .def("SetRingDimension", &SchSwchParams::SetRingDimension) .def("SetScalingModSize", &SchSwchParams::SetScalingModSize) .def("SetBatchSize", &SchSwchParams::SetBatchSize) + .def("SetParamsFromCKKSCryptocontextCalled", &SchSwchParams::SetParamsFromCKKSCryptocontextCalled) .def("__str__", [](const SchSwchParams ¶ms) { std::stringstream stream; stream << params; diff --git a/src/lib/pke/fbt.cpp b/src/lib/pke/fbt.cpp index fff39a5..f3edc85 100644 --- a/src/lib/pke/fbt.cpp +++ b/src/lib/pke/fbt.cpp @@ -188,6 +188,12 @@ void bind_fbt_crypto_context(DCRTCryptoContextClass &cls) { py::arg("ciphertexts"), py::arg("coeffs"), py::arg("digitBitSize"), py::arg("order") = (size_t)1) .def("EvalMVBNoDecoding", &CryptoContextImpl::EvalMVBNoDecoding>, py::arg("ciphertexts"), py::arg("coeffs"), py::arg("digitBitSize"), py::arg("order") = (size_t)1) + .def("EvalHermiteTrigSeries", &CryptoContextImpl::EvalHermiteTrigSeries, + py::arg("ciphertext"), py::arg("coefficientsCheb"), py::arg("a"), py::arg("b"), + py::arg("coefficientsHerm"), py::arg("precomp") = (size_t)0) + .def("EvalHermiteTrigSeries", &CryptoContextImpl::EvalHermiteTrigSeries>, + py::arg("ciphertext"), py::arg("coefficientsCheb"), py::arg("a"), py::arg("b"), + py::arg("coefficientsHerm"), py::arg("precomp") = (size_t)0) // FE functional bootstrapping .def("EvalFEFuncBootstrapSetup", &CryptoContextImpl::EvalFEFuncBootstrapSetup, py::arg("levelBudget") = std::vector({5, 4}), diff --git a/tests/test_api_coverage.py b/tests/test_api_coverage.py new file mode 100644 index 0000000..9c62d76 --- /dev/null +++ b/tests/test_api_coverage.py @@ -0,0 +1,310 @@ +import openfhe as fhe +import pytest + +pytestmark = pytest.mark.skipif(fhe.get_native_int() == 32, reason="CKKS is not supported for NATIVE_INT=32") + + +@pytest.fixture(scope="module") +def ckks_context(): + """A small CKKS context shared by the tests below, since setup is slow.""" + params = fhe.CCParamsCKKSRNS() + params.SetMultiplicativeDepth(6) + params.SetBatchSize(8) + params.SetScalingModSize(50) + params.SetRingDim(1024) + params.SetSecurityLevel(fhe.HEStd_NotSet) + + cc = fhe.GenCryptoContext(params) + for feature in [fhe.PKE, fhe.KEYSWITCH, fhe.LEVELEDSHE, fhe.ADVANCEDSHE, fhe.MULTIPARTY]: + cc.Enable(feature) + + keys = cc.KeyGen() + cc.EvalMultKeyGen(keys.secretKey) + cc.EvalSumKeyGen(keys.secretKey) + return cc, keys + + +def decrypt(cc, ciphertext, secret_key, length=2): + plaintext = cc.Decrypt(ciphertext, secret_key) + plaintext.SetLength(length) + return [round(value.real, 4) for value in plaintext.GetCKKSPackedValue()] + + +def test_spreadsheet_add_apis_are_exposed(): + expected_methods = { + fhe.CryptoContext: { + "ClearAllCKKSCaches", + "ClearSchemeSwitchPrecom", + "ComposedEvalMult", + "DeserializeEvalSumKey", + "EvalAddInPlaceNoCheck", + "EvalBootstrapPrecompute", + "EvalBootstrapStCFirst", + "EvalChebyPolys", + "EvalChebyshevSeriesWithPrecomp", + "EvalHermiteTrigSeries", + "EvalMultInPlace", + "EvalMultNoCheck", + "EvalMultNoRelinNoCheck", + "EvalPolyWithPrecomp", + "EvalPowers", + "GetAllEvalAutomorphismKeys", + "GetAllEvalMultKeys", + "GetAllEvalSumKeys", + "GetEncodingParams", + "GetEvalAutomorphismNoKeyIndices", + "GetPlaintextForDecrypt", + "GetRootOfUnity", + "GetScheme", + "getSchemeId", + "GetSwkFC", + "GetUniqueValues", + "KeySwitch", + "KeySwitchDown", + "KeySwitchDownFirstElement", + "KeySwitchExt", + "KeySwitchInPlace", + "LevelReduce", + "LevelReduceInPlace", + "MultiEvalAutomorphismKeyGen", + "RecoverSharedKey", + "SerializedObjectName", + "SerializedVersion", + "SerializeEvalSumKey", + "SetBinCCForSchemeSwitch", + "SetParamsFromCKKSCryptocontext", + "setSchemeId", + "SetSwkFC", + "ShareKeys", + "SparseKeyGen", + }, + fhe.CCParamsBFVRNS: {"GetThresholdNumOfParties"}, + fhe.CCParamsBGVRNS: {"GetThresholdNumOfParties"}, + fhe.CCParamsCKKSRNS: {"GetThresholdNumOfParties"}, + fhe.Ciphertext: { + "CloneEmpty", + "GetHopLevel", + "GetScalingFactorInt", + "NumberCiphertextElements", + "SetEncodingType", + "SetHopLevel", + "SetScalingFactorInt", + "SetKeyTag", + }, + fhe.Plaintext: { + "GetCKKSDataType", + "GetElementModulus", + "GetElementRingDimension", + "GetEncodingParams", + "GetEncodingType", + "GetScalingFactorInt", + "SetCKKSDataType", + "SetScalingFactorInt", + }, + fhe.PublicKey: {"GetCryptoContext"}, + fhe.SchSwchParams: {"SetParamsFromCKKSCryptocontextCalled"}, + } + + missing = { + cls.__name__: sorted(name for name in names if not hasattr(cls, name)) + for cls, names in expected_methods.items() + } + assert not {cls: names for cls, names in missing.items() if names} + + +def test_new_value_conversions_and_support_types(): + ciphertext = fhe.Ciphertext() + scaling_factor = 2**60 + 123 + ciphertext.SetScalingFactorInt(scaling_factor) + ciphertext.SetEncodingType(fhe.CKKS_PACKED_ENCODING) + ciphertext.SetHopLevel(3) + + assert ciphertext.GetScalingFactorInt() == scaling_factor + assert ciphertext.GetEncodingType() == fhe.CKKS_PACKED_ENCODING + assert ciphertext.GetHopLevel() == 3 + assert ciphertext.NumberCiphertextElements() == 0 + assert isinstance(ciphertext.CloneEmpty(), fhe.Ciphertext) + + params = fhe.CCParamsCKKSRNS() + assert params.GetThresholdNumOfParties() == 1 + + scheme_switch_params = fhe.SchSwchParams() + assert scheme_switch_params.SetParamsFromCKKSCryptocontextCalled() is None + + +@pytest.mark.parametrize("scalar", [2.0, complex(2.0, 0.0)], ids=["double", "complex"]) +@pytest.mark.parametrize("scalar_first", [False, True], ids=["ct_scalar", "scalar_ct"]) +def test_inplace_scalar_overloads_apply_the_right_operation(ckks_context, scalar, scalar_first): + """EvalMultInPlace used to be registered as EvalAddInPlace, so assert the values. + + Both scalar types and both argument orders are bound for each operation, so all + four overloads of each are exercised here. + """ + cc, keys = ckks_context + ciphertext = cc.Encrypt(keys.publicKey, cc.MakeCKKSPackedPlaintext([0.25, 0.5])) + + multiplied = ciphertext.Clone() + if scalar_first: + cc.EvalMultInPlace(scalar, multiplied) + else: + cc.EvalMultInPlace(multiplied, scalar) + assert decrypt(cc, multiplied, keys.secretKey) == [0.5, 1.0] + + added = ciphertext.Clone() + if scalar_first: + cc.EvalAddInPlace(scalar, added) + else: + cc.EvalAddInPlace(added, scalar) + assert decrypt(cc, added, keys.secretKey) == [2.25, 2.5] + + +def test_no_check_variants_match_their_checked_counterparts(ckks_context): + cc, keys = ckks_context + ciphertext = cc.Encrypt(keys.publicKey, cc.MakeCKKSPackedPlaintext([0.25, 0.5])) + + added = ciphertext.Clone() + cc.EvalAddInPlaceNoCheck(added, ciphertext) + assert decrypt(cc, added, keys.secretKey) == [0.5, 1.0] + + assert decrypt(cc, cc.EvalMultNoCheck(ciphertext, 2), keys.secretKey) == [0.5, 1.0] + assert decrypt(cc, cc.ComposedEvalMult(ciphertext, ciphertext), keys.secretKey) == [0.0625, 0.25] + + squared = cc.EvalMultNoRelinNoCheck(ciphertext, ciphertext) + assert squared.NumberCiphertextElements() == 3 # not relinearized back to 2 + assert decrypt(cc, cc.Relinearize(squared), keys.secretKey) == [0.0625, 0.25] + + +def test_series_precomputation_matches_direct_evaluation(ckks_context): + cc, keys = ckks_context + ciphertext = cc.Encrypt(keys.publicKey, cc.MakeCKKSPackedPlaintext([0.25, 0.5])) + coefficients = [1.0, 2.0, 3.0] + + powers = cc.EvalPowers(ciphertext, coefficients) + assert isinstance(powers, fhe.SeriesPowers) + assert decrypt(cc, cc.EvalPolyWithPrecomp(powers, coefficients), keys.secretKey) == decrypt( + cc, cc.EvalPoly(ciphertext, coefficients), keys.secretKey + ) + + cheby_polys = cc.EvalChebyPolys(ciphertext, coefficients, -1.0, 1.0) + assert isinstance(cheby_polys, fhe.SeriesPowers) + assert decrypt(cc, cc.EvalChebyshevSeriesWithPrecomp(cheby_polys, coefficients), keys.secretKey) == decrypt( + cc, cc.EvalChebyshevSeries(ciphertext, coefficients, -1.0, 1.0), keys.secretKey + ) + + +def test_key_sharing_round_trip(ckks_context): + """RecoverSharedKey dereferences sk->GetCryptoContext(), so the key needs a context.""" + cc, keys = ckks_context + number_of_parties, threshold = 3, 2 + + shares = cc.ShareKeys(keys.secretKey, number_of_parties, threshold, 1, "additive") + assert len(shares) == number_of_parties - 1 + + recovered = fhe.PrivateKey(cc) + assert recovered.GetCryptoContext() is not None + cc.RecoverSharedKey(recovered, shares, number_of_parties, threshold, "additive") + + ciphertext = cc.Encrypt(keys.publicKey, cc.MakeCKKSPackedPlaintext([0.25, 0.5])) + assert decrypt(cc, ciphertext, recovered) == [0.25, 0.5] + + +def test_private_key_without_context_has_none(): + assert fhe.PrivateKey().GetCryptoContext() is None + + +@pytest.mark.parametrize("sertype", [fhe.BINARY, fhe.JSON], ids=["binary", "json"]) +def test_eval_sum_key_serialization_round_trip(ckks_context, sertype, tmp_path): + cc, keys = ckks_context + key_tag = keys.secretKey.GetKeyTag() + + by_tag = str(tmp_path / "by_tag") + by_context = str(tmp_path / "by_context") + assert fhe.CryptoContext.SerializeEvalSumKey(by_tag, sertype, key_tag) + assert fhe.CryptoContext.SerializeEvalSumKey(by_context, sertype, cc) + + assert fhe.CryptoContext.DeserializeEvalSumKey(by_tag, sertype) + assert fhe.CryptoContext.DeserializeEvalSumKey(by_context, sertype) + assert key_tag in fhe.CryptoContext.GetAllEvalSumKeys() + + +@pytest.mark.parametrize("sertype", [fhe.BINARY, fhe.JSON], ids=["binary", "json"]) +def test_eval_sum_key_serialization_reports_unusable_paths(ckks_context, sertype): + """An unopenable file returns False instead of surfacing a cereal stream error.""" + cc, keys = ckks_context + unusable = "/nonexistent-directory-for-openfhe-tests/keys" + + assert fhe.CryptoContext.SerializeEvalSumKey(unusable, sertype, keys.secretKey.GetKeyTag()) is False + assert fhe.CryptoContext.SerializeEvalSumKey(unusable, sertype, cc) is False + assert fhe.CryptoContext.DeserializeEvalSumKey(unusable, sertype) is False + + +def test_element_params_feed_get_plaintext_for_decrypt(ckks_context): + cc, _ = ckks_context + element_params = cc.GetElementParams() + + assert element_params.GetRingDimension() == cc.GetRingDimension() + assert element_params.GetCyclotomicOrder() == cc.GetCyclotomicOrder() + + plaintext = fhe.CryptoContext.GetPlaintextForDecrypt( + fhe.CKKS_PACKED_ENCODING, element_params, cc.GetEncodingParams() + ) + assert isinstance(plaintext, fhe.Plaintext) + + +def test_context_getters(ckks_context): + cc, _ = ckks_context + + assert cc.getSchemeId() == fhe.CKKSRNS_SCHEME + assert cc.SerializedObjectName() == "CryptoContext" + assert fhe.CryptoContext.SerializedVersion() >= 1 + assert cc.GetEncodingParams().GetBatchSize() == 8 + assert isinstance(cc.GetScheme(), fhe.SchemeBase) + # DCRTPoly stores a root of unity per RNS tower, so the aggregate one is 0 + assert cc.GetRootOfUnity() == 0 + + +def test_static_key_maps_and_index_helpers(ckks_context): + cc, keys = ckks_context + key_tag = keys.secretKey.GetKeyTag() + + assert key_tag in fhe.CryptoContext.GetAllEvalMultKeys() + assert key_tag in fhe.CryptoContext.GetAllEvalSumKeys() + assert key_tag in fhe.CryptoContext.GetAllEvalAutomorphismKeys() + + assert fhe.CryptoContext.GetUniqueValues({1, 2, 3}, {3, 4, 5}) == {4, 5} + + existing = set(fhe.CryptoContext.GetExistingEvalAutomorphismKeyIndices(key_tag)) + assert existing + absent = max(existing) + 1 + assert fhe.CryptoContext.GetEvalAutomorphismNoKeyIndices(key_tag, existing | {absent}) == {absent} + + +def test_key_switching_and_level_reduction(ckks_context): + cc, keys = ckks_context + ciphertext = cc.Encrypt(keys.publicKey, cc.MakeCKKSPackedPlaintext([0.25, 0.5])) + + switch_key = cc.KeySwitchGen(keys.secretKey, keys.secretKey) + assert decrypt(cc, cc.KeySwitch(ciphertext, switch_key), keys.secretKey) == [0.25, 0.5] + + in_place = ciphertext.Clone() + cc.KeySwitchInPlace(in_place, switch_key) + assert decrypt(cc, in_place, keys.secretKey) == [0.25, 0.5] + + extended = cc.KeySwitchExt(ciphertext, True) + assert isinstance(cc.KeySwitchDownFirstElement(extended), fhe.DCRTPoly) + assert decrypt(cc, cc.KeySwitchDown(extended), keys.secretKey) == [0.25, 0.5] + + # LevelReduce drops RNS towers without touching GetLevel() under FLEXIBLEAUTOEXT, + # so assert the value survives rather than a level change. + reduced = cc.LevelReduce(ciphertext, None, 1) + assert decrypt(cc, reduced, keys.secretKey) == [0.25, 0.5] + + reduced_in_place = ciphertext.Clone() + cc.LevelReduceInPlace(reduced_in_place, None, 1) + assert decrypt(cc, reduced_in_place, keys.secretKey) == [0.25, 0.5] + + +def test_sparse_key_gen_and_public_key_context(ckks_context): + cc, keys = ckks_context + assert isinstance(cc.SparseKeyGen(), fhe.KeyPair) + assert isinstance(keys.publicKey.GetCryptoContext(), fhe.CryptoContext) From 74828e09ca425929300a60b06551a5d9e8790dbb Mon Sep 17 00:00:00 2001 From: Dmitriy Suponitskiy Date: Fri, 9 Oct 2026 15:02:35 -0400 Subject: [PATCH 07/16] Expose the BinFHE API-coverage backlog to Python Bind missing BinFHE APIs, plus LWESwitchingKey which KeySwitchGen returns and SwitchCTtoqn consumes. - Drop the LWEPrivateKey default constructor. BinFHEContext only checks for a null key, so an empty one segfaulted BTKeyGen, PubKeyGen and KeySwitchGen. Keys come from KeyGen(), KeyGenN() or deserialization. - Move the integer conversions into integer_conversions.h, replacing the copies in bindings.cpp, binfhe_bindings.cpp and fbt.cpp. - Widen LWECiphertext.GetModulus(), which truncated for NATIVE_INT=128. - Add tests/test_binfhe_api_coverage.py. --- src/include/integer_conversions.h | 62 ++++++++++++++++++ src/lib/bindings.cpp | 33 +++------- src/lib/binfhe_bindings.cpp | 45 ++++++++++++- src/lib/pke/fbt.cpp | 81 +++++++++++------------ tests/test_binfhe_api_coverage.py | 104 ++++++++++++++++++++++++++++++ 5 files changed, 259 insertions(+), 66 deletions(-) create mode 100644 src/include/integer_conversions.h create mode 100644 tests/test_binfhe_api_coverage.py diff --git a/src/include/integer_conversions.h b/src/include/integer_conversions.h new file mode 100644 index 0000000..163c842 --- /dev/null +++ b/src/include/integer_conversions.h @@ -0,0 +1,62 @@ +//================================================================================== +// BSD 2-Clause License +// +// Copyright (c) 2023-2026, Duality Technologies Inc. and other contributors +// +// All rights reserved. +// +// Author TPOC: contact@openfhe.org +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +//================================================================================== +#ifndef OPENFHE_PYTHON_INTEGER_CONVERSIONS_H +#define OPENFHE_PYTHON_INTEGER_CONVERSIONS_H + +#include + +#include + +namespace openfhe_python { + +// Convert an arbitrary-size OpenFHE integer to a Python integer without +// narrowing through a native C++ integer type. +template +pybind11::int_ IntegerToPyInt(const Integer& value) { + PyObject* result = PyLong_FromString(value.ToString().c_str(), nullptr, 10); + if (result == nullptr) + throw pybind11::error_already_set(); + return pybind11::reinterpret_steal(result); +} + +// Convert an arbitrary-size Python integer through its decimal representation. +template +Integer PyIntToInteger(const pybind11::int_& value) { + PyObject* result = PyObject_Str(value.ptr()); + if (result == nullptr) + throw pybind11::error_already_set(); + auto valueStr = pybind11::reinterpret_steal(result); + return Integer(valueStr.cast()); +} + +} // namespace openfhe_python + +#endif // OPENFHE_PYTHON_INTEGER_CONVERSIONS_H diff --git a/src/lib/bindings.cpp b/src/lib/bindings.cpp index 2b70e4a..a747c71 100644 --- a/src/lib/bindings.cpp +++ b/src/lib/bindings.cpp @@ -29,6 +29,7 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //================================================================================== #include "bindings.h" +#include "integer_conversions.h" #include "openfhe.h" @@ -62,21 +63,6 @@ inline std::shared_ptr GetParamsRNSChecked(const CryptoCont return ptr; } -// Converts BigInteger to an arbitrary-size Python integer using its decimal -// string representation, as BigInteger may not fit in any native integer type. -inline py::int_ BigIntegerToPyInt(const BigInteger& value) { - return py::reinterpret_steal(PyLong_FromString(value.ToString().c_str(), nullptr, 10)); -} - -inline py::int_ NativeIntegerToPyInt(const NativeInteger& value) { - return py::reinterpret_steal(PyLong_FromString(value.ToString().c_str(), nullptr, 10)); -} - -inline NativeInteger PyIntToNativeInteger(const py::int_& value) { - auto valueStr = py::reinterpret_steal(PyObject_Str(value.ptr())); - return NativeInteger(valueStr.cast()); -} - void bind_DCRTPoly(py::module &m) { py::class_(m, "DCRTPoly").def(py::init<>()); } @@ -422,7 +408,7 @@ void bind_crypto_context(py::module &m) { using ParmTypePtr = std::shared_ptr; py::class_(m, "ParmType") .def("GetModulus", - [](const ParmType& self) { return BigIntegerToPyInt(self.GetModulus()); }) + [](const ParmType& self) { return openfhe_python::IntegerToPyInt(self.GetModulus()); }) .def("GetRingDimension", &ParmType::GetRingDimension) .def("GetCyclotomicOrder", &ParmType::GetCyclotomicOrder); @@ -494,7 +480,7 @@ void bind_crypto_context(py::module &m) { }, py::doc(cc_GetElementParams_docs)) .def("GetRootOfUnity", [](const CC& self) { - return BigIntegerToPyInt(self.GetRootOfUnity()); + return openfhe_python::IntegerToPyInt(self.GetRootOfUnity()); }, py::doc(cc_GetRootOfUnity_docs)) .def("GetScheme", &CC::GetScheme) @@ -776,7 +762,8 @@ void bind_crypto_context(py::module &m) { .def("EvalMultNoRelinNoCheck", &CC::EvalMultNoRelinNoCheck, py::arg("ciphertext1"), py::arg("ciphertext2")) .def("EvalMultNoCheck", [](const CC& self, ConstCiphertext& ciphertext, const py::int_& scalar) { - return self.EvalMultNoCheck(ciphertext, PyIntToNativeInteger(scalar)); + return self.EvalMultNoCheck(ciphertext, + openfhe_python::PyIntToInteger(scalar)); }, py::arg("ciphertext"), py::arg("scalar")) .def("EvalAddInPlaceNoCheck", &CC::EvalAddInPlaceNoCheck, py::arg("ciphertext1"), py::arg("ciphertext2")) @@ -1768,10 +1755,10 @@ void bind_encodings(py::module &m) { py::class_, PlaintextImpl_helper>(m, "Plaintext") .def("GetScalingFactor", &PlaintextImpl::GetScalingFactor, ptx_GetScalingFactor_docs) .def("GetScalingFactorInt", [](const PlaintextImpl& self) { - return NativeIntegerToPyInt(self.GetScalingFactorInt()); + return openfhe_python::IntegerToPyInt(self.GetScalingFactorInt()); }) .def("SetScalingFactorInt", [](PlaintextImpl& self, const py::int_& sf) { - self.SetScalingFactorInt(PyIntToNativeInteger(sf)); + self.SetScalingFactorInt(openfhe_python::PyIntToInteger(sf)); }, py::arg("sf")) .def("SetScalingFactor", &PlaintextImpl::SetScalingFactor, py::arg("sf"), @@ -1783,7 +1770,7 @@ void bind_encodings(py::module &m) { .def("SetCKKSDataType", &PlaintextImpl::SetCKKSDataType, py::arg("dataType")) .def("GetElementRingDimension", &PlaintextImpl::GetElementRingDimension) .def("GetElementModulus", [](const PlaintextImpl& self) { - return BigIntegerToPyInt(self.GetElementModulus()); + return openfhe_python::IntegerToPyInt(self.GetElementModulus()); }) .def("GetLength", &PlaintextImpl::GetLength, ptx_GetLength_docs) .def("SetLength", &PlaintextImpl::SetLength, @@ -1855,10 +1842,10 @@ void bind_ciphertext(py::module &m) { .def("GetScalingFactor", &CiphertextImpl::GetScalingFactor) .def("SetScalingFactor", &CiphertextImpl::SetScalingFactor) .def("GetScalingFactorInt", [](const CiphertextImpl& self) { - return NativeIntegerToPyInt(self.GetScalingFactorInt()); + return openfhe_python::IntegerToPyInt(self.GetScalingFactorInt()); }) .def("SetScalingFactorInt", [](CiphertextImpl& self, const py::int_& sf) { - self.SetScalingFactorInt(PyIntToNativeInteger(sf)); + self.SetScalingFactorInt(openfhe_python::PyIntToInteger(sf)); }, py::arg("sf")) .def("GetSlots", &CiphertextImpl::GetSlots) .def("SetSlots", &CiphertextImpl::SetSlots) diff --git a/src/lib/binfhe_bindings.cpp b/src/lib/binfhe_bindings.cpp index 149639e..7c617d3 100644 --- a/src/lib/binfhe_bindings.cpp +++ b/src/lib/binfhe_bindings.cpp @@ -29,12 +29,14 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //================================================================================== #include "binfhe_bindings.h" +#include "integer_conversions.h" #include "openfhe.h" #include "binfhecontext.h" #include "binfhecontext-ser.h" #include "binfhecontext_docs.h" #include "binfhecontext_wrapper.h" +#include "lwe-keyswitchkey.h" #include #include @@ -159,18 +161,31 @@ void bind_binfhe_enums(py::module &m) { } void bind_binfhe_keys(py::module &m) { + // No default constructor: Python cannot populate the key, and BinFHEContext guards only + // against a null pointer, so an empty-but-non-null key segfaults BTKeyGen/PubKeyGen/ + // KeySwitchGen. Keys come from KeyGen(), KeyGenN(), KeyGenPair() or deserialization. py::class_>(m, "LWEPrivateKey") - .def(py::init<>()) .def("GetLength", &LWEPrivateKeyImpl::GetLength) + .def("GetModulus", + [](const LWEPrivateKeyImpl& self) { + return openfhe_python::IntegerToPyInt(self.GetModulus()); + }) .def(py::self == py::self) .def(py::self != py::self); py::class_>(m, "LWEPublicKey") .def(py::init<>()) .def("GetLength", &LWEPublicKeyImpl::GetLength) + .def("GetModulus", + [](const LWEPublicKeyImpl& self) { + return openfhe_python::IntegerToPyInt(self.GetModulus()); + }) .def(py::self == py::self) .def(py::self != py::self); + py::class_>(m, "LWESwitchingKey") + .def(py::init<>()); + py::class_>(m, "LWEKeyPair") .def_readonly("publicKey", &LWEKeyPairImpl::publicKey) .def_readonly("secretKey", &LWEKeyPairImpl::secretKey) @@ -186,9 +201,23 @@ void bind_binfhe_ciphertext(py::module &m) { .def(py::init<>()) .def("GetLength", &LWECiphertextImpl::GetLength) .def("GetModulus", - [](LWECiphertext& self) { - return self->GetModulus().ConvertToInt(); + [](const LWECiphertextImpl& self) { + return openfhe_python::IntegerToPyInt(self.GetModulus()); + }) + .def("GetptModulus", + [](const LWECiphertextImpl& self) { + return openfhe_python::IntegerToPyInt(self.GetptModulus()); }) + .def("SetModulus", + [](LWECiphertextImpl& self, const py::int_& modulus) { + self.SetModulus(openfhe_python::PyIntToInteger(modulus)); + }, + py::arg("modulus")) + .def("SetptModulus", + [](LWECiphertextImpl& self, const py::int_& plaintextModulus) { + self.SetptModulus(openfhe_python::PyIntToInteger(plaintextModulus)); + }, + py::arg("plaintextModulus")) .def(py::self == py::self) .def(py::self != py::self); } @@ -216,6 +245,14 @@ void bind_binfhe_context(py::module &m) { .def("KeyGen", &BinFHEContext::KeyGen, binfhe_KeyGen_docs) .def("KeyGenN", &BinFHEContext::KeyGenN) .def("KeyGenPair", &BinFHEContext::KeyGenPair) + .def("KeySwitchGen", &BinFHEContext::KeySwitchGen, + py::arg("sk"), + py::arg("skN")) + .def("PubKeyGen", &BinFHEContext::PubKeyGen, + py::arg("sk")) + .def("SwitchCTtoqn", &BinFHEContext::SwitchCTtoqn, + py::arg("ksk"), + py::arg("ct")) .def("BTKeyGen", &BinFHEContext::BTKeyGen, binfhe_BTKeyGen_docs, py::arg("sk"), @@ -311,6 +348,8 @@ void bind_binfhe_context(py::module &m) { py::arg("schemeSwitch") = false) .def("EvalConstant", &BinFHEContext::EvalConstant) .def("ClearBTKeys", &BinFHEContext::ClearBTKeys) + .def("HasInternal32RefreshKey", &BinFHEContext::HasInternal32RefreshKey) + .def("HasInternal32SwitchKey", &BinFHEContext::HasInternal32SwitchKey) .def("Bootstrap", &BinFHEContext::Bootstrap, py::arg("ct"), py::arg("extended") = false) .def("SerializedVersion", &BinFHEContext::SerializedVersion, binfhe_SerializedVersion_docs) diff --git a/src/lib/pke/fbt.cpp b/src/lib/pke/fbt.cpp index f3edc85..1244a60 100644 --- a/src/lib/pke/fbt.cpp +++ b/src/lib/pke/fbt.cpp @@ -34,6 +34,7 @@ // the scheme-switching data (de)serializer. #include "bindings.h" +#include "integer_conversions.h" #include "openfhe.h" #include "math/hermite.h" @@ -62,16 +63,6 @@ PYBIND11_MAKE_OPAQUE(std::vector); using ElementParams = ILDCRTParams; -// Converts an arbitrary-size Python integer to BigInteger (and back) using its decimal -// string representation, as BigInteger may not fit in any native integer type. -static BigInteger FBTPyIntToBigInteger(const py::int_& value) { - auto valueStr = py::reinterpret_steal(PyObject_Str(value.ptr())); - return BigInteger(valueStr.cast()); -} -static py::int_ FBTBigIntegerToPyInt(const BigInteger& value) { - return py::reinterpret_steal(PyLong_FromString(value.ToString().c_str(), nullptr, 10)); -} - void bind_fbt_crypto_context(DCRTCryptoContextClass &cls) { // Keep CKKS depth helpers on CryptoContext without exposing the scheme implementation class. // For functional bootstrapping, overloads taking a list of ints correspond to the optimized @@ -86,16 +77,18 @@ void bind_fbt_crypto_context(DCRTCryptoContextClass &cls) { .def_static("GetFBTDepth", [](const std::vector& levelBudget, const std::vector& coefficients, const py::int_& PInput, size_t order, SecretKeyDist skd, uint32_t firstModSize) { - return FHECKKSRNS::GetFBTDepth(levelBudget, coefficients, FBTPyIntToBigInteger(PInput), order, skd, - firstModSize); + return FHECKKSRNS::GetFBTDepth( + levelBudget, coefficients, openfhe_python::PyIntToInteger(PInput), order, skd, + firstModSize); }, py::arg("levelBudget"), py::arg("coefficients"), py::arg("PInput"), py::arg("order"), py::arg("skd"), py::arg("firstModSize") = 60) .def_static("GetFBTDepth", [](const std::vector& levelBudget, const std::vector>& coefficients, const py::int_& PInput, size_t order, SecretKeyDist skd, uint32_t firstModSize) { - return FHECKKSRNS::GetFBTDepth(levelBudget, coefficients, FBTPyIntToBigInteger(PInput), order, skd, - firstModSize); + return FHECKKSRNS::GetFBTDepth( + levelBudget, coefficients, openfhe_python::PyIntToInteger(PInput), order, skd, + firstModSize); }, py::arg("levelBudget"), py::arg("coefficients"), py::arg("PInput"), py::arg("order"), py::arg("skd"), py::arg("firstModSize") = 60) @@ -107,9 +100,10 @@ void bind_fbt_crypto_context(DCRTCryptoContextClass &cls) { const py::int_& POut, const py::int_& Bigq, const PublicKey& pubKey, const std::vector& dim1, const std::vector& levelBudget, uint32_t lvlsAfterBoot, uint32_t depthLeveledComputation, size_t order) { - self.EvalFBTSetup(coeffs, numSlots, FBTPyIntToBigInteger(PIn), FBTPyIntToBigInteger(POut), - FBTPyIntToBigInteger(Bigq), pubKey, dim1, levelBudget, lvlsAfterBoot, - depthLeveledComputation, order); + self.EvalFBTSetup(coeffs, numSlots, openfhe_python::PyIntToInteger(PIn), + openfhe_python::PyIntToInteger(POut), + openfhe_python::PyIntToInteger(Bigq), pubKey, dim1, levelBudget, + lvlsAfterBoot, depthLeveledComputation, order); }, py::arg("coeffs"), py::arg("numSlots"), py::arg("PIn"), py::arg("POut"), py::arg("Bigq"), py::arg("pubKey"), py::arg("dim1"), py::arg("levelBudget"), py::arg("lvlsAfterBoot") = 0, @@ -119,9 +113,10 @@ void bind_fbt_crypto_context(DCRTCryptoContextClass &cls) { const py::int_& POut, const py::int_& Bigq, const PublicKey& pubKey, const std::vector& dim1, const std::vector& levelBudget, uint32_t lvlsAfterBoot, uint32_t depthLeveledComputation, size_t order) { - self.EvalFBTSetup(coeffs, numSlots, FBTPyIntToBigInteger(PIn), FBTPyIntToBigInteger(POut), - FBTPyIntToBigInteger(Bigq), pubKey, dim1, levelBudget, lvlsAfterBoot, - depthLeveledComputation, order); + self.EvalFBTSetup(coeffs, numSlots, openfhe_python::PyIntToInteger(PIn), + openfhe_python::PyIntToInteger(POut), + openfhe_python::PyIntToInteger(Bigq), pubKey, dim1, levelBudget, + lvlsAfterBoot, depthLeveledComputation, order); }, py::arg("coeffs"), py::arg("numSlots"), py::arg("PIn"), py::arg("POut"), py::arg("Bigq"), py::arg("pubKey"), py::arg("dim1"), py::arg("levelBudget"), py::arg("lvlsAfterBoot") = 0, @@ -130,7 +125,8 @@ void bind_fbt_crypto_context(DCRTCryptoContextClass &cls) { [](CC& self, ConstCiphertext ciphertext, const std::vector& coeffs, uint32_t digitBitSize, const py::int_& initialScaling, uint64_t postScaling, uint32_t levelToReduce, size_t order) { - return self.EvalFBT(ciphertext, coeffs, digitBitSize, FBTPyIntToBigInteger(initialScaling), postScaling, + return self.EvalFBT(ciphertext, coeffs, digitBitSize, + openfhe_python::PyIntToInteger(initialScaling), postScaling, levelToReduce, order); }, py::arg("ciphertext"), py::arg("coeffs"), py::arg("digitBitSize"), py::arg("initialScaling"), @@ -139,7 +135,8 @@ void bind_fbt_crypto_context(DCRTCryptoContextClass &cls) { [](CC& self, ConstCiphertext ciphertext, const std::vector>& coeffs, uint32_t digitBitSize, const py::int_& initialScaling, uint64_t postScaling, uint32_t levelToReduce, size_t order) { - return self.EvalFBT(ciphertext, coeffs, digitBitSize, FBTPyIntToBigInteger(initialScaling), postScaling, + return self.EvalFBT(ciphertext, coeffs, digitBitSize, + openfhe_python::PyIntToInteger(initialScaling), postScaling, levelToReduce, order); }, py::arg("ciphertext"), py::arg("coeffs"), py::arg("digitBitSize"), py::arg("initialScaling"), @@ -147,16 +144,16 @@ void bind_fbt_crypto_context(DCRTCryptoContextClass &cls) { .def("EvalFBTNoDecoding", [](CC& self, ConstCiphertext ciphertext, const std::vector& coeffs, uint32_t digitBitSize, const py::int_& initialScaling, size_t order) { - return self.EvalFBTNoDecoding(ciphertext, coeffs, digitBitSize, FBTPyIntToBigInteger(initialScaling), - order); + return self.EvalFBTNoDecoding(ciphertext, coeffs, digitBitSize, + openfhe_python::PyIntToInteger(initialScaling), order); }, py::arg("ciphertext"), py::arg("coeffs"), py::arg("digitBitSize"), py::arg("initialScaling"), py::arg("order") = (size_t)1) .def("EvalFBTNoDecoding", [](CC& self, ConstCiphertext ciphertext, const std::vector>& coeffs, uint32_t digitBitSize, const py::int_& initialScaling, size_t order) { - return self.EvalFBTNoDecoding(ciphertext, coeffs, digitBitSize, FBTPyIntToBigInteger(initialScaling), - order); + return self.EvalFBTNoDecoding(ciphertext, coeffs, digitBitSize, + openfhe_python::PyIntToInteger(initialScaling), order); }, py::arg("ciphertext"), py::arg("coeffs"), py::arg("digitBitSize"), py::arg("initialScaling"), py::arg("order") = (size_t)1) @@ -165,16 +162,16 @@ void bind_fbt_crypto_context(DCRTCryptoContextClass &cls) { .def("EvalMVBPrecompute", [](CC& self, ConstCiphertext ciphertext, const std::vector& coeffs, uint32_t digitBitSize, const py::int_& initialScaling, size_t order) { - return self.EvalMVBPrecompute(ciphertext, coeffs, digitBitSize, FBTPyIntToBigInteger(initialScaling), - order); + return self.EvalMVBPrecompute(ciphertext, coeffs, digitBitSize, + openfhe_python::PyIntToInteger(initialScaling), order); }, py::arg("ciphertext"), py::arg("coeffs"), py::arg("digitBitSize"), py::arg("initialScaling"), py::arg("order") = (size_t)1) .def("EvalMVBPrecompute", [](CC& self, ConstCiphertext ciphertext, const std::vector>& coeffs, uint32_t digitBitSize, const py::int_& initialScaling, size_t order) { - return self.EvalMVBPrecompute(ciphertext, coeffs, digitBitSize, FBTPyIntToBigInteger(initialScaling), - order); + return self.EvalMVBPrecompute(ciphertext, coeffs, digitBitSize, + openfhe_python::PyIntToInteger(initialScaling), order); }, py::arg("ciphertext"), py::arg("coeffs"), py::arg("digitBitSize"), py::arg("initialScaling"), py::arg("order") = (size_t)1) @@ -221,7 +218,7 @@ void bind_fbt(py::module &m) { // switches every element of the RLWE ciphertext to the given modulus .def("SwitchModulus", [](std::vector& self, const py::int_& modulus) { - auto mod = FBTPyIntToBigInteger(modulus); + auto mod = openfhe_python::PyIntToInteger(modulus); for (auto& poly : self) poly.SwitchModulus(mod, 1, 0, 0); }, @@ -258,8 +255,9 @@ void bind_fbt(py::module &m) { [](const std::vector& input, const py::int_& Q, const py::int_& p, const PrivateKey& privateKey, const std::shared_ptr& elementParams, bool bitReverse) { - return SchemeletRLWEMP::EncryptCoeff(input, FBTPyIntToBigInteger(Q), FBTPyIntToBigInteger(p), - privateKey, elementParams, bitReverse); + return SchemeletRLWEMP::EncryptCoeff(input, openfhe_python::PyIntToInteger(Q), + openfhe_python::PyIntToInteger(p), privateKey, + elementParams, bitReverse); }, py::arg("input"), py::arg("Q"), py::arg("p"), py::arg("privateKey"), py::arg("elementParams"), py::arg("bitReverse") = false) @@ -267,32 +265,35 @@ void bind_fbt(py::module &m) { [](const std::vector& input, const py::int_& Q, const py::int_& p, const PrivateKey& privateKey, const std::shared_ptr& elementParams, uint32_t numSlots, uint32_t length, bool bitReverse) { - return SchemeletRLWEMP::DecryptCoeff(input, FBTPyIntToBigInteger(Q), FBTPyIntToBigInteger(p), - privateKey, elementParams, numSlots, length, bitReverse); + return SchemeletRLWEMP::DecryptCoeff(input, openfhe_python::PyIntToInteger(Q), + openfhe_python::PyIntToInteger(p), privateKey, + elementParams, numSlots, length, bitReverse); }, py::arg("input"), py::arg("Q"), py::arg("p"), py::arg("privateKey"), py::arg("elementParams"), py::arg("numSlots"), py::arg("length") = 0, py::arg("bitReverse") = false) .def_static("ModSwitch", [](std::vector& input, const py::int_& Q1, const py::int_& Q2) { - SchemeletRLWEMP::ModSwitch(input, FBTPyIntToBigInteger(Q1), FBTPyIntToBigInteger(Q2)); + SchemeletRLWEMP::ModSwitch(input, openfhe_python::PyIntToInteger(Q1), + openfhe_python::PyIntToInteger(Q2)); }, py::arg("input"), py::arg("Q1"), py::arg("Q2")) .def_static("ConvertRLWEToCKKS", [](const CryptoContext& cc, const std::vector& coeffs, const PublicKey& pubKey, const py::int_& Bigq, uint32_t slots, uint32_t level) { - return SchemeletRLWEMP::ConvertRLWEToCKKS(*cc, coeffs, pubKey, FBTPyIntToBigInteger(Bigq), slots, - level); + return SchemeletRLWEMP::ConvertRLWEToCKKS( + *cc, coeffs, pubKey, openfhe_python::PyIntToInteger(Bigq), slots, level); }, py::arg("cc"), py::arg("coeffs"), py::arg("pubKey"), py::arg("Bigq"), py::arg("slots"), py::arg("level") = 0) .def_static("ConvertCKKSToRLWE", [](ConstCiphertext ctxt, const py::int_& Q) { - return SchemeletRLWEMP::ConvertCKKSToRLWE(ctxt, FBTPyIntToBigInteger(Q)); + return SchemeletRLWEMP::ConvertCKKSToRLWE( + ctxt, openfhe_python::PyIntToInteger(Q)); }, py::arg("ctxt"), py::arg("Q")) .def_static("GetQPrime", [](const PublicKey& pubKey, uint32_t lvls) { - return FBTBigIntegerToPyInt(SchemeletRLWEMP::GetQPrime(pubKey, lvls)); + return openfhe_python::IntegerToPyInt(SchemeletRLWEMP::GetQPrime(pubKey, lvls)); }, py::arg("pubKey"), py::arg("lvls")); diff --git a/tests/test_binfhe_api_coverage.py b/tests/test_binfhe_api_coverage.py new file mode 100644 index 0000000..06b6deb --- /dev/null +++ b/tests/test_binfhe_api_coverage.py @@ -0,0 +1,104 @@ +import openfhe as fhe +import pytest + + +def test_spreadsheet_add_apis_are_exposed(): + expected_methods = { + fhe.BinFHEContext: { + "HasInternal32RefreshKey", + "HasInternal32SwitchKey", + "KeySwitchGen", + "PubKeyGen", + "SwitchCTtoqn", + }, + fhe.LWECiphertext: {"GetptModulus", "SetModulus", "SetptModulus"}, + fhe.LWEPrivateKey: {"GetModulus"}, + fhe.LWEPublicKey: {"GetModulus"}, + } + + missing = { + cls.__name__: sorted(name for name in names if not hasattr(cls, name)) + for cls, names in expected_methods.items() + } + assert not {cls: names for cls, names in missing.items() if names} + + +def test_new_binfhe_bindings_round_trip(): + cc = fhe.BinFHEContext() + cc.GenerateBinFHEContext(fhe.TOY, fhe.GINX) + + assert cc.HasInternal32RefreshKey() is False + assert cc.HasInternal32SwitchKey() is False + + sk = cc.KeyGen() + skN = cc.KeyGenN() + public_key = cc.PubKeyGen(skN) + + assert isinstance(sk.GetModulus(), int) + assert skN.GetModulus() == cc.GetQ() + assert public_key.GetModulus() == cc.GetQ() + + switching_key = cc.KeySwitchGen(sk, skN) + assert isinstance(switching_key, fhe.LWESwitchingKey) + + large_ciphertext = cc.Encrypt(skN, 1, fhe.LARGE_DIM, 4, cc.GetQ()) + switched_ciphertext = cc.SwitchCTtoqn(switching_key, large_ciphertext) + assert cc.Decrypt(sk, switched_ciphertext) == 1 + + ciphertext = cc.Encrypt(sk, 1, fhe.FRESH, 4) + assert ciphertext.GetptModulus() == 4 + ciphertext.SetptModulus(8) + assert ciphertext.GetptModulus() == 8 + + modulus = ciphertext.GetModulus() + ciphertext.SetModulus(modulus * 2) + assert ciphertext.GetModulus() == modulus * 2 + ciphertext.SetModulus(modulus) + assert ciphertext.GetModulus() == modulus + + +def test_private_key_cannot_be_default_constructed(): + """BinFHEContext guards only against a null key pointer. + + An empty-but-non-null LWEPrivateKey passes that guard and then segfaults in + BTKeyGen/PubKeyGen/KeySwitchGen, so Python must not be able to build one. + """ + with pytest.raises(TypeError): + fhe.LWEPrivateKey() + + +def test_generated_private_keys_are_unaffected(): + cc = fhe.BinFHEContext() + cc.GenerateBinFHEContext(fhe.TOY, fhe.GINX) + + assert cc.KeyGen().GetLength() > 0 + assert cc.KeyGenN().GetLength() > 0 + assert cc.KeyGenPair().secretKey.GetLength() > 0 + + +def test_deserialized_private_keys_are_unaffected(tmp_path): + cc = fhe.BinFHEContext() + cc.GenerateBinFHEContext(fhe.TOY, fhe.GINX) + sk = cc.KeyGen() + + path = str(tmp_path / "sk.bin") + assert fhe.SerializeToFile(path, sk, fhe.BINARY) + restored, ok = fhe.DeserializeLWEPrivateKey(path, fhe.BINARY) + assert ok is True + assert restored.GetLength() == sk.GetLength() + assert restored.GetModulus() == sk.GetModulus() + + # the restored key still decrypts what the original encrypted + ciphertext = cc.Encrypt(sk, 1, fhe.FRESH, 4) + assert cc.Decrypt(restored, ciphertext) == 1 + + +def test_failed_deserialization_yields_a_null_key(): + """A null key is caught by the C++ guard, unlike an empty-but-non-null one.""" + cc = fhe.BinFHEContext() + cc.GenerateBinFHEContext(fhe.TOY, fhe.GINX) + + key, ok = fhe.DeserializeLWEPrivateKey("/nonexistent-directory-for-openfhe-tests/sk", fhe.BINARY) + assert ok is False and key is None + with pytest.raises(RuntimeError): + cc.PubKeyGen(key) From f56fd13408c4c25624df28b23021c83b8510848e Mon Sep 17 00:00:00 2001 From: Dmitriy Suponitskiy Date: Sat, 10 Oct 2026 23:05:29 -0400 Subject: [PATCH 08/16] Document the new bindings and fix the existing docstrings - Add docstrings for the 79 bindings added by the two API-coverage commits, taken from the doxygen comments in openfhe-development. - Move the remaining docstrings out of the .cpp files into *_docs.h. fbt.cpp gets its own header, since these hold external-linkage globals. - Fix stale parameter names (keyId -> keyTag, i -> index and others), and add or drop the :param: and :return: entries that no longer matched. - Indent the 20 docstrings that were flush left. --- src/include/docstrings/binfhecontext_docs.h | 90 +- src/include/docstrings/ciphertext_docs.h | 63 ++ src/include/docstrings/cryptocontext_docs.h | 802 ++++++++++++++---- .../docstrings/cryptoparameters_docs.h | 15 +- src/include/docstrings/fbt_docs.h | 57 ++ src/include/docstrings/plaintext_docs.h | 81 ++ src/lib/bindings.cpp | 211 +++-- src/lib/binfhe_bindings.cpp | 30 +- src/lib/pke/fbt.cpp | 8 +- 9 files changed, 1095 insertions(+), 262 deletions(-) create mode 100644 src/include/docstrings/fbt_docs.h diff --git a/src/include/docstrings/binfhecontext_docs.h b/src/include/docstrings/binfhecontext_docs.h index 94f299c..c046d9d 100644 --- a/src/include/docstrings/binfhecontext_docs.h +++ b/src/include/docstrings/binfhecontext_docs.h @@ -39,8 +39,6 @@ const char* binfhe_GenerateBinFHEContext_parset_docs = R"pbdoc( :type set: BINFHE_PARAMSET :param method: the bootstrapping method (DM or CGGI or LMKCDEY) :type method: BINFHE_METHOD - :return: The created crypto context. - :rtype: BinFHEContext )pbdoc"; ////void GenerateBinFHEContext(BINFHE_PARAMSET set, bool arbFunc, uint32_t logQ = 11, int64_t N = 0, BINFHE_METHOD method = GINX, bool timeOptimization = false) @@ -59,8 +57,6 @@ const char* binfhe_GenerateBinFHEContext_docs = R"pbdoc( :type method: BINFHE_METHOD :param timeOptimization: whether to use dynamic bootstrapping technique :type timeOptimization: bool - :return: creates the cryptocontext. - :rtype: BinFHEContext )pbdoc"; // KeyGen @@ -77,6 +73,10 @@ const char* binfhe_BTKeyGen_docs = R"pbdoc( :param sk: The secret key. :type sk: LWEPrivateKey + :param keygenMode: SYM_ENCRYPT for symmetric-key or PUB_ENCRYPT for public-key bootstrapping keys + :type keygenMode: KEYGEN_MODE + :param internal32: whether to keep the keys in their 32-bit internal form + :type internal32: bool )pbdoc"; // Encrypt @@ -123,6 +123,8 @@ const char* binfhe_EvalBinGate_docs = R"pbdoc( :type ct2: LWECiphertext :return: The resulting ciphertext. :rtype: LWECiphertext + :param extended: whether to use the extended variant of the gate evaluation + :type extended: bool )pbdoc"; // EvalNOT @@ -202,4 +204,84 @@ const char* binfhe_SerializedObjectName_docs = R"pbdoc( :return: object name :rtype: std::string )pbdoc"; +const char* binfhe_HasInternal32RefreshKey_docs = R"pbdoc( + Check whether the refreshing key is held in its 32-bit internal form. + + :return: true if the resident refreshing key is the 32-bit one; always false in a 32-bit NATIVEINT build + :rtype: bool +)pbdoc"; + +const char* binfhe_HasInternal32SwitchKey_docs = R"pbdoc( + Check whether the key switching key is held in its 32-bit internal form. + + :return: true if the resident switching key is the 32-bit one; always false in a 32-bit NATIVEINT build + :rtype: bool +)pbdoc"; + +const char* binfhe_KeySwitchGen_docs = R"pbdoc( + Generate a key switching key from a secret key with (Q,N) to one with (q,n). + + :param sk: new secret key + :type sk: LWEPrivateKey + :param skN: old secret key + :type skN: LWEPrivateKey + :return: the switching key + :rtype: LWESwitchingKey +)pbdoc"; + +const char* binfhe_LWECiphertextGetptModulus_docs = R"pbdoc( + Get the plaintext modulus of the ciphertext. + + :return: the plaintext modulus + :rtype: int +)pbdoc"; + +const char* binfhe_LWECiphertextSetModulus_docs = R"pbdoc( + Set the ciphertext modulus and reduce the ciphertext modulo it. + + :param modulus: the ciphertext modulus + :type modulus: int +)pbdoc"; + +const char* binfhe_LWECiphertextSetptModulus_docs = R"pbdoc( + Set the plaintext modulus of the ciphertext. + + :param plaintextModulus: the plaintext modulus + :type plaintextModulus: int +)pbdoc"; + +const char* binfhe_LWEPrivateKeyGetModulus_docs = R"pbdoc( + Get the modulus of the secret key. + + :return: the ciphertext modulus q (the modulus of the vector "a") + :rtype: int +)pbdoc"; + +const char* binfhe_LWEPublicKeyGetModulus_docs = R"pbdoc( + Get the modulus of the public key. + + :return: the ciphertext modulus q (the modulus of the vector "a") + :rtype: int +)pbdoc"; + +const char* binfhe_PubKeyGen_docs = R"pbdoc( + Generate a public key for the given LWE secret key. + + :param sk: the secret key + :type sk: LWEPrivateKey + :return: the public key + :rtype: LWEPublicKey +)pbdoc"; + +const char* binfhe_SwitchCTtoqn_docs = R"pbdoc( + Convert a ciphertext with modulus Q and dimension N to one with q and n. + + :param ksk: the key switching key from secret key of dimension N to secret key of dimension n + :type ksk: LWESwitchingKey + :param ct: the ciphertext to convert + :type ct: LWECiphertext + :return: the ciphertext + :rtype: LWECiphertext +)pbdoc"; + #endif // __BINFHECONTEXT_DOCS_H diff --git a/src/include/docstrings/ciphertext_docs.h b/src/include/docstrings/ciphertext_docs.h index 08ce61a..fa96c57 100644 --- a/src/include/docstrings/ciphertext_docs.h +++ b/src/include/docstrings/ciphertext_docs.h @@ -61,4 +61,67 @@ const char* cc_RemoveElement_docs = R"pbdoc( :param index: The index of the element to remove. :type index: int )pbdoc"; +const char* ctx_CloneEmpty_docs = R"pbdoc( + Copy the ciphertext metadata, skipping the encrypted elements. + + :return: a new ciphertext with the same context, key tag, level/scaling data and metadata but no elements + :rtype: Ciphertext +)pbdoc"; + +const char* ctx_GetHopLevel_docs = R"pbdoc( + Get the re-encryption (hop) level of the ciphertext. + + :return: the number of times the ciphertext has been re-encrypted (proxy re-encryption hops) + :rtype: int +)pbdoc"; + +const char* ctx_GetScalingFactorInt_docs = R"pbdoc( + Get the integer scaling factor of the ciphertext. + + :return: the exact integer scaling factor of the encrypted message (used by BGV) + :rtype: int +)pbdoc"; + +const char* ctx_NumberCiphertextElements_docs = R"pbdoc( + Get the number of ring elements: 2 for a fresh encryption, 3 after a multiplication. + + :return: the number of ring elements + :rtype: int +)pbdoc"; + +const char* ctx_SetEncodingType_docs = R"pbdoc( + Set the encoding type of the ciphertext. + + :param encodingType: the encoding type + :type encodingType: PlaintextEncodings +)pbdoc"; + +const char* ctx_SetHopLevel_docs = R"pbdoc( + Set the re-encryption (hop) level of the ciphertext. + + :param hopLevel: the re-encryption level + :type hopLevel: int +)pbdoc"; + +const char* ctx_SetKeyTag_docs = R"pbdoc( + Set the tag of the key used to create the ciphertext. + + :param keyTag: secret key identifier + :type keyTag: str +)pbdoc"; + +const char* ctx_SetScalingFactorInt_docs = R"pbdoc( + Set the integer scaling factor of the ciphertext. + + :param sf: the integer scaling factor + :type sf: int +)pbdoc"; + +const char* key_GetCryptoContext_docs = R"pbdoc( + Get the crypto context this key belongs to. + + :return: the crypto context (may be null for a default-constructed object) + :rtype: CryptoContext +)pbdoc"; + #endif // __CIPHERTEXT_DOCS_H__ diff --git a/src/include/docstrings/cryptocontext_docs.h b/src/include/docstrings/cryptocontext_docs.h index d077b9b..5f6e4c2 100644 --- a/src/include/docstrings/cryptocontext_docs.h +++ b/src/include/docstrings/cryptocontext_docs.h @@ -184,8 +184,8 @@ const char* cc_MakeCKKSPackedPlaintextComplex_docs = R"pbdoc( :param value: input vector of complex numbers :type value: List[complex] - :param scaleDeg: degree of scaling factor used to encode the vector - :type scaleDeg: int + :param noiseScaleDeg: degree of scaling factor used to encode the vector + :type noiseScaleDeg: int :param level: level at each the vector will get encrypted :type level: int :param params: parameters to be used for the ciphertext (Only accepting params = None in this version) @@ -201,8 +201,8 @@ const char* cc_MakeCKKSPlaintextReal_docs = R"pbdoc( :param value: input vector (of floats) :type value: list - :param scaleDeg: degree of scaling factor used to encode the vector - :type scaleDeg: int + :param noiseScaleDeg: degree of scaling factor used to encode the vector + :type noiseScaleDeg: int :param level: level at each the vector will get encrypted :type level: int :param params: parameters to be used for the ciphertext (Only accepting params = None in this version) @@ -309,8 +309,8 @@ const char* cc_EvalAtIndex_docs = R"pbdoc( :param ciphertext: input ciphertext :type ciphertext: Ciphertext - :param i: rotation index - :type i: int + :param index: rotation index + :type index: int :return: a rotated ciphertext :rtype: Ciphertext )pbdoc"; @@ -348,14 +348,14 @@ const char* cc_EncryptPrivkey_docs = R"doc( )doc"; const char* cc_Decrypt_docs = R"pbdoc( -Decrypt a single ciphertext into the appropriate plaintext + Decrypt a single ciphertext into the appropriate plaintext -:param ciphertext: ciphertext to decrypt -:type ciphertext: Ciphertext -:param privateKey: decryption key -:type privateKey: PrivateKey -:return: decrypted plaintext -:rtype: Plaintext + :param ciphertext: ciphertext to decrypt + :type ciphertext: Ciphertext + :param privateKey: decryption key + :type privateKey: PrivateKey + :return: decrypted plaintext + :rtype: Plaintext )pbdoc"; const char* cc_KeySwitchGen_docs = R"pbdoc( @@ -370,78 +370,76 @@ const char* cc_KeySwitchGen_docs = R"pbdoc( )pbdoc"; const char* cc_EvalAdd_docs = R"pbdoc( -Homomorphic addition of two ciphertexts + Homomorphic addition of two ciphertexts -:param ciphertext1: first addend -:type ciphertext1: Ciphertext -:param ciphertext2: second addend -:type ciphertext2: Ciphertext -:return: the result as a new ciphertext -:rtype: Ciphertext + :param ciphertext1: first addend + :type ciphertext1: Ciphertext + :param ciphertext2: second addend + :type ciphertext2: Ciphertext + :return: the result as a new ciphertext + :rtype: Ciphertext )pbdoc"; const char* cc_EvalAddfloat_docs = R"pbdoc( -EvalAdd - OpenFHE EvalAdd method for a ciphertext and a real number. Supported only in CKKS. + EvalAdd - OpenFHE EvalAdd method for a ciphertext and a real number. Supported only in CKKS. -:param ciphertext: input ciphertext -:type ciphertext: Ciphertext -:param constant: a real number -:type constant: float -:return: new ciphertext for ciphertext + constant -:rtype: Ciphertext + :param ciphertext: input ciphertext + :type ciphertext: Ciphertext + :param scalar: a real number + :type scalar: float + :return: new ciphertext for ciphertext + constant + :rtype: Ciphertext )pbdoc"; const char* cc_EvalAddPlaintext_docs = R"pbdoc( -EvalAdd - OpenFHE EvalAdd method for a ciphertext and plaintext + EvalAdd - OpenFHE EvalAdd method for a ciphertext and plaintext -:param ciphertext: input ciphertext -:type ciphertext: Ciphertext -:param plaintex: input plaintext -:type plaintext: Plaintext -:return: new ciphertext for ciphertext + constant -:rtype: Ciphertext + :param ciphertext: input ciphertext + :type ciphertext: Ciphertext + :param plaintext: input plaintext + :type plaintext: Plaintext + :return: new ciphertext for ciphertext + constant + :rtype: Ciphertext )pbdoc"; const char* cc_EvalAddInPlace_docs = R"pbdoc( -In-place homomorphic addition of two ciphertexts + In-place homomorphic addition of two ciphertexts -:param ciphertext1: ciphertext1 -:type ciphertext1: Ciphertext -:param ciphertext2: second addend -:type ciphertext2: Ciphertext -:return: ciphertext1 contains ciphertext1 + ciphertext2 + :param ciphertext1: ciphertext1 + :type ciphertext1: Ciphertext + :param ciphertext2: second addend + :type ciphertext2: Ciphertext )pbdoc"; const char* cc_EvalAddInPlacePlaintext_docs = R"pbdoc( -In-place addition for a ciphertext and plaintext + In-place addition for a ciphertext and plaintext -:param ciphertext: Input/output ciphertext -:type ciphertext: Ciphertext -:param plaintext: Input plaintext -:type plaintext: Plaintext -:return: ciphertext contains ciphertext + plaintext + :param ciphertext: Input/output ciphertext + :type ciphertext: Ciphertext + :param plaintext: Input plaintext + :type plaintext: Plaintext )pbdoc"; const char* cc_EvalAddMutable_docs = R"pbdoc( -Homomorphic addition of two mutable ciphertexts (they can be changed during the operation) + Homomorphic addition of two mutable ciphertexts (they can be changed during the operation) -:param ciphertext1: first addend -:type ciphertext1: Ciphertext -:param ciphertext2: second addend -:type ciphertext2: Ciphertext -:return: the result as a new ciphertext -:rtype: Ciphertext + :param ciphertext1: first addend + :type ciphertext1: Ciphertext + :param ciphertext2: second addend + :type ciphertext2: Ciphertext + :return: the result as a new ciphertext + :rtype: Ciphertext )pbdoc"; const char* cc_EvalAddMutablePlaintext_docs = R"pbdoc( -Homomorphic addition a mutable ciphertext and plaintext + Homomorphic addition a mutable ciphertext and plaintext -:param ciphertext: ciphertext -:type ciphertext: Ciphertext -:param plaintext: plaintext -:type plaintext: Plaintext -:return: new ciphertext for ciphertext + plaintext -:rtype: Ciphertext + :param ciphertext: ciphertext + :type ciphertext: Ciphertext + :param plaintext: plaintext + :type plaintext: Plaintext + :return: new ciphertext for ciphertext + plaintext + :rtype: Ciphertext )pbdoc"; @@ -452,70 +450,68 @@ const char* cc_EvalAddMutableInPlace_docs = R"pbdoc( :type ciphertext1: Ciphertext :param ciphertext2: second addend :type ciphertext2: Ciphertext - :return: ciphertext1 contains ciphertext1 + ciphertext2 )pbdoc"; const char* cc_EvalSub_docs = R"pbdoc( -Homomorphic subtraction of two ciphertexts + Homomorphic subtraction of two ciphertexts -:param ciphertext1: minuend -:type ciphertext1: Ciphertext -:param ciphertext2: subtrahend -:type ciphertext2: Ciphertext -:return: the result as a new ciphertext -:rtype: Ciphertext + :param ciphertext1: minuend + :type ciphertext1: Ciphertext + :param ciphertext2: subtrahend + :type ciphertext2: Ciphertext + :return: the result as a new ciphertext + :rtype: Ciphertext )pbdoc"; const char* cc_EvalSubfloat_docs = R"pbdoc( -Subtraction of a ciphertext and a real number. Supported only in CKKS. + Subtraction of a ciphertext and a real number. Supported only in CKKS. -:param ciphertext: input ciphertext -:type ciphertext: Ciphertext -:param constant: a real number -:type constant: float -:return: new ciphertext for ciphertext - constant -:rtype: Ciphertext + :param ciphertext: input ciphertext + :type ciphertext: Ciphertext + :param scalar: a real number + :type scalar: float + :return: new ciphertext for ciphertext - constant + :rtype: Ciphertext )pbdoc"; const char* cc_EvalSubPlaintext_docs = R"pbdoc( -Subtraction of a ciphertext and a real number. Supported only in CKKS. + Subtraction of a ciphertext and a real number. Supported only in CKKS. -:param ciphertext: minuend -:type ciphertext: Ciphertext -:param plaintext: subtrahend -:type plaintext: Plaintext -:return: new ciphertext for ciphertext - plaintext -:rtype: Ciphertext + :param ciphertext: minuend + :type ciphertext: Ciphertext + :param plaintext: subtrahend + :type plaintext: Plaintext + :return: new ciphertext for ciphertext - plaintext + :rtype: Ciphertext )pbdoc"; const char* cc_EvalSubInPlace_docs = R"pbdoc( -In-place homomorphic subtraction of two ciphertexts + In-place homomorphic subtraction of two ciphertexts -:param ciphertext1: minuend -:type ciphertext1: Ciphertext -:param ciphertext2: subtrahend -:type ciphertext2: Ciphertext -:return: the result as a new ciphertext + :param ciphertext1: minuend + :type ciphertext1: Ciphertext + :param ciphertext2: subtrahend + :type ciphertext2: Ciphertext )pbdoc"; const char* cc_EvalSubInPlacefloat_docs = R"pbdoc( -In-place subtraction of a ciphertext and a real number. Supported only in CKKS. + In-place subtraction of a ciphertext and a real number. Supported only in CKKS. -:param ciphertext: input ciphertext -:type ciphertext: Ciphertext -:param constant: a real number -:type constant: float + :param ciphertext: input ciphertext + :type ciphertext: Ciphertext + :param scalar: a real number + :type scalar: float )pbdoc"; // EvalSubMutable const char* cc_EvalSubMutable_docs = R"pbdoc( -Homomorphic subtraction of two mutable ciphertexts + Homomorphic subtraction of two mutable ciphertexts -:param ciphertext1: minuend -:type ciphertext1: Ciphertext -:param ciphertext2: subtrahend -:type ciphertext2: Ciphertext -:return: the result as a new ciphertext + :param ciphertext1: minuend + :type ciphertext1: Ciphertext + :param ciphertext2: subtrahend + :type ciphertext2: Ciphertext + :return: the result as a new ciphertext )pbdoc"; // EvalSubMutableInPlace @@ -526,72 +522,71 @@ const char* cc_EvalSubMutableInPlace_docs = R"pbdoc( :type ciphertext1: Ciphertext :param ciphertext2: subtrahend :type ciphertext2: Ciphertext - :return: the updated minuend )pbdoc"; // EvalSubMutablePlaintext const char* cc_EvalSubMutablePlaintext_docs = R"pbdoc( -Homomorphic subtraction of mutable ciphertext and plaintext + Homomorphic subtraction of mutable ciphertext and plaintext -:param ciphertext: minuend -:type ciphertext: Ciphertext -:param plaintext: subtrahend -:type plaintext: Plaintext -:return: new ciphertext for ciphertext - plaintext + :param ciphertext: minuend + :type ciphertext: Ciphertext + :param plaintext: subtrahend + :type plaintext: Plaintext + :return: new ciphertext for ciphertext - plaintext )pbdoc"; const char* cc_EvalMult_docs = R"pbdoc( -EvalMult - OpenFHE EvalMult method for a pair of ciphertexts (uses a relinearization key from the crypto context) + EvalMult - OpenFHE EvalMult method for a pair of ciphertexts (uses a relinearization key from the crypto context) -:param ciphertext1: multiplier -:type ciphertext1: Ciphertext -:param ciphertext2: multiplicand -:type ciphertext2: Ciphertext -:return: new ciphertext for ciphertext1 * ciphertext2 -:rtype: Ciphertext + :param ciphertext1: multiplier + :type ciphertext1: Ciphertext + :param ciphertext2: multiplicand + :type ciphertext2: Ciphertext + :return: new ciphertext for ciphertext1 * ciphertext2 + :rtype: Ciphertext )pbdoc"; const char* cc_EvalMultfloat_docs = R"pbdoc( -Multiplication of a ciphertext by a real number. Supported only in CKKS. + Multiplication of a ciphertext by a real number. Supported only in CKKS. -:param ciphertext: multiplier -:type ciphertext: Ciphertext -:param constant: multiplicand -:type constant: float -:return: the result of multiplication -:rtype: Ciphertext + :param ciphertext: multiplier + :type ciphertext: Ciphertext + :param scalar: multiplicand + :type scalar: float + :return: the result of multiplication + :rtype: Ciphertext )pbdoc"; const char* cc_EvalMultPlaintext_docs = R"pbdoc( -Multiplication of a ciphertext by a plaintext + Multiplication of a ciphertext by a plaintext -:param ciphertext: multiplier -:type ciphertext: Ciphertext -:param plaintext: multiplicand -:type plaintext: Plaintext -:return: the result of multiplication -:rtype: Ciphertext + :param ciphertext: multiplier + :type ciphertext: Ciphertext + :param plaintext: multiplicand + :type plaintext: Plaintext + :return: the result of multiplication + :rtype: Ciphertext )pbdoc"; const char* cc_EvalMultMutable_docs = R"pbdoc( -EvalMult - OpenFHE EvalMult method for a pair of mutable ciphertexts (uses a relinearization key from the crypto context) + EvalMult - OpenFHE EvalMult method for a pair of mutable ciphertexts (uses a relinearization key from the crypto context) -:param ciphertext1: multiplier -:type ciphertext1: Ciphertext -:param ciphertext2: multiplicand -:type ciphertext2: Ciphertext -:return: new ciphertext for ciphertext1 * ciphertext2 -:rtype: Ciphertext + :param ciphertext1: multiplier + :type ciphertext1: Ciphertext + :param ciphertext2: multiplicand + :type ciphertext2: Ciphertext + :return: new ciphertext for ciphertext1 * ciphertext2 + :rtype: Ciphertext )pbdoc"; const char* cc_EvalMultMutablePlaintext_docs = R"pbdoc( -Multiplication of mutable ciphertext and plaintext -:param ciphertext: multiplier -:type ciphertext: Ciphertext -:param plaintext: multiplicand -:type plaintext: Plaintext -:return: the result of multiplication -:rtype: Ciphertext + Multiplication of mutable ciphertext and plaintext + :param ciphertext: multiplier + :type ciphertext: Ciphertext + :param plaintext: multiplicand + :type plaintext: Plaintext + :return: the result of multiplication + :rtype: Ciphertext )pbdoc"; const char* cc_EvalMultMutableInPlace_docs = R"pbdoc( @@ -626,7 +621,6 @@ const char* cc_EvalSquareInPlace_docs = R"pbdoc( :param ciphertext: input ciphertext :type ciphertext: Ciphertext - :return: squared ciphertext )pbdoc"; @@ -832,8 +826,6 @@ const char* cc_EvalSumColsKeyGen_docs = R"pbdoc( :param privateKey: Private key used for key generation. :type privateKey: PrivateKey - :param publicKey: Public key (used in NTRU schemes; unused now). - :type publicKey: PublicKey :return: Map of generated evaluation keys. :rtype: EvalKeyMap )pbdoc"; @@ -961,8 +953,8 @@ const char* cc_MultiKeySwitchGen_docs = R"pbdoc( const char* cc_GetEvalMultKeyVector_docs = R"pbdoc( Get relinearization keys for a specific secret key tag - :param keyId: key identifier used for private key - :type keyId: str + :param keyTag: key identifier used for private key + :type keyTag: str :return: EvalKeyVector: vector with all relinearization keys. :rtype: EvalKeyVector )pbdoc"; @@ -970,8 +962,8 @@ const char* cc_GetEvalMultKeyVector_docs = R"pbdoc( const char* cc_GetEvalAutomorphismKeyMap_docs = R"pbdoc( Get automorphism keys for a specific secret key tag - :param keyId: key identifier used for private key - :type keyId: str + :param keyTag: key identifier used for private key + :type keyTag: str :return: EvalKeyMap: map with all automorphism keys. :rtype: EvalKeyMap )pbdoc"; @@ -993,12 +985,16 @@ const char* cc_GetEvalSumKeyMap_docs = R"pbdoc( Get a map of summation keys (each is composed of several automorphism keys) for a specific secret key tag :return: EvalKeyMap: key map :rtype: EvalKeyMap + :param keyTag: secret key identifier + :type keyTag: str )pbdoc"; const char* cc_InsertEvalSumKey_docs = R"pbdoc( InsertEvalSumKey - add the given map of keys to the map, replacing the existing map if there :param evalKeyMap: key map :type evalKeyMap: EvalKeyMap + :param keyTag: secret key identifier; empty means the keys carry their own tag + :type keyTag: str )pbdoc"; const char* cc_MultiEvalAtIndexKeyGen_docs = R"pbdoc( @@ -1010,8 +1006,8 @@ const char* cc_MultiEvalAtIndexKeyGen_docs = R"pbdoc( :type evalKeyMap: EvalKeyMap :param indexList: a vector of rotation indices :type indexList: List[int32] - :param keyId: new key identifier used for resulting evaluation key - :type keyId: str + :param keyTag: new key identifier used for resulting evaluation key + :type keyTag: str :return: EvalKeyMap: a map with new joined rotation keys :rtype: EvalKeyMap )pbdoc"; @@ -1023,8 +1019,8 @@ const char* cc_MultiEvalSumKeyGen_docs = R"pbdoc( :type privateKey: PrivateKey :param evalKeyMap: a map with prior joined summation keys :type evalKeyMap: EvalKeyMap - :param keyId: new key identifier used for resulting evaluation key - :type keyId: str + :param keyTag: new key identifier used for resulting evaluation key + :type keyTag: str :return: EvalKeyMap: new joined summation keys :rtype: EvalKeyMap )pbdoc"; @@ -1036,8 +1032,8 @@ const char* cc_MultiAddEvalAutomorphismKeys_docs = R"pbdoc( :type evalKeyMap1: EvalKeyMap :param evalKeyMap2: second automorphism key set :type evalKeyMap2: EvalKeyMap - :param keyId: new key identifier used for resulting evaluation key - :type keyId: str + :param keyTag: new key identifier used for resulting evaluation key + :type keyTag: str :return: the new joined key set for summation :rtype: evalKeyMap )pbdoc"; @@ -1049,8 +1045,8 @@ const char* cc_MultiAddPubKeys_docs = R"pbdoc( :type publicKey1: PublicKey :param publicKey2: second public key :type publicKey2: PublicKey - :param keyId: new key identifier used for the resulting key - :type keyId: str + :param keyTag: new key identifier used for the resulting key + :type keyTag: str :return: the new combined key :rtype: PublicKey )pbdoc"; @@ -1062,8 +1058,8 @@ const char* cc_MultiAddEvalKeys_docs = R"pbdoc( :type evalKey1: EvalKey :param evalKey2: second evaluation key :type evalKey2: EvalKey - :param keyId: new key identifier used for resulting evaluation key - :type keyId: str + :param keyTag: new key identifier used for resulting evaluation key + :type keyTag: str :return: the new joined key :rtype: EvalKey )pbdoc"; @@ -1075,8 +1071,8 @@ const char* cc_MultiMultEvalKey_docs = R"pbdoc( :type privateKey: PrivateKey :param evalKey: prior evaluation key :type evalKey: EvalKey - :param keyId: new key identifier used for resulting evaluation key - :type keyId: str + :param keyTag: new key identifier used for resulting evaluation key + :type keyTag: str :return: the new joined key :rtype: EvalKey )pbdoc"; @@ -1088,8 +1084,8 @@ const char* cc_MultiAddEvalSumKeys_docs = R"pbdoc( :type evalKeyMap1: EvalKeyMap :param evalKeyMap2: second summation key set :type evalKeyMap2: EvalKeyMap - :param keyId: new key identifier used for resulting evaluation key - :type keyId: str + :param keyTag: new key identifier used for resulting evaluation key + :type keyTag: str :return: the neew joined key set for summation :rtype: EvalKeyMap )pbdoc"; @@ -1101,8 +1097,8 @@ const char* cc_MultiAddEvalMultKeys_docs = R"pbdoc( :type evalKey1: EvalKey :param evalKey2: second evaluation key :type evalKey2: EvalKey - :param keyId: new key identifier used for resulting evaluation key - :type keyId: str + :param keyTag: new key identifier used for resulting evaluation key + :type keyTag: str :return: the new joined key :rtype: EvalKey )pbdoc"; @@ -1192,8 +1188,8 @@ const char* cc_IntMPBootDecrypt_docs = R"pbdoc( const char* cc_IntMPBootAdd_docs = R"pbdoc( Threshold FHE: Aggregates a vector of masked decryptions and re-encryotion shares, which is the second step of the interactive multiparty bootstrapping procedure. - :param sharesPairVec: vector of pair of ciphertexts, each element of this vector contains (h_0i, h_1i) - the masked-decryption and encryption shares ofparty i - :type sharesPairVec: List[List[Ciphertext]] + :param sharePairVec: vector of pair of ciphertexts, each element of this vector contains (h_0i, h_1i) - the masked-decryption and encryption shares ofparty i + :type sharePairVec: List[List[Ciphertext]] :return: aggregated pair of shares ((h_0, h_1) :rtype: List[Ciphertext] )pbdoc"; @@ -1203,8 +1199,8 @@ const char* cc_IntMPBootEncrypt_docs = R"pbdoc( :param publicKey: the lead party's public key :type publicKey: PublicKey - :param sharesPair: aggregated decryption and re-encryption shares - :type sharesPair: List[Ciphertext] + :param sharePair: aggregated decryption and re-encryption shares + :type sharePair: List[Ciphertext] :param a: common random ring element :type a: Ciphertext :param ciphertext: input ciphertext @@ -1218,6 +1214,8 @@ const char* cc_InsertEvalMultKey_docs = R"pbdoc( :param evalKeyVec: vector of keys :type evalKeyVec: List[EvalKey] + :param keyTag: secret key identifier; empty means the keys carry their own tag + :type keyTag: str )pbdoc"; const char* cc_InsertEvalAutomorphismKey_docs = R"pbdoc( @@ -1358,6 +1356,10 @@ const char* cc_EvalBootstrapSetup_docs = R"pbdoc( :param correctionFactor: value to internally rescale message by to improve precision of bootstrapping. If set to 0, we use the default logic. This value is only used when NATIVE_SIZE=64. :type correctionFactor: int :return: None + :param precompute: whether to run the bootstrapping precomputation immediately + :type precompute: bool + :param BTSlotsEncoding: whether to use the slots-encoding bootstrapping variant + :type BTSlotsEncoding: bool )pbdoc"; const char* cc_EvalBootstrapKeyGen_docs = R"pbdoc( @@ -1415,6 +1417,8 @@ const char* cc_EvalCKKStoFHEW_docs = R"pbdoc( :type ciphertext: Ciphertext :param numCtxts: Number of coefficients to extract (defaults to number of slots if 0). :type numCtxts: int + :return: the extracted FHEW/LWE ciphertexts + :rtype: list[LWECiphertext] )pbdoc"; const char* cc_EvalFHEWtoCKKSSetup_docs = R"pbdoc( @@ -1641,8 +1645,8 @@ const char* cc_SerializeEvalAutomorphismKey_docs = R"pbdoc( :type filename: str :param sertype: serialization type :type sertype: SERJSON, SERBINARY - :param id: key to serialize; empty string means all keys - :type id: str + :param keyTag: key to serialize; empty string means all keys + :type keyTag: str :return: bool: true on success )pbdoc"; @@ -1653,8 +1657,8 @@ const char* cc_SerializeEvalMultKey_docs = R"pbdoc( :type filename: str :param sertype: type of serialization :type sertype: SERJSON, SERBINARY - :param id: for key to serialize - if empty string, serialize them all - :type id: str + :param keyTag: for key to serialize - if empty string, serialize them all + :type keyTag: str :return: bool: true on success (false on failure or no keys found) )pbdoc"; @@ -1679,4 +1683,458 @@ const char* cc_DeserializeEvalMultKey_docs = R"pbdoc( )pbdoc"; +const char* cc_ClearAllCKKSCaches_docs = R"pbdoc( + Release all scheme-level CKKS caches, both bootstrapping and scheme switching. +)pbdoc"; + +const char* cc_ClearSchemeSwitchPrecom_docs = R"pbdoc( + Clear the CKKS/FHEW scheme switching precomputations cached by this context. +)pbdoc"; + +const char* cc_ComposedEvalMult_docs = R"pbdoc( + Multiply two ciphertexts, then relinearize and rescale, in one step. + + :param ciphertext1: first ciphertext + :type ciphertext1: Ciphertext + :param ciphertext2: second ciphertext + :type ciphertext2: Ciphertext + :return: resulting ciphertext + :rtype: Ciphertext +)pbdoc"; + +const char* cc_DeserializeEvalSumKey_docs = R"pbdoc( + Deserialize summation keys from a file. + + :param filename: path of the file + :type filename: str + :param sertype: type of serialization + :type sertype: SERBINARY | SERJSON + :return: true on success + :rtype: bool +)pbdoc"; + +const char* cc_EvalAddInPlaceNoCheck_docs = R"pbdoc( + Add two ciphertexts in place without any type, level or scaling factor checks. + + :param ciphertext1: the first ciphertext + :type ciphertext1: Ciphertext + :param ciphertext2: the second ciphertext + :type ciphertext2: Ciphertext +)pbdoc"; + +const char* cc_EvalBootstrapPrecompute_docs = R"pbdoc( + Precompute the plaintexts used for encoding and decoding in bootstrapping. CKKS only. + + :param slots: number of slots to be bootstrapped + :type slots: int +)pbdoc"; + +const char* cc_EvalBootstrapStCFirst_docs = R"pbdoc( + Evaluate the slots-encoding variant of CKKS bootstrapping, applying SlotsToCoeffs first. + + :param ciphertext: input ciphertext + :type ciphertext: Ciphertext + :param numIterations: number of Meta-BTS iterations to improve precision (1 or 2) + :type numIterations: int + :param precision: initial bootstrapping precision (set to 0 for default; tune experimentally) + :type precision: int + :return: refreshed ciphertext + :rtype: Ciphertext +)pbdoc"; + +const char* cc_EvalChebyPolys_docs = R"pbdoc( + Compute the Chebyshev polynomials of a ciphertext for EvalChebyshevSeriesWithPrecomp (CKKS only). + + :param ciphertext: input ciphertext + :type ciphertext: Ciphertext + :param coefficients: polynomial coefficients (vector's size = (degree + 1)) + :type coefficients: list[int] | list[float] | list[complex] + :param a: lower bound of argument for which the coefficients were found + :type a: float + :param b: upper bound of argument for which the coefficients were found + :type b: float + :return: resulting structure of Chebyshev polynomials + :rtype: SeriesPowers +)pbdoc"; + +const char* cc_EvalChebyshevSeriesWithPrecomp_docs = R"pbdoc( + Evaluate a Chebyshev series from the polynomials precomputed by EvalChebyPolys (CKKS only). + + :param polys: chebyshev polynomials of the input ciphertext, as returned by EvalChebyPolys + :type polys: SeriesPowers + :param coefficients: chebyshev series coefficients + :type coefficients: list[int] | list[float] | list[complex] + :return: resulting ciphertext + :rtype: Ciphertext +)pbdoc"; + + +const char* cc_EvalMultNoCheck_docs = R"pbdoc( + Multiply a ciphertext by an unscaled integer without any checks. + + :param ciphertext: the ciphertext + :type ciphertext: Ciphertext + :param scalar: the scalar + :type scalar: int + :return: resulting ciphertext + :rtype: Ciphertext +)pbdoc"; + +const char* cc_EvalMultNoRelinNoCheck_docs = R"pbdoc( + Multiply two ciphertexts without relinearization and without any type, level or scaling factor checks. + + :param ciphertext1: the first ciphertext + :type ciphertext1: Ciphertext + :param ciphertext2: the second ciphertext + :type ciphertext2: Ciphertext + :return: resulting ciphertext + :rtype: Ciphertext +)pbdoc"; + +const char* cc_EvalPolyWithPrecomp_docs = R"pbdoc( + Evaluate a polynomial from the powers precomputed by EvalPowers (CKKS only). + + :param powers: powers of the input ciphertext, as returned by EvalPowers + :type powers: SeriesPowers + :param coefficients: polynomial coefficients (vector's size = (degree + 1)) + :type coefficients: list[int] | list[float] | list[complex] + :return: resulting ciphertext + :rtype: Ciphertext +)pbdoc"; + +const char* cc_EvalPowers_docs = R"pbdoc( + Compute the powers of a ciphertext for EvalPolyWithPrecomp (CKKS only). + + :param ciphertext: input ciphertext + :type ciphertext: Ciphertext + :param coefficients: polynomial coefficients (vector's size = (degree + 1)) + :type coefficients: list[int] | list[float] | list[complex] + :return: resulting structure of powers + :rtype: SeriesPowers +)pbdoc"; + +const char* cc_GetAllEvalAutomorphismKeys_docs = R"pbdoc( + Get all automorphism keys, as a map keyed by secret key tag. + + :return: std::map where the map key/data pair is "keyTag"/"shared_ptr to EvalMultKey map" + :rtype: dict +)pbdoc"; + +const char* cc_GetAllEvalMultKeys_docs = R"pbdoc( + Get all relinearization keys, as a map keyed by secret key tag. + + :return: std::map where the map key/data pair is "keyTag"/"EvalMultKeys vector" + :rtype: dict +)pbdoc"; + +const char* cc_GetAllEvalSumKeys_docs = R"pbdoc( + Get all summation keys, as a map keyed by secret key tag. + + :return: std::map where the map key/data pair is "keyTag"/"shared_ptr to EvalSumKey map" + :rtype: dict +)pbdoc"; + +const char* cc_GetEncodingParams_docs = R"pbdoc( + Getter for the encoding parameters. + + :return: encodingParams + :rtype: EncodingParams +)pbdoc"; + +const char* cc_GetEvalAutomorphismNoKeyIndices_docs = R"pbdoc( + Get the indices in the given set that have no automorphism key for a secret key tag. + + :param keyTag: secret key tag + :type keyTag: str + :param indices: set of specific indices to check the key map against + :type indices: set[int] + :return: indices that do not have automorphism keys associated with + :rtype: set[int] +)pbdoc"; + +const char* cc_GetPlaintextForDecrypt_docs = R"pbdoc( + Create an empty plaintext of the given encoding type, to decrypt into. + + :param encodingType: the encoding type + :type encodingType: PlaintextEncodings + :param elementParams: the element parameters + :type elementParams: ParmType + :param encodingParams: the encoding parameters + :type encodingParams: EncodingParams + :param ckksDataType: the CKKS data type + :type ckksDataType: CKKSDataType + :return: plaintext for decryption + :rtype: Plaintext +)pbdoc"; + +const char* cc_GetScheme_docs = R"pbdoc( + Getter for the scheme. + + :return: scheme object + :rtype: SchemeBase +)pbdoc"; + +const char* cc_GetSwkFC_docs = R"pbdoc( + Get the FHEW-to-CKKS scheme switching key. + + :return: switching key ciphertext + :rtype: Ciphertext +)pbdoc"; + +const char* cc_GetUniqueValues_docs = R"pbdoc( + Return the values present in the second set but not in the first. + + :param oldValues: first set to compare against + :type oldValues: set[int] + :param newValues: second set to extract unique values from + :type newValues: set[int] + :return: set of values present in newValues but not in oldValues + :rtype: set[int] +)pbdoc"; + +const char* cc_KeySwitchDownFirstElement_docs = R"pbdoc( + Scale the first polynomial down from the extended CRT basis P*Q to Q. Hybrid key switching only. + + :param ciphertext: input ciphertext in extended basis + :type ciphertext: Ciphertext + :return: scaled polynomial c0 in basis Q + :rtype: DCRTPoly +)pbdoc"; + +const char* cc_KeySwitchDown_docs = R"pbdoc( + Scale a ciphertext down from the extended CRT basis P*Q to Q. Hybrid key switching only. + + :param ciphertext: input ciphertext in extended basis + :type ciphertext: Ciphertext + :return: scaled ciphertext in basis Q + :rtype: Ciphertext +)pbdoc"; + +const char* cc_KeySwitchExt_docs = R"pbdoc( + Extend a ciphertext from basis Q to the extended CRT basis P*Q. Hybrid key switching only. + + :param ciphertext: input ciphertext in basis Q + :type ciphertext: Ciphertext + :param addFirst: if true, includes the first component c0 in the output + :type addFirst: bool + :return: extended ciphertext in basis P*Q + :rtype: Ciphertext +)pbdoc"; + +const char* cc_KeySwitchInPlace_docs = R"pbdoc( + Apply key switching to a ciphertext in place. + + :param ciphertext: ciphertext to modify + :type ciphertext: Ciphertext + :param evalKey: evaluation key for key switching + :type evalKey: EvalKey +)pbdoc"; + +const char* cc_KeySwitch_docs = R"pbdoc( + Apply key switching to a ciphertext using the given evaluation key. + + :param ciphertext: input ciphertext + :type ciphertext: Ciphertext + :param evalKey: evaluation key for key switching + :type evalKey: EvalKey + :return: ciphertext after key switching + :rtype: Ciphertext +)pbdoc"; + +const char* cc_LevelReduceInPlace_docs = R"pbdoc( + Reduce the number of RNS limbs (levels) in a ciphertext, in place. + + :param ciphertext: ciphertext to modify + :type ciphertext: Ciphertext + :param evalKey: evaluation key (currently unused; kept for API compatibility) + :type evalKey: EvalKey + :param levels: number of levels to drop + :type levels: int +)pbdoc"; + +const char* cc_LevelReduce_docs = R"pbdoc( + Reduce the number of RNS limbs (levels) in a ciphertext. + + :param ciphertext: input ciphertext + :type ciphertext: Ciphertext + :param evalKey: evaluation key (currently unused; kept for API compatibility) + :type evalKey: EvalKey + :param levels: number of levels to drop + :type levels: int + :return: ciphertext with reduced levels + :rtype: Ciphertext +)pbdoc"; + +const char* cc_MultiEvalAutomorphismKeyGen_docs = R"pbdoc( + Generate joined automorphism keys from the current secret share and prior keys, for threshold FHE. + + :param privateKey: secret key share + :type privateKey: PrivateKey + :param evalKeyMap: prior joined automorphism keys + :type evalKeyMap: EvalKeyMap + :param indexList: list of automorphism indices + :type indexList: list[int] + :param keyTag: secret key tag (optional) + :type keyTag: str + :return: map of updated joined automorphism keys + :rtype: EvalKeyMap +)pbdoc"; + +const char* cc_RecoverSharedKey_docs = R"pbdoc( + Recover a secret key from its shares, for threshold FHE with aborts. + + :param privateKey: the secret key + :type privateKey: PrivateKey + :param shares: the key shares + :type shares: dict[int, DCRTPoly] + :param N: total number of parties + :type N: int + :param threshold: threshold number of parties required to reconstruct the key + :type threshold: int + :param shareType: type of secret sharing ("additive" or "shamir") + :type shareType: str +)pbdoc"; + +const char* cc_SerializeEvalSumKeyByContext_docs = R"pbdoc( + Serialize to a file the summation keys belonging to the given crypto context. + + :param filename: path of the file + :type filename: str + :param sertype: type of serialization + :type sertype: SERBINARY | SERJSON + :param cryptoContext: the crypto context + :type cryptoContext: CryptoContext + :return: true on success + :rtype: bool +)pbdoc"; + +const char* cc_SerializeEvalSumKey_docs = R"pbdoc( + Serialize to a file all summation keys, or those for the given secret key tag. + + :param filename: path of the file + :type filename: str + :param sertype: type of serialization + :type sertype: SERBINARY | SERJSON + :param keyTag: secret key tag + :type keyTag: str + :return: true on success + :rtype: bool +)pbdoc"; + +const char* cc_SerializedObjectName_docs = R"pbdoc( + Get the name used for this object in serialization. + + :return: the serialized object name + :rtype: str +)pbdoc"; + +const char* cc_SerializedVersion_docs = R"pbdoc( + Get the serialization version of this object. + + :return: the serialization version + :rtype: int +)pbdoc"; + +const char* cc_SetBinCCForSchemeSwitch_docs = R"pbdoc( + Set the BinFHE context used for scheme switching. + + :param binFHEContext: the BinFHE context + :type binFHEContext: BinFHEContext +)pbdoc"; + +const char* cc_SetParamsFromCKKSCryptocontext_docs = R"pbdoc( + Set the scheme switching parameters from this CKKS crypto context. + + :param params: scheme switching parameter object to populate + :type params: SchSwchParams +)pbdoc"; + +const char* cc_SetSwkFC_docs = R"pbdoc( + Set the FHEW-to-CKKS scheme switching key. + + :param switchingKey: the scheme switching key + :type switchingKey: Ciphertext +)pbdoc"; + +const char* cc_ShareKeys_docs = R"pbdoc( + Secret-share a secret key, for threshold FHE with aborts. + + :param privateKey: the secret key + :type privateKey: PrivateKey + :param N: total number of parties + :type N: int + :param threshold: threshold number of parties required to reconstruct the key + :type threshold: int + :param index: index of the current party + :type index: int + :param shareType: type of secret sharing ("additive" or "shamir") + :type shareType: str + :return: map of secret key shares indexed by party ID + :rtype: dict[int, DCRTPoly] +)pbdoc"; + +const char* cc_SparseKeyGen_docs = R"pbdoc( + Generate a sparse key pair, for special use cases such as ring reduction. + + :return: generated key pair + :rtype: KeyPair +)pbdoc"; + +const char* cc_getSchemeId_docs = R"pbdoc( + Get the identifier of the scheme this crypto context implements. + + :return: the scheme identifier + :rtype: SCHEME +)pbdoc"; + +const char* cc_setSchemeId_docs = R"pbdoc( + Set the identifier of the scheme this crypto context implements. + + :param schemeTag: the scheme identifier + :type schemeTag: SCHEME +)pbdoc"; + +const char* cc_EvalAutomorphism_docs = R"pbdoc( + Apply an automorphism to a ciphertext using the given evaluation keys. + + :param ciphertext: input ciphertext + :type ciphertext: Ciphertext + :param i: the automorphism index + :type i: int + :param evalKeyMap: the automorphism keys + :type evalKeyMap: EvalKeyMap + :return: the transformed ciphertext + :rtype: Ciphertext +)pbdoc"; + +const char* cc_EvalLinearWSum_docs = R"pbdoc( + Evaluate a weighted sum of ciphertexts using scalar coefficients. + + :param ciphertextVec: the ciphertexts to combine + :type ciphertextVec: list[Ciphertext] + :param constantVec: the scalar weights + :type constantVec: list[int] | list[float] | list[complex] + :return: the weighted sum + :rtype: Ciphertext +)pbdoc"; + +const char* cc_EvalLinearWSumMutable_docs = R"pbdoc( + Evaluate a weighted sum of ciphertexts using scalar coefficients, modifying the inputs. + + :param constantsVec: the scalar weights + :type constantsVec: list[int] | list[float] | list[complex] + :param ciphertextVec: the ciphertexts to combine + :type ciphertextVec: list[Ciphertext] + :return: the weighted sum + :rtype: Ciphertext +)pbdoc"; + +const char* utils_EnablePrecomputeCRTTablesAfterDeserializaton_docs = R"pbdoc( + Enable CRT precomputation after deserialization. +)pbdoc"; + +const char* utils_DisablePrecomputeCRTTablesAfterDeserializaton_docs = R"pbdoc( + Disable CRT precomputation after deserialization. +)pbdoc"; + #endif // __CRYPTOCONTEXT_DOCS_H__ diff --git a/src/include/docstrings/cryptoparameters_docs.h b/src/include/docstrings/cryptoparameters_docs.h index fa5f938..eb78ba2 100644 --- a/src/include/docstrings/cryptoparameters_docs.h +++ b/src/include/docstrings/cryptoparameters_docs.h @@ -71,11 +71,22 @@ const char* ccparams_doc = R"doc( const char* cc_GetScalingFactorReal_docs = R"pbdoc( Method to retrieve the scaling factor of level l. For FIXEDMANUAL scaling technique method always returns 2^p, where p corresponds to plaintext modulus - :param l: For FLEXIBLEAUTO scaling technique the level whose scaling factor we want to learn. Levels start from 0 (no scaling done - all towers) and go up to K-1, where K is the number of towers supported. - :type l: int + :param level: For FLEXIBLEAUTO scaling technique the level whose scaling factor we want to learn. Levels start from 0 (no scaling done - all towers) and go up to K-1, where K is the number of towers supported. + :type level: int :return: the scaling factor. :rtype: float )pbdoc"; +const char* ccparams_GetThresholdNumOfParties_docs = R"pbdoc( + Get the number of parties in threshold FHE. + + :return: number of parties + :rtype: int +)pbdoc"; + +const char* schswchparams_SetParamsFromCKKSCryptocontextCalled_docs = R"pbdoc( + Mark the parameters as populated from a CKKS crypto context, which unlocks the getters. +)pbdoc"; + #endif // __CRYPTOPARAMETERS_DOCS_H__ diff --git a/src/include/docstrings/fbt_docs.h b/src/include/docstrings/fbt_docs.h new file mode 100644 index 0000000..1a91283 --- /dev/null +++ b/src/include/docstrings/fbt_docs.h @@ -0,0 +1,57 @@ +//================================================================================== +// BSD 2-Clause License +// +// Copyright (c) 2023-2026, Duality Technologies Inc. and other contributors +// +// All rights reserved. +// +// Author TPOC: contact@openfhe.org +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +//================================================================================== +#ifndef __FBT_DOCS_H__ +#define __FBT_DOCS_H__ + +// Docstrings for the functional bootstrapping bindings in src/lib/pke/fbt.cpp. +// These constants have external linkage, so this header must be included by +// fbt.cpp only, like the other docstring headers. + +const char* cc_EvalHermiteTrigSeries_docs = R"pbdoc( + Evaluate a trigonometric Hermite series approximating exp(i*Pi/2*x) over the interval [a, b]. + + :param ciphertext: input ciphertext + :type ciphertext: Ciphertext + :param coefficientsCheb: Chebyshev coefficients of the function to approximate + :type coefficientsCheb: list[complex] + :param a: lower bound of the interval + :type a: float + :param b: upper bound of the interval + :type b: float + :param coefficientsHerm: Hermite interpolation coefficients + :type coefficientsHerm: list[int] | list[complex] + :param precomp: precomputation selector + :type precomp: int + :return: the resulting ciphertext + :rtype: Ciphertext +)pbdoc"; + +#endif // __FBT_DOCS_H__ diff --git a/src/include/docstrings/plaintext_docs.h b/src/include/docstrings/plaintext_docs.h index f83b456..2c7f60c 100644 --- a/src/include/docstrings/plaintext_docs.h +++ b/src/include/docstrings/plaintext_docs.h @@ -90,11 +90,15 @@ const char* ptx_GetLogPrecision_docs = R"pbdoc( // Encode const char* ptx_Encode_docs = R"pbdoc( Encode the plaintext into a polynomial. + :return: true on success + :rtype: bool )pbdoc"; // Decode const char* ptx_Decode_docs = R"pbdoc( Decode the polynomial into a plaintext. + :return: true on success + :rtype: bool )pbdoc"; const char* ptx_LowBound_docs = R"pbdoc( @@ -136,4 +140,81 @@ const char* ptx_GetRealPackedValue_docs = R"pbdoc( )pbdoc"; +const char* encparams_GetBatchSize_docs = R"pbdoc( + Get the plaintext batch size. + + :return: the plaintext batch size + :rtype: int +)pbdoc"; + +const char* encparams_GetPlaintextGenerator_docs = R"pbdoc( + Get the plaintext generator. + + :return: the plaintext generator + :rtype: int +)pbdoc"; + +const char* encparams_GetPlaintextModulus_docs = R"pbdoc( + Get the plaintext modulus. + + :return: the plaintext modulus + :rtype: int +)pbdoc"; + +const char* ptx_GetCKKSDataType_docs = R"pbdoc( + Get the CKKS data type, real or complex. + + :return: CKKS data type with this plaintext + :rtype: CKKSDataType +)pbdoc"; + +const char* ptx_GetElementModulus_docs = R"pbdoc( + Get the modulus of the underlying element. + + :return: modulus on the underlying element + :rtype: int +)pbdoc"; + +const char* ptx_GetElementRingDimension_docs = R"pbdoc( + Get the ring dimension of the underlying element. + + :return: ring dimension on the underlying element + :rtype: int +)pbdoc"; + +const char* ptx_GetEncodingParams_docs = R"pbdoc( + Get the encoding parameters of the plaintext. + + :return: encoding params used with this plaintext + :rtype: EncodingParams +)pbdoc"; + +const char* ptx_GetEncodingType_docs = R"pbdoc( + Get the encoding type of the plaintext. + + :return: encoding type used by this plaintext + :rtype: PlaintextEncodings +)pbdoc"; + +const char* ptx_GetScalingFactorInt_docs = R"pbdoc( + Get the integer scaling factor of the plaintext. + + :return: the integer scaling factor + :rtype: int +)pbdoc"; + +const char* ptx_SetCKKSDataType_docs = R"pbdoc( + Set the CKKS data type, real or complex. + + :param dataType: the CKKS data type + :type dataType: CKKSDataType +)pbdoc"; + +const char* ptx_SetScalingFactorInt_docs = R"pbdoc( + Set the integer scaling factor of the plaintext. + + :param sf: the integer scaling factor + :type sf: int +)pbdoc"; + #endif // __PLAINTEXT_DOCS_H__ diff --git a/src/lib/bindings.cpp b/src/lib/bindings.cpp index a747c71..eac453d 100644 --- a/src/lib/bindings.cpp +++ b/src/lib/bindings.cpp @@ -86,7 +86,8 @@ void bind_parameters(py::module &m, const std::string name) { .def("GetDesiredPrecision", &CCParams::GetDesiredPrecision) .def("GetStatisticalSecurity", &CCParams::GetStatisticalSecurity) .def("GetNumAdversarialQueries", &CCParams::GetNumAdversarialQueries) - .def("GetThresholdNumOfParties", &CCParams::GetThresholdNumOfParties) + .def("GetThresholdNumOfParties", &CCParams::GetThresholdNumOfParties, + py::doc(ccparams_GetThresholdNumOfParties_docs)) .def("GetKeySwitchTechnique", &CCParams::GetKeySwitchTechnique) .def("GetScalingTechnique", &CCParams::GetScalingTechnique) .def("GetBatchSize", &CCParams::GetBatchSize) @@ -190,7 +191,7 @@ void bind_crypto_context_templates(py::class_>& cls) { ) const>(&CC::EvalLinearWSum), py::arg("ciphertextVec"), py::arg("constantVec"), - py::doc("Evaluate a weighted sum of ciphertexts using scalar coefficients")) + py::doc(cc_EvalLinearWSum_docs)) .def("EvalLinearWSumMutable", static_cast (CC::*)( const std::vector&, @@ -198,7 +199,7 @@ void bind_crypto_context_templates(py::class_>& cls) { ) const>(&CC::EvalLinearWSumMutable), py::arg("constantsVec"), py::arg("ciphertextVec"), - py::doc("Evaluate a weighted sum (mutable version) with given coefficients")) + py::doc(cc_EvalLinearWSumMutable_docs)) .def("EvalPoly", static_cast (CC::*)( ConstCiphertext&, @@ -227,22 +228,26 @@ void bind_crypto_context_templates(py::class_>& cls) { static_cast> (CC::*)( ConstCiphertext&, const std::vector&, double, double ) const>(&CC::EvalChebyPolys), - py::arg("ciphertext"), py::arg("coefficients"), py::arg("a"), py::arg("b")) + py::arg("ciphertext"), py::arg("coefficients"), py::arg("a"), py::arg("b"), + py::doc(cc_EvalChebyPolys_docs)) .def("EvalChebyshevSeriesWithPrecomp", static_cast (CC::*)( std::shared_ptr>, const std::vector& ) const>(&CC::EvalChebyshevSeriesWithPrecomp), - py::arg("polys"), py::arg("coefficients")) + py::arg("polys"), py::arg("coefficients"), + py::doc(cc_EvalChebyshevSeriesWithPrecomp_docs)) .def("EvalPolyWithPrecomp", static_cast (CC::*)( std::shared_ptr>, const std::vector& ) const>(&CC::EvalPolyWithPrecomp), - py::arg("powers"), py::arg("coefficients")) + py::arg("powers"), py::arg("coefficients"), + py::doc(cc_EvalPolyWithPrecomp_docs)) .def("EvalPowers", static_cast> (CC::*)( ConstCiphertext&, const std::vector& ) const>(&CC::EvalPowers), - py::arg("ciphertext"), py::arg("coefficients")); + py::arg("ciphertext"), py::arg("coefficients"), + py::doc(cc_EvalPowers_docs)); } void bind_eval_add_family(py::class_>& cls) { @@ -417,10 +422,13 @@ void bind_crypto_context(py::module &m) { auto cc_class = py::class_, std::shared_ptr>>(m, "CryptoContext"); cc_class.def(py::init<>()) - .def("ClearAllCKKSCaches", &CC::ClearAllCKKSCaches) - .def("ClearSchemeSwitchPrecom", &CC::ClearSchemeSwitchPrecom) + .def("ClearAllCKKSCaches", &CC::ClearAllCKKSCaches, + py::doc(cc_ClearAllCKKSCaches_docs)) + .def("ClearSchemeSwitchPrecom", &CC::ClearSchemeSwitchPrecom, + py::doc(cc_ClearSchemeSwitchPrecom_docs)) .def("ComposedEvalMult", &CC::ComposedEvalMult, - py::arg("ciphertext1"), py::arg("ciphertext2")) + py::arg("ciphertext1"), py::arg("ciphertext2"), + py::doc(cc_ComposedEvalMult_docs)) .def("GetKeyGenLevel", &CryptoContextImpl::GetKeyGenLevel, cc_GetKeyGenLevel_docs) .def("SetKeyGenLevel", &CryptoContextImpl::SetKeyGenLevel, py::arg("level"), @@ -473,7 +481,8 @@ void bind_crypto_context(py::module &m) { return self->GetCryptoParameters()->GetDigitSize(); }) .def("GetCyclotomicOrder", &CryptoContextImpl::GetCyclotomicOrder, cc_GetCyclotomicOrder_docs) - .def("GetEncodingParams", &CC::GetEncodingParams) + .def("GetEncodingParams", &CC::GetEncodingParams, + py::doc(cc_GetEncodingParams_docs)) .def("GetElementParams", [](const CC& self) { return self.GetCryptoParameters()->GetElementParams(); @@ -483,9 +492,12 @@ void bind_crypto_context(py::module &m) { return openfhe_python::IntegerToPyInt(self.GetRootOfUnity()); }, py::doc(cc_GetRootOfUnity_docs)) - .def("GetScheme", &CC::GetScheme) - .def("getSchemeId", &CC::getSchemeId) - .def("setSchemeId", &CC::setSchemeId, py::arg("schemeTag")) + .def("GetScheme", &CC::GetScheme, + py::doc(cc_GetScheme_docs)) + .def("getSchemeId", &CC::getSchemeId, + py::doc(cc_getSchemeId_docs)) + .def("setSchemeId", &CC::setSchemeId, py::arg("schemeTag"), + py::doc(cc_setSchemeId_docs)) .def("GetCKKSDataType", &CryptoContextImpl::GetCKKSDataType) .def("GetCKKSBootCorrectionFactor", &CC::GetCKKSBootCorrectionFactor) .def("SetCKKSBootCorrectionFactor", &CC::SetCKKSBootCorrectionFactor, py::arg("cf")) @@ -537,7 +549,8 @@ void bind_crypto_context(py::module &m) { py::arg("feature"), py::doc(cc_Enable_docs)) .def("KeyGen", &CryptoContextImpl::KeyGen, cc_KeyGen_docs) - .def("SparseKeyGen", &CC::SparseKeyGen) + .def("SparseKeyGen", &CC::SparseKeyGen, + py::doc(cc_SparseKeyGen_docs)) .def("EvalMultKeyGen", &CryptoContextImpl::EvalMultKeyGen, py::arg("privateKey"), py::doc(cc_EvalMultKeyGen_docs)) @@ -594,7 +607,7 @@ void bind_crypto_context(py::module &m) { py::arg("ciphertext"), py::arg("i"), py::arg("evalKeyMap"), - py::doc("Applies an automorphism to a ciphertext using the given evaluation keys.")) + py::doc(cc_EvalAutomorphism_docs)) .def("EvalRotate", &CryptoContextImpl::EvalRotate, py::arg("ciphertext"), py::arg("index"), @@ -685,11 +698,16 @@ void bind_crypto_context(py::module &m) { py::arg("oldPrivateKey"), py::arg("newPrivateKey"), py::doc(cc_KeySwitchGen_docs)) - .def("KeySwitch", &CC::KeySwitch, py::arg("ciphertext"), py::arg("evalKey")) - .def("KeySwitchInPlace", &CC::KeySwitchInPlace, py::arg("ciphertext"), py::arg("evalKey")) - .def("KeySwitchDown", &CC::KeySwitchDown, py::arg("ciphertext")) - .def("KeySwitchDownFirstElement", &CC::KeySwitchDownFirstElement, py::arg("ciphertext")) - .def("KeySwitchExt", &CC::KeySwitchExt, py::arg("ciphertext"), py::arg("addFirst")) + .def("KeySwitch", &CC::KeySwitch, py::arg("ciphertext"), py::arg("evalKey"), + py::doc(cc_KeySwitch_docs)) + .def("KeySwitchInPlace", &CC::KeySwitchInPlace, py::arg("ciphertext"), py::arg("evalKey"), + py::doc(cc_KeySwitchInPlace_docs)) + .def("KeySwitchDown", &CC::KeySwitchDown, py::arg("ciphertext"), + py::doc(cc_KeySwitchDown_docs)) + .def("KeySwitchDownFirstElement", &CC::KeySwitchDownFirstElement, py::arg("ciphertext"), + py::doc(cc_KeySwitchDownFirstElement_docs)) + .def("KeySwitchExt", &CC::KeySwitchExt, py::arg("ciphertext"), py::arg("addFirst"), + py::doc(cc_KeySwitchExt_docs)) .def("EvalAddMutable", py::overload_cast&, Ciphertext&>(&CryptoContextImpl::EvalAddMutable, py::const_), py::arg("ciphertext1"), @@ -760,13 +778,16 @@ void bind_crypto_context(py::module &m) { py::arg("ciphertext2"), py::doc(cc_EvalMultNoRelin_docs)) .def("EvalMultNoRelinNoCheck", &CC::EvalMultNoRelinNoCheck, - py::arg("ciphertext1"), py::arg("ciphertext2")) + py::arg("ciphertext1"), py::arg("ciphertext2"), + py::doc(cc_EvalMultNoRelinNoCheck_docs)) .def("EvalMultNoCheck", [](const CC& self, ConstCiphertext& ciphertext, const py::int_& scalar) { return self.EvalMultNoCheck(ciphertext, openfhe_python::PyIntToInteger(scalar)); - }, py::arg("ciphertext"), py::arg("scalar")) + }, py::arg("ciphertext"), py::arg("scalar"), + py::doc(cc_EvalMultNoCheck_docs)) .def("EvalAddInPlaceNoCheck", &CC::EvalAddInPlaceNoCheck, - py::arg("ciphertext1"), py::arg("ciphertext2")) + py::arg("ciphertext1"), py::arg("ciphertext2"), + py::doc(cc_EvalAddInPlaceNoCheck_docs)) .def("Relinearize", &CryptoContextImpl::Relinearize, py::arg("ciphertext"), py::doc(cc_Relinearize_docs)) @@ -778,9 +799,11 @@ void bind_crypto_context(py::module &m) { py::arg("ciphertext2"), py::doc(cc_EvalMultAndRelinearize_docs)) .def("LevelReduce", &CC::LevelReduce, - py::arg("ciphertext"), py::arg("evalKey"), py::arg("levels") = 1) + py::arg("ciphertext"), py::arg("evalKey"), py::arg("levels") = 1, + py::doc(cc_LevelReduce_docs)) .def("LevelReduceInPlace", &CC::LevelReduceInPlace, - py::arg("ciphertext"), py::arg("evalKey"), py::arg("levels") = 1) + py::arg("ciphertext"), py::arg("evalKey"), py::arg("levels") = 1, + py::doc(cc_LevelReduceInPlace_docs)) .def("EvalNegate", &CryptoContextImpl::EvalNegate, py::arg("ciphertext"), py::doc(cc_EvalNegate_docs)) @@ -903,9 +926,11 @@ void bind_crypto_context(py::module &m) { py::arg("partialCiphertextVec"), py::doc(cc_MultipartyDecryptFusion_docs)) .def("ShareKeys", &CC::ShareKeys, - py::arg("privateKey"), py::arg("N"), py::arg("threshold"), py::arg("index"), py::arg("shareType")) + py::arg("privateKey"), py::arg("N"), py::arg("threshold"), py::arg("index"), py::arg("shareType"), + py::doc(cc_ShareKeys_docs)) .def("RecoverSharedKey", &CC::RecoverSharedKey, - py::arg("privateKey"), py::arg("shares"), py::arg("N"), py::arg("threshold"), py::arg("shareType")) + py::arg("privateKey"), py::arg("shares"), py::arg("N"), py::arg("threshold"), py::arg("shareType"), + py::doc(cc_RecoverSharedKey_docs)) .def("MultiKeySwitchGen", &CryptoContextImpl::MultiKeySwitchGen, py::arg("originalPrivateKey"), py::arg("newPrivateKey"), @@ -925,7 +950,8 @@ void bind_crypto_context(py::module &m) { py::arg("keyTag") = "", py::doc(cc_MultiEvalAtIndexKeyGen_docs)) .def("MultiEvalAutomorphismKeyGen", &CC::MultiEvalAutomorphismKeyGen, - py::arg("privateKey"), py::arg("evalKeyMap"), py::arg("indexList"), py::arg("keyTag") = "") + py::arg("privateKey"), py::arg("evalKeyMap"), py::arg("indexList"), py::arg("keyTag") = "", + py::doc(cc_MultiEvalAutomorphismKeyGen_docs)) .def("MultiEvalSumKeyGen", &CryptoContextImpl::MultiEvalSumKeyGen, py::arg("privateKey"), py::arg("evalKeyMap"), @@ -1051,14 +1077,16 @@ void bind_crypto_context(py::module &m) { py::arg("privateKey"), py::arg("slots"), py::doc(cc_EvalBootstrapKeyGen_docs)) - .def("EvalBootstrapPrecompute", &CC::EvalBootstrapPrecompute, py::arg("slots") = 0) + .def("EvalBootstrapPrecompute", &CC::EvalBootstrapPrecompute, py::arg("slots") = 0, + py::doc(cc_EvalBootstrapPrecompute_docs)) .def("EvalBootstrap", &CryptoContextImpl::EvalBootstrap, py::arg("ciphertext"), py::arg("numIterations") = 1, py::arg("precision") = 0, py::doc(cc_EvalBootstrap_docs)) .def("EvalBootstrapStCFirst", &CC::EvalBootstrapStCFirst, - py::arg("ciphertext"), py::arg("numIterations") = 1, py::arg("precision") = 0) + py::arg("ciphertext"), py::arg("numIterations") = 1, py::arg("precision") = 0, + py::doc(cc_EvalBootstrapStCFirst_docs)) .def_static("ClearStaticMapsAndVectors", &CryptoContextImpl::ClearStaticMapsAndVectors) .def("EvalCKKStoFHEWSetup", &CryptoContextImpl::EvalCKKStoFHEWSetup, py::arg("schswchparams"), @@ -1099,10 +1127,14 @@ void bind_crypto_context(py::module &m) { .def("EvalSchemeSwitchingSetup", &CryptoContextImpl::EvalSchemeSwitchingSetup, py::arg("schswchparams"), py::doc(cc_EvalSchemeSwitchingSetup_docs)) - .def("SetBinCCForSchemeSwitch", &CC::SetBinCCForSchemeSwitch, py::arg("binFHEContext")) - .def("SetParamsFromCKKSCryptocontext", &CC::SetParamsFromCKKSCryptocontext, py::arg("params")) - .def("GetSwkFC", &CC::GetSwkFC) - .def("SetSwkFC", &CC::SetSwkFC, py::arg("switchingKey")) + .def("SetBinCCForSchemeSwitch", &CC::SetBinCCForSchemeSwitch, py::arg("binFHEContext"), + py::doc(cc_SetBinCCForSchemeSwitch_docs)) + .def("SetParamsFromCKKSCryptocontext", &CC::SetParamsFromCKKSCryptocontext, py::arg("params"), + py::doc(cc_SetParamsFromCKKSCryptocontext_docs)) + .def("GetSwkFC", &CC::GetSwkFC, + py::doc(cc_GetSwkFC_docs)) + .def("SetSwkFC", &CC::SetSwkFC, py::arg("switchingKey"), + py::doc(cc_SetSwkFC_docs)) //void EvalSchemeSwitchingKeyGen(const KeyPair &keyPair, ConstLWEPrivateKey &lwesk, uint32_t numValues = 0, bool oneHot = true, bool alt = false, uint32_t dim1CF = 0, uint32_t dim1FC = 0, uint32_t LCF = 1, uint32_t LFC = 0) .def("EvalSchemeSwitchingKeyGen", &CryptoContextImpl::EvalSchemeSwitchingKeyGen, py::arg("keyPair"), @@ -1239,28 +1271,36 @@ void bind_crypto_context(py::module &m) { for (const auto& [keyTag, keys] : CC::GetAllEvalAutomorphismKeys()) result[py::str(keyTag)] = py::cast(keys); return result; - }) + }, + py::doc(cc_GetAllEvalAutomorphismKeys_docs)) .def_static("GetAllEvalMultKeys", []() { py::dict result; for (const auto& [keyTag, keys] : CC::GetAllEvalMultKeys()) result[py::str(keyTag)] = py::cast(keys); return result; - }) + }, + py::doc(cc_GetAllEvalMultKeys_docs)) .def_static("GetAllEvalSumKeys", []() { py::dict result; for (const auto& [keyTag, keys] : CC::GetAllEvalSumKeys()) result[py::str(keyTag)] = py::cast(keys); return result; - }) + }, + py::doc(cc_GetAllEvalSumKeys_docs)) .def_static("GetEvalAutomorphismNoKeyIndices", &CC::GetEvalAutomorphismNoKeyIndices, - py::arg("keyTag"), py::arg("indices")) + py::arg("keyTag"), py::arg("indices"), + py::doc(cc_GetEvalAutomorphismNoKeyIndices_docs)) .def_static("GetUniqueValues", &CC::GetUniqueValues, - py::arg("oldValues"), py::arg("newValues")) + py::arg("oldValues"), py::arg("newValues"), + py::doc(cc_GetUniqueValues_docs)) .def_static("GetPlaintextForDecrypt", &CC::GetPlaintextForDecrypt, py::arg("encodingType"), py::arg("elementParams"), py::arg("encodingParams"), - py::arg("ckksDataType") = CKKSDataType::REAL) - .def("SerializedObjectName", &CC::SerializedObjectName) - .def_static("SerializedVersion", &CC::SerializedVersion) + py::arg("ckksDataType") = CKKSDataType::REAL, + py::doc(cc_GetPlaintextForDecrypt_docs)) + .def("SerializedObjectName", &CC::SerializedObjectName, + py::doc(cc_SerializedObjectName_docs)) + .def_static("SerializedVersion", &CC::SerializedVersion, + py::doc(cc_SerializedVersion_docs)) .def_static("SerializeEvalSumKey", [](const std::string& filename, const SerType::SERBINARY& sertype, const std::string& keyTag) { std::ofstream outfile(filename, std::ios::out | std::ios::binary); @@ -1271,7 +1311,8 @@ void bind_crypto_context(py::module &m) { bool res = CC::SerializeEvalSumKey(outfile, sertype, keyTag); outfile.close(); return res; - }, py::arg("filename"), py::arg("sertype"), py::arg("keyTag") = "") + }, py::arg("filename"), py::arg("sertype"), py::arg("keyTag") = "", + py::doc(cc_SerializeEvalSumKey_docs)) .def_static("SerializeEvalSumKey", [](const std::string& filename, const SerType::SERJSON& sertype, const std::string& keyTag) { std::ofstream outfile(filename, std::ios::out | std::ios::binary); @@ -1282,7 +1323,8 @@ void bind_crypto_context(py::module &m) { bool res = CC::SerializeEvalSumKey(outfile, sertype, keyTag); outfile.close(); return res; - }, py::arg("filename"), py::arg("sertype"), py::arg("keyTag") = "") + }, py::arg("filename"), py::arg("sertype"), py::arg("keyTag") = "", + py::doc(cc_SerializeEvalSumKey_docs)) // the CryptoContext overloads serialize only the keys belonging to the given context .def_static("SerializeEvalSumKey", [](const std::string& filename, const SerType::SERBINARY& sertype, const CryptoContext cryptoContext) { @@ -1294,7 +1336,8 @@ void bind_crypto_context(py::module &m) { bool res = CC::SerializeEvalSumKey(outfile, sertype, cryptoContext); outfile.close(); return res; - }, py::arg("filename"), py::arg("sertype"), py::arg("cryptoContext")) + }, py::arg("filename"), py::arg("sertype"), py::arg("cryptoContext"), + py::doc(cc_SerializeEvalSumKeyByContext_docs)) .def_static("SerializeEvalSumKey", [](const std::string& filename, const SerType::SERJSON& sertype, const CryptoContext cryptoContext) { std::ofstream outfile(filename, std::ios::out | std::ios::binary); @@ -1305,7 +1348,8 @@ void bind_crypto_context(py::module &m) { bool res = CC::SerializeEvalSumKey(outfile, sertype, cryptoContext); outfile.close(); return res; - }, py::arg("filename"), py::arg("sertype"), py::arg("cryptoContext")) + }, py::arg("filename"), py::arg("sertype"), py::arg("cryptoContext"), + py::doc(cc_SerializeEvalSumKeyByContext_docs)) .def_static("SerializeEvalMultKey", [](const std::string &filename, const SerType::SERBINARY &sertype, std::string keyTag = "") { std::ofstream outfile(filename, std::ios::out | std::ios::binary); bool res = CryptoContextImpl::SerializeEvalMultKey(outfile, sertype, keyTag); @@ -1353,7 +1397,8 @@ void bind_crypto_context(py::module &m) { return false; } return CC::DeserializeEvalSumKey(infile, sertype); - }, py::arg("filename"), py::arg("sertype")) + }, py::arg("filename"), py::arg("sertype"), + py::doc(cc_DeserializeEvalSumKey_docs)) .def_static("DeserializeEvalSumKey", [](const std::string& filename, const SerType::SERJSON& sertype) { std::ifstream infile(filename, std::ios::in | std::ios::binary); if (!infile.is_open()) { @@ -1361,7 +1406,8 @@ void bind_crypto_context(py::module &m) { return false; } return CC::DeserializeEvalSumKey(infile, sertype); - }, py::arg("filename"), py::arg("sertype")) + }, py::arg("filename"), py::arg("sertype"), + py::doc(cc_DeserializeEvalSumKey_docs)) .def_static("DeserializeEvalMultKey", [](const std::string &filename, const SerType::SERBINARY &sertype) { std::ifstream emkeys(filename, std::ios::in | std::ios::binary); if (!emkeys.is_open()) { @@ -1646,7 +1692,8 @@ void bind_enums_and_constants(py::module &m) { void bind_keys(py::module &m) { py::class_, std::shared_ptr>>(m, "PublicKey") .def(py::init<>()) - .def("GetCryptoContext", &PublicKeyImpl::GetCryptoContext) + .def("GetCryptoContext", &PublicKeyImpl::GetCryptoContext, + py::doc(key_GetCryptoContext_docs)) .def("GetKeyTag", &PublicKeyImpl::GetKeyTag) .def("SetKeyTag", &PublicKeyImpl::SetKeyTag); py::class_, std::shared_ptr>>(m, "PrivateKey") @@ -1748,30 +1795,41 @@ class PlaintextImpl_helper : public PlaintextImpl { void bind_encodings(py::module &m) { py::class_(m, "EncodingParams") - .def("GetPlaintextModulus", &EncodingParamsImpl::GetPlaintextModulus) - .def("GetBatchSize", &EncodingParamsImpl::GetBatchSize) - .def("GetPlaintextGenerator", &EncodingParamsImpl::GetPlaintextGenerator); + .def("GetPlaintextModulus", &EncodingParamsImpl::GetPlaintextModulus, + py::doc(encparams_GetPlaintextModulus_docs)) + .def("GetBatchSize", &EncodingParamsImpl::GetBatchSize, + py::doc(encparams_GetBatchSize_docs)) + .def("GetPlaintextGenerator", &EncodingParamsImpl::GetPlaintextGenerator, + py::doc(encparams_GetPlaintextGenerator_docs)); py::class_, PlaintextImpl_helper>(m, "Plaintext") .def("GetScalingFactor", &PlaintextImpl::GetScalingFactor, ptx_GetScalingFactor_docs) .def("GetScalingFactorInt", [](const PlaintextImpl& self) { return openfhe_python::IntegerToPyInt(self.GetScalingFactorInt()); - }) + }, + py::doc(ptx_GetScalingFactorInt_docs)) .def("SetScalingFactorInt", [](PlaintextImpl& self, const py::int_& sf) { self.SetScalingFactorInt(openfhe_python::PyIntToInteger(sf)); - }, py::arg("sf")) + }, py::arg("sf"), + py::doc(ptx_SetScalingFactorInt_docs)) .def("SetScalingFactor", &PlaintextImpl::SetScalingFactor, py::arg("sf"), py::doc(ptx_SetScalingFactor_docs)) .def("GetSchemeID", &PlaintextImpl::GetSchemeID, ptx_GetSchemeID_docs) - .def("GetEncodingType", &PlaintextImpl::GetEncodingType) - .def("GetEncodingParams", &PlaintextImpl::GetEncodingParams) - .def("GetCKKSDataType", &PlaintextImpl::GetCKKSDataType) - .def("SetCKKSDataType", &PlaintextImpl::SetCKKSDataType, py::arg("dataType")) - .def("GetElementRingDimension", &PlaintextImpl::GetElementRingDimension) + .def("GetEncodingType", &PlaintextImpl::GetEncodingType, + py::doc(ptx_GetEncodingType_docs)) + .def("GetEncodingParams", &PlaintextImpl::GetEncodingParams, + py::doc(ptx_GetEncodingParams_docs)) + .def("GetCKKSDataType", &PlaintextImpl::GetCKKSDataType, + py::doc(ptx_GetCKKSDataType_docs)) + .def("SetCKKSDataType", &PlaintextImpl::SetCKKSDataType, py::arg("dataType"), + py::doc(ptx_SetCKKSDataType_docs)) + .def("GetElementRingDimension", &PlaintextImpl::GetElementRingDimension, + py::doc(ptx_GetElementRingDimension_docs)) .def("GetElementModulus", [](const PlaintextImpl& self) { return openfhe_python::IntegerToPyInt(self.GetElementModulus()); - }) + }, + py::doc(ptx_GetElementModulus_docs)) .def("GetLength", &PlaintextImpl::GetLength, ptx_GetLength_docs) .def("SetLength", &PlaintextImpl::SetLength, py::arg("newSize"), @@ -1829,24 +1887,30 @@ void bind_ciphertext(py::module &m) { py::arg("level"), py::doc(ctx_SetLevel_docs)) .def("Clone", &CiphertextImpl::Clone) - .def("CloneEmpty", &CiphertextImpl::CloneEmpty) - .def("NumberCiphertextElements", &CiphertextImpl::NumberCiphertextElements) + .def("CloneEmpty", &CiphertextImpl::CloneEmpty, + py::doc(ctx_CloneEmpty_docs)) + .def("NumberCiphertextElements", &CiphertextImpl::NumberCiphertextElements, + py::doc(ctx_NumberCiphertextElements_docs)) .def("RemoveElement", [](Ciphertext& self, uint32_t index) { self->GetElements().erase(self->GetElements().begin() + index); }, py::arg("index"), py::doc(cc_RemoveElement_docs)) - .def("GetHopLevel", &CiphertextImpl::GetHopLevel) - .def("SetHopLevel", &CiphertextImpl::SetHopLevel, py::arg("hopLevel")) + .def("GetHopLevel", &CiphertextImpl::GetHopLevel, + py::doc(ctx_GetHopLevel_docs)) + .def("SetHopLevel", &CiphertextImpl::SetHopLevel, py::arg("hopLevel"), + py::doc(ctx_SetHopLevel_docs)) .def("GetScalingFactor", &CiphertextImpl::GetScalingFactor) .def("SetScalingFactor", &CiphertextImpl::SetScalingFactor) .def("GetScalingFactorInt", [](const CiphertextImpl& self) { return openfhe_python::IntegerToPyInt(self.GetScalingFactorInt()); - }) + }, + py::doc(ctx_GetScalingFactorInt_docs)) .def("SetScalingFactorInt", [](CiphertextImpl& self, const py::int_& sf) { self.SetScalingFactorInt(openfhe_python::PyIntToInteger(sf)); - }, py::arg("sf")) + }, py::arg("sf"), + py::doc(ctx_SetScalingFactorInt_docs)) .def("GetSlots", &CiphertextImpl::GetSlots) .def("SetSlots", &CiphertextImpl::SetSlots) .def("GetNoiseScaleDeg", &CiphertextImpl::GetNoiseScaleDeg) @@ -1854,8 +1918,10 @@ void bind_ciphertext(py::module &m) { .def("GetCryptoContext", &CiphertextImpl::GetCryptoContext) .def("GetKeyTag", &CiphertextImpl::GetKeyTag) .def("GetEncodingType", &CiphertextImpl::GetEncodingType) - .def("SetEncodingType", &CiphertextImpl::SetEncodingType, py::arg("encodingType")) - .def("SetKeyTag", &CiphertextImpl::SetKeyTag, py::arg("keyTag")) + .def("SetEncodingType", &CiphertextImpl::SetEncodingType, py::arg("encodingType"), + py::doc(ctx_SetEncodingType_docs)) + .def("SetKeyTag", &CiphertextImpl::SetKeyTag, py::arg("keyTag"), + py::doc(ctx_SetKeyTag_docs)) .def("GetElements", [](const CiphertextImpl& self) -> const std::vector& { return self.GetElements(); }, @@ -1913,7 +1979,8 @@ void bind_sch_swch_params(py::module &m) { .def("SetRingDimension", &SchSwchParams::SetRingDimension) .def("SetScalingModSize", &SchSwchParams::SetScalingModSize) .def("SetBatchSize", &SchSwchParams::SetBatchSize) - .def("SetParamsFromCKKSCryptocontextCalled", &SchSwchParams::SetParamsFromCKKSCryptocontextCalled) + .def("SetParamsFromCKKSCryptocontextCalled", &SchSwchParams::SetParamsFromCKKSCryptocontextCalled, + py::doc(schswchparams_SetParamsFromCKKSCryptocontextCalled_docs)) .def("__str__", [](const SchSwchParams ¶ms) { std::stringstream stream; stream << params; @@ -1923,9 +1990,9 @@ void bind_sch_swch_params(py::module &m) { void bind_utils(py::module& m) { m.def("EnablePrecomputeCRTTablesAfterDeserializaton", &EnablePrecomputeCRTTablesAfterDeserializaton, - py::doc("Enable CRT precomputation after deserialization")); + py::doc(utils_EnablePrecomputeCRTTablesAfterDeserializaton_docs)); m.def("DisablePrecomputeCRTTablesAfterDeserializaton", &DisablePrecomputeCRTTablesAfterDeserializaton, - py::doc("Disable CRT precomputation after deserialization")); + py::doc(utils_DisablePrecomputeCRTTablesAfterDeserializaton_docs)); } void bind_free_functions(py::module& m) { diff --git a/src/lib/binfhe_bindings.cpp b/src/lib/binfhe_bindings.cpp index 7c617d3..33a7527 100644 --- a/src/lib/binfhe_bindings.cpp +++ b/src/lib/binfhe_bindings.cpp @@ -169,7 +169,8 @@ void bind_binfhe_keys(py::module &m) { .def("GetModulus", [](const LWEPrivateKeyImpl& self) { return openfhe_python::IntegerToPyInt(self.GetModulus()); - }) + }, + py::doc(binfhe_LWEPrivateKeyGetModulus_docs)) .def(py::self == py::self) .def(py::self != py::self); @@ -179,7 +180,8 @@ void bind_binfhe_keys(py::module &m) { .def("GetModulus", [](const LWEPublicKeyImpl& self) { return openfhe_python::IntegerToPyInt(self.GetModulus()); - }) + }, + py::doc(binfhe_LWEPublicKeyGetModulus_docs)) .def(py::self == py::self) .def(py::self != py::self); @@ -207,17 +209,20 @@ void bind_binfhe_ciphertext(py::module &m) { .def("GetptModulus", [](const LWECiphertextImpl& self) { return openfhe_python::IntegerToPyInt(self.GetptModulus()); - }) + }, + py::doc(binfhe_LWECiphertextGetptModulus_docs)) .def("SetModulus", [](LWECiphertextImpl& self, const py::int_& modulus) { self.SetModulus(openfhe_python::PyIntToInteger(modulus)); }, - py::arg("modulus")) + py::arg("modulus"), + py::doc(binfhe_LWECiphertextSetModulus_docs)) .def("SetptModulus", [](LWECiphertextImpl& self, const py::int_& plaintextModulus) { self.SetptModulus(openfhe_python::PyIntToInteger(plaintextModulus)); }, - py::arg("plaintextModulus")) + py::arg("plaintextModulus"), + py::doc(binfhe_LWECiphertextSetptModulus_docs)) .def(py::self == py::self) .def(py::self != py::self); } @@ -247,12 +252,15 @@ void bind_binfhe_context(py::module &m) { .def("KeyGenPair", &BinFHEContext::KeyGenPair) .def("KeySwitchGen", &BinFHEContext::KeySwitchGen, py::arg("sk"), - py::arg("skN")) + py::arg("skN"), + py::doc(binfhe_KeySwitchGen_docs)) .def("PubKeyGen", &BinFHEContext::PubKeyGen, - py::arg("sk")) + py::arg("sk"), + py::doc(binfhe_PubKeyGen_docs)) .def("SwitchCTtoqn", &BinFHEContext::SwitchCTtoqn, py::arg("ksk"), - py::arg("ct")) + py::arg("ct"), + py::doc(binfhe_SwitchCTtoqn_docs)) .def("BTKeyGen", &BinFHEContext::BTKeyGen, binfhe_BTKeyGen_docs, py::arg("sk"), @@ -348,8 +356,10 @@ void bind_binfhe_context(py::module &m) { py::arg("schemeSwitch") = false) .def("EvalConstant", &BinFHEContext::EvalConstant) .def("ClearBTKeys", &BinFHEContext::ClearBTKeys) - .def("HasInternal32RefreshKey", &BinFHEContext::HasInternal32RefreshKey) - .def("HasInternal32SwitchKey", &BinFHEContext::HasInternal32SwitchKey) + .def("HasInternal32RefreshKey", &BinFHEContext::HasInternal32RefreshKey, + py::doc(binfhe_HasInternal32RefreshKey_docs)) + .def("HasInternal32SwitchKey", &BinFHEContext::HasInternal32SwitchKey, + py::doc(binfhe_HasInternal32SwitchKey_docs)) .def("Bootstrap", &BinFHEContext::Bootstrap, py::arg("ct"), py::arg("extended") = false) .def("SerializedVersion", &BinFHEContext::SerializedVersion, binfhe_SerializedVersion_docs) diff --git a/src/lib/pke/fbt.cpp b/src/lib/pke/fbt.cpp index 1244a60..829f467 100644 --- a/src/lib/pke/fbt.cpp +++ b/src/lib/pke/fbt.cpp @@ -34,6 +34,7 @@ // the scheme-switching data (de)serializer. #include "bindings.h" +#include "docstrings/fbt_docs.h" #include "integer_conversions.h" #include "openfhe.h" @@ -57,6 +58,7 @@ using namespace lbcrypto; using CC = CryptoContextImpl; namespace py = pybind11; + // disable the PYBIND11 template-based conversion for this type: a vector of Poly is // treated as an opaque RLWE ciphertext produced/consumed by SchemeletRLWEMP PYBIND11_MAKE_OPAQUE(std::vector); @@ -187,10 +189,12 @@ void bind_fbt_crypto_context(DCRTCryptoContextClass &cls) { py::arg("ciphertexts"), py::arg("coeffs"), py::arg("digitBitSize"), py::arg("order") = (size_t)1) .def("EvalHermiteTrigSeries", &CryptoContextImpl::EvalHermiteTrigSeries, py::arg("ciphertext"), py::arg("coefficientsCheb"), py::arg("a"), py::arg("b"), - py::arg("coefficientsHerm"), py::arg("precomp") = (size_t)0) + py::arg("coefficientsHerm"), py::arg("precomp") = (size_t)0, + py::doc(cc_EvalHermiteTrigSeries_docs)) .def("EvalHermiteTrigSeries", &CryptoContextImpl::EvalHermiteTrigSeries>, py::arg("ciphertext"), py::arg("coefficientsCheb"), py::arg("a"), py::arg("b"), - py::arg("coefficientsHerm"), py::arg("precomp") = (size_t)0) + py::arg("coefficientsHerm"), py::arg("precomp") = (size_t)0, + py::doc(cc_EvalHermiteTrigSeries_docs)) // FE functional bootstrapping .def("EvalFEFuncBootstrapSetup", &CryptoContextImpl::EvalFEFuncBootstrapSetup, py::arg("levelBudget") = std::vector({5, 4}), From 58f699d1b3b4a879fe3ad0bc83f8e7e6590643e2 Mon Sep 17 00:00:00 2001 From: Dmitriy Suponitskiy Date: Thu, 15 Oct 2026 16:03:08 -0400 Subject: [PATCH 09/16] Fix binding and build bugs, remove dead code, and list all examples in README - Fix MakeCoefPackedPlaintext kwarg bound as "noiseScaleDeg " (trailing space) - Return false early when EvalMultKey/EvalAutomorphismKey files cannot be opened - Make the GenerateLUTviaFunction callback exception-safe via thread_local + guard - Remove BinFHE bindings that could never be called (cereal save/load, getters returning unregistered types) and the unused PKE serialization wrappers/header - Replace manual enum re-export blocks with py::enum_::export_values() - Add missing py::arg names (CCParams/SchSwchParams setters, keys, plaintext, ciphertext) - Select Python before pybind11 in CMake and install to sysconfig platlib - Add regression tests; list all 59 examples in the README with fixed links --- CMakeLists.txt | 45 ++-- README.md | 63 ++++- src/include/bindings.h | 1 - src/include/pke/serialization.h | 82 ------ src/lib/bindings.cpp | 305 +++++++++-------------- src/lib/binfhe/binfhecontext_wrapper.cpp | 19 +- src/lib/binfhe_bindings.cpp | 82 ++---- src/lib/pke/serialization.cpp | 29 +-- tests/test_api_coverage.py | 19 ++ tests/test_bgv.py | 7 + tests/test_binfhe_api_coverage.py | 17 ++ 11 files changed, 255 insertions(+), 414 deletions(-) delete mode 100644 src/include/pke/serialization.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 03f5441..99f2e31 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,13 +4,13 @@ project (OpenFHE-Python) set(OPENFHE_PYTHON_VERSION_MAJOR 1) set(OPENFHE_PYTHON_VERSION_MINOR 6) -set(OPENFHE_PYTHON_VERSION_PATCH 0) +set(OPENFHE_PYTHON_VERSION_PATCH 1) set(OPENFHE_PYTHON_VERSION_TWEAK 0) set(OPENFHE_PYTHON_VERSION ${OPENFHE_PYTHON_VERSION_MAJOR}.${OPENFHE_PYTHON_VERSION_MINOR}.${OPENFHE_PYTHON_VERSION_PATCH}.${OPENFHE_PYTHON_VERSION_TWEAK}) -# OpenFHE version can be specified externally (-DOPENFHE_REQUIRED_VERSION=1.6.0) +# OpenFHE version can be specified externally (-DOPENFHE_REQUIRED_VERSION=1.6.1) if(NOT DEFINED OPENFHE_REQUIRED_VERSION) - set(OPENFHE_REQUIRED_VERSION "1.6.0" CACHE STRING "Required OpenFHE version") + set(OPENFHE_REQUIRED_VERSION "1.6.1" CACHE STRING "Required OpenFHE version") else() # User provided OPENFHE_REQUIRED_VERSION via -D message(STATUS "Using user-specified OpenFHE version: ${OPENFHE_REQUIRED_VERSION}") @@ -26,6 +26,15 @@ endif() find_package(OpenFHE ${OPENFHE_REQUIRED_VERSION} REQUIRED) message(STATUS "Building with OpenFHE version: ${OPENFHE_REQUIRED_VERSION}") +# Allow the user to specify the path to the Python executable (if not provided, find it). +# Python must be selected before pybind11 is found: pybind11 locks in the first Python +# that CMake discovers, and a later find_package(Python) with a different interpreter fails. +set(PYTHON_EXECUTABLE_PATH "" CACHE FILEPATH "Path to Python executable") +if(PYTHON_EXECUTABLE_PATH) + set(Python_EXECUTABLE "${PYTHON_EXECUTABLE_PATH}") +endif() +find_package(Python REQUIRED COMPONENTS Interpreter Development) + set(PYBIND11_FINDPYTHON ON) find_package(pybind11 REQUIRED) @@ -99,28 +108,14 @@ elseif(APPLE) endif() -### Python installation -# Allow the user to specify the path to Python executable (if not provided, find it) -option(PYTHON_EXECUTABLE_PATH "Path to Python executable" "") -if(PYTHON_EXECUTABLE_PATH) - set(Python_EXECUTABLE "${PYTHON_EXECUTABLE_PATH}") -endif() -find_package(Python REQUIRED COMPONENTS Interpreter Development) - -# Check Python version -if(${Python_VERSION_MAJOR} EQUAL 3 AND ${Python_VERSION_MINOR} GREATER_EQUAL 10) - execute_process( - COMMAND "${Python_EXECUTABLE}" -c "from sys import exec_prefix; print(exec_prefix)" - OUTPUT_VARIABLE PYTHON_SITE_PACKAGES - OUTPUT_STRIP_TRAILING_WHITESPACE - ) -else() - execute_process( - COMMAND "${Python_EXECUTABLE}" -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())" - OUTPUT_VARIABLE PYTHON_SITE_PACKAGES - OUTPUT_STRIP_TRAILING_WHITESPACE - ) -endif() +### Python installation +# sysconfig's platlib is the platform-specific site-packages directory (also inside venvs); +# sys.exec_prefix used here previously is the installation root, not site-packages. +execute_process( + COMMAND "${Python_EXECUTABLE}" -c "import sysconfig; print(sysconfig.get_path('platlib'))" + OUTPUT_VARIABLE PYTHON_SITE_PACKAGES + OUTPUT_STRIP_TRAILING_WHITESPACE +) message(STATUS "Python site packages directory: ${PYTHON_SITE_PACKAGES}") if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) diff --git a/README.md b/README.md index 68a179c..473227c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![License](https://img.shields.io/badge/License-BSD%202--Clause-blue.svg)](https://opensource.org/licenses/BSD-2-Clause) [![Python Versions](https://img.shields.io/badge/python-3.10%2B-blue)](https://www.python.org/) -[![OpenFHE Version](https://img.shields.io/badge/OpenFHE-1.6.0%2B-green)](https://github.com/openfheorg/openfhe-development) +[![OpenFHE Version](https://img.shields.io/badge/OpenFHE-1.6.1%2B-green)](https://github.com/openfheorg/openfhe-development) Python wrapper for the main cryptographic capabilities of the OpenFHE C++ library. `OpenFHE-Python` provides a more user-friendly interface for Python developers, while keeping the efficiency of C++ FHE operations. @@ -42,7 +42,7 @@ The legacy build instructions below are for other operating systems where [openf Before building, make sure you have the following dependencies installed: -- [OpenFHE 1.6.0+](https://github.com/openfheorg/openfhe-development) by following the instructions in [OpenFHE Documentation](https://openfhe-development.readthedocs.io/en/latest/sphinx_rsts/intro/installation/installation.html) +- [OpenFHE 1.6.1+](https://github.com/openfheorg/openfhe-development) by following the instructions in [OpenFHE Documentation](https://openfhe-development.readthedocs.io/en/latest/sphinx_rsts/intro/installation/installation.html) - [Python 3.10+](https://www.python.org/) - [pybind11](https://pybind11.readthedocs.io/en/stable/installing.html) @@ -127,33 +127,74 @@ To get familiar with the OpenFHE Python API, check out the examples: - FHE for arithmetic over integers (BFV): - [Simple Code Example](examples/pke/simple-integers.py) - + - [Simple Code Example with Serialization](examples/pke/simple-integers-serial.py) + - [Computation Circuit of Depth 3](examples/pke/depth-bfvrns.py) + - [Computation Circuit of Depth 3 with the BEHZ Multiplication Technique](examples/pke/depth-bfvrns-behz.py) - FHE for arithmetic over integers (BGV): - [Simple Code Example](examples/pke/simple-integers-bgvrns.py) - + - [Simple Code Example with Serialization](examples/pke/simple-integers-serial-bgvrns.py) + - [Computation Circuit of Depth 3](examples/pke/depth-bgvrns.py) - FHE for arithmetic over real numbers (CKKS): - [Simple Code Example](examples/pke/simple-real-numbers.py) + - [Simple Code Example with Serialization](examples/pke/simple-real-numbers-serial.py) + - [Simple Code Example for Complex Numbers](examples/pke/simple-complex-numbers.py) - [Advanced Code Example](examples/pke/advanced-real-numbers.py) - [Advanced Code Example for High-Precision CKKS](examples/pke/advanced-real-numbers-128.py) - [Arbitrary Smooth Function Evaluation](examples/pke/function-evaluation.py) + - [Polynomial Evaluation](examples/pke/polynomial-evaluation.py) + - [Linear Weighted Sum Evaluation](examples/pke/linearwsum-evaluation.py) + - [Decryption with Noise Flooding](examples/pke/ckks-noise-flooding.py) + - [Releasing Memory Used by Precomputations](examples/pke/ckks-release-memory.py) +- CKKS with Composite Scaling: + - [Simple Code Example](examples/pke/simple-real-numbers-composite-scaling.py) + - [Simple Code Example with Manual Rescaling](examples/pke/simple-composite-scaling-manual.py) + - [High-Precision Polynomial Evaluation](examples/pke/polynomial-evaluation-high-precision-composite-scaling.py) +- CKKS Bootstrapping: - [Simple CKKS Bootstrapping Example](examples/pke/simple-ckks-bootstrapping.py) - - [Advanced CKKS Bootstrapping Example](examples/pke/advanced-ckks-bootstrapping.cpp) + - [Advanced CKKS Bootstrapping Example](examples/pke/advanced-ckks-bootstrapping.py) - [Double-Precision (Iterative) Bootstrapping Example](examples/pke/iterative-ckks-bootstrapping.py) + - [Simple Bootstrapping Example with Composite Scaling](examples/pke/simple-ckks-bootstrapping-composite-scaling.py) + - [Double-Precision (Iterative) Bootstrapping Example with Composite Scaling](examples/pke/iterative-ckks-bootstrapping-composite-scaling.py) + - [Serialization of Bootstrapping Keys](examples/pke/ckks-bootstrap-keys-serial.py) + - [Functional Bootstrapping for RLWE Ciphertexts](examples/pke/functional-bootstrapping-ckks.py) + - [Fourier-Extension (FE) Functional Bootstrapping](examples/pke/FE-functional-bootstrapping-ckks.py) +- Operations over Encrypted Vectors (BFV and CKKS): + - [Vector Rotation Example](examples/pke/rotation.py) + - [Inner Product Example](examples/pke/inner-product.py) +- Proxy Re-Encryption (PRE): + - [Simple Code Example with Buffers (BFV)](examples/pke/pre-buffer.py) + - [HRA-Secure PRE with 13 Hops (BGV)](examples/pke/pre-hra-secure.py) - FHE for Boolean circuits and larger plaintext spaces (FHEW/TFHE): - [Simple Code Example with Symmetric Encryption](examples/binfhe/boolean.py) - - [Truth Table Example](examples/binfhe/boolean-truth-table.py) + - [Simple Code Example with the AP Bootstrapping Method](examples/binfhe/boolean-ap.py) + - [Simple Code Example with the LMKCDEY Bootstrapping Method](examples/binfhe/boolean-lmkcdey.py) + - [Multi-Input Gates Example](examples/binfhe/boolean-multi-input.py) + - [Truth Table Example](examples/binfhe/boolean-truth-tables.py) + - [Code with JSON Serialization](examples/binfhe/boolean-serial-json.py) + - [Code with Binary Serialization](examples/binfhe/boolean-serial-binary.py) + - [Large-Precision Comparison](examples/binfhe/eval-sign.py) + - [Small-Precision Arbitrary Function Evaluation](examples/binfhe/eval-function.py) + - [Homomorphic Digit Decomposition](examples/binfhe/eval-decomp.py) + - [Homomorphic Flooring](examples/binfhe/eval-flooring.py) + - [JSON Serialization for Large-Precision Sign Evaluation with Dynamic Bootstrapping Keys](examples/binfhe/boolean-serial-json-dynamic-large-precision.py) + - [Binary Serialization for Large-Precision Sign Evaluation with Dynamic Bootstrapping Keys](examples/binfhe/boolean-serial-binary-dynamic-large-precision.py) +- FHEW/TFHE with Public-Key Encryption: + - [Simple Code Example](examples/binfhe/pke/boolean-pke.py) + - [Simple Code Example with the AP Bootstrapping Method](examples/binfhe/pke/boolean-ap-pke.py) + - [Truth Table Example](examples/binfhe/pke/boolean-truth-tables-pke.py) + - [Code with JSON Serialization](examples/binfhe/pke/boolean-serial-json-pke.py) + - [Code with Binary Serialization](examples/binfhe/pke/boolean-serial-binary-pke.py) + - [Small-Precision Arbitrary Function Evaluation](examples/binfhe/pke/eval-function-pke.py) + - [Homomorphic Flooring](examples/binfhe/pke/eval-flooring-pke.py) - Scheme Switching: - [Examples with Scheme Switching between CKKS and FHEW/TFHE](examples/pke/scheme-switching.py) - - - - + - [Scheme Switching with Serialization](examples/pke/scheme-switching-serial.py) - Threshold FHE: - [Code Example for BGV, BFV, and CKKS](examples/pke/threshold-fhe.py) + - [Code Example for BFV with 5 parties](examples/pke/threshold-fhe-5p.py) - [2-party Interactive Bootstrapping Examples](examples/pke/interactive-bootstrapping.py) - [Simple n-party Interactive Bootstrapping Example](examples/pke/tckks-interactive-mp-bootstrapping.py) - [n-party Interactive Bootstrapping after Chebyshev Approximation](examples/pke/tckks-interactive-mp-bootstrapping-Chebyshev.py) - - [Code Example for BFV with 5 parties](examples/pke/threshold-fhe-5p.py) ## OpenFHE Python Wrapper Documentation diff --git a/src/include/bindings.h b/src/include/bindings.h index afe886f..cf238c8 100644 --- a/src/include/bindings.h +++ b/src/include/bindings.h @@ -41,7 +41,6 @@ using DCRTCryptoContextClass = pybind11::class_< lbcrypto::CryptoContextImpl, std::shared_ptr>>; -void bind_parameters(pybind11::module &m); void bind_crypto_context(pybind11::module &m); void bind_enums_and_constants(pybind11::module &m); void bind_keys(pybind11::module &m); diff --git a/src/include/pke/serialization.h b/src/include/pke/serialization.h deleted file mode 100644 index 7a19c6f..0000000 --- a/src/include/pke/serialization.h +++ /dev/null @@ -1,82 +0,0 @@ -//================================================================================== -// BSD 2-Clause License -// -// Copyright (c) 2023-2025, Duality Technologies Inc. and other contributors -// -// All rights reserved. -// -// Author TPOC: contact@openfhe.org -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -//================================================================================== -#ifndef __SERIALIZATION_H__ -#define __SERIALIZATION_H__ - -#include - - -template -bool SerializeEvalMultKeyWrapper(const std::string& filename, const ST& sertype, std::string id); - -template -bool SerializeEvalAutomorphismKeyWrapper(const std::string& filename, const ST& sertype, std::string id); - -template -bool DeserializeEvalMultKeyWrapper(const std::string& filename, const ST& sertype); - -template -std::string SerializeToStringWrapper(const T& obj, const ST& sertype); - -template -pybind11::bytes SerializeToBytesWrapper(const T& obj, const ST& sertype); - -template -T DeserializeFromStringWrapper(const std::string& str, const ST& sertype); - -template -T DeserializeFromBytesWrapper(const pybind11::bytes& bytes, const ST& sertype); - -template -std::string SerializeEvalMultKeyToStringWrapper(const ST& sertype, const std::string& id); - -template -pybind11::bytes SerializeEvalMultKeyToBytesWrapper(const ST& sertype, const std::string& id); - -template -std::string SerializeEvalAutomorphismKeyToStringWrapper(const ST& sertype, const std::string& id); - -template -pybind11::bytes SerializeEvalAutomorphismKeyToBytesWrapper(const ST& sertype, const std::string& id); - -template -void DeserializeEvalMultKeyFromStringWrapper(const std::string& data, const ST& sertype); - -template -void DeserializeEvalMultKeyFromBytesWrapper(const std::string& data, const ST& sertype); - -template -void DeserializeEvalAutomorphismKeyFromStringWrapper(const std::string& data, const ST& sertype); - -template -void DeserializeEvalAutomorphismKeyFromBytesWrapper(const std::string& data, const ST& sertype); - -#endif // __SERIALIZATION_H__ \ No newline at end of file diff --git a/src/lib/bindings.cpp b/src/lib/bindings.cpp index eac453d..bc1181c 100644 --- a/src/lib/bindings.cpp +++ b/src/lib/bindings.cpp @@ -107,38 +107,38 @@ void bind_parameters(py::module &m, const std::string name) { .def("GetRegisterWordSize", &CCParams::GetRegisterWordSize) .def("GetCKKSDataType", &CCParams::GetCKKSDataType) // setters - .def("SetPlaintextModulus", &CCParams::SetPlaintextModulus) - .def("SetDigitSize", &CCParams::SetDigitSize) - .def("SetStandardDeviation", &CCParams::SetStandardDeviation) - .def("SetSecretKeyDist", &CCParams::SetSecretKeyDist) - .def("SetMaxRelinSkDeg", &CCParams::SetMaxRelinSkDeg) - .def("SetPREMode", &CCParams::SetPREMode) - .def("SetMultipartyMode", &CCParams::SetMultipartyMode) - .def("SetExecutionMode", &CCParams::SetExecutionMode) - .def("SetDecryptionNoiseMode", &CCParams::SetDecryptionNoiseMode) - .def("SetNoiseEstimate", &CCParams::SetNoiseEstimate) - .def("SetDesiredPrecision", &CCParams::SetDesiredPrecision) - .def("SetStatisticalSecurity", &CCParams::SetStatisticalSecurity) - .def("SetNumAdversarialQueries", &CCParams::SetNumAdversarialQueries) - .def("SetThresholdNumOfParties", &CCParams::SetThresholdNumOfParties) - .def("SetKeySwitchTechnique", &CCParams::SetKeySwitchTechnique) - .def("SetScalingTechnique", &CCParams::SetScalingTechnique) - .def("SetBatchSize", &CCParams::SetBatchSize) - .def("SetFirstModSize", &CCParams::SetFirstModSize) - .def("SetNumLargeDigits", &CCParams::SetNumLargeDigits) - .def("SetMultiplicativeDepth", &CCParams::SetMultiplicativeDepth) - .def("SetScalingModSize", &CCParams::SetScalingModSize) - .def("SetSecurityLevel", &CCParams::SetSecurityLevel) - .def("SetRingDim", &CCParams::SetRingDim) - .def("SetEvalAddCount", &CCParams::SetEvalAddCount) - .def("SetKeySwitchCount", &CCParams::SetKeySwitchCount) - .def("SetEncryptionTechnique", &CCParams::SetEncryptionTechnique) - .def("SetMultiplicationTechnique", &CCParams::SetMultiplicationTechnique) - .def("SetPRENumHops", &CCParams::SetPRENumHops) - .def("SetInteractiveBootCompressionLevel", &CCParams::SetInteractiveBootCompressionLevel) - .def("SetCompositeDegree", &CCParams::SetCompositeDegree) - .def("SetRegisterWordSize", &CCParams::SetRegisterWordSize) - .def("SetCKKSDataType", &CCParams::SetCKKSDataType) + .def("SetPlaintextModulus", &CCParams::SetPlaintextModulus, py::arg("plaintextModulus")) + .def("SetDigitSize", &CCParams::SetDigitSize, py::arg("digitSize")) + .def("SetStandardDeviation", &CCParams::SetStandardDeviation, py::arg("standardDeviation")) + .def("SetSecretKeyDist", &CCParams::SetSecretKeyDist, py::arg("secretKeyDist")) + .def("SetMaxRelinSkDeg", &CCParams::SetMaxRelinSkDeg, py::arg("maxRelinSkDeg")) + .def("SetPREMode", &CCParams::SetPREMode, py::arg("PREMode")) + .def("SetMultipartyMode", &CCParams::SetMultipartyMode, py::arg("multipartyMode")) + .def("SetExecutionMode", &CCParams::SetExecutionMode, py::arg("executionMode")) + .def("SetDecryptionNoiseMode", &CCParams::SetDecryptionNoiseMode, py::arg("decryptionNoiseMode")) + .def("SetNoiseEstimate", &CCParams::SetNoiseEstimate, py::arg("noiseEstimate")) + .def("SetDesiredPrecision", &CCParams::SetDesiredPrecision, py::arg("desiredPrecision")) + .def("SetStatisticalSecurity", &CCParams::SetStatisticalSecurity, py::arg("statisticalSecurity")) + .def("SetNumAdversarialQueries", &CCParams::SetNumAdversarialQueries, py::arg("numAdversarialQueries")) + .def("SetThresholdNumOfParties", &CCParams::SetThresholdNumOfParties, py::arg("thresholdNumOfParties")) + .def("SetKeySwitchTechnique", &CCParams::SetKeySwitchTechnique, py::arg("keySwitchTechnique")) + .def("SetScalingTechnique", &CCParams::SetScalingTechnique, py::arg("scalingTechnique")) + .def("SetBatchSize", &CCParams::SetBatchSize, py::arg("batchSize")) + .def("SetFirstModSize", &CCParams::SetFirstModSize, py::arg("firstModSize")) + .def("SetNumLargeDigits", &CCParams::SetNumLargeDigits, py::arg("numLargeDigits")) + .def("SetMultiplicativeDepth", &CCParams::SetMultiplicativeDepth, py::arg("multiplicativeDepth")) + .def("SetScalingModSize", &CCParams::SetScalingModSize, py::arg("scalingModSize")) + .def("SetSecurityLevel", &CCParams::SetSecurityLevel, py::arg("securityLevel")) + .def("SetRingDim", &CCParams::SetRingDim, py::arg("ringDim")) + .def("SetEvalAddCount", &CCParams::SetEvalAddCount, py::arg("evalAddCount")) + .def("SetKeySwitchCount", &CCParams::SetKeySwitchCount, py::arg("keySwitchCount")) + .def("SetEncryptionTechnique", &CCParams::SetEncryptionTechnique, py::arg("encryptionTechnique")) + .def("SetMultiplicationTechnique", &CCParams::SetMultiplicationTechnique, py::arg("multiplicationTechnique")) + .def("SetPRENumHops", &CCParams::SetPRENumHops, py::arg("PRENumHops")) + .def("SetInteractiveBootCompressionLevel", &CCParams::SetInteractiveBootCompressionLevel, py::arg("interactiveBootCompressionLevel")) + .def("SetCompositeDegree", &CCParams::SetCompositeDegree, py::arg("compositeDegree")) + .def("SetRegisterWordSize", &CCParams::SetRegisterWordSize, py::arg("registerWordSize")) + .def("SetCKKSDataType", &CCParams::SetCKKSDataType, py::arg("ckksDataType")) .def("__str__",[](const CCParams ¶ms) { std::stringstream stream; stream << params; @@ -380,20 +380,6 @@ void bind_eval_mult_scalar_overloads(py::class_>& cls) { py::doc("")); // TODO (dsuponit): replace this with an actual docstring } -// void bind_eval_multinplace_family(py::class_>& cls) { -// cls.def("EvalMultInPlace", static_cast&, ConstCiphertext&) const>(&CC::EvalMultInPlace), -// py::arg("ciphertext1"), -// py::arg("ciphertext2"), -// py::doc(cc_EvalMultInPlace_docs)); -// cls.def("EvalMultInPlace", static_cast&, Plaintext&) const>(&CC::EvalMultInPlace), -// py::arg("ciphertext"), -// py::arg("plaintext"), -// py::doc(cc_EvalMultInPlacePlaintext_docs)); -// cls.def("EvalMultInPlace", static_cast&) const>(&CC::EvalMultInPlace), -// py::arg("plaintext"), -// py::arg("ciphertext"), -// py::doc("")); // TODO (dsuponit): replace this with an actual docstring -// } template void bind_eval_multinplace_scalar_overloads(py::class_>& cls) { cls.def("EvalMultInPlace", static_cast&, ScalarT) const>(&CC::EvalMultInPlace), @@ -571,7 +557,7 @@ void bind_crypto_context(py::module &m) { py::doc(cc_MakePackedPlaintext_docs)) .def("MakeCoefPackedPlaintext", &CryptoContextImpl::MakeCoefPackedPlaintext, py::arg("value"), - py::arg("noiseScaleDeg ") = 1, + py::arg("noiseScaleDeg") = 1, py::arg("level") = 0, py::doc(cc_MakeCoefPackedPlaintext_docs)) // TODO (Oliveira): allow user to specify different params values @@ -1000,14 +986,6 @@ void bind_crypto_context(py::module &m) { // 1. compiler tells your exactly what doesn’t match // 2. never silently selects the wrong overload if only one is visible ///////////////////////////////////////////////////////// - // .def("IntMPBootRandomElementGen", - // py::overload_cast>(&CryptoContextImpl::IntMPBootRandomElementGen, py::const_), - // py::arg("publicKey"), - // py::doc(cc_IntMPBootRandomElementGen_from_publickey_docs)) - // .def("IntMPBootRandomElementGen", - // py::overload_cast&>(&CryptoContextImpl::IntMPBootRandomElementGen, py::const_), - // py::arg("ciphertext"), - // py::doc(cc_IntMPBootRandomElementGen_from_ciphertext_docs)) .def("IntMPBootRandomElementGen", static_cast (CC::*)(PublicKey) const>(&CC::IntMPBootRandomElementGen), py::arg("publicKey"), @@ -1187,21 +1165,6 @@ void bind_crypto_context(py::module &m) { py::arg("scaleSign") = 1.0, py::doc(cc_EvalMaxSchemeSwitchingAlt_docs)) //TODO (Oliveira, R.): Solve pointer handling bug when returning EvalKeyMap objects for the next functions - // TODO (dsuponit): I'd suggest this version of EvalAutomorphismKeyGen instead: - // .def("EvalAutomorphismKeyGen", - // [](const CryptoContextImpl& cc, - // const PrivateKey& privateKey, - // const std::vector& indexList) -> py::dict { - // auto keyMapPtr = cc.EvalAutomorphismKeyGen(privateKey, indexList); - // py::dict result; - // for (const auto& kv : *keyMapPtr) { - // result[py::int_(kv.first)] = kv.second; - // } - // return result; - // }, - // py::doc(cc_EvalAutomorphismKeyGen_docs)) - // py::arg("privateKey"), - // py::arg("indexList")) .def("EvalAutomorphismKeyGen", py::overload_cast, const std::vector&>(&CryptoContextImpl::EvalAutomorphismKeyGen, py::const_), py::arg("privateKey"), @@ -1412,9 +1375,9 @@ void bind_crypto_context(py::module &m) { std::ifstream emkeys(filename, std::ios::in | std::ios::binary); if (!emkeys.is_open()) { std::cerr << "I cannot read serialization from " << filename << std::endl; + return false; } - bool res = CryptoContextImpl::DeserializeEvalMultKey(emkeys, sertype); - return res; + return CryptoContextImpl::DeserializeEvalMultKey(emkeys, sertype); }, py::arg("filename"), py::arg("sertype"), @@ -1423,9 +1386,9 @@ void bind_crypto_context(py::module &m) { std::ifstream emkeys(filename, std::ios::in | std::ios::binary); if (!emkeys.is_open()) { std::cerr << "I cannot read serialization from " << filename << std::endl; + return false; } - bool res = CryptoContextImpl::DeserializeEvalMultKey(emkeys, sertype); - return res; + return CryptoContextImpl::DeserializeEvalMultKey(emkeys, sertype); }, py::arg("filename"), py::arg("sertype"), @@ -1434,9 +1397,9 @@ void bind_crypto_context(py::module &m) { std::ifstream erkeys(filename, std::ios::in | std::ios::binary); if (!erkeys.is_open()) { std::cerr << "I cannot read serialization from " << filename << std::endl; + return false; } - bool res = CryptoContextImpl::DeserializeEvalAutomorphismKey(erkeys, sertype); - return res; + return CryptoContextImpl::DeserializeEvalAutomorphismKey(erkeys, sertype); }, py::arg("filename"), py::arg("sertype"), @@ -1445,9 +1408,9 @@ void bind_crypto_context(py::module &m) { std::ifstream erkeys(filename, std::ios::in | std::ios::binary); if (!erkeys.is_open()) { std::cerr << "I cannot read serialization from " << filename << std::endl; + return false; } - bool res = CryptoContextImpl::DeserializeEvalAutomorphismKey(erkeys, sertype); - return res; + return CryptoContextImpl::DeserializeEvalAutomorphismKey(erkeys, sertype); }, py::arg("filename"), py::arg("sertype"), @@ -1515,11 +1478,8 @@ void bind_enums_and_constants(py::module &m) { .value("INVALID_SCHEME", SCHEME::INVALID_SCHEME) .value("CKKSRNS_SCHEME", SCHEME::CKKSRNS_SCHEME) .value("BFVRNS_SCHEME", SCHEME::BFVRNS_SCHEME) - .value("BGVRNS_SCHEME", SCHEME::BGVRNS_SCHEME); - m.attr("INVALID_SCHEME") = py::cast(SCHEME::INVALID_SCHEME); - m.attr("CKKSRNS_SCHEME") = py::cast(SCHEME::CKKSRNS_SCHEME); - m.attr("BFVRNS_SCHEME") = py::cast(SCHEME::BFVRNS_SCHEME); - m.attr("BGVRNS_SCHEME") = py::cast(SCHEME::BGVRNS_SCHEME); + .value("BGVRNS_SCHEME", SCHEME::BGVRNS_SCHEME) + .export_values(); // PKE Features py::enum_(m, "PKESchemeFeature") @@ -1530,33 +1490,21 @@ void bind_enums_and_constants(py::module &m) { .value("ADVANCEDSHE", PKESchemeFeature::ADVANCEDSHE) .value("MULTIPARTY", PKESchemeFeature::MULTIPARTY) .value("FHE", PKESchemeFeature::FHE) - .value("SCHEMESWITCH", PKESchemeFeature::SCHEMESWITCH); - m.attr("PKE") = py::cast(PKESchemeFeature::PKE); - m.attr("KEYSWITCH") = py::cast(PKESchemeFeature::KEYSWITCH); - m.attr("PRE") = py::cast(PKESchemeFeature::PRE); - m.attr("LEVELEDSHE") = py::cast(PKESchemeFeature::LEVELEDSHE); - m.attr("ADVANCEDSHE") = py::cast(PKESchemeFeature::ADVANCEDSHE); - m.attr("MULTIPARTY") = py::cast(PKESchemeFeature::MULTIPARTY); - m.attr("FHE") = py::cast(PKESchemeFeature::FHE); - m.attr("SCHEMESWITCH") = py::cast(PKESchemeFeature::SCHEMESWITCH); + .value("SCHEMESWITCH", PKESchemeFeature::SCHEMESWITCH) + .export_values(); // Plaintext enums py::enum_(m, "Format") .value("EVALUATION", Format::EVALUATION) - .value("COEFFICIENT", Format::COEFFICIENT); - m.attr("EVALUATION") = py::cast(Format::EVALUATION); - m.attr("COEFFICIENT") = py::cast(Format::COEFFICIENT); + .value("COEFFICIENT", Format::COEFFICIENT) + .export_values(); py::enum_(m, "PlaintextEncodings") .value("INVALID_ENCODING", PlaintextEncodings::INVALID_ENCODING) .value("COEF_PACKED_ENCODING", PlaintextEncodings::COEF_PACKED_ENCODING) .value("PACKED_ENCODING", PlaintextEncodings::PACKED_ENCODING) .value("STRING_ENCODING", PlaintextEncodings::STRING_ENCODING) - .value("CKKS_PACKED_ENCODING", PlaintextEncodings::CKKS_PACKED_ENCODING); - m.attr("INVALID_ENCODING") = py::cast(PlaintextEncodings::INVALID_ENCODING); - m.attr("COEF_PACKED_ENCODING") = py::cast(PlaintextEncodings::COEF_PACKED_ENCODING); - m.attr("PACKED_ENCODING") = py::cast(PlaintextEncodings::PACKED_ENCODING); - m.attr("STRING_ENCODING") = py::cast(PlaintextEncodings::STRING_ENCODING); - m.attr("CKKS_PACKED_ENCODING") = py::cast(PlaintextEncodings::CKKS_PACKED_ENCODING); + .value("CKKS_PACKED_ENCODING", PlaintextEncodings::CKKS_PACKED_ENCODING) + .export_values(); // Serialization Types py::class_(m, "SERJSON"); py::class_(m, "SERBINARY"); @@ -1572,100 +1520,75 @@ void bind_enums_and_constants(py::module &m) { .value("NORESCALE", ScalingTechnique::NORESCALE) .value("COMPOSITESCALINGAUTO", ScalingTechnique::COMPOSITESCALINGAUTO) .value("COMPOSITESCALINGMANUAL", ScalingTechnique::COMPOSITESCALINGMANUAL) - .value("INVALID_RS_TECHNIQUE", ScalingTechnique::INVALID_RS_TECHNIQUE); - m.attr("FIXEDMANUAL") = py::cast(ScalingTechnique::FIXEDMANUAL); - m.attr("FIXEDAUTO") = py::cast(ScalingTechnique::FIXEDAUTO); - m.attr("FLEXIBLEAUTO") = py::cast(ScalingTechnique::FLEXIBLEAUTO); - m.attr("FLEXIBLEAUTOEXT") = py::cast(ScalingTechnique::FLEXIBLEAUTOEXT); - m.attr("NORESCALE") = py::cast(ScalingTechnique::NORESCALE); - m.attr("COMPOSITESCALINGAUTO") = py::cast(ScalingTechnique::COMPOSITESCALINGAUTO); - m.attr("COMPOSITESCALINGMANUAL") = py::cast(ScalingTechnique::COMPOSITESCALINGMANUAL); - m.attr("INVALID_RS_TECHNIQUE") = py::cast(ScalingTechnique::INVALID_RS_TECHNIQUE); + .value("INVALID_RS_TECHNIQUE", ScalingTechnique::INVALID_RS_TECHNIQUE) + .export_values(); // Key Switching Techniques py::enum_(m, "KeySwitchTechnique") .value("INVALID_KS_TECH", KeySwitchTechnique::INVALID_KS_TECH) .value("BV", KeySwitchTechnique::BV) - .value("HYBRID", KeySwitchTechnique::HYBRID); - m.attr("INVALID_KS_TECH") = py::cast(KeySwitchTechnique::INVALID_KS_TECH); - m.attr("BV") = py::cast(KeySwitchTechnique::BV); - m.attr("HYBRID") = py::cast(KeySwitchTechnique::HYBRID); + .value("HYBRID", KeySwitchTechnique::HYBRID) + .export_values(); // Secret Key Dist py::enum_(m, "SecretKeyDist") .value("GAUSSIAN", SecretKeyDist::GAUSSIAN) .value("UNIFORM_TERNARY", SecretKeyDist::UNIFORM_TERNARY) .value("SPARSE_TERNARY", SecretKeyDist::SPARSE_TERNARY) - .value("SPARSE_ENCAPSULATED", SecretKeyDist::SPARSE_ENCAPSULATED); - m.attr("GAUSSIAN") = py::cast(SecretKeyDist::GAUSSIAN); - m.attr("UNIFORM_TERNARY") = py::cast(SecretKeyDist::UNIFORM_TERNARY); - m.attr("SPARSE_TERNARY") = py::cast(SecretKeyDist::SPARSE_TERNARY); - m.attr("SPARSE_ENCAPSULATED") = py::cast(SecretKeyDist::SPARSE_ENCAPSULATED); + .value("SPARSE_ENCAPSULATED", SecretKeyDist::SPARSE_ENCAPSULATED) + .export_values(); // ProxyReEncryptionMode py::enum_(m, "ProxyReEncryptionMode") .value("NOT_SET", ProxyReEncryptionMode::NOT_SET) .value("INDCPA", ProxyReEncryptionMode::INDCPA) .value("FIXED_NOISE_HRA", ProxyReEncryptionMode::FIXED_NOISE_HRA) - .value("NOISE_FLOODING_HRA", ProxyReEncryptionMode::NOISE_FLOODING_HRA); - m.attr("NOT_SET") = py::cast(ProxyReEncryptionMode::NOT_SET); - m.attr("INDCPA") = py::cast(ProxyReEncryptionMode::INDCPA); - m.attr("FIXED_NOISE_HRA") = py::cast(ProxyReEncryptionMode::FIXED_NOISE_HRA); - m.attr("NOISE_FLOODING_HRA") = py::cast(ProxyReEncryptionMode::NOISE_FLOODING_HRA); - + .value("NOISE_FLOODING_HRA", ProxyReEncryptionMode::NOISE_FLOODING_HRA) + .export_values(); + // MultipartyMode py::enum_(m, "MultipartyMode") .value("INVALID_MULTIPARTY_MODE", MultipartyMode::INVALID_MULTIPARTY_MODE) .value("FIXED_NOISE_MULTIPARTY", MultipartyMode::FIXED_NOISE_MULTIPARTY) - .value("NOISE_FLOODING_MULTIPARTY", MultipartyMode::NOISE_FLOODING_MULTIPARTY); - m.attr("INVALID_MULTIPARTY_MODE") = py::cast(MultipartyMode::INVALID_MULTIPARTY_MODE); - m.attr("FIXED_NOISE_MULTIPARTY") = py::cast(MultipartyMode::FIXED_NOISE_MULTIPARTY); - m.attr("NOISE_FLOODING_MULTIPARTY") = py::cast(MultipartyMode::NOISE_FLOODING_MULTIPARTY); + .value("NOISE_FLOODING_MULTIPARTY", MultipartyMode::NOISE_FLOODING_MULTIPARTY) + .export_values(); // ExecutionMode py::enum_(m, "ExecutionMode") .value("EXEC_EVALUATION", ExecutionMode::EXEC_EVALUATION) - .value("EXEC_NOISE_ESTIMATION", ExecutionMode::EXEC_NOISE_ESTIMATION); - m.attr("EXEC_EVALUATION") = py::cast(ExecutionMode::EXEC_EVALUATION); - m.attr("EXEC_NOISE_ESTIMATION") = py::cast(ExecutionMode::EXEC_NOISE_ESTIMATION); + .value("EXEC_NOISE_ESTIMATION", ExecutionMode::EXEC_NOISE_ESTIMATION) + .export_values(); // DecryptionNoiseMode py::enum_(m, "DecryptionNoiseMode") .value("FIXED_NOISE_DECRYPT", DecryptionNoiseMode::FIXED_NOISE_DECRYPT) - .value("NOISE_FLOODING_DECRYPT", DecryptionNoiseMode::NOISE_FLOODING_DECRYPT); - m.attr("FIXED_NOISE_DECRYPT") = py::cast(DecryptionNoiseMode::FIXED_NOISE_DECRYPT); - m.attr("NOISE_FLOODING_DECRYPT") = py::cast(DecryptionNoiseMode::NOISE_FLOODING_DECRYPT); + .value("NOISE_FLOODING_DECRYPT", DecryptionNoiseMode::NOISE_FLOODING_DECRYPT) + .export_values(); // EncryptionTechnique py::enum_(m, "EncryptionTechnique") .value("STANDARD", EncryptionTechnique::STANDARD) - .value("EXTENDED", EncryptionTechnique::EXTENDED); - m.attr("STANDARD") = py::cast(EncryptionTechnique::STANDARD); - m.attr("EXTENDED") = py::cast(EncryptionTechnique::EXTENDED); + .value("EXTENDED", EncryptionTechnique::EXTENDED) + .export_values(); // MultiplicationTechnique py::enum_(m, "MultiplicationTechnique") .value("BEHZ", MultiplicationTechnique::BEHZ) .value("HPS", MultiplicationTechnique::HPS) .value("HPSPOVERQ", MultiplicationTechnique::HPSPOVERQ) - .value("HPSPOVERQLEVELED", MultiplicationTechnique::HPSPOVERQLEVELED); - m.attr("BEHZ") = py::cast(MultiplicationTechnique::BEHZ); - m.attr("HPS") = py::cast(MultiplicationTechnique::HPS); - m.attr("HPSPOVERQ") = py::cast(MultiplicationTechnique::HPSPOVERQ); - m.attr("HPSPOVERQLEVELED") = py::cast(MultiplicationTechnique::HPSPOVERQLEVELED); + .value("HPSPOVERQLEVELED", MultiplicationTechnique::HPSPOVERQLEVELED) + .export_values(); // Compression Leval py::enum_(m,"CompressionLevel") .value("COMPACT", CompressionLevel::COMPACT) - .value("SLACK", CompressionLevel::SLACK); - m.attr("COMPACT") = py::cast(CompressionLevel::COMPACT); - m.attr("SLACK") = py::cast(CompressionLevel::SLACK); + .value("SLACK", CompressionLevel::SLACK) + .export_values(); py::enum_(m,"CKKSDataType") .value("REAL", CKKSDataType::REAL) - .value("COMPLEX", CKKSDataType::COMPLEX); - m.attr("REAL") = py::cast(CKKSDataType::REAL); - m.attr("COMPLEX") = py::cast(CKKSDataType::COMPLEX); + .value("COMPLEX", CKKSDataType::COMPLEX) + .export_values(); /* ---- CORE enums ---- */ // Security Level @@ -1676,15 +1599,9 @@ void bind_enums_and_constants(py::module &m) { .value("HEStd_128_quantum", SecurityLevel::HEStd_128_quantum) .value("HEStd_192_quantum", SecurityLevel::HEStd_192_quantum) .value("HEStd_256_quantum", SecurityLevel::HEStd_256_quantum) - .value("HEStd_NotSet", SecurityLevel::HEStd_NotSet); - m.attr("HEStd_128_classic") = py::cast(SecurityLevel::HEStd_128_classic); - m.attr("HEStd_192_classic") = py::cast(SecurityLevel::HEStd_192_classic); - m.attr("HEStd_256_classic") = py::cast(SecurityLevel::HEStd_256_classic); - m.attr("HEStd_128_quantum") = py::cast(SecurityLevel::HEStd_128_quantum); - m.attr("HEStd_192_quantum") = py::cast(SecurityLevel::HEStd_192_quantum); - m.attr("HEStd_256_quantum") = py::cast(SecurityLevel::HEStd_256_quantum); - m.attr("HEStd_NotSet") = py::cast(SecurityLevel::HEStd_NotSet); - + .value("HEStd_NotSet", SecurityLevel::HEStd_NotSet) + .export_values(); + //NATIVEINT function m.def("get_native_int", &get_native_int); } @@ -1695,14 +1612,14 @@ void bind_keys(py::module &m) { .def("GetCryptoContext", &PublicKeyImpl::GetCryptoContext, py::doc(key_GetCryptoContext_docs)) .def("GetKeyTag", &PublicKeyImpl::GetKeyTag) - .def("SetKeyTag", &PublicKeyImpl::SetKeyTag); + .def("SetKeyTag", &PublicKeyImpl::SetKeyTag, py::arg("keyTag")); py::class_, std::shared_ptr>>(m, "PrivateKey") .def(py::init<>()) // a key created without a CryptoContext cannot be passed to RecoverSharedKey() .def(py::init&>(), py::arg("cryptoContext")) .def("GetCryptoContext", &PrivateKeyImpl::GetCryptoContext) .def("GetKeyTag", &PrivateKeyImpl::GetKeyTag) - .def("SetKeyTag", &PrivateKeyImpl::SetKeyTag); + .def("SetKeyTag", &PrivateKeyImpl::SetKeyTag, py::arg("keyTag")); py::class_>(m, "KeyPair") .def_readwrite("publicKey", &KeyPair::publicKey) .def_readwrite("secretKey", &KeyPair::secretKey) @@ -1710,7 +1627,7 @@ void bind_keys(py::module &m) { py::class_, std::shared_ptr>>(m, "EvalKey") .def(py::init<>()) .def("GetKeyTag", &EvalKeyImpl::GetKeyTag) - .def("SetKeyTag", &EvalKeyImpl::SetKeyTag); + .def("SetKeyTag", &EvalKeyImpl::SetKeyTag, py::arg("keyTag")); py::class_>, std::shared_ptr>>>(m, "EvalKeyMap") .def(py::init<>()); } @@ -1838,7 +1755,9 @@ void bind_encodings(py::module &m) { .def("GetLogPrecision", &PlaintextImpl::GetLogPrecision, ptx_GetLogPrecision_docs) .def("Encode", &PlaintextImpl::Encode, ptx_Encode_docs) .def("Decode", py::overload_cast<>(&PlaintextImpl::Decode), ptx_Decode_docs) - .def("Decode", py::overload_cast(&PlaintextImpl::Decode), ptx_Decode_docs) + .def("Decode", py::overload_cast(&PlaintextImpl::Decode), + py::arg("depth"), py::arg("scalingFactor"), py::arg("scalTech"), py::arg("executionMode"), + py::doc(ptx_Decode_docs)) .def("LowBound", &PlaintextImpl::LowBound, ptx_LowBound_docs) .def("HighBound", &PlaintextImpl::HighBound, ptx_HighBound_docs) .def("SetFormat", &PlaintextImpl::SetFormat, @@ -1849,17 +1768,17 @@ void bind_encodings(py::module &m) { .def("GetCKKSPackedValue", &PlaintextImpl::GetCKKSPackedValue, ptx_GetCKKSPackedValue_docs) .def("GetRealPackedValue", &PlaintextImpl::GetRealPackedValue, ptx_GetRealPackedValue_docs) .def("GetLevel", &PlaintextImpl::GetLevel) - .def("SetLevel", &PlaintextImpl::SetLevel) + .def("SetLevel", &PlaintextImpl::SetLevel, py::arg("level")) .def("GetNoiseScaleDeg", &PlaintextImpl::GetNoiseScaleDeg) - .def("SetNoiseScaleDeg", &PlaintextImpl::SetNoiseScaleDeg) + .def("SetNoiseScaleDeg", &PlaintextImpl::SetNoiseScaleDeg, py::arg("noiseScaleDeg")) .def("GetSlots", &PlaintextImpl::GetSlots) - .def("SetSlots", &PlaintextImpl::SetSlots) + .def("SetSlots", &PlaintextImpl::SetSlots, py::arg("slots")) .def("GetLogError", &PlaintextImpl::GetLogError) .def("GetLogPrecision", &PlaintextImpl::GetLogPrecision) .def("GetStringValue", &PlaintextImpl::GetStringValue) - .def("SetStringValue", &PlaintextImpl::SetStringValue) - .def("SetIntVectorValue", &PlaintextImpl::SetIntVectorValue) - .def("GetFormattedValues", &PlaintextImpl::GetFormattedValues) + .def("SetStringValue", &PlaintextImpl::SetStringValue, py::arg("value")) + .def("SetIntVectorValue", &PlaintextImpl::SetIntVectorValue, py::arg("value")) + .def("GetFormattedValues", &PlaintextImpl::GetFormattedValues, py::arg("precision")) .def("__repr__", [](const PlaintextImpl &p) { std::stringstream ss; ss << ""; @@ -1902,7 +1821,7 @@ void bind_ciphertext(py::module &m) { .def("SetHopLevel", &CiphertextImpl<DCRTPoly>::SetHopLevel, py::arg("hopLevel"), py::doc(ctx_SetHopLevel_docs)) .def("GetScalingFactor", &CiphertextImpl<DCRTPoly>::GetScalingFactor) - .def("SetScalingFactor", &CiphertextImpl<DCRTPoly>::SetScalingFactor) + .def("SetScalingFactor", &CiphertextImpl<DCRTPoly>::SetScalingFactor, py::arg("sf")) .def("GetScalingFactorInt", [](const CiphertextImpl<DCRTPoly>& self) { return openfhe_python::IntegerToPyInt(self.GetScalingFactorInt()); }, @@ -1912,9 +1831,9 @@ void bind_ciphertext(py::module &m) { }, py::arg("sf"), py::doc(ctx_SetScalingFactorInt_docs)) .def("GetSlots", &CiphertextImpl<DCRTPoly>::GetSlots) - .def("SetSlots", &CiphertextImpl<DCRTPoly>::SetSlots) + .def("SetSlots", &CiphertextImpl<DCRTPoly>::SetSlots, py::arg("slots")) .def("GetNoiseScaleDeg", &CiphertextImpl<DCRTPoly>::GetNoiseScaleDeg) - .def("SetNoiseScaleDeg", &CiphertextImpl<DCRTPoly>::SetNoiseScaleDeg) + .def("SetNoiseScaleDeg", &CiphertextImpl<DCRTPoly>::SetNoiseScaleDeg, py::arg("noiseScaleDeg")) .def("GetCryptoContext", &CiphertextImpl<DCRTPoly>::GetCryptoContext) .def("GetKeyTag", &CiphertextImpl<DCRTPoly>::GetKeyTag) .def("GetEncodingType", &CiphertextImpl<DCRTPoly>::GetEncodingType) @@ -1932,10 +1851,12 @@ void bind_ciphertext(py::module &m) { py::return_value_policy::reference_internal) .def("SetElements", [](CiphertextImpl<DCRTPoly>& self, const std::vector<DCRTPoly>& elems) { self.SetElements(elems); - }) + }, + py::arg("elements")) .def("SetElementsMove", [](CiphertextImpl<DCRTPoly>& self, std::vector<DCRTPoly>&& elems) { self.SetElements(std::move(elems)); - }); + }, + py::arg("elements")); } void bind_sch_swch_params(py::module &m) { @@ -1960,25 +1881,25 @@ void bind_sch_swch_params(py::module &m) { .def("GetRingDimension", &SchSwchParams::GetRingDimension) .def("GetScalingModSize", &SchSwchParams::GetScalingModSize) .def("GetBatchSize", &SchSwchParams::GetBatchSize) - .def("SetSecurityLevelCKKS", &SchSwchParams::SetSecurityLevelCKKS) - .def("SetSecurityLevelFHEW", &SchSwchParams::SetSecurityLevelFHEW) - .def("SetArbitraryFunctionEvaluation", &SchSwchParams::SetArbitraryFunctionEvaluation) - .def("SetUseDynamicModeFHEW", &SchSwchParams::SetUseDynamicModeFHEW) - .def("SetComputeArgmin", &SchSwchParams::SetComputeArgmin) - .def("SetOneHotEncoding", &SchSwchParams::SetOneHotEncoding) - .def("SetUseAltArgmin", &SchSwchParams::SetUseAltArgmin) - .def("SetNumSlotsCKKS", &SchSwchParams::SetNumSlotsCKKS) - .def("SetNumValues", &SchSwchParams::SetNumValues) - .def("SetCtxtModSizeFHEWLargePrec", &SchSwchParams::SetCtxtModSizeFHEWLargePrec) - .def("SetCtxtModSizeFHEWIntermedSwch", &SchSwchParams::SetCtxtModSizeFHEWIntermedSwch) - .def("SetBStepLTrCKKStoFHEW", &SchSwchParams::SetBStepLTrCKKStoFHEW) - .def("SetBStepLTrFHEWtoCKKS", &SchSwchParams::SetBStepLTrFHEWtoCKKS) - .def("SetLevelLTrCKKStoFHEW", &SchSwchParams::SetLevelLTrCKKStoFHEW) - .def("SetLevelLTrFHEWtoCKKS", &SchSwchParams::SetLevelLTrFHEWtoCKKS) - .def("SetInitialCKKSModulus", &SchSwchParams::SetInitialCKKSModulus) - .def("SetRingDimension", &SchSwchParams::SetRingDimension) - .def("SetScalingModSize", &SchSwchParams::SetScalingModSize) - .def("SetBatchSize", &SchSwchParams::SetBatchSize) + .def("SetSecurityLevelCKKS", &SchSwchParams::SetSecurityLevelCKKS, py::arg("securityLevelCKKS")) + .def("SetSecurityLevelFHEW", &SchSwchParams::SetSecurityLevelFHEW, py::arg("securityLevelFHEW")) + .def("SetArbitraryFunctionEvaluation", &SchSwchParams::SetArbitraryFunctionEvaluation, py::arg("arbitraryFunctionEvaluation")) + .def("SetUseDynamicModeFHEW", &SchSwchParams::SetUseDynamicModeFHEW, py::arg("useDynamicModeFHEW")) + .def("SetComputeArgmin", &SchSwchParams::SetComputeArgmin, py::arg("computeArgmin")) + .def("SetOneHotEncoding", &SchSwchParams::SetOneHotEncoding, py::arg("oneHotEncoding")) + .def("SetUseAltArgmin", &SchSwchParams::SetUseAltArgmin, py::arg("useAltArgmin")) + .def("SetNumSlotsCKKS", &SchSwchParams::SetNumSlotsCKKS, py::arg("numSlotsCKKS")) + .def("SetNumValues", &SchSwchParams::SetNumValues, py::arg("numValues")) + .def("SetCtxtModSizeFHEWLargePrec", &SchSwchParams::SetCtxtModSizeFHEWLargePrec, py::arg("ctxtModSizeFHEWLargePrec")) + .def("SetCtxtModSizeFHEWIntermedSwch", &SchSwchParams::SetCtxtModSizeFHEWIntermedSwch, py::arg("ctxtModSizeFHEWIntermedSwch")) + .def("SetBStepLTrCKKStoFHEW", &SchSwchParams::SetBStepLTrCKKStoFHEW, py::arg("bStepLTrCKKStoFHEW")) + .def("SetBStepLTrFHEWtoCKKS", &SchSwchParams::SetBStepLTrFHEWtoCKKS, py::arg("bStepLTrFHEWtoCKKS")) + .def("SetLevelLTrCKKStoFHEW", &SchSwchParams::SetLevelLTrCKKStoFHEW, py::arg("levelLTrCKKStoFHEW")) + .def("SetLevelLTrFHEWtoCKKS", &SchSwchParams::SetLevelLTrFHEWtoCKKS, py::arg("levelLTrFHEWtoCKKS")) + .def("SetInitialCKKSModulus", &SchSwchParams::SetInitialCKKSModulus, py::arg("initialCKKSModulus")) + .def("SetRingDimension", &SchSwchParams::SetRingDimension, py::arg("ringDimension")) + .def("SetScalingModSize", &SchSwchParams::SetScalingModSize, py::arg("scalingModSize")) + .def("SetBatchSize", &SchSwchParams::SetBatchSize, py::arg("batchSize")) .def("SetParamsFromCKKSCryptocontextCalled", &SchSwchParams::SetParamsFromCKKSCryptocontextCalled, py::doc(schswchparams_SetParamsFromCKKSCryptocontextCalled_docs)) .def("__str__", [](const SchSwchParams &params) { diff --git a/src/lib/binfhe/binfhecontext_wrapper.cpp b/src/lib/binfhe/binfhecontext_wrapper.cpp index 6273531..621faf8 100644 --- a/src/lib/binfhe/binfhecontext_wrapper.cpp +++ b/src/lib/binfhe/binfhecontext_wrapper.cpp @@ -38,24 +38,27 @@ using namespace lbcrypto; namespace py = pybind11; -// Define static variables to hold the state -py::function* static_f = nullptr; +// GenerateLUTviaFunction takes a plain function pointer, so the Python callable is +// passed through thread-local state instead of a capturing lambda. +static thread_local py::function* static_f = nullptr; -// Define a static function that uses the static variables NativeInteger StaticFunction(NativeInteger m, NativeInteger p) { - // Call the Python function py::object result_py = (*static_f)(m.ConvertToInt<uint64_t>(), p.ConvertToInt<uint64_t>()); - // Convert the result to a NativeInteger return NativeInteger(py::cast<uint64_t>(result_py)); } std::vector<uint64_t> GenerateLUTviaFunctionWrapper(BinFHEContext &self, py::function f, uint64_t p) { - static_f = &f; + // restore the previous callable even if GenerateLUTviaFunction throws + struct CallbackGuard { + py::function* previous; + explicit CallbackGuard(py::function* f) : previous(static_f) { static_f = f; } + ~CallbackGuard() { static_f = previous; } + } guard(&f); + std::vector<NativeInteger> result = self.GenerateLUTviaFunction(StaticFunction, NativeInteger(p)); - static_f = nullptr; std::vector<uint64_t> result_uint64_t; - result.reserve(result.size()); + result_uint64_t.reserve(result.size()); for (const auto& value : result) result_uint64_t.emplace_back(value.ConvertToInt<uint64_t>()); diff --git a/src/lib/binfhe_bindings.cpp b/src/lib/binfhe_bindings.cpp index 33a7527..021921f 100644 --- a/src/lib/binfhe_bindings.cpp +++ b/src/lib/binfhe_bindings.cpp @@ -41,8 +41,6 @@ #include <pybind11/stl.h> #include <pybind11/operators.h> -#include "cereal/archives/binary.hpp" - #include <map> #include <string> #include <tuple> @@ -75,59 +73,28 @@ void bind_binfhe_enums(py::module &m) { .value("STD128Q_4_LMKCDEY", BINFHE_PARAMSET::STD128Q_4_LMKCDEY) .value("STD192Q_4", BINFHE_PARAMSET::STD192Q_4) .value("STD256Q_4", BINFHE_PARAMSET::STD256Q_4) - .value("SIGNED_MOD_TEST", BINFHE_PARAMSET::SIGNED_MOD_TEST); - m.attr("TOY") = py::cast(BINFHE_PARAMSET::TOY); - m.attr("MEDIUM") = py::cast(BINFHE_PARAMSET::MEDIUM); - m.attr("STD128_LMKCDEY") = py::cast(BINFHE_PARAMSET::STD128_LMKCDEY); - m.attr("STD128_AP") = py::cast(BINFHE_PARAMSET::STD128_AP); - m.attr("STD128") = py::cast(BINFHE_PARAMSET::STD128); - m.attr("STD192") = py::cast(BINFHE_PARAMSET::STD192); - m.attr("STD256") = py::cast(BINFHE_PARAMSET::STD256); - m.attr("STD128Q") = py::cast(BINFHE_PARAMSET::STD128Q); - m.attr("STD128Q_LMKCDEY") = py::cast(BINFHE_PARAMSET::STD128Q_LMKCDEY); - m.attr("STD192Q") = py::cast(BINFHE_PARAMSET::STD192Q); - m.attr("STD256Q") = py::cast(BINFHE_PARAMSET::STD256Q); - m.attr("STD128_3") = py::cast(BINFHE_PARAMSET::STD128_3); - m.attr("STD128_3_LMKCDEY") = py::cast(BINFHE_PARAMSET::STD128_3_LMKCDEY); - m.attr("STD128Q_3") = py::cast(BINFHE_PARAMSET::STD128Q_3); - m.attr("STD128Q_3_LMKCDEY") = py::cast(BINFHE_PARAMSET::STD128Q_3_LMKCDEY); - m.attr("STD192Q_3") = py::cast(BINFHE_PARAMSET::STD192Q_3); - m.attr("STD256Q_3") = py::cast(BINFHE_PARAMSET::STD256Q_3); - m.attr("STD128_4") = py::cast(BINFHE_PARAMSET::STD128_4); - m.attr("STD128_4_LMKCDEY") = py::cast(BINFHE_PARAMSET::STD128_4_LMKCDEY); - m.attr("STD128Q_4") = py::cast(BINFHE_PARAMSET::STD128Q_4); - m.attr("STD128Q_4_LMKCDEY") = py::cast(BINFHE_PARAMSET::STD128Q_4_LMKCDEY); - m.attr("STD192Q_4") = py::cast(BINFHE_PARAMSET::STD192Q_4); - m.attr("STD256Q_4") = py::cast(BINFHE_PARAMSET::STD256Q_4); - m.attr("SIGNED_MOD_TEST") = py::cast(BINFHE_PARAMSET::SIGNED_MOD_TEST); + .value("SIGNED_MOD_TEST", BINFHE_PARAMSET::SIGNED_MOD_TEST) + .export_values(); py::enum_<BINFHE_METHOD>(m, "BINFHE_METHOD") .value("INVALID_METHOD", BINFHE_METHOD::INVALID_METHOD) .value("AP", BINFHE_METHOD::AP) .value("GINX", BINFHE_METHOD::GINX) - .value("LMKCDEY", BINFHE_METHOD::LMKCDEY); - m.attr("INVALID_METHOD") = py::cast(BINFHE_METHOD::INVALID_METHOD); - m.attr("GINX") = py::cast(BINFHE_METHOD::GINX); - m.attr("AP") = py::cast(BINFHE_METHOD::AP); - m.attr("LMKCDEY") = py::cast(BINFHE_METHOD::LMKCDEY); + .value("LMKCDEY", BINFHE_METHOD::LMKCDEY) + .export_values(); py::enum_<KEYGEN_MODE>(m, "KEYGEN_MODE") .value("SYM_ENCRYPT", KEYGEN_MODE::SYM_ENCRYPT) - .value("PUB_ENCRYPT", KEYGEN_MODE::PUB_ENCRYPT); - m.attr("SYM_ENCRYPT") = py::cast(KEYGEN_MODE::SYM_ENCRYPT); - m.attr("PUB_ENCRYPT") = py::cast(KEYGEN_MODE::PUB_ENCRYPT); + .value("PUB_ENCRYPT", KEYGEN_MODE::PUB_ENCRYPT) + .export_values(); py::enum_<BINFHE_OUTPUT>(m, "BINFHE_OUTPUT") .value("INVALID_OUTPUT", BINFHE_OUTPUT::INVALID_OUTPUT) .value("FRESH", BINFHE_OUTPUT::FRESH) .value("BOOTSTRAPPED", BINFHE_OUTPUT::BOOTSTRAPPED) .value("LARGE_DIM", BINFHE_OUTPUT::LARGE_DIM) - .value("SMALL_DIM", BINFHE_OUTPUT::SMALL_DIM); - m.attr("INVALID_OUTPUT") = py::cast(BINFHE_OUTPUT::INVALID_OUTPUT); - m.attr("FRESH") = py::cast(BINFHE_OUTPUT::FRESH); - m.attr("BOOTSTRAPPED") = py::cast(BINFHE_OUTPUT::BOOTSTRAPPED); - m.attr("LARGE_DIM") = py::cast(BINFHE_OUTPUT::LARGE_DIM); - m.attr("SMALL_DIM") = py::cast(BINFHE_OUTPUT::SMALL_DIM); + .value("SMALL_DIM", BINFHE_OUTPUT::SMALL_DIM) + .export_values(); py::enum_<BINGATE>(m, "BINGATE") .value("OR", BINGATE::OR) @@ -143,21 +110,8 @@ void bind_binfhe_enums(py::module &m) { .value("OR3", BINGATE::OR3) .value("AND4", BINGATE::AND4) .value("OR4", BINGATE::OR4) - .value("CMUX", BINGATE::CMUX); - m.attr("OR") = py::cast(BINGATE::OR); - m.attr("AND") = py::cast(BINGATE::AND); - m.attr("NOR") = py::cast(BINGATE::NOR); - m.attr("NAND") = py::cast(BINGATE::NAND); - m.attr("XOR_FAST") = py::cast(BINGATE::XOR_FAST); - m.attr("XNOR_FAST") = py::cast(BINGATE::XNOR_FAST); - m.attr("XOR") = py::cast(BINGATE::XOR); - m.attr("XNOR") = py::cast(BINGATE::XNOR); - m.attr("MAJORITY") = py::cast(BINGATE::MAJORITY); - m.attr("AND3") = py::cast(BINGATE::AND3); - m.attr("OR3") = py::cast(BINGATE::OR3); - m.attr("AND4") = py::cast(BINGATE::AND4); - m.attr("OR4") = py::cast(BINGATE::OR4); - m.attr("CMUX") = py::cast(BINGATE::CMUX); + .value("CMUX", BINGATE::CMUX) + .export_values(); } void bind_binfhe_keys(py::module &m) { @@ -354,7 +308,8 @@ void bind_binfhe_context(py::module &m) { binfhe_EvalSign_docs, py::arg("ct"), py::arg("schemeSwitch") = false) - .def("EvalConstant", &BinFHEContext::EvalConstant) + .def("EvalConstant", &BinFHEContext::EvalConstant, + py::arg("value")) .def("ClearBTKeys", &BinFHEContext::ClearBTKeys) .def("HasInternal32RefreshKey", &BinFHEContext::HasInternal32RefreshKey, py::doc(binfhe_HasInternal32RefreshKey_docs)) @@ -365,18 +320,11 @@ void bind_binfhe_context(py::module &m) { binfhe_SerializedVersion_docs) .def("SerializedObjectName", &BinFHEContext::SerializedObjectName, binfhe_SerializedObjectName_docs) - .def("SaveJSON", &BinFHEContext::save<cereal::JSONOutputArchive>) - .def("LoadJSON", &BinFHEContext::load<cereal::JSONInputArchive>) - .def("SaveBinary", &BinFHEContext::save<cereal::BinaryOutputArchive>) - .def("LoadBinary", &BinFHEContext::load<cereal::BinaryInputArchive>) - .def("SavePortableBinary", &BinFHEContext::save<cereal::PortableBinaryOutputArchive>) - .def("LoadPortableBinary", &BinFHEContext::load<cereal::PortableBinaryInputArchive>) + // note: save/load (cereal archives), GetRefreshKey, GetBinFHEScheme, GetLWEScheme and + // GetParams are not bound: their argument/return types are not exposed to Python, so + // the bindings could never be called successfully .def("GetPublicKey", &BinFHEContext::GetPublicKey) .def("GetSwitchKey", &BinFHEContext::GetSwitchKey) - .def("GetRefreshKey", &BinFHEContext::GetRefreshKey) - .def("GetBinFHEScheme", &BinFHEContext::GetBinFHEScheme) - .def("GetLWEScheme", &BinFHEContext::GetLWEScheme) - .def("GetParams", &BinFHEContext::GetParams) .def("GetN", [](BinFHEContext& self) { return self.GetParams()->GetLWEParams()->GetN(); diff --git a/src/lib/pke/serialization.cpp b/src/lib/pke/serialization.cpp index 210c63c..f2e35ec 100644 --- a/src/lib/pke/serialization.cpp +++ b/src/lib/pke/serialization.cpp @@ -28,8 +28,7 @@ // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //================================================================================== -#include "serialization.h" - +#include <pybind11/pybind11.h> #include <pybind11/stl.h> // #include <pybind11/stl_bind.h> - not needed for now @@ -47,32 +46,6 @@ namespace py = pybind11; PYBIND11_MAKE_OPAQUE(std::map<uint32_t, EvalKey<DCRTPoly>>); -template <typename ST> -bool SerializeEvalMultKeyWrapper(const std::string& filename, const ST& sertype, std::string keyTag) { - std::ofstream outfile(filename, std::ios::out | std::ios::binary); - bool res = CryptoContextImpl<DCRTPoly>::SerializeEvalMultKey<ST>(outfile, sertype, keyTag); - outfile.close(); - return res; -} - -template <typename ST> -bool SerializeEvalAutomorphismKeyWrapper(const std::string& filename, const ST& sertype, std::string keyTag) { - std::ofstream outfile(filename, std::ios::out | std::ios::binary); - bool res = CryptoContextImpl<DCRTPoly>::SerializeEvalAutomorphismKey<ST>(outfile, sertype, keyTag); - outfile.close(); - return res; -} - -template <typename ST> -bool DeserializeEvalMultKeyWrapper(const std::string& filename, const ST& sertype) { - std::ifstream emkeys(filename, std::ios::in | std::ios::binary); - if (!emkeys.is_open()) { - std::cerr << "I cannot read serialization from " << filename << std::endl; - } - bool res = CryptoContextImpl<DCRTPoly>::DeserializeEvalMultKey<ST>(emkeys, sertype); - return res; -} - template <typename ST> bool SerializeEvalBootstrapKeyWrapper(const std::string& filename, const ST& sertype, const CryptoContextDCRT& cc, const std::string& keyTag, uint32_t slots) { diff --git a/tests/test_api_coverage.py b/tests/test_api_coverage.py index 9c62d76..71ed1b1 100644 --- a/tests/test_api_coverage.py +++ b/tests/test_api_coverage.py @@ -238,6 +238,25 @@ def test_eval_sum_key_serialization_reports_unusable_paths(ckks_context, sertype assert fhe.CryptoContext.DeserializeEvalSumKey(unusable, sertype) is False +@pytest.mark.parametrize("sertype", [fhe.BINARY, fhe.JSON], ids=["binary", "json"]) +def test_mult_and_automorphism_key_deserialization_reports_unusable_paths(sertype): + """An unopenable file returns False instead of deserializing from a closed stream.""" + unusable = "/nonexistent-directory-for-openfhe-tests/keys" + + assert fhe.CryptoContext.DeserializeEvalMultKey(unusable, sertype) is False + assert fhe.CryptoContext.DeserializeEvalAutomorphismKey(unusable, sertype) is False + + +def test_enum_members_are_exported_at_module_level(): + """Every pybind11 enum member must also be addressable as fhe.<MEMBER> (export_values).""" + enum_types = [obj for obj in vars(fhe).values() + if isinstance(obj, type) and hasattr(obj, "__members__")] + assert len(enum_types) >= 20 + for enum_type in enum_types: + for name, member in enum_type.__members__.items(): + assert getattr(fhe, name) == member, f"{enum_type.__name__}.{name} is not exported" + + def test_element_params_feed_get_plaintext_for_decrypt(ckks_context): cc, _ = ckks_context element_params = cc.GetElementParams() diff --git a/tests/test_bgv.py b/tests/test_bgv.py index a7340b7..0e16dbd 100644 --- a/tests/test_bgv.py +++ b/tests/test_bgv.py @@ -72,6 +72,13 @@ def test_shift(n, final): assert shift(list(range(1, 9)), n) == final +def test_make_coef_packed_plaintext_accepts_keywords(bgv_context): + """noiseScaleDeg was once bound with a trailing space, breaking keyword calls.""" + _, crypto_context, _ = bgv_context + plaintext = crypto_context.MakeCoefPackedPlaintext([1, 2, 3], noiseScaleDeg=1, level=0) + assert plaintext.GetCoefPackedValue()[:3] == [1, 2, 3] + + def test_simple_integers(bgv_context): parameters, crypto_context, key_pair = bgv_context rng = random.Random(342342) diff --git a/tests/test_binfhe_api_coverage.py b/tests/test_binfhe_api_coverage.py index 06b6deb..43a94b4 100644 --- a/tests/test_binfhe_api_coverage.py +++ b/tests/test_binfhe_api_coverage.py @@ -23,6 +23,23 @@ def test_spreadsheet_add_apis_are_exposed(): assert not {cls: names for cls, names in missing.items() if names} +def test_generate_lut_via_function_recovers_from_callback_errors(): + """The LUT callback is routed through thread-local state; a raising callback + must propagate its exception and leave the wrapper usable afterwards.""" + cc = fhe.BinFHEContext() + cc.GenerateBinFHEContext(fhe.TOY, False, 12) + p = 8 + + def bad(m, q): + raise ValueError("callback failure") + + with pytest.raises(ValueError): + cc.GenerateLUTviaFunction(bad, p) + + lut = cc.GenerateLUTviaFunction(lambda m, q: (m * 3) % q, p) + assert lut, "LUT generation must still work after a failed callback" + + def test_new_binfhe_bindings_round_trip(): cc = fhe.BinFHEContext() cc.GenerateBinFHEContext(fhe.TOY, fhe.GINX) From c6367c73137e62d4652426f97109b4b29591d1ce Mon Sep 17 00:00:00 2001 From: Dmitriy Suponitskiy <dsuponitskiy@dualitytech.com> Date: Thu, 15 Oct 2026 20:09:44 -0400 Subject: [PATCH 10/16] Fix various bugs: - Reject negative, oversized, and bool Python ints in PyIntToInteger - Fix and enable simple_bootstrap_stc_example() - Remove the unsafe default LWEPublicKey constructor - Check is_open() in the EvalMultKey/EvalAutomorphismKey/EvalBootstrapKey serializers - Correct BinFHE docstrings and add a timeout to the example runner --- examples/pke/simple-ckks-bootstrapping.py | 5 ++-- src/include/docstrings/binfhecontext_docs.h | 30 ++++++++++++++++++++- src/include/integer_conversions.h | 20 ++++++++++++-- src/lib/bindings.cpp | 16 +++++++++++ src/lib/binfhe_bindings.cpp | 8 +++--- src/lib/pke/serialization.cpp | 4 +++ tests/test_api_coverage.py | 17 ++++++++++++ tests/test_binfhe_api_coverage.py | 7 +++++ tests/test_examples.py | 3 ++- 9 files changed, 101 insertions(+), 9 deletions(-) diff --git a/examples/pke/simple-ckks-bootstrapping.py b/examples/pke/simple-ckks-bootstrapping.py index 7dc06e3..2ecc70d 100644 --- a/examples/pke/simple-ckks-bootstrapping.py +++ b/examples/pke/simple-ckks-bootstrapping.py @@ -2,6 +2,7 @@ def main(): simple_bootstrap_example() + simple_bootstrap_stc_example() def simple_bootstrap_example(): parameters = CCParamsCKKSRNS() @@ -101,7 +102,7 @@ def simple_bootstrap_stc_example(): levels_available_after_bootstrap = 10 + level_budget[1] - depth = levels_available_after_bootstrap + CryptoContext.GetBootstrapDepth({level_budget[0], 0}, secret_key_dist) + depth = levels_available_after_bootstrap + CryptoContext.GetBootstrapDepth([level_budget[0], 0], secret_key_dist) parameters.SetMultiplicativeDepth(depth) @@ -126,7 +127,7 @@ def simple_bootstrap_stc_example(): x = [0.25, 0.5, 0.75, 1.0, 2.0, 3.0, 4.0, 5.0] encoded_length = len(x) - ptxt = cryptocontext.MakeCKKSPackedPlaintext(x, 1, depth-1-levelBudget[1], None, num_slots) + ptxt = cryptocontext.MakeCKKSPackedPlaintext(x, 1, depth-1-level_budget[1], None, num_slots) ptxt.SetLength(encoded_length) print(f"Input: {ptxt}") diff --git a/src/include/docstrings/binfhecontext_docs.h b/src/include/docstrings/binfhecontext_docs.h index c046d9d..ba2a0ce 100644 --- a/src/include/docstrings/binfhecontext_docs.h +++ b/src/include/docstrings/binfhecontext_docs.h @@ -97,6 +97,34 @@ const char* binfhe_Encrypt_docs = R"pbdoc( :rtype: LWECiphertext )pbdoc"; +// Encrypt with a public key +const char* binfhe_EncryptPubKey_docs = R"pbdoc( + Encrypts a bit or integer using a public key (public key encryption). + + :param pk: The public key. + :type pk: LWEPublicKey + :param m: The plaintext. + :type m: int + :param output: SMALL_DIM to key switch the fresh ciphertext to the secret-key dimension (default), LARGE_DIM to keep the public-key dimension. + :type output: BINFHE_OUTPUT + :param p: Plaintext modulus (default 4). + :type p: int + :param mod: Encrypt according to mod instead of m_q if mod != 0. + :type mod: int + :return: The ciphertext. + :rtype: LWECiphertext +)pbdoc"; + +// GetSwitchKey +const char* binfhe_GetSwitchKey_docs = R"pbdoc( + Get the 64-bit key switching key. + + With the default BTKeyGen(internal32=True) the keys are kept in their 32-bit + internal form and this returns None; use HasInternal32SwitchKey() to check for + them and GetBTKey() to access the generated keys. Run BTKeyGen(sk, internal32=False) + to populate the 64-bit key. +)pbdoc"; + // Decrypt const char* binfhe_Decrypt_docs = R"pbdoc( Decrypts a ciphertext using a secret key. @@ -260,7 +288,7 @@ const char* binfhe_LWEPrivateKeyGetModulus_docs = R"pbdoc( const char* binfhe_LWEPublicKeyGetModulus_docs = R"pbdoc( Get the modulus of the public key. - :return: the ciphertext modulus q (the modulus of the vector "a") + :return: the large ciphertext modulus Q (public keys are generated for the ring dimension N) :rtype: int )pbdoc"; diff --git a/src/include/integer_conversions.h b/src/include/integer_conversions.h index 163c842..3ac7659 100644 --- a/src/include/integer_conversions.h +++ b/src/include/integer_conversions.h @@ -48,13 +48,29 @@ pybind11::int_ IntegerToPyInt(const Integer& value) { } // Convert an arbitrary-size Python integer through its decimal representation. +// OpenFHE integers are unsigned: a negative input is rejected here because the +// OpenFHE string parser hangs on a leading '-', and a value wider than the target +// type is rejected because the constructor silently wraps it. template <typename Integer> Integer PyIntToInteger(const pybind11::int_& value) { + if (pybind11::isinstance<pybind11::bool_>(value)) + throw pybind11::type_error("expected an int, got a bool"); + PyObject* result = PyObject_Str(value.ptr()); if (result == nullptr) throw pybind11::error_already_set(); - auto valueStr = pybind11::reinterpret_steal<pybind11::str>(result); - return Integer(valueStr.cast<std::string>()); + auto valueStr = pybind11::reinterpret_steal<pybind11::str>(result).cast<std::string>(); + + if (!valueStr.empty() && valueStr[0] == '-') + throw pybind11::value_error("OpenFHE integers cannot be negative, got " + valueStr); + + Integer converted(valueStr); + if (converted.ToString() != valueStr) { + PyErr_SetString(PyExc_OverflowError, + ("value too large for the target OpenFHE integer type: " + valueStr).c_str()); + throw pybind11::error_already_set(); + } + return converted; } } // namespace openfhe_python diff --git a/src/lib/bindings.cpp b/src/lib/bindings.cpp index bc1181c..f606814 100644 --- a/src/lib/bindings.cpp +++ b/src/lib/bindings.cpp @@ -1315,6 +1315,10 @@ void bind_crypto_context(py::module &m) { py::doc(cc_SerializeEvalSumKeyByContext_docs)) .def_static("SerializeEvalMultKey", [](const std::string &filename, const SerType::SERBINARY &sertype, std::string keyTag = "") { std::ofstream outfile(filename, std::ios::out | std::ios::binary); + if (!outfile.is_open()) { + std::cerr << "I cannot write serialization to " << filename << std::endl; + return false; + } bool res = CryptoContextImpl<DCRTPoly>::SerializeEvalMultKey<SerType::SERBINARY>(outfile, sertype, keyTag); outfile.close(); return res; @@ -1325,6 +1329,10 @@ void bind_crypto_context(py::module &m) { py::doc(cc_SerializeEvalMultKey_docs)) .def_static("SerializeEvalMultKey", [](const std::string &filename, const SerType::SERJSON &sertype, std::string keyTag = "") { std::ofstream outfile(filename, std::ios::out | std::ios::binary); + if (!outfile.is_open()) { + std::cerr << "I cannot write serialization to " << filename << std::endl; + return false; + } bool res = CryptoContextImpl<DCRTPoly>::SerializeEvalMultKey<SerType::SERJSON>(outfile, sertype, keyTag); outfile.close(); return res; @@ -1335,6 +1343,10 @@ void bind_crypto_context(py::module &m) { py::doc(cc_SerializeEvalMultKey_docs)) .def_static("SerializeEvalAutomorphismKey", [](const std::string &filename, const SerType::SERBINARY &sertype, std::string keyTag = "") { std::ofstream outfile(filename, std::ios::out | std::ios::binary); + if (!outfile.is_open()) { + std::cerr << "I cannot write serialization to " << filename << std::endl; + return false; + } bool res = CryptoContextImpl<DCRTPoly>::SerializeEvalAutomorphismKey<SerType::SERBINARY>(outfile, sertype, keyTag); outfile.close(); return res; @@ -1345,6 +1357,10 @@ void bind_crypto_context(py::module &m) { py::doc(cc_SerializeEvalAutomorphismKey_docs)) .def_static("SerializeEvalAutomorphismKey", [](const std::string &filename, const SerType::SERJSON &sertype, std::string keyTag = "") { std::ofstream outfile(filename, std::ios::out | std::ios::binary); + if (!outfile.is_open()) { + std::cerr << "I cannot write serialization to " << filename << std::endl; + return false; + } bool res = CryptoContextImpl<DCRTPoly>::SerializeEvalAutomorphismKey<SerType::SERJSON>(outfile, sertype, keyTag); outfile.close(); return res; diff --git a/src/lib/binfhe_bindings.cpp b/src/lib/binfhe_bindings.cpp index 021921f..adeb1b9 100644 --- a/src/lib/binfhe_bindings.cpp +++ b/src/lib/binfhe_bindings.cpp @@ -128,8 +128,9 @@ void bind_binfhe_keys(py::module &m) { .def(py::self == py::self) .def(py::self != py::self); + // No default constructor for the same reason as LWEPrivateKey: an empty-but-non-null + // public key passes the null check and Encrypt() silently produces a length-0 ciphertext. py::class_<LWEPublicKeyImpl, std::shared_ptr<LWEPublicKeyImpl>>(m, "LWEPublicKey") - .def(py::init<>()) .def("GetLength", &LWEPublicKeyImpl::GetLength) .def("GetModulus", [](const LWEPublicKeyImpl& self) { @@ -239,7 +240,7 @@ void bind_binfhe_context(py::module &m) { py::arg("output") = SMALL_DIM, py::arg("p") = 4, py::arg("mod") = 0, - py::doc(binfhe_Encrypt_docs)) + py::doc(binfhe_EncryptPubKey_docs)) .def("Decrypt", [](BinFHEContext& self, ConstLWEPrivateKey sk, ConstLWECiphertext ct, LWEPlaintextModulus p) { LWEPlaintext result; @@ -324,7 +325,8 @@ void bind_binfhe_context(py::module &m) { // GetParams are not bound: their argument/return types are not exposed to Python, so // the bindings could never be called successfully .def("GetPublicKey", &BinFHEContext::GetPublicKey) - .def("GetSwitchKey", &BinFHEContext::GetSwitchKey) + .def("GetSwitchKey", &BinFHEContext::GetSwitchKey, + py::doc(binfhe_GetSwitchKey_docs)) .def("GetN", [](BinFHEContext& self) { return self.GetParams()->GetLWEParams()->GetN(); diff --git a/src/lib/pke/serialization.cpp b/src/lib/pke/serialization.cpp index f2e35ec..961b588 100644 --- a/src/lib/pke/serialization.cpp +++ b/src/lib/pke/serialization.cpp @@ -50,6 +50,10 @@ template <typename ST> bool SerializeEvalBootstrapKeyWrapper(const std::string& filename, const ST& sertype, const CryptoContextDCRT& cc, const std::string& keyTag, uint32_t slots) { std::ofstream outfile(filename, std::ios::out | std::ios::binary); + if (!outfile.is_open()) { + std::cerr << "I cannot write serialization to " << filename << std::endl; + return false; + } bool res = CryptoContextImpl<DCRTPoly>::SerializeEvalBootstrapKey(outfile, sertype, cc, keyTag, slots); outfile.close(); return res; diff --git a/tests/test_api_coverage.py b/tests/test_api_coverage.py index 71ed1b1..3157c64 100644 --- a/tests/test_api_coverage.py +++ b/tests/test_api_coverage.py @@ -247,6 +247,23 @@ def test_mult_and_automorphism_key_deserialization_reports_unusable_paths(sertyp assert fhe.CryptoContext.DeserializeEvalAutomorphismKey(unusable, sertype) is False +def test_integer_conversions_reject_invalid_python_ints(ckks_context): + """Arbitrary-size int conversion must reject what OpenFHE cannot represent: + negatives hang the OpenFHE string parser, oversized values silently wrap.""" + cc, _ = ckks_context + plaintext = cc.MakeCKKSPackedPlaintext([0.5]) + + with pytest.raises(ValueError): + fhe.PolyVector().SwitchModulus(-1) + with pytest.raises(OverflowError): + plaintext.SetScalingFactorInt(3 * 10**19) + with pytest.raises(TypeError): + plaintext.SetScalingFactorInt(True) + + plaintext.SetScalingFactorInt(2**50) + assert plaintext.GetScalingFactorInt() == 2**50 + + def test_enum_members_are_exported_at_module_level(): """Every pybind11 enum member must also be addressable as fhe.<MEMBER> (export_values).""" enum_types = [obj for obj in vars(fhe).values() diff --git a/tests/test_binfhe_api_coverage.py b/tests/test_binfhe_api_coverage.py index 43a94b4..8cde1e6 100644 --- a/tests/test_binfhe_api_coverage.py +++ b/tests/test_binfhe_api_coverage.py @@ -23,6 +23,13 @@ def test_spreadsheet_add_apis_are_exposed(): assert not {cls: names for cls, names in missing.items() if names} +def test_lwe_public_key_cannot_be_default_constructed(): + """An empty-but-non-null public key passes the C++ null check and Encrypt() + silently returns a length-0 ciphertext, so the default constructor is not exposed.""" + with pytest.raises(TypeError): + fhe.LWEPublicKey() + + def test_generate_lut_via_function_recovers_from_callback_errors(): """The LUT callback is routed through thread-local state; a raising callback must propagate its exception and leave the wrapper usable afterwards.""" diff --git a/tests/test_examples.py b/tests/test_examples.py index 47ba3c3..b08fea4 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -52,7 +52,8 @@ def run_example(scripts_path, raw_modulename, require_main): code = f"import {modulename}; getattr({modulename}, 'main', lambda: None)()" env = dict(os.environ) env["PYTHONPATH"] = td + os.pathsep + env.get("PYTHONPATH", "") - subprocess.run([sys.executable, "-c", code], cwd=td, env=env, check=True) + # timeout so a hung example fails the test instead of stalling CI indefinitely + subprocess.run([sys.executable, "-c", code], cwd=td, env=env, check=True, timeout=600) @pytest.mark.parametrize( From bac51de445f1a496caa6beaead2c709f94bb6786 Mon Sep 17 00:00:00 2001 From: Dmitriy Suponitskiy <dsuponitskiy@dualitytech.com> Date: Thu, 15 Oct 2026 21:24:50 -0400 Subject: [PATCH 11/16] Add null-context guards and strengthen the test suite - Reject None CryptoContext in ConvertRLWEToCKKS and SchemeSwitchingDataSerializer, with tests covering all three serializer arguments passed as None - Add regression tests for unwritable key-serialization paths - Use tmp_path-based unusable paths; make the eval-sum round trip able to fail - Gate slow examples with the long marker; FE example behind RUN_HIGH_MEMORY_EXAMPLES - Automate the nested LUT callback test --- src/lib/pke/fbt.cpp | 16 ++++++++- tests/test_api_coverage.py | 54 +++++++++++++++++++++++++------ tests/test_binfhe_api_coverage.py | 21 ++++++++++-- tests/test_examples.py | 26 +++++++++------ 4 files changed, 95 insertions(+), 22 deletions(-) diff --git a/src/lib/pke/fbt.cpp b/src/lib/pke/fbt.cpp index 829f467..fc1e2ec 100644 --- a/src/lib/pke/fbt.cpp +++ b/src/lib/pke/fbt.cpp @@ -284,6 +284,9 @@ void bind_fbt(py::module &m) { .def_static("ConvertRLWEToCKKS", [](const CryptoContext<DCRTPoly>& cc, const std::vector<Poly>& coeffs, const PublicKey<DCRTPoly>& pubKey, const py::int_& Bigq, uint32_t slots, uint32_t level) { + // cc is dereferenced below, so a None argument would segfault without this check + if (!cc) + OPENFHE_THROW("cc is nullptr"); return SchemeletRLWEMP::ConvertRLWEToCKKS( *cc, coeffs, pubKey, openfhe_python::PyIntToInteger<BigInteger>(Bigq), slots, level); }, @@ -303,7 +306,18 @@ void bind_fbt(py::module &m) { // Serializer/deserializer for all the data needed by CKKS <-> FHEW scheme switching py::class_<SchemeSwitchingDataSerializer>(m, "SchemeSwitchingDataSerializer") - .def(py::init<CryptoContext<DCRTPoly>, PublicKey<DCRTPoly>, Ciphertext<DCRTPoly>>(), + // the C++ constructor dereferences cryptoContext in its initializer list, + // so None arguments must be rejected here to avoid a segfault + .def(py::init([](const CryptoContext<DCRTPoly>& cryptoContext, const PublicKey<DCRTPoly>& publicKey, + const Ciphertext<DCRTPoly>& RAWCiphertext) { + if (!cryptoContext) + OPENFHE_THROW("cryptoContext is nullptr"); + if (!publicKey) + OPENFHE_THROW("publicKey is nullptr"); + if (!RAWCiphertext) + OPENFHE_THROW("RAWCiphertext is nullptr"); + return SchemeSwitchingDataSerializer(cryptoContext, publicKey, RAWCiphertext); + }), py::arg("cryptoContext"), py::arg("publicKey"), py::arg("RAWCiphertext")) .def("SetDataDirectory", &SchemeSwitchingDataSerializer::SetDataDirectory, py::arg("dir")) .def("Serialize", &SchemeSwitchingDataSerializer::Serialize); diff --git a/tests/test_api_coverage.py b/tests/test_api_coverage.py index 3157c64..bff64bc 100644 --- a/tests/test_api_coverage.py +++ b/tests/test_api_coverage.py @@ -222,29 +222,65 @@ def test_eval_sum_key_serialization_round_trip(ckks_context, sertype, tmp_path): assert fhe.CryptoContext.SerializeEvalSumKey(by_tag, sertype, key_tag) assert fhe.CryptoContext.SerializeEvalSumKey(by_context, sertype, cc) + # clear the global key map first, otherwise the round trip cannot fail + fhe.ClearEvalSumKeys() + assert key_tag not in fhe.CryptoContext.GetAllEvalSumKeys() + assert fhe.CryptoContext.DeserializeEvalSumKey(by_tag, sertype) + assert key_tag in fhe.CryptoContext.GetAllEvalSumKeys() + + fhe.ClearEvalSumKeys() assert fhe.CryptoContext.DeserializeEvalSumKey(by_context, sertype) assert key_tag in fhe.CryptoContext.GetAllEvalSumKeys() +@pytest.fixture +def unusable_path(tmp_path): + """A path whose parent directory does not exist, so no stream can open it.""" + return str(tmp_path / "missing-parent" / "keys") + + @pytest.mark.parametrize("sertype", [fhe.BINARY, fhe.JSON], ids=["binary", "json"]) -def test_eval_sum_key_serialization_reports_unusable_paths(ckks_context, sertype): +def test_eval_sum_key_serialization_reports_unusable_paths(ckks_context, sertype, unusable_path): """An unopenable file returns False instead of surfacing a cereal stream error.""" cc, keys = ckks_context - unusable = "/nonexistent-directory-for-openfhe-tests/keys" - assert fhe.CryptoContext.SerializeEvalSumKey(unusable, sertype, keys.secretKey.GetKeyTag()) is False - assert fhe.CryptoContext.SerializeEvalSumKey(unusable, sertype, cc) is False - assert fhe.CryptoContext.DeserializeEvalSumKey(unusable, sertype) is False + assert fhe.CryptoContext.SerializeEvalSumKey(unusable_path, sertype, keys.secretKey.GetKeyTag()) is False + assert fhe.CryptoContext.SerializeEvalSumKey(unusable_path, sertype, cc) is False + assert fhe.CryptoContext.DeserializeEvalSumKey(unusable_path, sertype) is False @pytest.mark.parametrize("sertype", [fhe.BINARY, fhe.JSON], ids=["binary", "json"]) -def test_mult_and_automorphism_key_deserialization_reports_unusable_paths(sertype): +def test_mult_and_automorphism_key_deserialization_reports_unusable_paths(sertype, unusable_path): """An unopenable file returns False instead of deserializing from a closed stream.""" - unusable = "/nonexistent-directory-for-openfhe-tests/keys" + assert fhe.CryptoContext.DeserializeEvalMultKey(unusable_path, sertype) is False + assert fhe.CryptoContext.DeserializeEvalAutomorphismKey(unusable_path, sertype) is False + - assert fhe.CryptoContext.DeserializeEvalMultKey(unusable, sertype) is False - assert fhe.CryptoContext.DeserializeEvalAutomorphismKey(unusable, sertype) is False +@pytest.mark.parametrize("sertype", [fhe.BINARY, fhe.JSON], ids=["binary", "json"]) +def test_key_serialization_reports_unwritable_paths(ckks_context, sertype, unusable_path): + """An unwritable file returns False instead of reporting a successful no-op write.""" + cc, keys = ckks_context + key_tag = keys.secretKey.GetKeyTag() + + assert fhe.CryptoContext.SerializeEvalMultKey(unusable_path, sertype, key_tag) is False + assert fhe.CryptoContext.SerializeEvalAutomorphismKey(unusable_path, sertype, key_tag) is False + assert fhe.SerializeEvalBootstrapKey(unusable_path, sertype, cc, key_tag, 8) is False + + +def test_null_context_arguments_raise_instead_of_crashing(ckks_context): + """A None CryptoContext used to be dereferenced in C++ and segfault.""" + cc, keys = ckks_context + ciphertext = cc.Encrypt(keys.publicKey, cc.MakeCKKSPackedPlaintext([0.5])) + + with pytest.raises(RuntimeError): + fhe.SchemeletRLWEMP.ConvertRLWEToCKKS(None, fhe.PolyVector(), keys.publicKey, 1, 1, 0) + with pytest.raises(RuntimeError): + fhe.SchemeSwitchingDataSerializer(None, keys.publicKey, ciphertext) + with pytest.raises(RuntimeError): + fhe.SchemeSwitchingDataSerializer(cc, None, ciphertext) + with pytest.raises(RuntimeError): + fhe.SchemeSwitchingDataSerializer(cc, keys.publicKey, None) def test_integer_conversions_reject_invalid_python_ints(ckks_context): diff --git a/tests/test_binfhe_api_coverage.py b/tests/test_binfhe_api_coverage.py index 8cde1e6..7558d28 100644 --- a/tests/test_binfhe_api_coverage.py +++ b/tests/test_binfhe_api_coverage.py @@ -47,6 +47,23 @@ def bad(m, q): assert lut, "LUT generation must still work after a failed callback" +def test_generate_lut_via_function_supports_nested_calls(): + """The callback slot is saved and restored by a guard, so a callback may + itself generate a LUT without corrupting the outer generation.""" + cc = fhe.BinFHEContext() + cc.GenerateBinFHEContext(fhe.TOY, False, 12) + p = 8 + nested = {} + + def outer(m, q): + if not nested: + nested["lut"] = cc.GenerateLUTviaFunction(lambda mi, qi: (mi + 1) % qi, p) + return (m * 3) % q + + assert cc.GenerateLUTviaFunction(outer, p) == cc.GenerateLUTviaFunction(lambda m, q: (m * 3) % q, p) + assert nested["lut"] == cc.GenerateLUTviaFunction(lambda m, q: (m + 1) % q, p) + + def test_new_binfhe_bindings_round_trip(): cc = fhe.BinFHEContext() cc.GenerateBinFHEContext(fhe.TOY, fhe.GINX) @@ -117,12 +134,12 @@ def test_deserialized_private_keys_are_unaffected(tmp_path): assert cc.Decrypt(restored, ciphertext) == 1 -def test_failed_deserialization_yields_a_null_key(): +def test_failed_deserialization_yields_a_null_key(tmp_path): """A null key is caught by the C++ guard, unlike an empty-but-non-null one.""" cc = fhe.BinFHEContext() cc.GenerateBinFHEContext(fhe.TOY, fhe.GINX) - key, ok = fhe.DeserializeLWEPrivateKey("/nonexistent-directory-for-openfhe-tests/sk", fhe.BINARY) + key, ok = fhe.DeserializeLWEPrivateKey(str(tmp_path / "missing-parent" / "sk"), fhe.BINARY) assert ok is False and key is None with pytest.raises(RuntimeError): cc.PubKeyGen(key) diff --git a/tests/test_examples.py b/tests/test_examples.py index b08fea4..d22e05f 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -102,18 +102,23 @@ def test_run_scripts(raw_modulename): run_example(EXAMPLES_SCRIPTS_PATH, raw_modulename, require_main=True) -# The functional bootstrapping examples take too long for the regular CI runs -# (FE-functional-bootstrapping-ckks uses ring dimension 2^16 with 2^15 slots), -# so they are run only when RUN_SLOW_EXAMPLES is set in the environment. -@pytest.mark.skipif( - not os.environ.get("RUN_SLOW_EXAMPLES"), - reason="set RUN_SLOW_EXAMPLES=1 to run the slow functional bootstrapping examples", -) +# The functional bootstrapping examples take too long for the regular CI runs, +# so functional-bootstrapping-ckks uses the regular `long` marker (--run-long or +# --run-all). FE-functional-bootstrapping-ckks additionally needs more than 16 GB +# of RAM (ring dimension 2^16 with 2^15 slots) and gets OOM-killed on smaller +# hosts, so it stays behind an explicit environment variable instead of a marker: +# --run-all must not take down the machine. @pytest.mark.parametrize( "raw_modulename", [ - "functional-bootstrapping-ckks.py", - "FE-functional-bootstrapping-ckks.py", + pytest.param("functional-bootstrapping-ckks.py", marks=pytest.mark.long), + pytest.param( + "FE-functional-bootstrapping-ckks.py", + marks=pytest.mark.skipif( + not os.environ.get("RUN_HIGH_MEMORY_EXAMPLES"), + reason="needs more than 16 GB of RAM; set RUN_HIGH_MEMORY_EXAMPLES=1 to run", + ), + ), ], ) def test_run_slow_scripts(raw_modulename): @@ -155,7 +160,8 @@ def test_run_binfhe_scripts(raw_modulename): def _parametrized_example_names(test_function): for mark in test_function.pytestmark: if mark.name == "parametrize" and mark.args[0] == "raw_modulename": - return set(mark.args[1]) + # entries are either plain strings or pytest.param(...) wrappers + return {p.values[0] if hasattr(p, "values") else p for p in mark.args[1]} raise AssertionError(f"{test_function.__name__} has no raw_modulename parametrization") From e494ba37312309f8c40d2fa6e1d01b26a31026bf Mon Sep 17 00:00:00 2001 From: Dmitriy Suponitskiy <dsuponitskiy@dualitytech.com> Date: Thu, 15 Oct 2026 22:30:24 -0400 Subject: [PATCH 12/16] Range-check integer conversions against a cached maximum instead of a decimal round trip, improving conversion performance. --- src/include/integer_conversions.h | 34 +++++++++++++++++++++++-------- tests/test_api_coverage.py | 11 ++++++++-- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/include/integer_conversions.h b/src/include/integer_conversions.h index 3ac7659..28c8884 100644 --- a/src/include/integer_conversions.h +++ b/src/include/integer_conversions.h @@ -33,10 +33,19 @@ #include <pybind11/pybind11.h> +#include <limits> #include <string> +#include <type_traits> namespace openfhe_python { +template <typename Integer, typename = void> +struct IsNativeInteger : std::false_type {}; + +template <typename Integer> +struct IsNativeInteger<Integer, std::void_t<typename Integer::Integer, decltype(Integer::IsNativeInt())>> + : std::bool_constant<Integer::IsNativeInt()> {}; + // Convert an arbitrary-size OpenFHE integer to a Python integer without // narrowing through a native C++ integer type. template <typename Integer> @@ -48,9 +57,11 @@ pybind11::int_ IntegerToPyInt(const Integer& value) { } // Convert an arbitrary-size Python integer through its decimal representation. -// OpenFHE integers are unsigned: a negative input is rejected here because the -// OpenFHE string parser hangs on a leading '-', and a value wider than the target -// type is rejected because the constructor silently wraps it. +// OpenFHE integers are unsigned: a negative input is rejected here because some +// OpenFHE string parsers hang on a leading '-'. NativeInteger's parser does not +// detect every overflow, so check its range before construction. BigInteger's +// dynamic backend has no fixed-width overflow, and the fixed backend checks its +// own bound; neither needs a costly integer-to-decimal round trip after parsing. template <typename Integer> Integer PyIntToInteger(const pybind11::int_& value) { if (pybind11::isinstance<pybind11::bool_>(value)) @@ -64,13 +75,18 @@ Integer PyIntToInteger(const pybind11::int_& value) { if (!valueStr.empty() && valueStr[0] == '-') throw pybind11::value_error("OpenFHE integers cannot be negative, got " + valueStr); - Integer converted(valueStr); - if (converted.ToString() != valueStr) { - PyErr_SetString(PyExc_OverflowError, - ("value too large for the target OpenFHE integer type: " + valueStr).c_str()); - throw pybind11::error_already_set(); + if constexpr (IsNativeInteger<Integer>::value) { + using NativeType = typename Integer::Integer; + static const std::string maxValue = Integer(std::numeric_limits<NativeType>::max()).ToString(); + if (valueStr.size() > maxValue.size() || + (valueStr.size() == maxValue.size() && valueStr > maxValue)) { + PyErr_SetString(PyExc_OverflowError, + ("value too large for the target OpenFHE integer type: " + valueStr).c_str()); + throw pybind11::error_already_set(); + } } - return converted; + + return Integer(valueStr); } } // namespace openfhe_python diff --git a/tests/test_api_coverage.py b/tests/test_api_coverage.py index bff64bc..ad35abe 100644 --- a/tests/test_api_coverage.py +++ b/tests/test_api_coverage.py @@ -288,16 +288,23 @@ def test_integer_conversions_reject_invalid_python_ints(ckks_context): negatives hang the OpenFHE string parser, oversized values silently wrap.""" cc, _ = ckks_context plaintext = cc.MakeCKKSPackedPlaintext([0.5]) + native_bits = fhe.get_native_int() + native_max = (1 << native_bits) - 1 with pytest.raises(ValueError): fhe.PolyVector().SwitchModulus(-1) with pytest.raises(OverflowError): plaintext.SetScalingFactorInt(3 * 10**19) + with pytest.raises(OverflowError): + plaintext.SetScalingFactorInt(native_max + 1) with pytest.raises(TypeError): plaintext.SetScalingFactorInt(True) - plaintext.SetScalingFactorInt(2**50) - assert plaintext.GetScalingFactorInt() == 2**50 + plaintext.SetScalingFactorInt(native_max) + assert plaintext.GetScalingFactorInt() == native_max + + # BigInteger is not limited to the configured native word size. + fhe.PolyVector().SwitchModulus(2**220 + 123456789) def test_enum_members_are_exported_at_module_level(): From a33e9443736aacacd493a1d4cc07b3b6f1a397fa Mon Sep 17 00:00:00 2001 From: Dmitriy Suponitskiy <dsuponitskiy@dualitytech.com> Date: Thu, 15 Oct 2026 23:08:10 -0400 Subject: [PATCH 13/16] Add a deferred CI workflow, expose __version__, and update the docs - New CI workflow reusing the Manual pipeline, pinned to OpenFHE v1.6.1; triggers stay commented out, with the PR-checkout and runner-policy preconditions documented in the file - Inject openfhe.__version__ from CMake and re-export it from the package, since the star import skips underscore names - Pin the version test to CMakeLists.txt as the single source of truth - Derive the Sphinx release from the module; refresh the copyright - Document LWESwitchingKey and the BinFHE deserialization functions --- .github/workflows/ci.yml | 40 ++++++++++++++++++++++++++++++++++++++ CMakeLists.txt | 1 + docs/binfhe.rst | 25 +++++++++++++++++++++++- docs/conf.py | 18 +++++++++++------ src/__init__.py | 2 ++ src/lib/bindings.cpp | 5 +++++ tests/test_api_coverage.py | 16 +++++++++++++++ 7 files changed, 100 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..444e399 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,40 @@ +# Automatic build-and-test. It reuses the same pipeline as the Manual workflow, +# pinned to the supported OpenFHE version. +# +# The triggers below stay commented out until after the next release. Before +# enabling them, note: +# - push: safe to enable as-is (the downstream builder checks out +# ${{ github.ref_name }}, which exists in this repository for pushes). +# - pull_request: NOT functional yet. For PR events github.ref_name is +# "<number>/merge", which the builder's fresh clone cannot check out, and fork +# branches do not exist in this repository. The reusable workflow must first be +# taught to fetch the PR merge/head SHA explicitly (or build the caller's +# checked-out workspace). +# - runner policy: the pipeline runs on ${{ vars.RUNNER }}. If that is a +# self-hosted runner, do not run fork-PR code on it; also consider pinning the +# reusable workflow/action/packager references to commit SHAs instead of the +# mutable github-ci/main branches. +name: CI + +permissions: + contents: read + +on: + workflow_dispatch: + # push: + # branches: + # - main + # - dev + # pull_request: # see the notes above before enabling + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-and-test: + uses: ./.github/workflows/manual.yml + with: + compiler: 'CLANG-18' + native_backend: '64' + openfhe_development_branch: 'v1.6.1' diff --git a/CMakeLists.txt b/CMakeLists.txt index 99f2e31..81f34e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,6 +75,7 @@ endif() ### add_executable( test demo-simple-example.cpp ) ### Pybind Modules +add_compile_definitions(OPENFHE_PYTHON_VERSION_STR="${OPENFHE_PYTHON_VERSION}") pybind11_add_module(openfhe src/lib/bindings.cpp src/lib/binfhe_bindings.cpp diff --git a/docs/binfhe.rst b/docs/binfhe.rst index 3f4a147..1a0702c 100644 --- a/docs/binfhe.rst +++ b/docs/binfhe.rst @@ -27,6 +27,13 @@ LWEPublicKey :undoc-members: :show-inheritance: +LWESwitchingKey +---------------- +.. autoclass:: openfhe.LWESwitchingKey + :members: + :undoc-members: + :show-inheritance: + LWEKeyPair ---------------- .. autoclass:: openfhe.LWEKeyPair @@ -39,4 +46,20 @@ RingGSWBTKey .. autoclass:: openfhe.RingGSWBTKey :members: :undoc-members: - :show-inheritance: \ No newline at end of file + :show-inheritance: + +Serialization +---------------- +BinFHE objects are serialized with the overloads of ``SerializeToFile`` and +deserialized with the functions below, each returning an ``(object, success)`` +tuple. + +.. autofunction:: openfhe.DeserializeBinFHECryptoContext + +.. autofunction:: openfhe.DeserializeLWECiphertext + +.. autofunction:: openfhe.DeserializeLWEPrivateKey + +.. autofunction:: openfhe.DeserializeLWEPublicKey + +.. autofunction:: openfhe.DeserializeBinFHEBTKey diff --git a/docs/conf.py b/docs/conf.py index ca48034..2e7f3eb 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -20,13 +20,19 @@ # -- Project information ----------------------------------------------------- project = 'openfhe-python' -copyright = '2023, Yuriy Polyakov, Ian Quah, Rener Oliveira, and Matthew Triplett.' -author = 'Yuriy Polyakov, Ian Quah, Rener Oliveira, and Matthew Triplett.' - +copyright = '2023-2026, Yuriy Polyakov, Ian Quah, Rener Oliveira, Matthew Triplett, and Dmitriy Suponitskiy.' +author = 'Yuriy Polyakov, Ian Quah, Rener Oliveira, Matthew Triplett, and Dmitriy Suponitskiy.' + +# The full version, including alpha/beta/rc tags. Taken from the built module +# when it is importable; the fallback must match OPENFHE_PYTHON_VERSION in +# CMakeLists.txt. +try: + import openfhe + release = openfhe.__version__ +except Exception: + release = '1.6.1.0' # The short X.Y version -version = '' -# The full version, including alpha/beta/rc tags -release = 'v1.5.1.0' +version = '.'.join(release.split('.')[:2]) # -- General configuration --------------------------------------------------- diff --git a/src/__init__.py b/src/__init__.py index 8d1bfc6..bba2d17 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -1 +1,3 @@ from .openfhe import * +# a star import skips underscore-prefixed names, so re-export the version explicitly +from .openfhe import __version__ diff --git a/src/lib/bindings.cpp b/src/lib/bindings.cpp index f606814..cba5dc8 100644 --- a/src/lib/bindings.cpp +++ b/src/lib/bindings.cpp @@ -1946,9 +1946,14 @@ void bind_free_functions(py::module& m) { py::arg("degree")); } +#ifndef OPENFHE_PYTHON_VERSION_STR + #define OPENFHE_PYTHON_VERSION_STR "unknown" +#endif + PYBIND11_MODULE(openfhe, m) { // sequence of function calls matters m.doc() = "Open-Source Fully Homomorphic Encryption Library"; + m.attr("__version__") = OPENFHE_PYTHON_VERSION_STR; bind_DCRTPoly(m); // binfhe library bind_binfhe_enums(m); diff --git a/tests/test_api_coverage.py b/tests/test_api_coverage.py index ad35abe..222483f 100644 --- a/tests/test_api_coverage.py +++ b/tests/test_api_coverage.py @@ -1,3 +1,6 @@ +import re +from pathlib import Path + import openfhe as fhe import pytest @@ -307,6 +310,19 @@ def test_integer_conversions_reject_invalid_python_ints(ckks_context): fhe.PolyVector().SwitchModulus(2**220 + 123456789) +def test_module_exposes_its_version(): + """__version__ must match CMakeLists.txt, and the docs fallback must not drift.""" + repo = Path(__file__).parent.parent + cmake = (repo / "CMakeLists.txt").read_text() + parts = dict(re.findall(r"set\(OPENFHE_PYTHON_VERSION_(MAJOR|MINOR|PATCH|TWEAK) (\d+)\)", cmake)) + expected = ".".join(parts[k] for k in ("MAJOR", "MINOR", "PATCH", "TWEAK")) + + assert fhe.__version__ == expected + + fallback = re.search(r"release = '([0-9.]+)'", (repo / "docs" / "conf.py").read_text()) + assert fallback and fallback.group(1) == expected + + def test_enum_members_are_exported_at_module_level(): """Every pybind11 enum member must also be addressable as fhe.<MEMBER> (export_values).""" enum_types = [obj for obj in vars(fhe).values() From 0935d5152cdeb0d7c8e0b86a6ebcd4b936aa1126 Mon Sep 17 00:00:00 2001 From: Dmitriy Suponitskiy <dsuponitskiy@dualitytech.com> Date: Fri, 16 Oct 2026 00:01:35 -0400 Subject: [PATCH 14/16] Fill the missing docstrings and fix the LUT tests on 32-bit builds - Replace all empty py::doc("") placeholders; commuted subtraction overloads get direction-correct text - Document CCParams, SchSwchParams, Ciphertext, and BinFHE context methods, noting that SchSwchParams getters require an object populated from a CKKS cryptocontext and that GetElements/GetElementsMutable return copies, not views - Use the plain TOY context in the LUT callback tests: the large-precision overload needs primes wider than NATIVE_SIZE=32 supports --- src/lib/bindings.cpp | 400 ++++++++++++++++++++---------- src/lib/binfhe_bindings.cpp | 48 ++-- tests/test_binfhe_api_coverage.py | 11 +- 3 files changed, 304 insertions(+), 155 deletions(-) diff --git a/src/lib/bindings.cpp b/src/lib/bindings.cpp index cba5dc8..99e9c61 100644 --- a/src/lib/bindings.cpp +++ b/src/lib/bindings.cpp @@ -72,73 +72,137 @@ void bind_parameters(py::module &m, const std::string name) { py::class_<CCParams<T>>(m, name.c_str()) .def(py::init<>()) // getters - .def("GetPlaintextModulus", &CCParams<T>::GetPlaintextModulus) - .def("GetScheme", &CCParams<T>::GetScheme) - .def("GetDigitSize", &CCParams<T>::GetDigitSize) - .def("GetStandardDeviation", &CCParams<T>::GetStandardDeviation) - .def("GetSecretKeyDist", &CCParams<T>::GetSecretKeyDist) - .def("GetMaxRelinSkDeg", &CCParams<T>::GetMaxRelinSkDeg) - .def("GetPREMode", &CCParams<T>::GetPREMode) - .def("GetMultipartyMode", &CCParams<T>::GetMultipartyMode) - .def("GetExecutionMode", &CCParams<T>::GetExecutionMode) - .def("GetDecryptionNoiseMode", &CCParams<T>::GetDecryptionNoiseMode) - .def("GetNoiseEstimate", &CCParams<T>::GetNoiseEstimate) - .def("GetDesiredPrecision", &CCParams<T>::GetDesiredPrecision) - .def("GetStatisticalSecurity", &CCParams<T>::GetStatisticalSecurity) - .def("GetNumAdversarialQueries", &CCParams<T>::GetNumAdversarialQueries) + .def("GetPlaintextModulus", &CCParams<T>::GetPlaintextModulus, + py::doc("Get the plaintext modulus (used by BGV/BFV).")) + .def("GetScheme", &CCParams<T>::GetScheme, + py::doc("Get the scheme type.")) + .def("GetDigitSize", &CCParams<T>::GetDigitSize, + py::doc("Get the digit size used in BV key switching.")) + .def("GetStandardDeviation", &CCParams<T>::GetStandardDeviation, + py::doc("Get the standard deviation of the Gaussian error distribution.")) + .def("GetSecretKeyDist", &CCParams<T>::GetSecretKeyDist, + py::doc("Get the secret key distribution.")) + .def("GetMaxRelinSkDeg", &CCParams<T>::GetMaxRelinSkDeg, + py::doc("Get the maximum power of the secret key for which relinearization keys are generated.")) + .def("GetPREMode", &CCParams<T>::GetPREMode, + py::doc("Get the proxy re-encryption security mode.")) + .def("GetMultipartyMode", &CCParams<T>::GetMultipartyMode, + py::doc("Get the multiparty security mode.")) + .def("GetExecutionMode", &CCParams<T>::GetExecutionMode, + py::doc("Get the execution mode (evaluation or noise estimation).")) + .def("GetDecryptionNoiseMode", &CCParams<T>::GetDecryptionNoiseMode, + py::doc("Get the decryption noise mode (fixed noise or noise flooding).")) + .def("GetNoiseEstimate", &CCParams<T>::GetNoiseEstimate, + py::doc("Get the noise estimate obtained in the noise-estimation execution mode.")) + .def("GetDesiredPrecision", &CCParams<T>::GetDesiredPrecision, + py::doc("Get the desired precision for 128-bit CKKS.")) + .def("GetStatisticalSecurity", &CCParams<T>::GetStatisticalSecurity, + py::doc("Get the statistical security parameter (bits) used for noise flooding.")) + .def("GetNumAdversarialQueries", &CCParams<T>::GetNumAdversarialQueries, + py::doc("Get the assumed bound on the number of adversarial queries used for noise flooding.")) .def("GetThresholdNumOfParties", &CCParams<T>::GetThresholdNumOfParties, py::doc(ccparams_GetThresholdNumOfParties_docs)) - .def("GetKeySwitchTechnique", &CCParams<T>::GetKeySwitchTechnique) - .def("GetScalingTechnique", &CCParams<T>::GetScalingTechnique) - .def("GetBatchSize", &CCParams<T>::GetBatchSize) - .def("GetFirstModSize", &CCParams<T>::GetFirstModSize) - .def("GetNumLargeDigits", &CCParams<T>::GetNumLargeDigits) - .def("GetMultiplicativeDepth", &CCParams<T>::GetMultiplicativeDepth) - .def("GetScalingModSize", &CCParams<T>::GetScalingModSize) - .def("GetSecurityLevel", &CCParams<T>::GetSecurityLevel) - .def("GetRingDim", &CCParams<T>::GetRingDim) - .def("GetEvalAddCount", &CCParams<T>::GetEvalAddCount) - .def("GetKeySwitchCount", &CCParams<T>::GetKeySwitchCount) - .def("GetEncryptionTechnique", &CCParams<T>::GetEncryptionTechnique) - .def("GetMultiplicationTechnique", &CCParams<T>::GetMultiplicationTechnique) - .def("GetPRENumHops", &CCParams<T>::GetPRENumHops) - .def("GetInteractiveBootCompressionLevel", &CCParams<T>::GetInteractiveBootCompressionLevel) - .def("GetCompositeDegree", &CCParams<T>::GetCompositeDegree) - .def("GetRegisterWordSize", &CCParams<T>::GetRegisterWordSize) - .def("GetCKKSDataType", &CCParams<T>::GetCKKSDataType) + .def("GetKeySwitchTechnique", &CCParams<T>::GetKeySwitchTechnique, + py::doc("Get the key switching technique (BV or HYBRID).")) + .def("GetScalingTechnique", &CCParams<T>::GetScalingTechnique, + py::doc("Get the rescaling/modulus switching technique (CKKS/BGV).")) + .def("GetBatchSize", &CCParams<T>::GetBatchSize, + py::doc("Get the maximum batch size (number of packed slots).")) + .def("GetFirstModSize", &CCParams<T>::GetFirstModSize, + py::doc("Get the bit size of the first ciphertext prime.")) + .def("GetNumLargeDigits", &CCParams<T>::GetNumLargeDigits, + py::doc("Get the number of digits used in HYBRID key switching.")) + .def("GetMultiplicativeDepth", &CCParams<T>::GetMultiplicativeDepth, + py::doc("Get the multiplicative depth the parameters support.")) + .def("GetScalingModSize", &CCParams<T>::GetScalingModSize, + py::doc("Get the bit size of the scaling ciphertext primes.")) + .def("GetSecurityLevel", &CCParams<T>::GetSecurityLevel, + py::doc("Get the security level according to the HE standard.")) + .def("GetRingDim", &CCParams<T>::GetRingDim, + py::doc("Get the polynomial ring dimension.")) + .def("GetEvalAddCount", &CCParams<T>::GetEvalAddCount, + py::doc("Get the maximum number of additions assumed for BGV/BFV noise estimation.")) + .def("GetKeySwitchCount", &CCParams<T>::GetKeySwitchCount, + py::doc("Get the maximum number of key switching operations assumed for BGV/BFV noise estimation.")) + .def("GetEncryptionTechnique", &CCParams<T>::GetEncryptionTechnique, + py::doc("Get the encryption technique (BFV).")) + .def("GetMultiplicationTechnique", &CCParams<T>::GetMultiplicationTechnique, + py::doc("Get the multiplication technique (BFV).")) + .def("GetPRENumHops", &CCParams<T>::GetPRENumHops, + py::doc("Get the number of hops supported for proxy re-encryption.")) + .def("GetInteractiveBootCompressionLevel", &CCParams<T>::GetInteractiveBootCompressionLevel, + py::doc("Get the compression level for interactive multiparty CKKS bootstrapping.")) + .def("GetCompositeDegree", &CCParams<T>::GetCompositeDegree, + py::doc("Get the composite degree used by CKKS composite scaling.")) + .def("GetRegisterWordSize", &CCParams<T>::GetRegisterWordSize, + py::doc("Get the target hardware register word size for CKKS composite scaling.")) + .def("GetCKKSDataType", &CCParams<T>::GetCKKSDataType, + py::doc("Get the CKKS data type (real or complex).")) // setters - .def("SetPlaintextModulus", &CCParams<T>::SetPlaintextModulus, py::arg("plaintextModulus")) - .def("SetDigitSize", &CCParams<T>::SetDigitSize, py::arg("digitSize")) - .def("SetStandardDeviation", &CCParams<T>::SetStandardDeviation, py::arg("standardDeviation")) - .def("SetSecretKeyDist", &CCParams<T>::SetSecretKeyDist, py::arg("secretKeyDist")) - .def("SetMaxRelinSkDeg", &CCParams<T>::SetMaxRelinSkDeg, py::arg("maxRelinSkDeg")) - .def("SetPREMode", &CCParams<T>::SetPREMode, py::arg("PREMode")) - .def("SetMultipartyMode", &CCParams<T>::SetMultipartyMode, py::arg("multipartyMode")) - .def("SetExecutionMode", &CCParams<T>::SetExecutionMode, py::arg("executionMode")) - .def("SetDecryptionNoiseMode", &CCParams<T>::SetDecryptionNoiseMode, py::arg("decryptionNoiseMode")) - .def("SetNoiseEstimate", &CCParams<T>::SetNoiseEstimate, py::arg("noiseEstimate")) - .def("SetDesiredPrecision", &CCParams<T>::SetDesiredPrecision, py::arg("desiredPrecision")) - .def("SetStatisticalSecurity", &CCParams<T>::SetStatisticalSecurity, py::arg("statisticalSecurity")) - .def("SetNumAdversarialQueries", &CCParams<T>::SetNumAdversarialQueries, py::arg("numAdversarialQueries")) - .def("SetThresholdNumOfParties", &CCParams<T>::SetThresholdNumOfParties, py::arg("thresholdNumOfParties")) - .def("SetKeySwitchTechnique", &CCParams<T>::SetKeySwitchTechnique, py::arg("keySwitchTechnique")) - .def("SetScalingTechnique", &CCParams<T>::SetScalingTechnique, py::arg("scalingTechnique")) - .def("SetBatchSize", &CCParams<T>::SetBatchSize, py::arg("batchSize")) - .def("SetFirstModSize", &CCParams<T>::SetFirstModSize, py::arg("firstModSize")) - .def("SetNumLargeDigits", &CCParams<T>::SetNumLargeDigits, py::arg("numLargeDigits")) - .def("SetMultiplicativeDepth", &CCParams<T>::SetMultiplicativeDepth, py::arg("multiplicativeDepth")) - .def("SetScalingModSize", &CCParams<T>::SetScalingModSize, py::arg("scalingModSize")) - .def("SetSecurityLevel", &CCParams<T>::SetSecurityLevel, py::arg("securityLevel")) - .def("SetRingDim", &CCParams<T>::SetRingDim, py::arg("ringDim")) - .def("SetEvalAddCount", &CCParams<T>::SetEvalAddCount, py::arg("evalAddCount")) - .def("SetKeySwitchCount", &CCParams<T>::SetKeySwitchCount, py::arg("keySwitchCount")) - .def("SetEncryptionTechnique", &CCParams<T>::SetEncryptionTechnique, py::arg("encryptionTechnique")) - .def("SetMultiplicationTechnique", &CCParams<T>::SetMultiplicationTechnique, py::arg("multiplicationTechnique")) - .def("SetPRENumHops", &CCParams<T>::SetPRENumHops, py::arg("PRENumHops")) - .def("SetInteractiveBootCompressionLevel", &CCParams<T>::SetInteractiveBootCompressionLevel, py::arg("interactiveBootCompressionLevel")) - .def("SetCompositeDegree", &CCParams<T>::SetCompositeDegree, py::arg("compositeDegree")) - .def("SetRegisterWordSize", &CCParams<T>::SetRegisterWordSize, py::arg("registerWordSize")) - .def("SetCKKSDataType", &CCParams<T>::SetCKKSDataType, py::arg("ckksDataType")) + .def("SetPlaintextModulus", &CCParams<T>::SetPlaintextModulus, py::arg("plaintextModulus"), + py::doc("Set the plaintext modulus (used by BGV/BFV).")) + .def("SetDigitSize", &CCParams<T>::SetDigitSize, py::arg("digitSize"), + py::doc("Set the digit size used in BV key switching.")) + .def("SetStandardDeviation", &CCParams<T>::SetStandardDeviation, py::arg("standardDeviation"), + py::doc("Set the standard deviation of the Gaussian error distribution.")) + .def("SetSecretKeyDist", &CCParams<T>::SetSecretKeyDist, py::arg("secretKeyDist"), + py::doc("Set the secret key distribution.")) + .def("SetMaxRelinSkDeg", &CCParams<T>::SetMaxRelinSkDeg, py::arg("maxRelinSkDeg"), + py::doc("Set the maximum power of the secret key for which relinearization keys are generated.")) + .def("SetPREMode", &CCParams<T>::SetPREMode, py::arg("PREMode"), + py::doc("Set the proxy re-encryption security mode.")) + .def("SetMultipartyMode", &CCParams<T>::SetMultipartyMode, py::arg("multipartyMode"), + py::doc("Set the multiparty security mode.")) + .def("SetExecutionMode", &CCParams<T>::SetExecutionMode, py::arg("executionMode"), + py::doc("Set the execution mode (evaluation or noise estimation).")) + .def("SetDecryptionNoiseMode", &CCParams<T>::SetDecryptionNoiseMode, py::arg("decryptionNoiseMode"), + py::doc("Set the decryption noise mode (fixed noise or noise flooding).")) + .def("SetNoiseEstimate", &CCParams<T>::SetNoiseEstimate, py::arg("noiseEstimate"), + py::doc("Set the noise estimate obtained in the noise-estimation execution mode.")) + .def("SetDesiredPrecision", &CCParams<T>::SetDesiredPrecision, py::arg("desiredPrecision"), + py::doc("Set the desired precision for 128-bit CKKS.")) + .def("SetStatisticalSecurity", &CCParams<T>::SetStatisticalSecurity, py::arg("statisticalSecurity"), + py::doc("Set the statistical security parameter (bits) used for noise flooding.")) + .def("SetNumAdversarialQueries", &CCParams<T>::SetNumAdversarialQueries, py::arg("numAdversarialQueries"), + py::doc("Set the assumed bound on the number of adversarial queries used for noise flooding.")) + .def("SetThresholdNumOfParties", &CCParams<T>::SetThresholdNumOfParties, py::arg("thresholdNumOfParties"), + py::doc("Set the number of parties in a threshold application.")) + .def("SetKeySwitchTechnique", &CCParams<T>::SetKeySwitchTechnique, py::arg("keySwitchTechnique"), + py::doc("Set the key switching technique (BV or HYBRID).")) + .def("SetScalingTechnique", &CCParams<T>::SetScalingTechnique, py::arg("scalingTechnique"), + py::doc("Set the rescaling/modulus switching technique (CKKS/BGV).")) + .def("SetBatchSize", &CCParams<T>::SetBatchSize, py::arg("batchSize"), + py::doc("Set the maximum batch size (number of packed slots).")) + .def("SetFirstModSize", &CCParams<T>::SetFirstModSize, py::arg("firstModSize"), + py::doc("Set the bit size of the first ciphertext prime.")) + .def("SetNumLargeDigits", &CCParams<T>::SetNumLargeDigits, py::arg("numLargeDigits"), + py::doc("Set the number of digits used in HYBRID key switching.")) + .def("SetMultiplicativeDepth", &CCParams<T>::SetMultiplicativeDepth, py::arg("multiplicativeDepth"), + py::doc("Set the multiplicative depth the parameters must support.")) + .def("SetScalingModSize", &CCParams<T>::SetScalingModSize, py::arg("scalingModSize"), + py::doc("Set the bit size of the scaling ciphertext primes.")) + .def("SetSecurityLevel", &CCParams<T>::SetSecurityLevel, py::arg("securityLevel"), + py::doc("Set the security level according to the HE standard.")) + .def("SetRingDim", &CCParams<T>::SetRingDim, py::arg("ringDim"), + py::doc("Set the polynomial ring dimension.")) + .def("SetEvalAddCount", &CCParams<T>::SetEvalAddCount, py::arg("evalAddCount"), + py::doc("Set the maximum number of additions assumed for BGV/BFV noise estimation.")) + .def("SetKeySwitchCount", &CCParams<T>::SetKeySwitchCount, py::arg("keySwitchCount"), + py::doc("Set the maximum number of key switching operations assumed for BGV/BFV noise estimation.")) + .def("SetEncryptionTechnique", &CCParams<T>::SetEncryptionTechnique, py::arg("encryptionTechnique"), + py::doc("Set the encryption technique (BFV).")) + .def("SetMultiplicationTechnique", &CCParams<T>::SetMultiplicationTechnique, py::arg("multiplicationTechnique"), + py::doc("Set the multiplication technique (BFV).")) + .def("SetPRENumHops", &CCParams<T>::SetPRENumHops, py::arg("PRENumHops"), + py::doc("Set the number of hops supported for proxy re-encryption.")) + .def("SetInteractiveBootCompressionLevel", &CCParams<T>::SetInteractiveBootCompressionLevel, py::arg("interactiveBootCompressionLevel"), + py::doc("Set the compression level for interactive multiparty CKKS bootstrapping.")) + .def("SetCompositeDegree", &CCParams<T>::SetCompositeDegree, py::arg("compositeDegree"), + py::doc("Set the composite degree used by CKKS composite scaling.")) + .def("SetRegisterWordSize", &CCParams<T>::SetRegisterWordSize, py::arg("registerWordSize"), + py::doc("Set the target hardware register word size for CKKS composite scaling.")) + .def("SetCKKSDataType", &CCParams<T>::SetCKKSDataType, py::arg("ckksDataType"), + py::doc("Set the CKKS data type (real or complex).")) .def("__str__",[](const CCParams<T> &params) { std::stringstream stream; stream << params; @@ -262,7 +326,7 @@ void bind_eval_add_family(py::class_<CC, std::shared_ptr<CC>>& cls) { cls.def("EvalAdd", static_cast<Ciphertext<DCRTPoly> (CC::*)(Plaintext&, ConstCiphertext<DCRTPoly>&) const>(&CC::EvalAdd), py::arg("plaintext"), py::arg("ciphertext"), - py::doc("")); // TODO (dsuponit): replace this with an actual docstring + py::doc(cc_EvalAddPlaintext_docs)); } template <typename ScalarT> void bind_eval_add_scalar_overloads(py::class_<CC, std::shared_ptr<CC>>& cls) { @@ -273,7 +337,7 @@ void bind_eval_add_scalar_overloads(py::class_<CC, std::shared_ptr<CC>>& cls) { cls.def("EvalAdd", static_cast<Ciphertext<DCRTPoly> (CC::*)(ScalarT, ConstCiphertext<DCRTPoly>&) const>(&CC::EvalAdd), py::arg("scalar"), py::arg("ciphertext"), - py::doc("")); // TODO (dsuponit): replace this with an actual docstring + py::doc(cc_EvalAddfloat_docs)); } void bind_eval_addinplace_family(py::class_<CC, std::shared_ptr<CC>>& cls) { @@ -288,18 +352,18 @@ void bind_eval_addinplace_family(py::class_<CC, std::shared_ptr<CC>>& cls) { cls.def("EvalAddInPlace", static_cast<void (CC::*)(Plaintext&, Ciphertext<DCRTPoly>&) const>(&CC::EvalAddInPlace), py::arg("plaintext"), py::arg("ciphertext"), - py::doc("")); // TODO (dsuponit): replace this with an actual docstring + py::doc(cc_EvalAddInPlacePlaintext_docs)); } template <typename ScalarT> void bind_eval_addinplace_scalar_overloads(py::class_<CC, std::shared_ptr<CC>>& cls) { cls.def("EvalAddInPlace", static_cast<void (CC::*)(Ciphertext<DCRTPoly>&, ScalarT) const>(&CC::EvalAddInPlace), py::arg("ciphertext"), py::arg("scalar"), - py::doc("")); // TODO (dsuponit): replace this with an actual docstring + py::doc("In-place homomorphic addition of a scalar: ciphertext += scalar.")); cls.def("EvalAddInPlace", static_cast<void (CC::*)(ScalarT, Ciphertext<DCRTPoly>&) const>(&CC::EvalAddInPlace), py::arg("scalar"), py::arg("ciphertext"), - py::doc("")); // TODO (dsuponit): replace this with an actual docstring + py::doc("In-place homomorphic addition of a scalar: ciphertext += scalar.")); } void bind_eval_sub_family(py::class_<CC, std::shared_ptr<CC>>& cls) { @@ -314,7 +378,7 @@ void bind_eval_sub_family(py::class_<CC, std::shared_ptr<CC>>& cls) { cls.def("EvalSub", static_cast<Ciphertext<DCRTPoly> (CC::*)(Plaintext&, ConstCiphertext<DCRTPoly>&) const>(&CC::EvalSub), py::arg("plaintext"), py::arg("ciphertext"), - py::doc("")); // TODO (dsuponit): replace this with an actual docstring + py::doc("Homomorphic subtraction of the ciphertext from the plaintext: plaintext - ciphertext.")); } template <typename ScalarT> void bind_eval_sub_scalar_overloads(py::class_<CC, std::shared_ptr<CC>>& cls) { @@ -325,7 +389,7 @@ void bind_eval_sub_scalar_overloads(py::class_<CC, std::shared_ptr<CC>>& cls) { cls.def("EvalSub", static_cast<Ciphertext<DCRTPoly> (CC::*)(ScalarT, ConstCiphertext<DCRTPoly>&) const>(&CC::EvalSub), py::arg("scalar"), py::arg("ciphertext"), - py::doc("")); // TODO (dsuponit): replace this with an actual docstring + py::doc("Homomorphic subtraction of the ciphertext from a scalar: scalar - ciphertext.")); } void bind_eval_subinplace_family(py::class_<CC, std::shared_ptr<CC>>& cls) { @@ -336,11 +400,11 @@ void bind_eval_subinplace_family(py::class_<CC, std::shared_ptr<CC>>& cls) { cls.def("EvalSubInPlace", static_cast<void (CC::*)(Ciphertext<DCRTPoly>&, ConstPlaintext&) const>(&CC::EvalSubInPlace), py::arg("ciphertext"), py::arg("plaintext"), - py::doc("")); // TODO (dsuponit): replace this with an actual docstring + py::doc("In-place homomorphic subtraction of a plaintext: ciphertext -= plaintext.")); cls.def("EvalSubInPlace", static_cast<void (CC::*)(Plaintext&, Ciphertext<DCRTPoly>&) const>(&CC::EvalSubInPlace), py::arg("plaintext"), py::arg("ciphertext"), - py::doc("")); // TODO (dsuponit): replace this with an actual docstring + py::doc("In-place computation of plaintext - ciphertext; the result is stored in the ciphertext argument.")); } template <typename ScalarT> void bind_eval_subinplace_scalar_overloads(py::class_<CC, std::shared_ptr<CC>>& cls) { @@ -351,7 +415,7 @@ void bind_eval_subinplace_scalar_overloads(py::class_<CC, std::shared_ptr<CC>>& cls.def("EvalSubInPlace", static_cast<void (CC::*)(ScalarT, Ciphertext<DCRTPoly>&) const>(&CC::EvalSubInPlace), py::arg("scalar"), py::arg("ciphertext"), - py::doc("")); // TODO (dsuponit): replace this with an actual docstring + py::doc("In-place computation of scalar - ciphertext; the result is stored in the ciphertext argument.")); } void bind_eval_mult_family(py::class_<CC, std::shared_ptr<CC>>& cls) { @@ -366,7 +430,7 @@ void bind_eval_mult_family(py::class_<CC, std::shared_ptr<CC>>& cls) { cls.def("EvalMult", static_cast<Ciphertext<DCRTPoly> (CC::*)(ConstPlaintext&, ConstCiphertext<DCRTPoly>&) const>(&CC::EvalMult), py::arg("plaintext"), py::arg("ciphertext"), - py::doc("")); // TODO (dsuponit): replace this with an actual docstring + py::doc(cc_EvalMultPlaintext_docs)); } template <typename ScalarT> void bind_eval_mult_scalar_overloads(py::class_<CC, std::shared_ptr<CC>>& cls) { @@ -377,7 +441,7 @@ void bind_eval_mult_scalar_overloads(py::class_<CC, std::shared_ptr<CC>>& cls) { cls.def("EvalMult", static_cast<Ciphertext<DCRTPoly> (CC::*)(ScalarT, ConstCiphertext<DCRTPoly>&) const>(&CC::EvalMult), py::arg("scalar"), py::arg("ciphertext"), - py::doc("")); // TODO (dsuponit): replace this with an actual docstring + py::doc(cc_EvalMultfloat_docs)); } template <typename ScalarT> @@ -385,11 +449,11 @@ void bind_eval_multinplace_scalar_overloads(py::class_<CC, std::shared_ptr<CC>>& cls.def("EvalMultInPlace", static_cast<void (CC::*)(Ciphertext<DCRTPoly>&, ScalarT) const>(&CC::EvalMultInPlace), py::arg("ciphertext"), py::arg("scalar"), - py::doc("")); + py::doc("In-place homomorphic multiplication by a scalar: ciphertext *= scalar.")); cls.def("EvalMultInPlace", static_cast<void (CC::*)(ScalarT, Ciphertext<DCRTPoly>&) const>(&CC::EvalMultInPlace), py::arg("scalar"), py::arg("ciphertext"), - py::doc("")); // TODO (dsuponit): replace this with an actual docstring + py::doc("In-place homomorphic multiplication by a scalar: ciphertext *= scalar.")); } void bind_crypto_context(py::module &m) { @@ -707,7 +771,7 @@ void bind_crypto_context(py::module &m) { .def("EvalAddMutable", py::overload_cast<Plaintext&, Ciphertext<DCRTPoly>&>(&CryptoContextImpl<DCRTPoly>::EvalAddMutable, py::const_), py::arg("plaintext"), py::arg("ciphertext"), - py::doc("")) // TODO (dsuponit): replace this with an actual docstring + py::doc(cc_EvalAddMutablePlaintext_docs)) .def("EvalAddMutableInPlace", &CryptoContextImpl<DCRTPoly>::EvalAddMutableInPlace, py::arg("ciphertext1"), py::arg("ciphertext2"), @@ -726,7 +790,8 @@ void bind_crypto_context(py::module &m) { py::overload_cast<Plaintext&, Ciphertext<DCRTPoly>&>(&CryptoContextImpl<DCRTPoly>::EvalSubMutable, py::const_), py::arg("plaintext"), py::arg("ciphertext"), - py::doc("")) // TODO (dsuponit): replace this with an actual docstring + py::doc("Homomorphic subtraction of the ciphertext from the plaintext (plaintext - ciphertext) " + "for mutable ciphertexts; scaling factors may be adjusted.")) .def("EvalSubMutableInPlace", &CryptoContextImpl<DCRTPoly>::EvalSubMutableInPlace, py::arg("ciphertext1"), py::arg("ciphertext2"), @@ -745,7 +810,7 @@ void bind_crypto_context(py::module &m) { py::overload_cast<Plaintext&, Ciphertext<DCRTPoly>&>(&CryptoContextImpl<DCRTPoly>::EvalMultMutable, py::const_), py::arg("plaintext"), py::arg("ciphertext"), - py::doc("")) // TODO (dsuponit): replace this with an actual docstring + py::doc(cc_EvalMultMutablePlaintext_docs)) .def("EvalMultMutableInPlace", &CryptoContextImpl<DCRTPoly>::EvalMultMutableInPlace, py::arg("ciphertext1"), py::arg("ciphertext2"), @@ -1173,7 +1238,9 @@ void bind_crypto_context(py::module &m) { .def("Compress", &CryptoContextImpl<DCRTPoly>::Compress, py::arg("ciphertext"), py::arg("towersLeft") = 1, - py::arg("noiseScaleDeg") = 1) + py::arg("noiseScaleDeg") = 1, + py::doc("Compress the ciphertext down to the given number of RNS towers, " + "reducing its size before storage or transmission.")) .def("EvalMultMany", &CryptoContextImpl<DCRTPoly>::EvalMultMany, py::arg("ciphertextVec")) .def("EvalAddMany", &CryptoContextImpl<DCRTPoly>::EvalAddMany, @@ -1821,7 +1888,8 @@ void bind_ciphertext(py::module &m) { .def("SetLevel", &CiphertextImpl<DCRTPoly>::SetLevel, py::arg("level"), py::doc(ctx_SetLevel_docs)) - .def("Clone", &CiphertextImpl<DCRTPoly>::Clone) + .def("Clone", &CiphertextImpl<DCRTPoly>::Clone, + py::doc("Return a deep copy of the ciphertext.")) .def("CloneEmpty", &CiphertextImpl<DCRTPoly>::CloneEmpty, py::doc(ctx_CloneEmpty_docs)) .def("NumberCiphertextElements", &CiphertextImpl<DCRTPoly>::NumberCiphertextElements, @@ -1836,8 +1904,10 @@ void bind_ciphertext(py::module &m) { py::doc(ctx_GetHopLevel_docs)) .def("SetHopLevel", &CiphertextImpl<DCRTPoly>::SetHopLevel, py::arg("hopLevel"), py::doc(ctx_SetHopLevel_docs)) - .def("GetScalingFactor", &CiphertextImpl<DCRTPoly>::GetScalingFactor) - .def("SetScalingFactor", &CiphertextImpl<DCRTPoly>::SetScalingFactor, py::arg("sf")) + .def("GetScalingFactor", &CiphertextImpl<DCRTPoly>::GetScalingFactor, + py::doc("Get the CKKS scaling factor of the ciphertext.")) + .def("SetScalingFactor", &CiphertextImpl<DCRTPoly>::SetScalingFactor, py::arg("sf"), + py::doc("Set the CKKS scaling factor of the ciphertext.")) .def("GetScalingFactorInt", [](const CiphertextImpl<DCRTPoly>& self) { return openfhe_python::IntegerToPyInt(self.GetScalingFactorInt()); }, @@ -1846,13 +1916,20 @@ void bind_ciphertext(py::module &m) { self.SetScalingFactorInt(openfhe_python::PyIntToInteger<NativeInteger>(sf)); }, py::arg("sf"), py::doc(ctx_SetScalingFactorInt_docs)) - .def("GetSlots", &CiphertextImpl<DCRTPoly>::GetSlots) - .def("SetSlots", &CiphertextImpl<DCRTPoly>::SetSlots, py::arg("slots")) - .def("GetNoiseScaleDeg", &CiphertextImpl<DCRTPoly>::GetNoiseScaleDeg) - .def("SetNoiseScaleDeg", &CiphertextImpl<DCRTPoly>::SetNoiseScaleDeg, py::arg("noiseScaleDeg")) - .def("GetCryptoContext", &CiphertextImpl<DCRTPoly>::GetCryptoContext) - .def("GetKeyTag", &CiphertextImpl<DCRTPoly>::GetKeyTag) - .def("GetEncodingType", &CiphertextImpl<DCRTPoly>::GetEncodingType) + .def("GetSlots", &CiphertextImpl<DCRTPoly>::GetSlots, + py::doc("Get the number of packed slots.")) + .def("SetSlots", &CiphertextImpl<DCRTPoly>::SetSlots, py::arg("slots"), + py::doc("Set the number of packed slots.")) + .def("GetNoiseScaleDeg", &CiphertextImpl<DCRTPoly>::GetNoiseScaleDeg, + py::doc("Get the degree of the scaling factor for the encrypted message.")) + .def("SetNoiseScaleDeg", &CiphertextImpl<DCRTPoly>::SetNoiseScaleDeg, py::arg("noiseScaleDeg"), + py::doc("Set the degree of the scaling factor for the encrypted message.")) + .def("GetCryptoContext", &CiphertextImpl<DCRTPoly>::GetCryptoContext, + py::doc("Get the CryptoContext this ciphertext belongs to.")) + .def("GetKeyTag", &CiphertextImpl<DCRTPoly>::GetKeyTag, + py::doc("Get the tag of the key the ciphertext was encrypted with.")) + .def("GetEncodingType", &CiphertextImpl<DCRTPoly>::GetEncodingType, + py::doc("Get the plaintext encoding type the ciphertext carries.")) .def("SetEncodingType", &CiphertextImpl<DCRTPoly>::SetEncodingType, py::arg("encodingType"), py::doc(ctx_SetEncodingType_docs)) .def("SetKeyTag", &CiphertextImpl<DCRTPoly>::SetKeyTag, py::arg("keyTag"), @@ -1860,62 +1937,115 @@ void bind_ciphertext(py::module &m) { .def("GetElements", [](const CiphertextImpl<DCRTPoly>& self) -> const std::vector<DCRTPoly>& { return self.GetElements(); }, - py::return_value_policy::reference_internal) + py::return_value_policy::reference_internal, + py::doc("Return a copy of the ciphertext's ring elements.")) .def("GetElementsMutable", [](CiphertextImpl<DCRTPoly>& self) -> std::vector<DCRTPoly>& { return self.GetElements(); }, - py::return_value_policy::reference_internal) + py::return_value_policy::reference_internal, + py::doc("Return a mutable Python list copied from the ciphertext's ring elements; " + "call SetElements to apply changes.")) .def("SetElements", [](CiphertextImpl<DCRTPoly>& self, const std::vector<DCRTPoly>& elems) { self.SetElements(elems); }, - py::arg("elements")) + py::arg("elements"), + py::doc("Replace the ciphertext's ring elements with the given elements.")) .def("SetElementsMove", [](CiphertextImpl<DCRTPoly>& self, std::vector<DCRTPoly>&& elems) { self.SetElements(std::move(elems)); }, - py::arg("elements")); + py::arg("elements"), + py::doc("Replace the ciphertext's ring elements; the C++ vector converted from the " + "given list is moved into the ciphertext, the Python list is not consumed.")); } void bind_sch_swch_params(py::module &m) { - py::class_<SchSwchParams>(m, "SchSwchParams") + py::class_<SchSwchParams>(m, "SchSwchParams", + "Parameters for switching between CKKS and FHEW/TFHE.\n\n" + "The getters raise an exception until the object has been populated from a CKKS\n" + "cryptocontext (see CryptoContext.SetParamsFromCKKSCryptocontext). The initial CKKS\n" + "modulus, ring dimension, scaling modulus size and batch size are filled in\n" + "internally during that call and are not intended to be set by users.") .def(py::init<>()) - .def("GetSecurityLevelCKKS", &SchSwchParams::GetSecurityLevelCKKS) - .def("GetSecurityLevelFHEW", &SchSwchParams::GetSecurityLevelFHEW) - .def("GetArbitraryFunctionEvaluation", &SchSwchParams::GetArbitraryFunctionEvaluation) - .def("GetUseDynamicModeFHEW", &SchSwchParams::GetUseDynamicModeFHEW) - .def("GetComputeArgmin", &SchSwchParams::GetComputeArgmin) - .def("GetOneHotEncoding", &SchSwchParams::GetOneHotEncoding) - .def("GetUseAltArgmin", &SchSwchParams::GetUseAltArgmin) - .def("GetNumSlotsCKKS", &SchSwchParams::GetNumSlotsCKKS) - .def("GetNumValues", &SchSwchParams::GetNumValues) - .def("GetCtxtModSizeFHEWLargePrec", &SchSwchParams::GetCtxtModSizeFHEWLargePrec) - .def("GetCtxtModSizeFHEWIntermedSwch", &SchSwchParams::GetCtxtModSizeFHEWIntermedSwch) - .def("GetBStepLTrCKKStoFHEW", &SchSwchParams::GetBStepLTrCKKStoFHEW) - .def("GetBStepLTrFHEWtoCKKS", &SchSwchParams::GetBStepLTrFHEWtoCKKS) - .def("GetLevelLTrCKKStoFHEW", &SchSwchParams::GetLevelLTrCKKStoFHEW) - .def("GetLevelLTrFHEWtoCKKS", &SchSwchParams::GetLevelLTrFHEWtoCKKS) - .def("GetInitialCKKSModulus", &SchSwchParams::GetInitialCKKSModulus) - .def("GetRingDimension", &SchSwchParams::GetRingDimension) - .def("GetScalingModSize", &SchSwchParams::GetScalingModSize) - .def("GetBatchSize", &SchSwchParams::GetBatchSize) - .def("SetSecurityLevelCKKS", &SchSwchParams::SetSecurityLevelCKKS, py::arg("securityLevelCKKS")) - .def("SetSecurityLevelFHEW", &SchSwchParams::SetSecurityLevelFHEW, py::arg("securityLevelFHEW")) - .def("SetArbitraryFunctionEvaluation", &SchSwchParams::SetArbitraryFunctionEvaluation, py::arg("arbitraryFunctionEvaluation")) - .def("SetUseDynamicModeFHEW", &SchSwchParams::SetUseDynamicModeFHEW, py::arg("useDynamicModeFHEW")) - .def("SetComputeArgmin", &SchSwchParams::SetComputeArgmin, py::arg("computeArgmin")) - .def("SetOneHotEncoding", &SchSwchParams::SetOneHotEncoding, py::arg("oneHotEncoding")) - .def("SetUseAltArgmin", &SchSwchParams::SetUseAltArgmin, py::arg("useAltArgmin")) - .def("SetNumSlotsCKKS", &SchSwchParams::SetNumSlotsCKKS, py::arg("numSlotsCKKS")) - .def("SetNumValues", &SchSwchParams::SetNumValues, py::arg("numValues")) - .def("SetCtxtModSizeFHEWLargePrec", &SchSwchParams::SetCtxtModSizeFHEWLargePrec, py::arg("ctxtModSizeFHEWLargePrec")) - .def("SetCtxtModSizeFHEWIntermedSwch", &SchSwchParams::SetCtxtModSizeFHEWIntermedSwch, py::arg("ctxtModSizeFHEWIntermedSwch")) - .def("SetBStepLTrCKKStoFHEW", &SchSwchParams::SetBStepLTrCKKStoFHEW, py::arg("bStepLTrCKKStoFHEW")) - .def("SetBStepLTrFHEWtoCKKS", &SchSwchParams::SetBStepLTrFHEWtoCKKS, py::arg("bStepLTrFHEWtoCKKS")) - .def("SetLevelLTrCKKStoFHEW", &SchSwchParams::SetLevelLTrCKKStoFHEW, py::arg("levelLTrCKKStoFHEW")) - .def("SetLevelLTrFHEWtoCKKS", &SchSwchParams::SetLevelLTrFHEWtoCKKS, py::arg("levelLTrFHEWtoCKKS")) - .def("SetInitialCKKSModulus", &SchSwchParams::SetInitialCKKSModulus, py::arg("initialCKKSModulus")) - .def("SetRingDimension", &SchSwchParams::SetRingDimension, py::arg("ringDimension")) - .def("SetScalingModSize", &SchSwchParams::SetScalingModSize, py::arg("scalingModSize")) - .def("SetBatchSize", &SchSwchParams::SetBatchSize, py::arg("batchSize")) + .def("GetSecurityLevelCKKS", &SchSwchParams::GetSecurityLevelCKKS, + py::doc("Get the security level of the CKKS cryptocontext.")) + .def("GetSecurityLevelFHEW", &SchSwchParams::GetSecurityLevelFHEW, + py::doc("Get the security level of the FHEW cryptocontext.")) + .def("GetArbitraryFunctionEvaluation", &SchSwchParams::GetArbitraryFunctionEvaluation, + py::doc("Get whether arbitrary function evaluation is needed in FHEW.")) + .def("GetUseDynamicModeFHEW", &SchSwchParams::GetUseDynamicModeFHEW, + py::doc("Get whether the dynamic mode is used in FHEW.")) + .def("GetComputeArgmin", &SchSwchParams::GetComputeArgmin, + py::doc("Get whether argmin is computed.")) + .def("GetOneHotEncoding", &SchSwchParams::GetOneHotEncoding, + py::doc("Get whether the argmin result is one-hot encoded.")) + .def("GetUseAltArgmin", &SchSwchParams::GetUseAltArgmin, + py::doc("Get whether the alternative argmin method is used.")) + .def("GetNumSlotsCKKS", &SchSwchParams::GetNumSlotsCKKS, + py::doc("Get the number of CKKS slots to switch.")) + .def("GetNumValues", &SchSwchParams::GetNumValues, + py::doc("Get the number of values used in argmin computations.")) + .def("GetCtxtModSizeFHEWLargePrec", &SchSwchParams::GetCtxtModSizeFHEWLargePrec, + py::doc("Get the FHEW ciphertext modulus size for large-precision evaluation.")) + .def("GetCtxtModSizeFHEWIntermedSwch", &SchSwchParams::GetCtxtModSizeFHEWIntermedSwch, + py::doc("Get the FHEW ciphertext modulus size for the intermediate switching.")) + .def("GetBStepLTrCKKStoFHEW", &SchSwchParams::GetBStepLTrCKKStoFHEW, + py::doc("Get the baby step of the CKKS-to-FHEW linear transform.")) + .def("GetBStepLTrFHEWtoCKKS", &SchSwchParams::GetBStepLTrFHEWtoCKKS, + py::doc("Get the baby step of the FHEW-to-CKKS linear transform.")) + .def("GetLevelLTrCKKStoFHEW", &SchSwchParams::GetLevelLTrCKKStoFHEW, + py::doc("Get the level of the CKKS-to-FHEW linear transform.")) + .def("GetLevelLTrFHEWtoCKKS", &SchSwchParams::GetLevelLTrFHEWtoCKKS, + py::doc("Get the level of the FHEW-to-CKKS linear transform.")) + .def("GetInitialCKKSModulus", &SchSwchParams::GetInitialCKKSModulus, + py::doc("Get the initial CKKS modulus (the value itself, not its bit size).")) + .def("GetRingDimension", &SchSwchParams::GetRingDimension, + py::doc("Get the CKKS ring dimension.")) + .def("GetScalingModSize", &SchSwchParams::GetScalingModSize, + py::doc("Get the CKKS scaling modulus size.")) + .def("GetBatchSize", &SchSwchParams::GetBatchSize, + py::doc("Get the CKKS batch size.")) + .def("SetSecurityLevelCKKS", &SchSwchParams::SetSecurityLevelCKKS, py::arg("securityLevelCKKS"), + py::doc("Set the security level of the CKKS cryptocontext.")) + .def("SetSecurityLevelFHEW", &SchSwchParams::SetSecurityLevelFHEW, py::arg("securityLevelFHEW"), + py::doc("Set the security level of the FHEW cryptocontext.")) + .def("SetArbitraryFunctionEvaluation", &SchSwchParams::SetArbitraryFunctionEvaluation, py::arg("arbitraryFunctionEvaluation"), + py::doc("Set whether arbitrary function evaluation is needed in FHEW.")) + .def("SetUseDynamicModeFHEW", &SchSwchParams::SetUseDynamicModeFHEW, py::arg("useDynamicModeFHEW"), + py::doc("Set whether the dynamic mode is used in FHEW.")) + .def("SetComputeArgmin", &SchSwchParams::SetComputeArgmin, py::arg("computeArgmin"), + py::doc("Set whether argmin is computed.")) + .def("SetOneHotEncoding", &SchSwchParams::SetOneHotEncoding, py::arg("oneHotEncoding"), + py::doc("Set whether the argmin result is one-hot encoded.")) + .def("SetUseAltArgmin", &SchSwchParams::SetUseAltArgmin, py::arg("useAltArgmin"), + py::doc("Set whether the alternative argmin method is used.")) + .def("SetNumSlotsCKKS", &SchSwchParams::SetNumSlotsCKKS, py::arg("numSlotsCKKS"), + py::doc("Set the number of CKKS slots to switch.")) + .def("SetNumValues", &SchSwchParams::SetNumValues, py::arg("numValues"), + py::doc("Set the number of values used in argmin computations.")) + .def("SetCtxtModSizeFHEWLargePrec", &SchSwchParams::SetCtxtModSizeFHEWLargePrec, py::arg("ctxtModSizeFHEWLargePrec"), + py::doc("Set the FHEW ciphertext modulus size for large-precision evaluation.")) + .def("SetCtxtModSizeFHEWIntermedSwch", &SchSwchParams::SetCtxtModSizeFHEWIntermedSwch, py::arg("ctxtModSizeFHEWIntermedSwch"), + py::doc("Set the FHEW ciphertext modulus size for the intermediate switching.")) + .def("SetBStepLTrCKKStoFHEW", &SchSwchParams::SetBStepLTrCKKStoFHEW, py::arg("bStepLTrCKKStoFHEW"), + py::doc("Set the baby step of the CKKS-to-FHEW linear transform.")) + .def("SetBStepLTrFHEWtoCKKS", &SchSwchParams::SetBStepLTrFHEWtoCKKS, py::arg("bStepLTrFHEWtoCKKS"), + py::doc("Set the baby step of the FHEW-to-CKKS linear transform.")) + .def("SetLevelLTrCKKStoFHEW", &SchSwchParams::SetLevelLTrCKKStoFHEW, py::arg("levelLTrCKKStoFHEW"), + py::doc("Set the level of the CKKS-to-FHEW linear transform.")) + .def("SetLevelLTrFHEWtoCKKS", &SchSwchParams::SetLevelLTrFHEWtoCKKS, py::arg("levelLTrFHEWtoCKKS"), + py::doc("Set the level of the FHEW-to-CKKS linear transform.")) + .def("SetInitialCKKSModulus", &SchSwchParams::SetInitialCKKSModulus, py::arg("initialCKKSModulus"), + py::doc("Set the initial CKKS modulus (populated internally from the CKKS cryptocontext; " + "not intended for user configuration).")) + .def("SetRingDimension", &SchSwchParams::SetRingDimension, py::arg("ringDimension"), + py::doc("Set the CKKS ring dimension (populated internally from the CKKS cryptocontext; " + "not intended for user configuration).")) + .def("SetScalingModSize", &SchSwchParams::SetScalingModSize, py::arg("scalingModSize"), + py::doc("Set the CKKS scaling modulus size (populated internally from the CKKS cryptocontext; " + "not intended for user configuration).")) + .def("SetBatchSize", &SchSwchParams::SetBatchSize, py::arg("batchSize"), + py::doc("Set the CKKS batch size (populated internally from the CKKS cryptocontext; " + "not intended for user configuration).")) .def("SetParamsFromCKKSCryptocontextCalled", &SchSwchParams::SetParamsFromCKKSCryptocontextCalled, py::doc(schswchparams_SetParamsFromCKKSCryptocontextCalled_docs)) .def("__str__", [](const SchSwchParams &params) { diff --git a/src/lib/binfhe_bindings.cpp b/src/lib/binfhe_bindings.cpp index adeb1b9..8b76a9c 100644 --- a/src/lib/binfhe_bindings.cpp +++ b/src/lib/binfhe_bindings.cpp @@ -203,8 +203,10 @@ void bind_binfhe_context(py::module &m) { py::arg("method") = GINX, py::arg("timeOptimization") = false) .def("KeyGen", &BinFHEContext::KeyGen, binfhe_KeyGen_docs) - .def("KeyGenN", &BinFHEContext::KeyGenN) - .def("KeyGenPair", &BinFHEContext::KeyGenPair) + .def("KeyGenN", &BinFHEContext::KeyGenN, + py::doc("Generate an LWE secret key for the ring dimension N (used with key switching).")) + .def("KeyGenPair", &BinFHEContext::KeyGenPair, + py::doc("Generate an LWE secret/public key pair for public-key encryption.")) .def("KeySwitchGen", &BinFHEContext::KeySwitchGen, py::arg("sk"), py::arg("skN"), @@ -269,19 +271,23 @@ void bind_binfhe_context(py::module &m) { .def("Getn", [](BinFHEContext& self) { return self.GetParams()->GetLWEParams()->Getn(); - }) + }, + py::doc("Get the LWE lattice parameter n (the small dimension).")) .def("Getq", [](BinFHEContext& self) { return self.GetParams()->GetLWEParams()->Getq().ConvertToInt<uint64_t>(); - }) + }, + py::doc("Get the small LWE ciphertext modulus q.")) .def("GetMaxPlaintextSpace", [](BinFHEContext& self) { return self.GetMaxPlaintextSpace().ConvertToInt<uint64_t>(); - }) + }, + py::doc("Get the maximum plaintext space supported by one bootstrapping invocation.")) .def("GetBeta", [](BinFHEContext& self) { return self.GetBeta().ConvertToInt<uint64_t>(); - }) + }, + py::doc("Get the noise bound beta used in decryption and function evaluation.")) .def("EvalDecomp", &BinFHEContext::EvalDecomp, binfhe_EvalDecomp_docs, py::arg("ct")) @@ -310,13 +316,16 @@ void bind_binfhe_context(py::module &m) { py::arg("ct"), py::arg("schemeSwitch") = false) .def("EvalConstant", &BinFHEContext::EvalConstant, - py::arg("value")) - .def("ClearBTKeys", &BinFHEContext::ClearBTKeys) + py::arg("value"), + py::doc("Create a trivial (noiseless) encryption of the given bit.")) + .def("ClearBTKeys", &BinFHEContext::ClearBTKeys, + py::doc("Clear the bootstrapping keys held by the context.")) .def("HasInternal32RefreshKey", &BinFHEContext::HasInternal32RefreshKey, py::doc(binfhe_HasInternal32RefreshKey_docs)) .def("HasInternal32SwitchKey", &BinFHEContext::HasInternal32SwitchKey, py::doc(binfhe_HasInternal32SwitchKey_docs)) - .def("Bootstrap", &BinFHEContext::Bootstrap, py::arg("ct"), py::arg("extended") = false) + .def("Bootstrap", &BinFHEContext::Bootstrap, py::arg("ct"), py::arg("extended") = false, + py::doc("Refresh a noisy ciphertext without evaluating a gate, producing a fresh ciphertext.")) .def("SerializedVersion", &BinFHEContext::SerializedVersion, binfhe_SerializedVersion_docs) .def("SerializedObjectName", &BinFHEContext::SerializedObjectName, @@ -324,28 +333,35 @@ void bind_binfhe_context(py::module &m) { // note: save/load (cereal archives), GetRefreshKey, GetBinFHEScheme, GetLWEScheme and // GetParams are not bound: their argument/return types are not exposed to Python, so // the bindings could never be called successfully - .def("GetPublicKey", &BinFHEContext::GetPublicKey) + .def("GetPublicKey", &BinFHEContext::GetPublicKey, + py::doc("Get the LWE public key held by the context (generated by PubKeyGen or BTKeyGen with PUB_ENCRYPT).")) .def("GetSwitchKey", &BinFHEContext::GetSwitchKey, py::doc(binfhe_GetSwitchKey_docs)) .def("GetN", [](BinFHEContext& self) { return self.GetParams()->GetLWEParams()->GetN(); - }) + }, + py::doc("Get the ring dimension N used for bootstrapping.")) .def("GetQ", [](BinFHEContext& self) { return self.GetParams()->GetLWEParams()->GetQ().ConvertToInt<uint64_t>(); - }) - .def("GetBTKey", &BinFHEContext::GetBTKey) + }, + py::doc("Get the large ciphertext modulus Q used for bootstrapping.")) + .def("GetBTKey", &BinFHEContext::GetBTKey, + py::doc("Get the bootstrapping key set (refreshing, key switching and public keys) generated by BTKeyGen.")) .def("BTKeyLoad", &BinFHEContext::BTKeyLoad, py::arg("key"), - py::arg("internal32") = true) + py::arg("internal32") = true, + py::doc("Load a previously generated or deserialized bootstrapping key set into the context.")) .def("GetBTKeyMap", [](BinFHEContext& self) { return *self.GetBTKeyMap(); - }) + }, + py::doc("Get the map of bootstrapping key sets indexed by the gadget base baseG.")) .def("BTKeyMapLoadSingleElement", &BinFHEContext::BTKeyMapLoadSingleElement, py::arg("baseG"), - py::arg("key")); + py::arg("key"), + py::doc("Insert one bootstrapping key set into the key map under the given gadget base.")); } // binfhe serialization helpers, following the same patterns as src/lib/pke/serialization.cpp diff --git a/tests/test_binfhe_api_coverage.py b/tests/test_binfhe_api_coverage.py index 7558d28..9450b9a 100644 --- a/tests/test_binfhe_api_coverage.py +++ b/tests/test_binfhe_api_coverage.py @@ -33,9 +33,11 @@ def test_lwe_public_key_cannot_be_default_constructed(): def test_generate_lut_via_function_recovers_from_callback_errors(): """The LUT callback is routed through thread-local state; a raising callback must propagate its exception and leave the wrapper usable afterwards.""" + # the plain TOY context works on every NATIVE_SIZE; the large-precision + # overload needs primes wider than a 32-bit build can represent cc = fhe.BinFHEContext() - cc.GenerateBinFHEContext(fhe.TOY, False, 12) - p = 8 + cc.GenerateBinFHEContext(fhe.TOY, fhe.GINX) + p = cc.GetMaxPlaintextSpace() def bad(m, q): raise ValueError("callback failure") @@ -50,9 +52,10 @@ def bad(m, q): def test_generate_lut_via_function_supports_nested_calls(): """The callback slot is saved and restored by a guard, so a callback may itself generate a LUT without corrupting the outer generation.""" + # the plain TOY context works on every NATIVE_SIZE (see the test above) cc = fhe.BinFHEContext() - cc.GenerateBinFHEContext(fhe.TOY, False, 12) - p = 8 + cc.GenerateBinFHEContext(fhe.TOY, fhe.GINX) + p = cc.GetMaxPlaintextSpace() nested = {} def outer(m, q): From a19086cb39f21897c5dfc1661eddb312fd829332 Mon Sep 17 00:00:00 2001 From: Dmitriy Suponitskiy <dsuponitskiy@dualitytech.com> Date: Fri, 16 Oct 2026 02:07:37 -0400 Subject: [PATCH 15/16] Fix the integer-overflow test on 128-bit builds The test used the hardcoded value 3*10**19, which overflows a 64-bit NativeInteger but is representable on NATIVE_SIZE=128, so no OverflowError was raised and the CI job failed. Derive the overflow probes from the configured native size instead: native_max + 1 and native_max * 10. The conversion guard itself was correct. --- tests/test_api_coverage.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/test_api_coverage.py b/tests/test_api_coverage.py index 222483f..ba347ad 100644 --- a/tests/test_api_coverage.py +++ b/tests/test_api_coverage.py @@ -296,10 +296,13 @@ def test_integer_conversions_reject_invalid_python_ints(ckks_context): with pytest.raises(ValueError): fhe.PolyVector().SwitchModulus(-1) - with pytest.raises(OverflowError): - plaintext.SetScalingFactorInt(3 * 10**19) + # native_max + 1 == 2**native_bits has as many decimal digits as native_max, + # exercising the lexicographic branch of the overflow check; native_max * 10 + # is one digit longer, exercising the string-length branch. with pytest.raises(OverflowError): plaintext.SetScalingFactorInt(native_max + 1) + with pytest.raises(OverflowError): + plaintext.SetScalingFactorInt(native_max * 10) with pytest.raises(TypeError): plaintext.SetScalingFactorInt(True) From 88fc2fb69b0aa565fe733877727b4e17f3f1ee84 Mon Sep 17 00:00:00 2001 From: Dmitriy Suponitskiy <dsuponitskiy@dualitytech.com> Date: Fri, 16 Oct 2026 10:03:55 -0400 Subject: [PATCH 16/16] Add docstrings to all remaining bindings All 661 public APIs in the openfhe module now carry a docstring (verified at runtime). Adds docstrings/serialization_docs.h for the (de)serialization functions, inline py::doc() for the CryptoContext getters/setters, key, Plaintext, Ciphertext and BinFHE/LWE methods and free functions, and class docstrings for every exposed class. --- src/include/docstrings/fbt_docs.h | 584 ++++++++++++++++++++ src/include/docstrings/serialization_docs.h | 311 +++++++++++ src/lib/bindings.cpp | 267 ++++++--- src/lib/binfhe_bindings.cpp | 70 ++- src/lib/pke/fbt.cpp | 148 +++-- src/lib/pke/serialization.cpp | 194 ++++--- 6 files changed, 1364 insertions(+), 210 deletions(-) create mode 100644 src/include/docstrings/serialization_docs.h diff --git a/src/include/docstrings/fbt_docs.h b/src/include/docstrings/fbt_docs.h index 1a91283..83d434f 100644 --- a/src/include/docstrings/fbt_docs.h +++ b/src/include/docstrings/fbt_docs.h @@ -35,6 +35,238 @@ // These constants have external linkage, so this header must be included by // fbt.cpp only, like the other docstring headers. +const char* cc_GetBootstrapDepthApproxMod_docs = R"pbdoc( + Multiplicative depth of CKKS bootstrapping for a user-supplied depth of the approximate + modular reduction. To be deprecated; prefer GetBootstrapDepth(levelBudget, keyDist). + + :param depth: depth of the Chebyshev interpolation of the approximate modular reduction; + the double-angle iterations of UNIFORM_TERNARY are added internally + :type depth: int + :param levelBudget: levels spent on CoeffsToSlots and SlotsToCoeffs, respectively + :type levelBudget: list[int] + :param keyDist: secret key distribution of the cryptocontext + :type keyDist: SecretKeyDist + :return: the multiplicative depth consumed by EvalBootstrap + :rtype: int +)pbdoc"; + +const char* cc_GetBootstrapDepth_docs = R"pbdoc( + Multiplicative depth consumed by CKKS bootstrapping: the approximate modular reduction + (Chebyshev interpolation and double-angle iterations, selected by the secret key + distribution) plus the CoeffsToSlots and SlotsToCoeffs level budgets. Add the number of + levels needed after bootstrapping to obtain the multiplicative depth of the cryptocontext. + + :param levelBudget: levels spent on CoeffsToSlots and SlotsToCoeffs, respectively + :type levelBudget: list[int] + :param keyDist: secret key distribution of the cryptocontext + :type keyDist: SecretKeyDist + :return: the multiplicative depth consumed by EvalBootstrap + :rtype: int +)pbdoc"; + +const char* cc_GetFBTDepth_docs = R"pbdoc( + Multiplicative depth consumed by functional bootstrapping (EvalFBT): the CoeffsToSlots and + SlotsToCoeffs level budgets plus the depth of the look-up table evaluation. For + SPARSE_ENCAPSULATED, firstModSize (the size of the first modulus in bits) selects the + approximation tables: a first modulus above 60 bits gives the sparse secret Hamming weight + 64 and uses the K = 28 tables of SPARSE_TERNARY, which need one more level than the K = 16 + tables of the default Hamming weight 32. + + :param levelBudget: levels spent on CoeffsToSlots and SlotsToCoeffs, respectively + :type levelBudget: list[int] + :param coefficients: trigonometric Hermite interpolation coefficients of the look-up table + (complex, or integer for a Boolean function of order 1) + :type coefficients: list[int] | list[complex] + :param PInput: plaintext modulus of the RLWE input + :type PInput: int + :param order: order of the trigonometric Hermite interpolation (1, 2 or 3) + :type order: int + :param skd: secret key distribution of the cryptocontext + :type skd: SecretKeyDist + :param firstModSize: size of the first modulus in bits (only used for SPARSE_ENCAPSULATED) + :type firstModSize: int + :return: the multiplicative depth consumed by EvalFBT + :rtype: int +)pbdoc"; + +const char* cc_GetFEFBTDepth_docs = R"pbdoc( + Multiplicative depth consumed by FE functional bootstrapping (EvalFEFuncBootstrap): the + CoeffsToSlots and SlotsToCoeffs level budgets, the complex exponential approximation and + its double-angle iterations (selected by the secret key distribution and, for + SPARSE_ENCAPSULATED, by firstModSize as in GetFBTDepth), and the evaluation of the Fourier + series. + + :param levelBudget: levels spent on CoeffsToSlots and SlotsToCoeffs, respectively + :type levelBudget: list[int] + :param coefficients: Fourier coefficients of the function to be evaluated + :type coefficients: list[complex] + :param skd: secret key distribution of the cryptocontext + :type skd: SecretKeyDist + :param firstModSize: size of the first modulus in bits (only used for SPARSE_ENCAPSULATED) + :type firstModSize: int + :return: the multiplicative depth consumed by EvalFEFuncBootstrap + :rtype: int +)pbdoc"; + +const char* cc_EvalFBTSetup_docs = R"pbdoc( + Sets up CKKS functional bootstrapping of a look-up table: computes its depth and + precomputes the homomorphic encoding and decoding matrices with the required scalings + folded in. Supported only in CKKS with HYBRID key switching (64-bit build). Shares the + precomputation entry for numSlots with EvalBootstrapSetup and EvalFEFuncBootstrapSetup. + + :param coeffs: trigonometric Hermite interpolation coefficients of the look-up table + (complex, or integer for a Boolean function of order 1); only their number + is used here + :type coeffs: list[int] | list[complex] + :param numSlots: number of slots to be bootstrapped (0 = full packing) + :type numSlots: int + :param PIn: plaintext modulus of the RLWE input + :type PIn: int + :param POut: plaintext modulus of the RLWE output + :type POut: int + :param Bigq: ciphertext modulus of the RLWE scheme the input is converted from + :type Bigq: int + :param pubKey: public key (its element parameters define the modulus chain) + :type pubKey: PublicKey + :param dim1: baby-step dimensions for CoeffsToSlots and SlotsToCoeffs (0 = automatic) + :type dim1: list[int] + :param levelBudget: levels spent on CoeffsToSlots and SlotsToCoeffs (each in [1, log2(slots)]) + :type levelBudget: list[int] + :param lvlsAfterBoot: number of levels remaining after functional bootstrapping + :type lvlsAfterBoot: int + :param depthLeveledComputation: depth reserved for a leveled computation between + EvalFBTNoDecoding and EvalHomDecoding + :type depthLeveledComputation: int + :param order: order of the trigonometric Hermite interpolation (1, 2 or 3) + :type order: int +)pbdoc"; + +const char* cc_EvalFBT_docs = R"pbdoc( + Functional bootstrapping of a look-up table on a CKKS ciphertext imported from RLWE + (SchemeletRLWEMP.ConvertRLWEToCKKS): modulus raise, CoeffsToSlots, complex exponential (or + cosine for a Boolean function of order 1), trigonometric Hermite series, SlotsToCoeffs and + scalings. Supported only in CKKS. + + :param ciphertext: input ciphertext holding the RLWE input in its coefficients + :type ciphertext: Ciphertext + :param coeffs: trigonometric Hermite interpolation coefficients of the look-up table + (complex, or integer for a Boolean function of order 1), scaled to + magnitude at most one + :type coeffs: list[int] | list[complex] + :param digitBitSize: bit size of the look-up table input (log2 of the input plaintext modulus) + :type digitBitSize: int + :param initialScaling: scale of the imported message (the RLWE ciphertext modulus of the input) + :type initialScaling: int + :param postScaling: integer the result is multiplied by after SlotsToCoeffs (typically the + scale the coefficients were divided by; values of at most 1 are ignored) + :type postScaling: int + :param levelToReduce: number of levels to drop before SlotsToCoeffs + :type levelToReduce: int + :param order: order of the trigonometric Hermite interpolation (1, 2 or 3) + :type order: int + :return: ciphertext holding the look-up table outputs in its coefficients, ready for + SchemeletRLWEMP.ConvertCKKSToRLWE + :rtype: Ciphertext +)pbdoc"; + +const char* cc_EvalFBTNoDecoding_docs = R"pbdoc( + Functional bootstrapping of a look-up table without the final homomorphic decoding: the + outputs stay in the slots so that a leveled computation can be applied before + EvalHomDecoding. Supported only in CKKS. + + :param ciphertext: input ciphertext holding the RLWE input in its coefficients + :type ciphertext: Ciphertext + :param coeffs: trigonometric Hermite interpolation coefficients of the look-up table + :type coeffs: list[int] | list[complex] + :param digitBitSize: bit size of the look-up table input (log2 of the input plaintext modulus) + :type digitBitSize: int + :param initialScaling: scale of the imported message (the RLWE ciphertext modulus of the input) + :type initialScaling: int + :param order: order of the trigonometric Hermite interpolation (1, 2 or 3) + :type order: int + :return: ciphertext holding the look-up table outputs in its slots + :rtype: Ciphertext +)pbdoc"; + +const char* cc_EvalHomDecoding_docs = R"pbdoc( + Homomorphic decoding step of functional bootstrapping: drops levels if requested, applies + SlotsToCoeffs, multiplies by postScaling and rescales to noise scale degree 1. Supported + only in CKKS. + + :param ciphertext: ciphertext with the look-up table outputs in its slots + :type ciphertext: Ciphertext + :param postScaling: integer the result is multiplied by after SlotsToCoeffs (values of at + most 1 are ignored) + :type postScaling: int + :param levelToReduce: number of levels to drop before SlotsToCoeffs + :type levelToReduce: int + :return: ciphertext holding the outputs in its coefficients, at noise scale degree 1 + :rtype: Ciphertext +)pbdoc"; + +const char* cc_EvalMVBPrecompute_docs = R"pbdoc( + Precomputes the complex-exponential powers for multi-value bootstrapping (modulus raise, + CoeffsToSlots, complex exponential and its powers). Every look-up table later evaluated on + the result (EvalMVB, EvalMVBNoDecoding) must be interpolated with the same shape (same + plaintext modulus and order) as coeffs, since those determine which powers are computed. + Supported only in CKKS. + + :param ciphertext: input ciphertext holding the RLWE input in its coefficients + :type ciphertext: Ciphertext + :param coeffs: trigonometric Hermite interpolation coefficients of one of the look-up tables + :type coeffs: list[int] | list[complex] + :param digitBitSize: bit size of the look-up table input (log2 of the input plaintext modulus) + :type digitBitSize: int + :param initialScaling: scale of the imported message (the RLWE ciphertext modulus of the input) + :type initialScaling: int + :param order: order of the trigonometric Hermite interpolation (1, 2 or 3) + :type order: int + :return: powers of the complex exponential + :rtype: SeriesPowers +)pbdoc"; + +const char* cc_EvalMVB_docs = R"pbdoc( + Multi-value bootstrapping: evaluates the trigonometric Hermite series of a look-up table + on the powers from EvalMVBPrecompute and applies the homomorphic decoding. Supported only + in CKKS. + + :param ciphertexts: powers of the complex exponential from EvalMVBPrecompute + :type ciphertexts: SeriesPowers + :param coeffs: trigonometric Hermite interpolation coefficients of the look-up table (same + shape as in EvalMVBPrecompute) + :type coeffs: list[int] | list[complex] + :param digitBitSize: bit size of the look-up table input (log2 of the input plaintext modulus) + :type digitBitSize: int + :param postScaling: integer the result is multiplied by after SlotsToCoeffs (values of at + most 1 are ignored) + :type postScaling: int + :param levelToReduce: number of levels to drop before SlotsToCoeffs + :type levelToReduce: int + :param order: order of the trigonometric Hermite interpolation (1, 2 or 3) + :type order: int + :return: ciphertext holding the look-up table outputs in its coefficients, at noise scale + degree 1 + :rtype: Ciphertext +)pbdoc"; + +const char* cc_EvalMVBNoDecoding_docs = R"pbdoc( + Multi-value bootstrapping without the final homomorphic decoding: the look-up table + outputs stay in the slots so that a leveled computation can be applied before + EvalHomDecoding. Supported only in CKKS. + + :param ciphertexts: powers of the complex exponential from EvalMVBPrecompute + :type ciphertexts: SeriesPowers + :param coeffs: trigonometric Hermite interpolation coefficients of the look-up table (same + shape as in EvalMVBPrecompute) + :type coeffs: list[int] | list[complex] + :param digitBitSize: bit size of the look-up table input (log2 of the input plaintext modulus) + :type digitBitSize: int + :param order: order of the trigonometric Hermite interpolation (1, 2 or 3) + :type order: int + :return: ciphertext holding the look-up table outputs in its slots + :rtype: Ciphertext +)pbdoc"; + const char* cc_EvalHermiteTrigSeries_docs = R"pbdoc( Evaluate a trigonometric Hermite series approximating exp(i*Pi/2*x) over the interval [a, b]. @@ -54,4 +286,356 @@ const char* cc_EvalHermiteTrigSeries_docs = R"pbdoc( :rtype: Ciphertext )pbdoc"; +const char* cc_EvalFEFuncBootstrapSetup_docs = R"pbdoc( + Precomputes the encoding/decoding plaintexts for FE functional bootstrapping. Supported + only in CKKS, with HYBRID key switching and FirstModSize == ScalingModSize + 1. Shares the + bootstrapping precomputation slot for the given slot count with EvalBootstrapSetup and + EvalFBTSetup, so a context can hold the precomputation for only one of them per slot count. + + :param levelBudget: levels spent on CoeffsToSlots and SlotsToCoeffs + :type levelBudget: list[int] + :param dim1: baby-step dimensions for the two linear transforms (0 = choose automatically) + :type dim1: list[int] + :param slots: number of slots to be bootstrapped (0 = full packing) + :type slots: int +)pbdoc"; + +const char* cc_EvalFEFuncBootstrap_docs = R"pbdoc( + Refreshes a ciphertext and evaluates a function on it in one pass, by evaluating the + function's Fourier extension over the bootstrapped message. Supported only in CKKS. + + The message is embedded into half of the series period (t = m/2), so the input must lie in + [-1/2, 1/2) and coefficients must be the Fourier extension of the target function over + that domain. The result is 2*Re(c_0 + sum_{j>=1} c_j exp(2*Pi*i*j*t)) and is therefore + always real-valued: with CKKSDataType COMPLEX the imaginary part of each input slot is + discarded and the output slots have zero imaginary part. Use CKKSDataType REAL unless + complex intermediates are needed elsewhere in the computation. + + :param ciphertext: input ciphertext, with slot values in [-1/2, 1/2) + :type ciphertext: Ciphertext + :param coefficients: Fourier coefficients c_j of the target function, c_0 first + :type coefficients: list[complex] + :return: refreshed ciphertext holding the function values + :rtype: Ciphertext +)pbdoc"; + +const char* cc_EvalFEFuncBootstrapPrecompute_docs = R"pbdoc( + Runs the function-independent part of FE functional bootstrapping and returns the powers + of the complex exponential, so that several functions can be evaluated on one bootstrapped + ciphertext. Supported only in CKKS. + + The refresh itself - SlotsToCoeffs, modulus raise, CoeffsToSlots and the complex + exponential - happens here and dominates the cost; each subsequent + EvalFEFuncBootstrapWithPrecomp only evaluates a series. The Paterson-Stockmeyer shape is + fixed by coefficients, which must have degree at least 5, so pass the longest series of + the family: every series later evaluated against these powers may have at most the degree + of coefficients rounded up to the shape's capacity (EvalFEFuncBootstrapWithPrecomp reports + the exact bound if it is exceeded). A series below degree 5 is evaluated straight from the + power basis and is accepted as long as its degree does not exceed the number of powers the + shape holds. + + :param ciphertext: input ciphertext, with slot values in [-1/2, 1/2) + :type ciphertext: Ciphertext + :param coefficients: Fourier coefficients of the longest series to be evaluated, c_0 first + :type coefficients: list[complex] + :return: powers of the complex exponential, to be passed to EvalFEFuncBootstrapWithPrecomp + :rtype: SeriesPowers +)pbdoc"; + +const char* cc_EvalFEFuncBootstrapWithPrecomp_docs = R"pbdoc( + Evaluates one function's Fourier series against powers from EvalFEFuncBootstrapPrecompute. + Supported only in CKKS. + + :param powers: powers returned by EvalFEFuncBootstrapPrecompute + :type powers: SeriesPowers + :param coefficients: Fourier coefficients c_j of this function, c_0 first + :type coefficients: list[complex] + :return: refreshed ciphertext holding the function values + :rtype: Ciphertext +)pbdoc"; + +const char* cc_ClearBootstrapPrecom_docs = R"pbdoc( + Clear the CKKS bootstrap precomputations cached by this context's scheme. +)pbdoc"; + +const char* fbt_SeriesPowers_docs = R"pbdoc( + Opaque holder for the powers of the complex exponential precomputed by + EvalMVBPrecompute or EvalFEFuncBootstrapPrecompute. +)pbdoc"; + +const char* fbt_PolyVector_docs = R"pbdoc( + Opaque holder for an RLWE ciphertext: the pair {b, a} of polynomials modulo a ciphertext + modulus Q, as produced and consumed by SchemeletRLWEMP. +)pbdoc"; + +const char* fbt_PolyVector_len_docs = R"pbdoc( + Number of polynomials in the RLWE ciphertext. + + :return: the number of polynomials + :rtype: int +)pbdoc"; + +const char* fbt_PolyVector_Clone_docs = R"pbdoc( + Returns a deep copy of the RLWE ciphertext. + + :return: the copy + :rtype: PolyVector +)pbdoc"; + +const char* fbt_PolyVector_SwitchModulus_docs = R"pbdoc( + Switches every polynomial of the RLWE ciphertext to the given modulus in place. + + :param modulus: the new modulus + :type modulus: int +)pbdoc"; + +const char* fbt_PolyVector_sub_docs = R"pbdoc( + Element-wise subtraction of two RLWE ciphertexts of the same size. + + :return: the difference + :rtype: PolyVector +)pbdoc"; + +const char* fbt_GetMultiplicativeDepthByCoeffVector_docs = R"pbdoc( + Gets the multiplicative depth of evaluating a series with the given coefficients with the + Paterson-Stockmeyer algorithm, based on the table described in + src/pke/examples/FUNCTION_EVALUATION.md of OpenFHE. + + :param vec: vector of coefficients + :type vec: list[int] | list[complex] + :param isNormalized: true if the vector is normalized + :type isNormalized: bool + :return: the multiplicative depth + :rtype: int +)pbdoc"; + +const char* fbt_GetHermiteTrigCoefficients_docs = R"pbdoc( + Calculates the intermediate trigonometric Hermite interpolation coefficients of a look-up + table given as a Python function. These coefficients can be evaluated over ciphertexts + encrypting exp(2*Pi*i*x) (e.g. by EvalFBT) to compute the function. The coefficients are + divided by 2 to account for the fact that the real part of the evaluation needs to be + taken to get the Hermite trigonometric interpolation result. + + :param func: the function to be approximated, mapping an integer to an integer + :type func: Callable[[int], int] + :param p: number of interpolation points (the plaintext modulus) + :type p: int + :param order: order of the Hermite interpolation (1 or 2) + :type order: int + :param scale: scaling factor the coefficients are divided by + :type scale: int + :return: the coefficients of the intermediate Hermite trigonometric interpolation + :rtype: list[complex] +)pbdoc"; + +const char* fbt_SchemeletRLWEMP_docs = R"pbdoc( + Helper ("schemelet") for RLWE ciphertexts with a message in the coefficients, used + together with the CKKS functional bootstrapping (EvalFBT). + + An RLWE ciphertext is represented as a pair {b, a} of polynomials modulo a ciphertext + modulus Q such that b + a*s = (Q/p)*m + e for the CKKS secret key s, a plaintext modulus p + and an integer message vector m stored in the polynomial coefficients (BFV-style scaling). + The static methods encrypt and decrypt such ciphertexts under the CKKS secret key with + arbitrary Q and p, switch their modulus, and convert them to and from CKKS ciphertexts so + that EvalFBT can evaluate a function on the encrypted values and return an exact RLWE + ciphertext. +)pbdoc"; + +const char* schemelet_GetElementParams_docs = R"pbdoc( + Returns the RNS element parameters of the secret key's crypto context with the towers of + the given number of levels removed from the end. One level corresponds to one tower, + except that under COMPOSITESCALINGAUTO and COMPOSITESCALINGMANUAL every level consists of + compositeDegree towers and under FLEXIBLEAUTOEXT one extra tower is removed. The modulus + of the result is the modulus Q' that EncryptCoeff() samples its encryption of zero over. + + :param privateKey: the CKKS secret key whose crypto context supplies the element parameters + :type privateKey: PrivateKey + :param level: number of levels to remove from the full parameters (0 keeps all towers) + :type level: int + :return: the reduced element parameters + :rtype: ParmType +)pbdoc"; + +const char* schemelet_EncryptCoeff_docs = R"pbdoc( + Encrypts a vector of integers as the coefficients of an RLWE ciphertext under the CKKS + secret key. An RLWE encryption of zero is sampled over the modulus of elementParams, + switched to Q, and the message scaled by Q/p is added. With N the ring dimension, the + entries are placed at a stride of N/(2*len(input)); when the stride is greater than 1 the + same values are repeated in the second half of the coefficient vector, and when the input + has more than N/2 entries they are placed consecutively (at most N of them are used). + + :param input: the integer message; every entry is reduced modulo p as a signed value + :type input: list[int] + :param Q: ciphertext modulus of the returned RLWE ciphertext + :type Q: int + :param p: plaintext modulus + :type p: int + :param privateKey: the CKKS secret key + :type privateKey: PrivateKey + :param elementParams: RNS parameters whose modulus Q' the encryption of zero is generated + over (see GetElementParams()) + :type elementParams: ParmType + :param bitReverse: if true, the input is permuted in bit-reversed order before encoding + (each half separately when the input has more than N/2 entries); this + matches the slot order of the CKKS conversions when rotations are + applied in the CKKS domain + :type bitReverse: bool + :return: the RLWE ciphertext as the pair {b, a} of polynomials modulo Q + :rtype: PolyVector +)pbdoc"; + +const char* schemelet_DecryptCoeff_docs = R"pbdoc( + Decrypts an RLWE ciphertext {b, a} modulo Q (as produced by EncryptCoeff() or + ConvertCKKSToRLWE()) with the CKKS secret key. The ciphertext is switched to the modulus + of elementParams, b + a*s is computed, switched back to Q and rounded to the plaintext + modulus p, and the message coefficients are read at a stride of N/(2*numSlots) (N = ring + dimension) as signed integers in (-p/2, p/2]. + + :param input: the RLWE ciphertext {b, a} modulo Q + :type input: PolyVector + :param Q: ciphertext modulus of the input + :type Q: int + :param p: plaintext modulus + :type p: int + :param privateKey: the CKKS secret key + :type privateKey: PrivateKey + :param elementParams: RNS parameters used for the decryption (see GetElementParams()) + :type elementParams: ParmType + :param numSlots: number of slots used in the CKKS computation; determines the stride + between message coefficients + :type numSlots: int + :param length: number of values to return (0 means numSlots) + :type length: int + :param bitReverse: if true, the bit-reversal permutation applied at encryption is undone + (each half separately when numSlots is less than length) + :type bitReverse: bool + :return: the decrypted integer values + :rtype: list[int] +)pbdoc"; + +const char* schemelet_ModSwitch_docs = R"pbdoc( + Switches an RLWE ciphertext {b, a} from modulus Q2 to modulus Q1 in place by scaling both + polynomials by Q1/Q2 with rounding. + + :param input: the ciphertext polynomials, currently modulo Q2; replaced by the polynomials + modulo Q1 + :type input: PolyVector + :param Q1: the target modulus + :type Q1: int + :param Q2: the current modulus + :type Q2: int +)pbdoc"; + +const char* schemelet_ConvertRLWEToCKKS_docs = R"pbdoc( + Embeds an RLWE ciphertext {b, a} modulo Bigq into a CKKS ciphertext so that EvalFBT can + process it. A CKKS ciphertext with the requested number of slots is created at the given + level by encrypting zero under the public key (this supplies the ciphertext metadata), + and its two elements are replaced by the RLWE polynomials scaled and switched from Bigq + to the CKKS modulus at that level. The level is adjusted for the scaling technique in the + same way as in GetElementParams(). + + :param cc: the CKKS crypto context + :type cc: CryptoContext + :param coeffs: the RLWE ciphertext {b, a} + :type coeffs: PolyVector + :param pubKey: CKKS public key used for the encryption of zero + :type pubKey: PublicKey + :param Bigq: modulus of the RLWE ciphertext + :type Bigq: int + :param slots: number of slots recorded in the CKKS ciphertext + :type slots: int + :param level: level of the resulting CKKS ciphertext + :type level: int + :return: a CKKS ciphertext whose elements are the modulus-switched RLWE polynomials + :rtype: Ciphertext +)pbdoc"; + +const char* schemelet_ConvertCKKSToRLWE_docs = R"pbdoc( + Extracts an RLWE ciphertext {b, a} modulo Q from a CKKS ciphertext: both elements are + brought to coefficient representation, CRT-interpolated to polynomials over the CKKS + modulus and scaled and switched to Q with rounding. + + :param ctxt: the CKKS ciphertext (two elements) + :type ctxt: Ciphertext + :param Q: the target modulus of the RLWE ciphertext + :type Q: int + :return: the pair {b, a} of polynomials modulo Q + :rtype: PolyVector +)pbdoc"; + +const char* schemelet_GetQPrime_docs = R"pbdoc( + Computes the product of the first lvls+1 RNS moduli q_0, ..., q_lvls of the public key's + element parameters. + + :param pubKey: a CKKS public key supplying the RNS moduli + :type pubKey: PublicKey + :param lvls: number of moduli after q_0 to include in the product + :type lvls: int + :return: q_0 * q_1 * ... * q_lvls + :rtype: int +)pbdoc"; + +const char* fbt_SchemeSwitchingDataSerializer_docs = R"pbdoc( + Serializer for all the data needed by CKKS <-> FHEW scheme switching: the CKKS crypto + context with its evaluation keys, the public key, the FHEW crypto context with its keys + and the raw ciphertext. +)pbdoc"; + +const char* fbt_SchemeSwitchingDataSerializer_init_docs = R"pbdoc( + Constructor collecting the objects to serialize. None is not accepted for any argument. + + :param cryptoContext: the CKKS crypto context (must have the FHEW context and switching + key for scheme switching set) + :type cryptoContext: CryptoContext + :param publicKey: the CKKS public key + :type publicKey: PublicKey + :param RAWCiphertext: the ciphertext to serialize + :type RAWCiphertext: Ciphertext +)pbdoc"; + +const char* fbt_SetDataDirectory_docs = R"pbdoc( + Sets the directory the serialized files are written to or read from. A trailing slash is + removed; an empty string is rejected. + + :param dir: the data directory + :type dir: str +)pbdoc"; + +const char* fbt_Serialize_docs = R"pbdoc( + Writes all the scheme-switching data (crypto contexts, keys and the ciphertext) to the + data directory. +)pbdoc"; + +const char* fbt_SchemeSwitchingDataDeserializer_docs = R"pbdoc( + Deserializer for the data written by SchemeSwitchingDataSerializer: reads the CKKS crypto + context with its evaluation keys, the public key, the FHEW crypto context with its keys + and the raw ciphertext, available through the getters after Deserialize(). +)pbdoc"; + +const char* fbt_getCryptoContext_docs = R"pbdoc( + Getter for the deserialized CKKS crypto context. + + :return: the crypto context + :rtype: CryptoContext +)pbdoc"; + +const char* fbt_getPublicKey_docs = R"pbdoc( + Getter for the deserialized public key. + + :return: the public key + :rtype: PublicKey +)pbdoc"; + +const char* fbt_getRAWCiphertext_docs = R"pbdoc( + Getter for the deserialized ciphertext. + + :return: the ciphertext + :rtype: Ciphertext +)pbdoc"; + +const char* fbt_Deserialize_docs = R"pbdoc( + Reads all the scheme-switching data (crypto contexts, keys and the ciphertext) from the + data directory and populates the getters. +)pbdoc"; + #endif // __FBT_DOCS_H__ diff --git a/src/include/docstrings/serialization_docs.h b/src/include/docstrings/serialization_docs.h new file mode 100644 index 0000000..cfb13a3 --- /dev/null +++ b/src/include/docstrings/serialization_docs.h @@ -0,0 +1,311 @@ +//================================================================================== +// BSD 2-Clause License +// +// Copyright (c) 2023-2026, Duality Technologies Inc. and other contributors +// +// All rights reserved. +// +// Author TPOC: contact@openfhe.org +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +//================================================================================== +#ifndef __SERIALIZATION_DOCS_H__ +#define __SERIALIZATION_DOCS_H__ + +// Docstrings for the (de)serialization bindings in src/lib/pke/serialization.cpp. +// These constants have external linkage, so this header must be included by +// serialization.cpp only, like the other docstring headers. + +const char* serial_SerializeEvalBootstrapKey_docs = R"pbdoc( + Serialize the CKKS bootstrapping automorphism keys for the given key tag and slot count + to a file. + + :param filename: path of the file to write + :type filename: str + :param sertype: serialization format (JSON or BINARY) + :type sertype: SERJSON | SERBINARY + :param cc: crypto context whose bootstrapping keys are serialized + :type cc: CryptoContext + :param keyTag: tag of the secret key the bootstrapping keys belong to + :type keyTag: str + :param slots: slot count whose bootstrapping keys are serialized + :type slots: int + :return: true on success; false if the file cannot be written (a message is printed) + :rtype: bool +)pbdoc"; + +const char* serial_DeserializeEvalBootstrapKey_docs = R"pbdoc( + Deserialize CKKS bootstrapping automorphism keys from a file and insert them into the + static key cache for the given key tag and slot count, replacing any matching keys. + + :param filename: path of the file to read + :type filename: str + :param sertype: serialization format (JSON or BINARY) + :type sertype: SERJSON | SERBINARY + :param cc: crypto context the keys are loaded for + :type cc: CryptoContext + :param keyTag: tag of the secret key the bootstrapping keys belong to + :type keyTag: str + :param slots: slot count whose bootstrapping keys are loaded + :type slots: int + :return: true on success; false if the file cannot be read (a message is printed) + :rtype: bool +)pbdoc"; + +const char* serial_DeserializeEvalBootstrapKeyIndex_docs = R"pbdoc( + Deserialize only the CKKS bootstrapping automorphism keys with the given rotation indices + from a file and insert them into the static key cache for the given key tag, replacing any + matching keys. + + :param filename: path of the file to read + :type filename: str + :param sertype: serialization format (JSON or BINARY) + :type sertype: SERJSON | SERBINARY + :param keyTag: tag of the secret key the bootstrapping keys belong to + :type keyTag: str + :param indexList: automorphism indices of the keys to load + :type indexList: list[int] + :return: true on success; false if the file cannot be read (a message is printed) + :rtype: bool +)pbdoc"; + +const char* serial_SerializeToFile_docs = R"pbdoc( + Serialize an object (CryptoContext, PublicKey, PrivateKey, Ciphertext, EvalKey or + EvalKeyMap) to a file in the given format. + + :param filename: path of the file to write + :type filename: str + :param obj: the object to serialize + :param sertype: serialization format (JSON or BINARY) + :type sertype: SERJSON | SERBINARY + :return: true on success + :rtype: bool +)pbdoc"; + +const char* serial_DeserializeCryptoContext_docs = R"pbdoc( + Deserialize a CryptoContext from a file written by SerializeToFile. + + :param filename: path of the file to read + :type filename: str + :param sertype: serialization format the file was written in (JSON or BINARY) + :type sertype: SERJSON | SERBINARY + :return: the deserialized object and a success flag + :rtype: tuple[CryptoContext, bool] +)pbdoc"; + +const char* serial_DeserializePublicKey_docs = R"pbdoc( + Deserialize a PublicKey from a file written by SerializeToFile. + + :param filename: path of the file to read + :type filename: str + :param sertype: serialization format the file was written in (JSON or BINARY) + :type sertype: SERJSON | SERBINARY + :return: the deserialized object and a success flag + :rtype: tuple[PublicKey, bool] +)pbdoc"; + +const char* serial_DeserializePrivateKey_docs = R"pbdoc( + Deserialize a PrivateKey from a file written by SerializeToFile. + + :param filename: path of the file to read + :type filename: str + :param sertype: serialization format the file was written in (JSON or BINARY) + :type sertype: SERJSON | SERBINARY + :return: the deserialized object and a success flag + :rtype: tuple[PrivateKey, bool] +)pbdoc"; + +const char* serial_DeserializeCiphertext_docs = R"pbdoc( + Deserialize a Ciphertext from a file written by SerializeToFile. + + :param filename: path of the file to read + :type filename: str + :param sertype: serialization format the file was written in (JSON or BINARY) + :type sertype: SERJSON | SERBINARY + :return: the deserialized object and a success flag + :rtype: tuple[Ciphertext, bool] +)pbdoc"; + +const char* serial_DeserializeEvalKey_docs = R"pbdoc( + Deserialize an EvalKey from a file written by SerializeToFile. + + :param filename: path of the file to read + :type filename: str + :param sertype: serialization format the file was written in (JSON or BINARY) + :type sertype: SERJSON | SERBINARY + :return: the deserialized object and a success flag + :rtype: tuple[EvalKey, bool] +)pbdoc"; + +const char* serial_DeserializeEvalKeyMap_docs = R"pbdoc( + Deserialize an EvalKeyMap from a file written by SerializeToFile. + + :param filename: path of the file to read + :type filename: str + :param sertype: serialization format the file was written in (JSON or BINARY) + :type sertype: SERJSON | SERBINARY + :return: the deserialized object and a success flag + :rtype: tuple[EvalKeyMap, bool] +)pbdoc"; + +const char* serial_Serialize_docs = R"pbdoc( + Serialize an object (CryptoContext, PublicKey, PrivateKey, Ciphertext, EvalKey or + EvalKeyMap) in the given format and return it as a str (JSON) or bytes (BINARY; an + EvalKeyMap and a list of EvalKeys are returned as bytes in both formats). + + :param obj: the object to serialize + :param sertype: serialization format (JSON or BINARY) + :type sertype: SERJSON | SERBINARY + :return: the serialized object + :rtype: str | bytes +)pbdoc"; + +const char* serial_DeserializeCryptoContextString_docs = R"pbdoc( + Deserialize a CryptoContext from a str (JSON) or bytes (BINARY) produced by Serialize. + + :param str: the serialized object + :type str: str | bytes + :param sertype: serialization format the data was written in (JSON or BINARY) + :type sertype: SERJSON | SERBINARY + :return: the deserialized object + :rtype: CryptoContext +)pbdoc"; + +const char* serial_DeserializePublicKeyString_docs = R"pbdoc( + Deserialize a PublicKey from a str (JSON) or bytes (BINARY) produced by Serialize. + + :param str: the serialized object + :type str: str | bytes + :param sertype: serialization format the data was written in (JSON or BINARY) + :type sertype: SERJSON | SERBINARY + :return: the deserialized object + :rtype: PublicKey +)pbdoc"; + +const char* serial_DeserializePrivateKeyString_docs = R"pbdoc( + Deserialize a PrivateKey from a str (JSON) or bytes (BINARY) produced by Serialize. + + :param str: the serialized object + :type str: str | bytes + :param sertype: serialization format the data was written in (JSON or BINARY) + :type sertype: SERJSON | SERBINARY + :return: the deserialized object + :rtype: PrivateKey +)pbdoc"; + +const char* serial_DeserializeCiphertextString_docs = R"pbdoc( + Deserialize a Ciphertext from a str (JSON) or bytes (BINARY) produced by Serialize. + + :param str: the serialized object + :type str: str | bytes + :param sertype: serialization format the data was written in (JSON or BINARY) + :type sertype: SERJSON | SERBINARY + :return: the deserialized object + :rtype: Ciphertext +)pbdoc"; + +const char* serial_DeserializeEvalKeyString_docs = R"pbdoc( + Deserialize an EvalKey from a str (JSON) or bytes (BINARY) produced by Serialize. + + :param str: the serialized object + :type str: str | bytes + :param sertype: serialization format the data was written in (JSON or BINARY) + :type sertype: SERJSON | SERBINARY + :return: the deserialized object + :rtype: EvalKey +)pbdoc"; + +const char* serial_DeserializeEvalKeyMapString_docs = R"pbdoc( + Deserialize an EvalKeyMap from bytes produced by Serialize (both formats return bytes for + an EvalKeyMap). + + :param str: the serialized object + :type str: bytes + :param sertype: serialization format the data was written in (JSON or BINARY) + :type sertype: SERJSON | SERBINARY + :return: the deserialized object + :rtype: EvalKeyMap +)pbdoc"; + +const char* serial_DeserializeEvalKeyMapVectorString_docs = R"pbdoc( + Deserialize a list of EvalKeys from bytes produced by Serialize. + + :param str: the serialized object + :type str: bytes + :param sertype: serialization format the data was written in (BINARY) + :type sertype: SERBINARY + :return: the deserialized list of evaluation keys + :rtype: list[EvalKey] +)pbdoc"; + +const char* serial_SerializeEvalMultKeyString_docs = R"pbdoc( + Serialize the EvalMult keys for the given key tag (all EvalMult keys if the tag is empty) + from the static key cache and return them as a str (JSON) or bytes (BINARY). + + :param sertype: serialization format (JSON or BINARY) + :type sertype: SERJSON | SERBINARY + :param keyTag: tag of the secret key whose EvalMult keys are serialized ("" for all) + :type keyTag: str + :return: the serialized keys + :rtype: str | bytes + :raises RuntimeError: if the serialization fails +)pbdoc"; + +const char* serial_DeserializeEvalMultKeyString_docs = R"pbdoc( + Deserialize EvalMult keys from a str (JSON) or bytes (BINARY) produced by + SerializeEvalMultKeyString and insert them into the static key cache, silently replacing + any matching keys. + + :param data: the serialized keys + :type data: str | bytes + :param sertype: serialization format the data was written in (JSON or BINARY) + :type sertype: SERJSON | SERBINARY + :raises RuntimeError: if the deserialization fails +)pbdoc"; + +const char* serial_SerializeEvalAutomorphismKeyString_docs = R"pbdoc( + Serialize the automorphism (rotation) keys for the given key tag (all automorphism keys if + the tag is empty) from the static key cache and return them as a str (JSON) or bytes + (BINARY). + + :param sertype: serialization format (JSON or BINARY) + :type sertype: SERJSON | SERBINARY + :param keyTag: tag of the secret key whose automorphism keys are serialized ("" for all) + :type keyTag: str + :return: the serialized keys + :rtype: str | bytes + :raises RuntimeError: if the serialization fails +)pbdoc"; + +const char* serial_DeserializeEvalAutomorphismKeyString_docs = R"pbdoc( + Deserialize automorphism (rotation) keys from a str (JSON) or bytes (BINARY) produced by + SerializeEvalAutomorphismKeyString and insert them into the static key cache, silently + replacing any matching keys. + + :param data: the serialized keys + :type data: str | bytes + :param sertype: serialization format the data was written in (JSON or BINARY) + :type sertype: SERJSON | SERBINARY + :raises RuntimeError: if the deserialization fails +)pbdoc"; + +#endif // __SERIALIZATION_DOCS_H__ diff --git a/src/lib/bindings.cpp b/src/lib/bindings.cpp index 99e9c61..62e3cc8 100644 --- a/src/lib/bindings.cpp +++ b/src/lib/bindings.cpp @@ -64,12 +64,15 @@ inline std::shared_ptr<CryptoParametersRNS> GetParamsRNSChecked(const CryptoCont } void bind_DCRTPoly(py::module &m) { - py::class_<DCRTPoly>(m, "DCRTPoly").def(py::init<>()); + py::class_<DCRTPoly>(m, "DCRTPoly", + "Ring element in double-CRT (RNS) representation, the element type underlying the " + "DCRT-based schemes (BFV, BGV, CKKS); treated as an opaque object in Python.") + .def(py::init<>()); } template <typename T> void bind_parameters(py::module &m, const std::string name) { - py::class_<CCParams<T>>(m, name.c_str()) + py::class_<CCParams<T>>(m, name.c_str(), ccparams_doc) .def(py::init<>()) // getters .def("GetPlaintextModulus", &CCParams<T>::GetPlaintextModulus, @@ -461,15 +464,26 @@ void bind_crypto_context(py::module &m) { // TODO (Oliveira): If we expose Poly's and ParmType, this block will go somewhere else using ParmType = typename DCRTPoly::Params; using ParmTypePtr = std::shared_ptr<ParmType>; - py::class_<ParmType, ParmTypePtr>(m, "ParmType") + py::class_<ParmType, ParmTypePtr>(m, "ParmType", + "Element (ring) parameters of a DCRTPoly: the ciphertext modulus, ring dimension " + "and cyclotomic order.") .def("GetModulus", - [](const ParmType& self) { return openfhe_python::IntegerToPyInt(self.GetModulus()); }) - .def("GetRingDimension", &ParmType::GetRingDimension) - .def("GetCyclotomicOrder", &ParmType::GetCyclotomicOrder); + [](const ParmType& self) { return openfhe_python::IntegerToPyInt(self.GetModulus()); }, + py::doc("Get the ciphertext modulus (the product of all RNS moduli).")) + .def("GetRingDimension", &ParmType::GetRingDimension, + py::doc("Get the ring dimension N (the polynomials live in Z_Q[x]/(X^N+1)).")) + .def("GetCyclotomicOrder", &ParmType::GetCyclotomicOrder, + py::doc("Get the cyclotomic order (2N for power-of-two cyclotomics).")); - py::class_<SchemeBase<DCRTPoly>, std::shared_ptr<SchemeBase<DCRTPoly>>>(m, "SchemeBase"); + py::class_<SchemeBase<DCRTPoly>, std::shared_ptr<SchemeBase<DCRTPoly>>>(m, "SchemeBase", + "Scheme implementation object of a crypto context (returned by CryptoContext.GetScheme); " + "treated as an opaque object in Python."); - auto cc_class = py::class_<CryptoContextImpl<DCRTPoly>, std::shared_ptr<CryptoContextImpl<DCRTPoly>>>(m, "CryptoContext"); + auto cc_class = py::class_<CryptoContextImpl<DCRTPoly>, std::shared_ptr<CryptoContextImpl<DCRTPoly>>>(m, "CryptoContext", + "Crypto context for the DCRTPoly-based schemes (BFV, BGV, CKKS): the main API object " + "holding the scheme implementation, crypto parameters and precomputations, with methods " + "for key generation, encoding, encryption and homomorphic evaluation. Create it with " + "GenCryptoContext(params)."); cc_class.def(py::init<>()) .def("ClearAllCKKSCaches", &CC::ClearAllCKKSCaches, @@ -484,7 +498,8 @@ void bind_crypto_context(py::module &m) { py::arg("level"), py::doc(cc_SetKeyGenLevel_docs)) .def("get_ptr",[](const CryptoContext<DCRTPoly> &self) { - std::cout << "CC shared ptr (python cc)" << self << std::endl; }) + std::cout << "CC shared ptr (python cc)" << self << std::endl; }, + py::doc("Debug helper: print the address held by the shared CryptoContext pointer.")) .def("GetRingDimension", &CryptoContextImpl<DCRTPoly>::GetRingDimension, cc_GetRingDimension_docs) .def("GetPlaintextModulus", [](CryptoContext<DCRTPoly>& self) { @@ -494,7 +509,8 @@ void bind_crypto_context(py::module &m) { .def("GetBatchSize", [](CryptoContext<DCRTPoly>& self) { return self->GetCryptoParameters()->GetBatchSize(); - }) + }, + py::doc("Get the maximum batch size (number of packed slots).")) .def("GetModulus", [](CryptoContext<DCRTPoly>& self) { return self->GetCryptoParameters() @@ -509,7 +525,8 @@ void bind_crypto_context(py::module &m) { if (!cryptoParams) OPENFHE_THROW("std::dynamic_pointer_cast<CryptoParametersCKKSRNS>() failed"); return cryptoParams->GetElementParams()->GetParams()[0]->GetModulus().ConvertToInt<uint64_t>(); - }) + }, + py::doc("Get the first RNS modulus q_0 of a CKKS crypto context.")) .def("GetScalingFactorReal", [](CryptoContext<DCRTPoly>& self, uint32_t level) { auto cryptoParams = std::dynamic_pointer_cast<CryptoParametersRNS>(self->GetCryptoParameters()); @@ -525,11 +542,13 @@ void bind_crypto_context(py::module &m) { if (!cryptoParams) OPENFHE_THROW("std::dynamic_pointer_cast<CryptoParametersRNS>() failed"); return cryptoParams->GetScalingTechnique(); - }) + }, + py::doc("Get the rescaling/modulus switching technique (CKKS/BGV).")) .def("GetDigitSize", [](CryptoContext<DCRTPoly>& self) { return self->GetCryptoParameters()->GetDigitSize(); - }) + }, + py::doc("Get the digit size used in BV key switching.")) .def("GetCyclotomicOrder", &CryptoContextImpl<DCRTPoly>::GetCyclotomicOrder, cc_GetCyclotomicOrder_docs) .def("GetEncodingParams", &CC::GetEncodingParams, py::doc(cc_GetEncodingParams_docs)) @@ -548,53 +567,76 @@ void bind_crypto_context(py::module &m) { py::doc(cc_getSchemeId_docs)) .def("setSchemeId", &CC::setSchemeId, py::arg("schemeTag"), py::doc(cc_setSchemeId_docs)) - .def("GetCKKSDataType", &CryptoContextImpl<DCRTPoly>::GetCKKSDataType) - .def("GetCKKSBootCorrectionFactor", &CC::GetCKKSBootCorrectionFactor) - .def("SetCKKSBootCorrectionFactor", &CC::SetCKKSBootCorrectionFactor, py::arg("cf")) + .def("GetCKKSDataType", &CryptoContextImpl<DCRTPoly>::GetCKKSDataType, + py::doc("Get the CKKS data type (real or complex).")) + .def("GetCKKSBootCorrectionFactor", &CC::GetCKKSBootCorrectionFactor, + py::doc("Get the correction factor of CKKS bootstrapping (the number of bits the message " + "is scaled down by in total before the approximate modular reduction to improve " + "precision), as set by EvalBootstrapSetup. Supported only in CKKS.")) + .def("SetCKKSBootCorrectionFactor", &CC::SetCKKSBootCorrectionFactor, py::arg("cf"), + py::doc("Set the correction factor of CKKS bootstrapping. Supported only in CKKS.")) .def("GetNoiseEstimate", [](CryptoContext<DCRTPoly>& self) { return GetParamsRNSChecked(self, "GetNoiseEstimate")->GetNoiseEstimate(); - }) + }, + py::doc("Get the noise estimate used for CKKS noise flooding (RNS schemes only).")) .def("SetNoiseEstimate", [](CryptoContext<DCRTPoly>& self, double noiseEstimate) { GetParamsRNSChecked(self, "SetNoiseEstimate")->SetNoiseEstimate(noiseEstimate); }, - py::arg("noiseEstimate")) + py::arg("noiseEstimate"), + py::doc("Set the noise estimate used for CKKS noise flooding (RNS schemes only).")) .def("GetMultiplicativeDepth", [](CryptoContext<DCRTPoly>& self) { return GetParamsRNSChecked(self, "GetMultiplicativeDepth")->GetMultiplicativeDepth(); - }) + }, + py::doc("Get the multiplicative depth supported by the crypto parameters (RNS schemes only).")) .def("SetMultiplicativeDepth", [](CryptoContext<DCRTPoly>& self, uint32_t multiplicativeDepth) { GetParamsRNSChecked(self, "SetMultiplicativeDepth")->SetMultiplicativeDepth(multiplicativeDepth); }, - py::arg("multiplicativeDepth")) + py::arg("multiplicativeDepth"), + py::doc("Set the multiplicative depth recorded in the crypto parameters (RNS schemes only).")) .def("GetEvalAddCount", [](CryptoContext<DCRTPoly>& self) { return GetParamsRNSChecked(self, "GetEvalAddCount")->GetEvalAddCount(); - }) + }, + py::doc("Get the maximum number of additions assumed for BGV/BFV noise estimation " + "(RNS schemes only).")) .def("SetEvalAddCount", [](CryptoContext<DCRTPoly>& self, uint32_t evalAddCount) { GetParamsRNSChecked(self, "SetEvalAddCount")->SetEvalAddCount(evalAddCount); }, - py::arg("evalAddCount")) + py::arg("evalAddCount"), + py::doc("Set the maximum number of additions assumed for BGV/BFV noise estimation " + "(RNS schemes only).")) .def("GetKeySwitchCount", [](CryptoContext<DCRTPoly>& self) { return GetParamsRNSChecked(self, "GetKeySwitchCount")->GetKeySwitchCount(); - }) + }, + py::doc("Get the maximum number of key switching operations assumed for BGV/BFV noise " + "estimation (RNS schemes only).")) .def("SetKeySwitchCount", [](CryptoContext<DCRTPoly>& self, uint32_t keySwitchCount) { GetParamsRNSChecked(self, "SetKeySwitchCount")->SetKeySwitchCount(keySwitchCount); }, - py::arg("keySwitchCount")) + py::arg("keySwitchCount"), + py::doc("Set the maximum number of key switching operations assumed for BGV/BFV noise " + "estimation (RNS schemes only).")) .def("GetPRENumHops", [](CryptoContext<DCRTPoly>& self) { return GetParamsRNSChecked(self, "GetPRENumHops")->GetPRENumHops(); - }) + }, + py::doc("Get the number of hops supported for proxy re-encryption (RNS schemes only).")) .def("SetPRENumHops", [](CryptoContext<DCRTPoly>& self, uint32_t PRENumHops) { GetParamsRNSChecked(self, "SetPRENumHops")->SetPRENumHops(PRENumHops); }, - py::arg("PRENumHops")) + py::arg("PRENumHops"), + py::doc("Set the number of hops supported for proxy re-encryption (RNS schemes only).")) .def("GetRegisterWordSize", [](CryptoContext<DCRTPoly>& self) { return GetParamsRNSChecked(self, "GetRegisterWordSize")->GetRegisterWordSize(); - }) + }, + py::doc("Get the target hardware register word size for CKKS composite scaling " + "(RNS schemes only).")) .def("GetCompositeDegree", [](CryptoContext<DCRTPoly>& self) { return GetParamsRNSChecked(self, "GetCompositeDegree")->GetCompositeDegree(); - }) + }, + py::doc("Get the composite degree used by CKKS composite scaling (RNS schemes only).")) .def("GetKeySwitchTechnique", [](CryptoContext<DCRTPoly>& self) { return GetParamsRNSChecked(self, "GetKeySwitchTechnique")->GetKeySwitchTechnique(); - }) + }, + py::doc("Get the key switching technique (BV or HYBRID; RNS schemes only).")) .def("Enable", py::overload_cast<PKESchemeFeature>(&CryptoContextImpl<DCRTPoly>::Enable), py::arg("feature"), py::doc(cc_Enable_docs)) @@ -1130,7 +1172,9 @@ void bind_crypto_context(py::module &m) { .def("EvalBootstrapStCFirst", &CC::EvalBootstrapStCFirst, py::arg("ciphertext"), py::arg("numIterations") = 1, py::arg("precision") = 0, py::doc(cc_EvalBootstrapStCFirst_docs)) - .def_static("ClearStaticMapsAndVectors", &CryptoContextImpl<DCRTPoly>::ClearStaticMapsAndVectors) + .def_static("ClearStaticMapsAndVectors", &CryptoContextImpl<DCRTPoly>::ClearStaticMapsAndVectors, + py::doc("Clear various static caches within the library (evaluation-key maps and the " + "context registry).")) .def("EvalCKKStoFHEWSetup", &CryptoContextImpl<DCRTPoly>::EvalCKKStoFHEWSetup, py::arg("schswchparams"), py::doc(cc_EvalCKKStoFHEWSetup_docs)) @@ -1242,11 +1286,19 @@ void bind_crypto_context(py::module &m) { py::doc("Compress the ciphertext down to the given number of RNS towers, " "reducing its size before storage or transmission.")) .def("EvalMultMany", &CryptoContextImpl<DCRTPoly>::EvalMultMany, - py::arg("ciphertextVec")) + py::arg("ciphertextVec"), + py::doc("Homomorphic multiplication of multiple ciphertexts using a binary tree approach, " + "with relinearization to two elements after each multiplication. Assumes each " + "multiplication stays within the supported ring size, and throws otherwise.")) .def("EvalAddMany", &CryptoContextImpl<DCRTPoly>::EvalAddMany, - py::arg("ciphertextVec")) + py::arg("ciphertextVec"), + py::doc("Homomorphic addition of multiple ciphertexts (null entries are skipped). The " + "result never aliases an input, so it is always safe to mutate.")) .def("EvalAddManyInPlace", &CryptoContextImpl<DCRTPoly>::EvalAddManyInPlace, - py::arg("ciphertextVec")) + py::arg("ciphertextVec"), + py::doc("In-place homomorphic addition of multiple ciphertexts (null entries are skipped; " + "the vector is modified to store intermediate results and the result is also left " + "in slot 0).")) .def("FindAutomorphismIndex", [](const CryptoContextImpl<DCRTPoly>& self, int32_t idx) { return self.FindAutomorphismIndex(static_cast<uint32_t>(idx)); }, @@ -1264,7 +1316,8 @@ void bind_crypto_context(py::module &m) { }, py::arg("keyTag"), py::doc(cc_GetEvalSumKeyMap_docs)) - .def("GetBinCCForSchemeSwitch", &CryptoContextImpl<DCRTPoly>::GetBinCCForSchemeSwitch) + .def("GetBinCCForSchemeSwitch", &CryptoContextImpl<DCRTPoly>::GetBinCCForSchemeSwitch, + py::doc("Get the BinFHE context used for CKKS <-> FHEW scheme switching.")) .def_static("InsertEvalSumKey", &CryptoContextImpl<DCRTPoly>::InsertEvalSumKey, py::arg("evalKeyMap"), py::arg("keyTag") = "", @@ -1530,18 +1583,26 @@ void bind_crypto_context(py::module &m) { // Generator Functions m.def("GenCryptoContext", &GenCryptoContext<CryptoContextBFVRNS>, - py::arg("params")); + py::arg("params"), + py::doc("Generate a BFV RNS crypto context from the given CCParamsBFVRNS parameters.")); m.def("GenCryptoContext", &GenCryptoContext<CryptoContextBGVRNS>, - py::arg("params")); + py::arg("params"), + py::doc("Generate a BGV RNS crypto context from the given CCParamsBGVRNS parameters.")); m.def("GenCryptoContext", &GenCryptoContext<CryptoContextCKKSRNS>, - py::arg("params")); + py::arg("params"), + py::doc("Generate a CKKS RNS crypto context from the given CCParamsCKKSRNS parameters.")); - m.def("GetAllContexts", &CryptoContextFactory<DCRTPoly>::GetAllContexts); + m.def("GetAllContexts", &CryptoContextFactory<DCRTPoly>::GetAllContexts, + py::doc("Return all crypto contexts registered in the library-wide context registry.")); - m.def("ReleaseAllContexts", &CryptoContextFactory<DCRTPoly>::ReleaseAllContexts); + m.def("ReleaseAllContexts", &CryptoContextFactory<DCRTPoly>::ReleaseAllContexts, + py::doc("Release all registered crypto contexts: clears their CKKS caches and the static " + "evaluation-key maps, empties the registry and returns freed memory to the system.")); - m.def("ClearEvalMultKeys", static_cast<void (*)()>(&CryptoContextImpl<DCRTPoly>::ClearEvalMultKeys)); - m.def("ClearEvalSumKeys", static_cast<void (*)()>(&CryptoContextImpl<DCRTPoly>::ClearEvalSumKeys)); + m.def("ClearEvalMultKeys", static_cast<void (*)()>(&CryptoContextImpl<DCRTPoly>::ClearEvalMultKeys), + py::doc("Clear the entire static EvalMult key cache.")); + m.def("ClearEvalSumKeys", static_cast<void (*)()>(&CryptoContextImpl<DCRTPoly>::ClearEvalSumKeys), + py::doc("Clear the entire static EvalSum key cache.")); } int get_native_int() { @@ -1589,8 +1650,12 @@ void bind_enums_and_constants(py::module &m) { .value("CKKS_PACKED_ENCODING", PlaintextEncodings::CKKS_PACKED_ENCODING) .export_values(); // Serialization Types - py::class_<SerType::SERJSON>(m, "SERJSON"); - py::class_<SerType::SERBINARY>(m, "SERBINARY"); + py::class_<SerType::SERJSON>(m, "SERJSON", + "Serialization format tag for human-readable JSON; pass JSON (its instance) as the " + "sertype argument of the (de)serialization functions."); + py::class_<SerType::SERBINARY>(m, "SERBINARY", + "Serialization format tag for the compact binary format; pass BINARY (its instance) as " + "the sertype argument of the (de)serialization functions."); m.attr("JSON") = py::cast(SerType::JSON); m.attr("BINARY") = py::cast(SerType::BINARY); @@ -1686,32 +1751,50 @@ void bind_enums_and_constants(py::module &m) { .export_values(); //NATIVEINT function - m.def("get_native_int", &get_native_int); + m.def("get_native_int", &get_native_int, + py::doc("Return the native integer bit width the library was built with (64 or 128).")); } void bind_keys(py::module &m) { - py::class_<PublicKeyImpl<DCRTPoly>, std::shared_ptr<PublicKeyImpl<DCRTPoly>>>(m, "PublicKey") + py::class_<PublicKeyImpl<DCRTPoly>, std::shared_ptr<PublicKeyImpl<DCRTPoly>>>(m, "PublicKey", + "Public encryption key of a DCRTPoly-based scheme, generated by CryptoContext.KeyGen().") .def(py::init<>()) .def("GetCryptoContext", &PublicKeyImpl<DCRTPoly>::GetCryptoContext, py::doc(key_GetCryptoContext_docs)) - .def("GetKeyTag", &PublicKeyImpl<DCRTPoly>::GetKeyTag) - .def("SetKeyTag", &PublicKeyImpl<DCRTPoly>::SetKeyTag, py::arg("keyTag")); - py::class_<PrivateKeyImpl<DCRTPoly>, std::shared_ptr<PrivateKeyImpl<DCRTPoly>>>(m, "PrivateKey") + .def("GetKeyTag", &PublicKeyImpl<DCRTPoly>::GetKeyTag, + py::doc("Get the tag identifying the secret key this public key belongs to.")) + .def("SetKeyTag", &PublicKeyImpl<DCRTPoly>::SetKeyTag, py::arg("keyTag"), + py::doc("Set the tag identifying the secret key this public key belongs to.")); + py::class_<PrivateKeyImpl<DCRTPoly>, std::shared_ptr<PrivateKeyImpl<DCRTPoly>>>(m, "PrivateKey", + "Secret decryption key of a DCRTPoly-based scheme, generated by CryptoContext.KeyGen().") .def(py::init<>()) // a key created without a CryptoContext cannot be passed to RecoverSharedKey() .def(py::init<const CryptoContext<DCRTPoly>&>(), py::arg("cryptoContext")) - .def("GetCryptoContext", &PrivateKeyImpl<DCRTPoly>::GetCryptoContext) - .def("GetKeyTag", &PrivateKeyImpl<DCRTPoly>::GetKeyTag) - .def("SetKeyTag", &PrivateKeyImpl<DCRTPoly>::SetKeyTag, py::arg("keyTag")); - py::class_<KeyPair<DCRTPoly>>(m, "KeyPair") - .def_readwrite("publicKey", &KeyPair<DCRTPoly>::publicKey) - .def_readwrite("secretKey", &KeyPair<DCRTPoly>::secretKey) + .def("GetCryptoContext", &PrivateKeyImpl<DCRTPoly>::GetCryptoContext, + py::doc("Get the CryptoContext this key was generated for.")) + .def("GetKeyTag", &PrivateKeyImpl<DCRTPoly>::GetKeyTag, + py::doc("Get the tag identifying this secret key.")) + .def("SetKeyTag", &PrivateKeyImpl<DCRTPoly>::SetKeyTag, py::arg("keyTag"), + py::doc("Set the tag identifying this secret key.")); + py::class_<KeyPair<DCRTPoly>>(m, "KeyPair", + "Pair of a public key and the matching secret key, as returned by CryptoContext.KeyGen().") + .def_readwrite("publicKey", &KeyPair<DCRTPoly>::publicKey, + "The public key of the pair.") + .def_readwrite("secretKey", &KeyPair<DCRTPoly>::secretKey, + "The secret key of the pair.") .def("good", &KeyPair<DCRTPoly>::good,kp_good_docs); - py::class_<EvalKeyImpl<DCRTPoly>, std::shared_ptr<EvalKeyImpl<DCRTPoly>>>(m, "EvalKey") + py::class_<EvalKeyImpl<DCRTPoly>, std::shared_ptr<EvalKeyImpl<DCRTPoly>>>(m, "EvalKey", + "Evaluation key (for relinearization, rotation or key switching), generated by the " + "CryptoContext key-generation methods; treated as an opaque object in Python.") .def(py::init<>()) - .def("GetKeyTag", &EvalKeyImpl<DCRTPoly>::GetKeyTag) - .def("SetKeyTag", &EvalKeyImpl<DCRTPoly>::SetKeyTag, py::arg("keyTag")); - py::class_<std::map<uint32_t, EvalKey<DCRTPoly>>, std::shared_ptr<std::map<uint32_t, EvalKey<DCRTPoly>>>>(m, "EvalKeyMap") + .def("GetKeyTag", &EvalKeyImpl<DCRTPoly>::GetKeyTag, + py::doc("Get the tag identifying the secret key this evaluation key belongs to.")) + .def("SetKeyTag", &EvalKeyImpl<DCRTPoly>::SetKeyTag, py::arg("keyTag"), + py::doc("Set the tag identifying the secret key this evaluation key belongs to.")); + py::class_<std::map<uint32_t, EvalKey<DCRTPoly>>, std::shared_ptr<std::map<uint32_t, EvalKey<DCRTPoly>>>>(m, "EvalKeyMap", + "Opaque map from automorphism indices to evaluation keys, as produced by the rotation " + "and summation key-generation methods and consumed by the key-insertion and " + "serialization functions.") .def(py::init<>()); } @@ -1794,7 +1877,9 @@ class PlaintextImpl_helper : public PlaintextImpl { }; void bind_encodings(py::module &m) { - py::class_<EncodingParamsImpl, EncodingParams>(m, "EncodingParams") + py::class_<EncodingParamsImpl, EncodingParams>(m, "EncodingParams", + "Parameters of the plaintext encoding: the plaintext modulus, batch size and plaintext " + "generator (returned by CryptoContext.GetEncodingParams).") .def("GetPlaintextModulus", &EncodingParamsImpl::GetPlaintextModulus, py::doc(encparams_GetPlaintextModulus_docs)) .def("GetBatchSize", &EncodingParamsImpl::GetBatchSize, @@ -1802,7 +1887,10 @@ void bind_encodings(py::module &m) { .def("GetPlaintextGenerator", &EncodingParamsImpl::GetPlaintextGenerator, py::doc(encparams_GetPlaintextGenerator_docs)); - py::class_<PlaintextImpl, std::shared_ptr<PlaintextImpl>, PlaintextImpl_helper>(m, "Plaintext") + py::class_<PlaintextImpl, std::shared_ptr<PlaintextImpl>, PlaintextImpl_helper>(m, "Plaintext", + "Plaintext encoded for one of the DCRTPoly-based schemes, produced by the CryptoContext " + "Make*Plaintext methods and by decryption; holds the encoded values together with the " + "encoding metadata (level, scaling factor, number of slots, ...).") .def("GetScalingFactor", &PlaintextImpl::GetScalingFactor, ptx_GetScalingFactor_docs) .def("GetScalingFactorInt", [](const PlaintextImpl& self) { return openfhe_python::IntegerToPyInt(self.GetScalingFactorInt()); @@ -1846,22 +1934,36 @@ void bind_encodings(py::module &m) { .def("SetFormat", &PlaintextImpl::SetFormat, py::arg("fmt"), py::doc(ptx_SetFormat_docs)) - .def("GetCoefPackedValue", &PlaintextImpl::GetCoefPackedValue) - .def("GetPackedValue", &PlaintextImpl::GetPackedValue) + .def("GetCoefPackedValue", &PlaintextImpl::GetCoefPackedValue, + py::doc("Get the plaintext value as a list of signed integers (COEF_PACKED encoding).")) + .def("GetPackedValue", &PlaintextImpl::GetPackedValue, + py::doc("Get the plaintext value as a list of signed integers (PACKED encoding).")) .def("GetCKKSPackedValue", &PlaintextImpl::GetCKKSPackedValue, ptx_GetCKKSPackedValue_docs) .def("GetRealPackedValue", &PlaintextImpl::GetRealPackedValue, ptx_GetRealPackedValue_docs) - .def("GetLevel", &PlaintextImpl::GetLevel) - .def("SetLevel", &PlaintextImpl::SetLevel, py::arg("level")) - .def("GetNoiseScaleDeg", &PlaintextImpl::GetNoiseScaleDeg) - .def("SetNoiseScaleDeg", &PlaintextImpl::SetNoiseScaleDeg, py::arg("noiseScaleDeg")) - .def("GetSlots", &PlaintextImpl::GetSlots) - .def("SetSlots", &PlaintextImpl::SetSlots, py::arg("slots")) - .def("GetLogError", &PlaintextImpl::GetLogError) + .def("GetLevel", &PlaintextImpl::GetLevel, + py::doc("Get the level of the plaintext (the number of levels already consumed).")) + .def("SetLevel", &PlaintextImpl::SetLevel, py::arg("level"), + py::doc("Set the level of the plaintext (the number of levels already consumed).")) + .def("GetNoiseScaleDeg", &PlaintextImpl::GetNoiseScaleDeg, + py::doc("Get the degree of the scaling factor for the encoded message.")) + .def("SetNoiseScaleDeg", &PlaintextImpl::SetNoiseScaleDeg, py::arg("noiseScaleDeg"), + py::doc("Set the degree of the scaling factor for the encoded message.")) + .def("GetSlots", &PlaintextImpl::GetSlots, + py::doc("Get the number of packed slots.")) + .def("SetSlots", &PlaintextImpl::SetSlots, py::arg("slots"), + py::doc("Set the number of packed slots.")) + .def("GetLogError", &PlaintextImpl::GetLogError, + py::doc("Get log2 of the estimated standard deviation of the CKKS approximation error.")) .def("GetLogPrecision", &PlaintextImpl::GetLogPrecision) - .def("GetStringValue", &PlaintextImpl::GetStringValue) - .def("SetStringValue", &PlaintextImpl::SetStringValue, py::arg("value")) - .def("SetIntVectorValue", &PlaintextImpl::SetIntVectorValue, py::arg("value")) - .def("GetFormattedValues", &PlaintextImpl::GetFormattedValues, py::arg("precision")) + .def("GetStringValue", &PlaintextImpl::GetStringValue, + py::doc("Get the plaintext value as a string (STRING encoding).")) + .def("SetStringValue", &PlaintextImpl::SetStringValue, py::arg("value"), + py::doc("Set the plaintext value from a string (STRING encoding).")) + .def("SetIntVectorValue", &PlaintextImpl::SetIntVectorValue, py::arg("value"), + py::doc("Set the plaintext value from a list of integers (COEF_PACKED or PACKED encoding).")) + .def("GetFormattedValues", &PlaintextImpl::GetFormattedValues, py::arg("precision"), + py::doc("Return the plaintext values as a string, with floating-point values printed " + "at the given precision.")) .def("__repr__", [](const PlaintextImpl &p) { std::stringstream ss; ss << "<Plaintext Object: " << p << ">"; @@ -1875,12 +1977,16 @@ void bind_encodings(py::module &m) { } void bind_ciphertext(py::module &m) { - py::class_<CiphertextImpl<DCRTPoly>, std::shared_ptr<CiphertextImpl<DCRTPoly>>>(m, "Ciphertext") + py::class_<CiphertextImpl<DCRTPoly>, std::shared_ptr<CiphertextImpl<DCRTPoly>>>(m, "Ciphertext", + "Ciphertext of one of the DCRTPoly-based schemes (BFV, BGV, CKKS), produced by " + "CryptoContext.Encrypt and the homomorphic evaluation methods.") .def(py::init<>()) .def("__add__", [](const Ciphertext<DCRTPoly> &a, const Ciphertext<DCRTPoly> &b) { return a + b; }, - py::is_operator(), pybind11::keep_alive<0, 1>()) + py::is_operator(), pybind11::keep_alive<0, 1>(), + py::doc("Homomorphic addition of two ciphertexts: returns a new ciphertext encrypting " + "the element-wise sum.")) // .def(py::self + py::self); // .def("GetDepth", &CiphertextImpl<DCRTPoly>::GetDepth) // .def("SetDepth", &CiphertextImpl<DCRTPoly>::SetDepth) @@ -2067,13 +2173,20 @@ void bind_free_functions(py::module& m) { py::arg("func"), py::arg("a"), py::arg("b"), - py::arg("degree")); + py::arg("degree"), + py::doc("Compute the Chebyshev coefficients of the given degree approximating the function " + "func over [a, b]: f(x) ~ coeffs[0]/2 + sum_{i>=1} coeffs[i]*T_i(x), where T_i are " + "Chebyshev polynomials of the first kind (note that the first coefficient is " + "divided by two). The coefficients are used by EvalChebyshevSeries.")); m.def("EvalChebyshevFunctionPtxt", &EvalChebyshevFunctionPtxt, py::arg("func"), py::arg("ptxt"), py::arg("a"), py::arg("b"), - py::arg("degree")); + py::arg("degree"), + py::doc("Cleartext version of CryptoContext.EvalChebyshevFunction: evaluate the Chebyshev " + "approximation of func of the given degree over [a, b] on each of the plaintext " + "inputs and return the results.")); } #ifndef OPENFHE_PYTHON_VERSION_STR diff --git a/src/lib/binfhe_bindings.cpp b/src/lib/binfhe_bindings.cpp index 8b76a9c..9f02f19 100644 --- a/src/lib/binfhe_bindings.cpp +++ b/src/lib/binfhe_bindings.cpp @@ -118,8 +118,11 @@ void bind_binfhe_keys(py::module &m) { // No default constructor: Python cannot populate the key, and BinFHEContext guards only // against a null pointer, so an empty-but-non-null key segfaults BTKeyGen/PubKeyGen/ // KeySwitchGen. Keys come from KeyGen(), KeyGenN(), KeyGenPair() or deserialization. - py::class_<LWEPrivateKeyImpl, std::shared_ptr<LWEPrivateKeyImpl>>(m, "LWEPrivateKey") - .def("GetLength", &LWEPrivateKeyImpl::GetLength) + py::class_<LWEPrivateKeyImpl, std::shared_ptr<LWEPrivateKeyImpl>>(m, "LWEPrivateKey", + "LWE secret key of the FHEW/TFHE scheme, generated by BinFHEContext.KeyGen() or " + "KeyGenN(), or loaded by deserialization.") + .def("GetLength", &LWEPrivateKeyImpl::GetLength, + py::doc("Get the length (lattice dimension) of the secret key vector.")) .def("GetModulus", [](const LWEPrivateKeyImpl& self) { return openfhe_python::IntegerToPyInt(self.GetModulus()); @@ -130,8 +133,11 @@ void bind_binfhe_keys(py::module &m) { // No default constructor for the same reason as LWEPrivateKey: an empty-but-non-null // public key passes the null check and Encrypt() silently produces a length-0 ciphertext. - py::class_<LWEPublicKeyImpl, std::shared_ptr<LWEPublicKeyImpl>>(m, "LWEPublicKey") - .def("GetLength", &LWEPublicKeyImpl::GetLength) + py::class_<LWEPublicKeyImpl, std::shared_ptr<LWEPublicKeyImpl>>(m, "LWEPublicKey", + "LWE public key of the FHEW/TFHE scheme, generated by BinFHEContext.KeyGenPair() or " + "PubKeyGen(), or loaded by deserialization.") + .def("GetLength", &LWEPublicKeyImpl::GetLength, + py::doc("Get the length (lattice dimension) of the public key.")) .def("GetModulus", [](const LWEPublicKeyImpl& self) { return openfhe_python::IntegerToPyInt(self.GetModulus()); @@ -140,27 +146,40 @@ void bind_binfhe_keys(py::module &m) { .def(py::self == py::self) .def(py::self != py::self); - py::class_<LWESwitchingKeyImpl, std::shared_ptr<LWESwitchingKeyImpl>>(m, "LWESwitchingKey") + py::class_<LWESwitchingKeyImpl, std::shared_ptr<LWESwitchingKeyImpl>>(m, "LWESwitchingKey", + "LWE key switching key, generated by BinFHEContext.KeySwitchGen(); treated as an " + "opaque object in Python.") .def(py::init<>()); - py::class_<LWEKeyPairImpl, std::shared_ptr<LWEKeyPairImpl>>(m, "LWEKeyPair") - .def_readonly("publicKey", &LWEKeyPairImpl::publicKey) - .def_readonly("secretKey", &LWEKeyPairImpl::secretKey) - .def("good", &LWEKeyPairImpl::good); + py::class_<LWEKeyPairImpl, std::shared_ptr<LWEKeyPairImpl>>(m, "LWEKeyPair", + "Pair of an LWE public key and the matching secret key, as returned by " + "BinFHEContext.KeyGenPair().") + .def_readonly("publicKey", &LWEKeyPairImpl::publicKey, + "The LWE public key of the pair.") + .def_readonly("secretKey", &LWEKeyPairImpl::secretKey, + "The LWE secret key of the pair.") + .def("good", &LWEKeyPairImpl::good, + py::doc("Return true if both the public key and the secret key are set (non-null).")); // struct holding the refreshing, key switching and public keys generated by BTKeyGen // (treated as an opaque object in Python: generate, serialize and load it) - py::class_<RingGSWBTKey>(m, "RingGSWBTKey") + py::class_<RingGSWBTKey>(m, "RingGSWBTKey", + "Opaque holder for the refreshing, key switching and public keys generated by " + "BinFHEContext.BTKeyGen(); generate, serialize and load it as a unit.") .def(py::init<>()); } void bind_binfhe_ciphertext(py::module &m) { - py::class_<LWECiphertextImpl, std::shared_ptr<LWECiphertextImpl>>(m, "LWECiphertext") + py::class_<LWECiphertextImpl, std::shared_ptr<LWECiphertextImpl>>(m, "LWECiphertext", + "LWE ciphertext of the FHEW/TFHE scheme (the vector a together with the scalar b), " + "encrypting a small integer message.") .def(py::init<>()) - .def("GetLength", &LWECiphertextImpl::GetLength) + .def("GetLength", &LWECiphertextImpl::GetLength, + py::doc("Get the length (lattice dimension) of the ciphertext vector a.")) .def("GetModulus", [](const LWECiphertextImpl& self) { return openfhe_python::IntegerToPyInt(self.GetModulus()); - }) + }, + py::doc("Get the ciphertext modulus q.")) .def("GetptModulus", [](const LWECiphertextImpl& self) { return openfhe_python::IntegerToPyInt(self.GetptModulus()); @@ -183,7 +202,10 @@ void bind_binfhe_ciphertext(py::module &m) { } void bind_binfhe_context(py::module &m) { - py::class_<BinFHEContext, std::shared_ptr<BinFHEContext>>(m, "BinFHEContext") + py::class_<BinFHEContext, std::shared_ptr<BinFHEContext>>(m, "BinFHEContext", + "Crypto context for the FHEW/TFHE Boolean scheme: holds the LWE scheme parameters and " + "keys and provides key generation, encryption, Boolean gate evaluation, arbitrary " + "function evaluation and bootstrapping. Initialize it with GenerateBinFHEContext.") .def(py::init<>()) .def("GenerateBinFHEContext", py::overload_cast<BINFHE_PARAMSET, BINFHE_METHOD>(&BinFHEContext::GenerateBinFHEContext), @@ -380,31 +402,41 @@ static void bind_binfhe_serialization_for_sertype(py::module &m) { &Serial::SerializeToFile<BinFHEContext>), py::arg("filename"), py::arg("obj"), py::arg("sertype")); m.def("DeserializeBinFHECryptoContext", &BinFHEDeserializeFromFileWrapper<BinFHEContext, ST>, - py::arg("filename"), py::arg("sertype")); + py::arg("filename"), py::arg("sertype"), + py::doc("Deserialize a BinFHEContext from a file written by SerializeToFile; returns the " + "deserialized object and a success flag.")); m.def("SerializeToFile", static_cast<bool (*)(const std::string&, const LWECiphertext&, const ST&)>( &Serial::SerializeToFile<LWECiphertext>), py::arg("filename"), py::arg("obj"), py::arg("sertype")); m.def("DeserializeLWECiphertext", &BinFHEDeserializeFromFileWrapper<LWECiphertext, ST>, - py::arg("filename"), py::arg("sertype")); + py::arg("filename"), py::arg("sertype"), + py::doc("Deserialize an LWECiphertext from a file written by SerializeToFile; returns the " + "deserialized object and a success flag.")); m.def("SerializeToFile", static_cast<bool (*)(const std::string&, const LWEPrivateKey&, const ST&)>( &Serial::SerializeToFile<LWEPrivateKey>), py::arg("filename"), py::arg("obj"), py::arg("sertype")); m.def("DeserializeLWEPrivateKey", &BinFHEDeserializeFromFileWrapper<LWEPrivateKey, ST>, - py::arg("filename"), py::arg("sertype")); + py::arg("filename"), py::arg("sertype"), + py::doc("Deserialize an LWEPrivateKey from a file written by SerializeToFile; returns the " + "deserialized object and a success flag.")); m.def("SerializeToFile", static_cast<bool (*)(const std::string&, const LWEPublicKey&, const ST&)>( &Serial::SerializeToFile<LWEPublicKey>), py::arg("filename"), py::arg("obj"), py::arg("sertype")); m.def("DeserializeLWEPublicKey", &BinFHEDeserializeFromFileWrapper<LWEPublicKey, ST>, - py::arg("filename"), py::arg("sertype")); + py::arg("filename"), py::arg("sertype"), + py::doc("Deserialize an LWEPublicKey from a file written by SerializeToFile; returns the " + "deserialized object and a success flag.")); m.def("SerializeToFile", static_cast<bool (*)(const std::string&, const RingGSWBTKey&, const ST&)>( &Serial::SerializeToFile<RingGSWBTKey>), py::arg("filename"), py::arg("obj"), py::arg("sertype")); m.def("DeserializeBinFHEBTKey", &BinFHEDeserializeFromFileWrapper<RingGSWBTKey, ST>, - py::arg("filename"), py::arg("sertype")); + py::arg("filename"), py::arg("sertype"), + py::doc("Deserialize a RingGSWBTKey (the FHEW bootstrapping keys) from a file written by " + "SerializeToFile; returns the deserialized object and a success flag.")); } void bind_binfhe_serialization(py::module &m) { diff --git a/src/lib/pke/fbt.cpp b/src/lib/pke/fbt.cpp index fc1e2ec..0153646 100644 --- a/src/lib/pke/fbt.cpp +++ b/src/lib/pke/fbt.cpp @@ -72,10 +72,12 @@ void bind_fbt_crypto_context(DCRTCryptoContextClass &cls) { // convert arbitrary-size Python ints to BigInteger. cls.def_static("GetBootstrapDepth", py::overload_cast<uint32_t, const std::vector<uint32_t>&, SecretKeyDist>(&FHECKKSRNS::GetBootstrapDepth), - py::arg("depth"), py::arg("levelBudget"), py::arg("keyDist")) + py::arg("depth"), py::arg("levelBudget"), py::arg("keyDist"), + py::doc(cc_GetBootstrapDepthApproxMod_docs)) .def_static("GetBootstrapDepth", py::overload_cast<const std::vector<uint32_t>&, SecretKeyDist>(&FHECKKSRNS::GetBootstrapDepth), - py::arg("levelBudget"), py::arg("keyDist")) + py::arg("levelBudget"), py::arg("keyDist"), + py::doc(cc_GetBootstrapDepth_docs)) .def_static("GetFBTDepth", [](const std::vector<uint32_t>& levelBudget, const std::vector<int64_t>& coefficients, const py::int_& PInput, size_t order, SecretKeyDist skd, uint32_t firstModSize) { @@ -84,7 +86,8 @@ void bind_fbt_crypto_context(DCRTCryptoContextClass &cls) { firstModSize); }, py::arg("levelBudget"), py::arg("coefficients"), py::arg("PInput"), py::arg("order"), py::arg("skd"), - py::arg("firstModSize") = 60) + py::arg("firstModSize") = 60, + py::doc(cc_GetFBTDepth_docs)) .def_static("GetFBTDepth", [](const std::vector<uint32_t>& levelBudget, const std::vector<std::complex<double>>& coefficients, const py::int_& PInput, size_t order, SecretKeyDist skd, uint32_t firstModSize) { @@ -93,10 +96,12 @@ void bind_fbt_crypto_context(DCRTCryptoContextClass &cls) { firstModSize); }, py::arg("levelBudget"), py::arg("coefficients"), py::arg("PInput"), py::arg("order"), py::arg("skd"), - py::arg("firstModSize") = 60) + py::arg("firstModSize") = 60, + py::doc(cc_GetFBTDepth_docs)) .def_static("GetFEFBTDepth", &FHECKKSRNS::GetFEFBTDepth<std::complex<double>>, py::arg("levelBudget"), py::arg("coefficients"), py::arg("skd") = SPARSE_TERNARY, - py::arg("firstModSize") = 60) + py::arg("firstModSize") = 60, + py::doc(cc_GetFEFBTDepth_docs)) .def("EvalFBTSetup", [](CC& self, const std::vector<int64_t>& coeffs, uint32_t numSlots, const py::int_& PIn, const py::int_& POut, const py::int_& Bigq, const PublicKey<DCRTPoly>& pubKey, @@ -109,7 +114,8 @@ void bind_fbt_crypto_context(DCRTCryptoContextClass &cls) { }, py::arg("coeffs"), py::arg("numSlots"), py::arg("PIn"), py::arg("POut"), py::arg("Bigq"), py::arg("pubKey"), py::arg("dim1"), py::arg("levelBudget"), py::arg("lvlsAfterBoot") = 0, - py::arg("depthLeveledComputation") = 0, py::arg("order") = (size_t)1) + py::arg("depthLeveledComputation") = 0, py::arg("order") = (size_t)1, + py::doc(cc_EvalFBTSetup_docs)) .def("EvalFBTSetup", [](CC& self, const std::vector<std::complex<double>>& coeffs, uint32_t numSlots, const py::int_& PIn, const py::int_& POut, const py::int_& Bigq, const PublicKey<DCRTPoly>& pubKey, @@ -122,7 +128,8 @@ void bind_fbt_crypto_context(DCRTCryptoContextClass &cls) { }, py::arg("coeffs"), py::arg("numSlots"), py::arg("PIn"), py::arg("POut"), py::arg("Bigq"), py::arg("pubKey"), py::arg("dim1"), py::arg("levelBudget"), py::arg("lvlsAfterBoot") = 0, - py::arg("depthLeveledComputation") = 0, py::arg("order") = (size_t)1) + py::arg("depthLeveledComputation") = 0, py::arg("order") = (size_t)1, + py::doc(cc_EvalFBTSetup_docs)) .def("EvalFBT", [](CC& self, ConstCiphertext<DCRTPoly> ciphertext, const std::vector<int64_t>& coeffs, uint32_t digitBitSize, const py::int_& initialScaling, uint64_t postScaling, uint32_t levelToReduce, @@ -132,7 +139,8 @@ void bind_fbt_crypto_context(DCRTCryptoContextClass &cls) { levelToReduce, order); }, py::arg("ciphertext"), py::arg("coeffs"), py::arg("digitBitSize"), py::arg("initialScaling"), - py::arg("postScaling"), py::arg("levelToReduce") = 0, py::arg("order") = (size_t)1) + py::arg("postScaling"), py::arg("levelToReduce") = 0, py::arg("order") = (size_t)1, + py::doc(cc_EvalFBT_docs)) .def("EvalFBT", [](CC& self, ConstCiphertext<DCRTPoly> ciphertext, const std::vector<std::complex<double>>& coeffs, uint32_t digitBitSize, const py::int_& initialScaling, uint64_t postScaling, uint32_t levelToReduce, @@ -142,7 +150,8 @@ void bind_fbt_crypto_context(DCRTCryptoContextClass &cls) { levelToReduce, order); }, py::arg("ciphertext"), py::arg("coeffs"), py::arg("digitBitSize"), py::arg("initialScaling"), - py::arg("postScaling"), py::arg("levelToReduce") = 0, py::arg("order") = (size_t)1) + py::arg("postScaling"), py::arg("levelToReduce") = 0, py::arg("order") = (size_t)1, + py::doc(cc_EvalFBT_docs)) .def("EvalFBTNoDecoding", [](CC& self, ConstCiphertext<DCRTPoly> ciphertext, const std::vector<int64_t>& coeffs, uint32_t digitBitSize, const py::int_& initialScaling, size_t order) { @@ -150,7 +159,8 @@ void bind_fbt_crypto_context(DCRTCryptoContextClass &cls) { openfhe_python::PyIntToInteger<BigInteger>(initialScaling), order); }, py::arg("ciphertext"), py::arg("coeffs"), py::arg("digitBitSize"), py::arg("initialScaling"), - py::arg("order") = (size_t)1) + py::arg("order") = (size_t)1, + py::doc(cc_EvalFBTNoDecoding_docs)) .def("EvalFBTNoDecoding", [](CC& self, ConstCiphertext<DCRTPoly> ciphertext, const std::vector<std::complex<double>>& coeffs, uint32_t digitBitSize, const py::int_& initialScaling, size_t order) { @@ -158,9 +168,11 @@ void bind_fbt_crypto_context(DCRTCryptoContextClass &cls) { openfhe_python::PyIntToInteger<BigInteger>(initialScaling), order); }, py::arg("ciphertext"), py::arg("coeffs"), py::arg("digitBitSize"), py::arg("initialScaling"), - py::arg("order") = (size_t)1) + py::arg("order") = (size_t)1, + py::doc(cc_EvalFBTNoDecoding_docs)) .def("EvalHomDecoding", &CryptoContextImpl<DCRTPoly>::EvalHomDecoding, - py::arg("ciphertext"), py::arg("postScaling"), py::arg("levelToReduce") = 0) + py::arg("ciphertext"), py::arg("postScaling"), py::arg("levelToReduce") = 0, + py::doc(cc_EvalHomDecoding_docs)) .def("EvalMVBPrecompute", [](CC& self, ConstCiphertext<DCRTPoly> ciphertext, const std::vector<int64_t>& coeffs, uint32_t digitBitSize, const py::int_& initialScaling, size_t order) { @@ -168,7 +180,8 @@ void bind_fbt_crypto_context(DCRTCryptoContextClass &cls) { openfhe_python::PyIntToInteger<BigInteger>(initialScaling), order); }, py::arg("ciphertext"), py::arg("coeffs"), py::arg("digitBitSize"), py::arg("initialScaling"), - py::arg("order") = (size_t)1) + py::arg("order") = (size_t)1, + py::doc(cc_EvalMVBPrecompute_docs)) .def("EvalMVBPrecompute", [](CC& self, ConstCiphertext<DCRTPoly> ciphertext, const std::vector<std::complex<double>>& coeffs, uint32_t digitBitSize, const py::int_& initialScaling, size_t order) { @@ -176,17 +189,22 @@ void bind_fbt_crypto_context(DCRTCryptoContextClass &cls) { openfhe_python::PyIntToInteger<BigInteger>(initialScaling), order); }, py::arg("ciphertext"), py::arg("coeffs"), py::arg("digitBitSize"), py::arg("initialScaling"), - py::arg("order") = (size_t)1) + py::arg("order") = (size_t)1, + py::doc(cc_EvalMVBPrecompute_docs)) .def("EvalMVB", &CryptoContextImpl<DCRTPoly>::EvalMVB<int64_t>, py::arg("ciphertexts"), py::arg("coeffs"), py::arg("digitBitSize"), py::arg("postScaling"), - py::arg("levelToReduce") = 0, py::arg("order") = (size_t)1) + py::arg("levelToReduce") = 0, py::arg("order") = (size_t)1, + py::doc(cc_EvalMVB_docs)) .def("EvalMVB", &CryptoContextImpl<DCRTPoly>::EvalMVB<std::complex<double>>, py::arg("ciphertexts"), py::arg("coeffs"), py::arg("digitBitSize"), py::arg("postScaling"), - py::arg("levelToReduce") = 0, py::arg("order") = (size_t)1) + py::arg("levelToReduce") = 0, py::arg("order") = (size_t)1, + py::doc(cc_EvalMVB_docs)) .def("EvalMVBNoDecoding", &CryptoContextImpl<DCRTPoly>::EvalMVBNoDecoding<int64_t>, - py::arg("ciphertexts"), py::arg("coeffs"), py::arg("digitBitSize"), py::arg("order") = (size_t)1) + py::arg("ciphertexts"), py::arg("coeffs"), py::arg("digitBitSize"), py::arg("order") = (size_t)1, + py::doc(cc_EvalMVBNoDecoding_docs)) .def("EvalMVBNoDecoding", &CryptoContextImpl<DCRTPoly>::EvalMVBNoDecoding<std::complex<double>>, - py::arg("ciphertexts"), py::arg("coeffs"), py::arg("digitBitSize"), py::arg("order") = (size_t)1) + py::arg("ciphertexts"), py::arg("coeffs"), py::arg("digitBitSize"), py::arg("order") = (size_t)1, + py::doc(cc_EvalMVBNoDecoding_docs)) .def("EvalHermiteTrigSeries", &CryptoContextImpl<DCRTPoly>::EvalHermiteTrigSeries<int64_t>, py::arg("ciphertext"), py::arg("coefficientsCheb"), py::arg("a"), py::arg("b"), py::arg("coefficientsHerm"), py::arg("precomp") = (size_t)0, @@ -199,26 +217,34 @@ void bind_fbt_crypto_context(DCRTCryptoContextClass &cls) { .def("EvalFEFuncBootstrapSetup", &CryptoContextImpl<DCRTPoly>::EvalFEFuncBootstrapSetup, py::arg("levelBudget") = std::vector<uint32_t>({5, 4}), py::arg("dim1") = std::vector<uint32_t>({0, 0}), - py::arg("slots") = 0) + py::arg("slots") = 0, + py::doc(cc_EvalFEFuncBootstrapSetup_docs)) .def("EvalFEFuncBootstrap", &CryptoContextImpl<DCRTPoly>::EvalFEFuncBootstrap, - py::arg("ciphertext"), py::arg("coefficients")) + py::arg("ciphertext"), py::arg("coefficients"), + py::doc(cc_EvalFEFuncBootstrap_docs)) .def("EvalFEFuncBootstrapPrecompute", &CryptoContextImpl<DCRTPoly>::EvalFEFuncBootstrapPrecompute, - py::arg("ciphertext"), py::arg("coefficients")) + py::arg("ciphertext"), py::arg("coefficients"), + py::doc(cc_EvalFEFuncBootstrapPrecompute_docs)) .def("EvalFEFuncBootstrapWithPrecomp", &CryptoContextImpl<DCRTPoly>::EvalFEFuncBootstrapWithPrecomp, - py::arg("powers"), py::arg("coefficients")) - .def("ClearBootstrapPrecom", &CryptoContextImpl<DCRTPoly>::ClearBootstrapPrecom); + py::arg("powers"), py::arg("coefficients"), + py::doc(cc_EvalFEFuncBootstrapWithPrecomp_docs)) + .def("ClearBootstrapPrecom", &CryptoContextImpl<DCRTPoly>::ClearBootstrapPrecom, + py::doc(cc_ClearBootstrapPrecom_docs)); } void bind_fbt(py::module &m) { // opaque holder for the powers of the complex exponential precomputed by // EvalMVBPrecompute/EvalFEFuncBootstrapPrecompute - py::class_<seriesPowers<DCRTPoly>, std::shared_ptr<seriesPowers<DCRTPoly>>>(m, "SeriesPowers"); + py::class_<seriesPowers<DCRTPoly>, std::shared_ptr<seriesPowers<DCRTPoly>>>(m, "SeriesPowers", + fbt_SeriesPowers_docs); // opaque holder for an RLWE ciphertext (a vector of Poly) used by SchemeletRLWEMP - py::class_<std::vector<Poly>>(m, "PolyVector") + py::class_<std::vector<Poly>>(m, "PolyVector", fbt_PolyVector_docs) .def(py::init<>()) - .def("__len__", [](const std::vector<Poly>& self) { return self.size(); }) - .def("Clone", [](const std::vector<Poly>& self) { return std::vector<Poly>(self); }) + .def("__len__", [](const std::vector<Poly>& self) { return self.size(); }, + py::doc(fbt_PolyVector_len_docs)) + .def("Clone", [](const std::vector<Poly>& self) { return std::vector<Poly>(self); }, + py::doc(fbt_PolyVector_Clone_docs)) // switches every element of the RLWE ciphertext to the given modulus .def("SwitchModulus", [](std::vector<Poly>& self, const py::int_& modulus) { @@ -226,7 +252,8 @@ void bind_fbt(py::module &m) { for (auto& poly : self) poly.SwitchModulus(mod, 1, 0, 0); }, - py::arg("modulus")) + py::arg("modulus"), + py::doc(fbt_PolyVector_SwitchModulus_docs)) // element-wise subtraction of two RLWE ciphertexts .def("__sub__", [](const std::vector<Poly>& self, const std::vector<Poly>& other) { @@ -236,25 +263,30 @@ void bind_fbt(py::module &m) { for (size_t i = 0; i < result.size(); ++i) result[i] = result[i] - other[i]; return result; - }); + }, + py::doc(fbt_PolyVector_sub_docs)); // Multiplicative depth needed to evaluate the series with the given coefficients m.def("GetMultiplicativeDepthByCoeffVector", &GetMultiplicativeDepthByCoeffVector<int64_t>, - py::arg("vec"), py::arg("isNormalized") = false); + py::arg("vec"), py::arg("isNormalized") = false, + py::doc(fbt_GetMultiplicativeDepthByCoeffVector_docs)); m.def("GetMultiplicativeDepthByCoeffVector", &GetMultiplicativeDepthByCoeffVector<std::complex<double>>, - py::arg("vec"), py::arg("isNormalized") = false); + py::arg("vec"), py::arg("isNormalized") = false, + py::doc(fbt_GetMultiplicativeDepthByCoeffVector_docs)); // note: the element parameters type (ILDCRTParams<DCRTPoly::Integer>) returned by // SchemeletRLWEMP::GetElementParams is already registered as "ParmType" in bind_crypto_context // Trigonometric Hermite interpolation coefficients of a look-up table given as a Python function m.def("GetHermiteTrigCoefficients", &GetHermiteTrigCoefficients, - py::arg("func"), py::arg("p"), py::arg("order"), py::arg("scale")); + py::arg("func"), py::arg("p"), py::arg("order"), py::arg("scale"), + py::doc(fbt_GetHermiteTrigCoefficients_docs)); // Conversions between an RLWE scheme and CKKS sharing the same secret key - py::class_<SchemeletRLWEMP>(m, "SchemeletRLWEMP") + py::class_<SchemeletRLWEMP>(m, "SchemeletRLWEMP", fbt_SchemeletRLWEMP_docs) .def_static("GetElementParams", &SchemeletRLWEMP::GetElementParams, - py::arg("privateKey"), py::arg("level") = 0) + py::arg("privateKey"), py::arg("level") = 0, + py::doc(schemelet_GetElementParams_docs)) .def_static("EncryptCoeff", [](const std::vector<int64_t>& input, const py::int_& Q, const py::int_& p, const PrivateKey<DCRTPoly>& privateKey, const std::shared_ptr<ElementParams>& elementParams, @@ -264,7 +296,8 @@ void bind_fbt(py::module &m) { elementParams, bitReverse); }, py::arg("input"), py::arg("Q"), py::arg("p"), py::arg("privateKey"), py::arg("elementParams"), - py::arg("bitReverse") = false) + py::arg("bitReverse") = false, + py::doc(schemelet_EncryptCoeff_docs)) .def_static("DecryptCoeff", [](const std::vector<Poly>& input, const py::int_& Q, const py::int_& p, const PrivateKey<DCRTPoly>& privateKey, const std::shared_ptr<ElementParams>& elementParams, @@ -274,13 +307,15 @@ void bind_fbt(py::module &m) { elementParams, numSlots, length, bitReverse); }, py::arg("input"), py::arg("Q"), py::arg("p"), py::arg("privateKey"), py::arg("elementParams"), - py::arg("numSlots"), py::arg("length") = 0, py::arg("bitReverse") = false) + py::arg("numSlots"), py::arg("length") = 0, py::arg("bitReverse") = false, + py::doc(schemelet_DecryptCoeff_docs)) .def_static("ModSwitch", [](std::vector<Poly>& input, const py::int_& Q1, const py::int_& Q2) { SchemeletRLWEMP::ModSwitch(input, openfhe_python::PyIntToInteger<BigInteger>(Q1), openfhe_python::PyIntToInteger<BigInteger>(Q2)); }, - py::arg("input"), py::arg("Q1"), py::arg("Q2")) + py::arg("input"), py::arg("Q1"), py::arg("Q2"), + py::doc(schemelet_ModSwitch_docs)) .def_static("ConvertRLWEToCKKS", [](const CryptoContext<DCRTPoly>& cc, const std::vector<Poly>& coeffs, const PublicKey<DCRTPoly>& pubKey, const py::int_& Bigq, uint32_t slots, uint32_t level) { @@ -291,21 +326,25 @@ void bind_fbt(py::module &m) { *cc, coeffs, pubKey, openfhe_python::PyIntToInteger<BigInteger>(Bigq), slots, level); }, py::arg("cc"), py::arg("coeffs"), py::arg("pubKey"), py::arg("Bigq"), py::arg("slots"), - py::arg("level") = 0) + py::arg("level") = 0, + py::doc(schemelet_ConvertRLWEToCKKS_docs)) .def_static("ConvertCKKSToRLWE", [](ConstCiphertext<DCRTPoly> ctxt, const py::int_& Q) { return SchemeletRLWEMP::ConvertCKKSToRLWE( ctxt, openfhe_python::PyIntToInteger<BigInteger>(Q)); }, - py::arg("ctxt"), py::arg("Q")) + py::arg("ctxt"), py::arg("Q"), + py::doc(schemelet_ConvertCKKSToRLWE_docs)) .def_static("GetQPrime", [](const PublicKey<DCRTPoly>& pubKey, uint32_t lvls) { return openfhe_python::IntegerToPyInt(SchemeletRLWEMP::GetQPrime(pubKey, lvls)); }, - py::arg("pubKey"), py::arg("lvls")); + py::arg("pubKey"), py::arg("lvls"), + py::doc(schemelet_GetQPrime_docs)); // Serializer/deserializer for all the data needed by CKKS <-> FHEW scheme switching - py::class_<SchemeSwitchingDataSerializer>(m, "SchemeSwitchingDataSerializer") + py::class_<SchemeSwitchingDataSerializer>(m, "SchemeSwitchingDataSerializer", + fbt_SchemeSwitchingDataSerializer_docs) // the C++ constructor dereferences cryptoContext in its initializer list, // so None arguments must be rejected here to avoid a segfault .def(py::init([](const CryptoContext<DCRTPoly>& cryptoContext, const PublicKey<DCRTPoly>& publicKey, @@ -318,15 +357,24 @@ void bind_fbt(py::module &m) { OPENFHE_THROW("RAWCiphertext is nullptr"); return SchemeSwitchingDataSerializer(cryptoContext, publicKey, RAWCiphertext); }), - py::arg("cryptoContext"), py::arg("publicKey"), py::arg("RAWCiphertext")) - .def("SetDataDirectory", &SchemeSwitchingDataSerializer::SetDataDirectory, py::arg("dir")) - .def("Serialize", &SchemeSwitchingDataSerializer::Serialize); + py::arg("cryptoContext"), py::arg("publicKey"), py::arg("RAWCiphertext"), + py::doc(fbt_SchemeSwitchingDataSerializer_init_docs)) + .def("SetDataDirectory", &SchemeSwitchingDataSerializer::SetDataDirectory, py::arg("dir"), + py::doc(fbt_SetDataDirectory_docs)) + .def("Serialize", &SchemeSwitchingDataSerializer::Serialize, + py::doc(fbt_Serialize_docs)); - py::class_<SchemeSwitchingDataDeserializer>(m, "SchemeSwitchingDataDeserializer") + py::class_<SchemeSwitchingDataDeserializer>(m, "SchemeSwitchingDataDeserializer", + fbt_SchemeSwitchingDataDeserializer_docs) .def(py::init<>()) - .def("SetDataDirectory", &SchemeSwitchingDataDeserializer::SetDataDirectory, py::arg("dir")) - .def("getCryptoContext", &SchemeSwitchingDataDeserializer::getCryptoContext) - .def("getPublicKey", &SchemeSwitchingDataDeserializer::getPublicKey) - .def("getRAWCiphertext", &SchemeSwitchingDataDeserializer::getRAWCiphertext) - .def("Deserialize", &SchemeSwitchingDataDeserializer::Deserialize); + .def("SetDataDirectory", &SchemeSwitchingDataDeserializer::SetDataDirectory, py::arg("dir"), + py::doc(fbt_SetDataDirectory_docs)) + .def("getCryptoContext", &SchemeSwitchingDataDeserializer::getCryptoContext, + py::doc(fbt_getCryptoContext_docs)) + .def("getPublicKey", &SchemeSwitchingDataDeserializer::getPublicKey, + py::doc(fbt_getPublicKey_docs)) + .def("getRAWCiphertext", &SchemeSwitchingDataDeserializer::getRAWCiphertext, + py::doc(fbt_getRAWCiphertext_docs)) + .def("Deserialize", &SchemeSwitchingDataDeserializer::Deserialize, + py::doc(fbt_Deserialize_docs)); } diff --git a/src/lib/pke/serialization.cpp b/src/lib/pke/serialization.cpp index 961b588..87b0b1a 100644 --- a/src/lib/pke/serialization.cpp +++ b/src/lib/pke/serialization.cpp @@ -38,6 +38,8 @@ #include "cryptocontext-ser.h" #include "key/key-ser.h" +#include "docstrings/serialization_docs.h" + using namespace lbcrypto; using CryptoContextDCRT = CryptoContext<DCRTPoly>; namespace py = pybind11; @@ -239,145 +241,209 @@ void DeserializeEvalAutomorphismKeyFromBytesWrapper(const py::bytes& bytes, cons void bind_serialization(pybind11::module &m) { // CKKS bootstrapping key (de)serialization m.def("SerializeEvalBootstrapKey", &SerializeEvalBootstrapKeyWrapper<SerType::SERJSON>, - py::arg("filename"), py::arg("sertype"), py::arg("cc"), py::arg("keyTag"), py::arg("slots")); + py::arg("filename"), py::arg("sertype"), py::arg("cc"), py::arg("keyTag"), py::arg("slots"), + py::doc(serial_SerializeEvalBootstrapKey_docs)); m.def("SerializeEvalBootstrapKey", &SerializeEvalBootstrapKeyWrapper<SerType::SERBINARY>, - py::arg("filename"), py::arg("sertype"), py::arg("cc"), py::arg("keyTag"), py::arg("slots")); + py::arg("filename"), py::arg("sertype"), py::arg("cc"), py::arg("keyTag"), py::arg("slots"), + py::doc(serial_SerializeEvalBootstrapKey_docs)); m.def("DeserializeEvalBootstrapKey", &DeserializeEvalBootstrapKeyWrapper<SerType::SERJSON>, - py::arg("filename"), py::arg("sertype"), py::arg("cc"), py::arg("keyTag"), py::arg("slots")); + py::arg("filename"), py::arg("sertype"), py::arg("cc"), py::arg("keyTag"), py::arg("slots"), + py::doc(serial_DeserializeEvalBootstrapKey_docs)); m.def("DeserializeEvalBootstrapKey", &DeserializeEvalBootstrapKeyWrapper<SerType::SERBINARY>, - py::arg("filename"), py::arg("sertype"), py::arg("cc"), py::arg("keyTag"), py::arg("slots")); + py::arg("filename"), py::arg("sertype"), py::arg("cc"), py::arg("keyTag"), py::arg("slots"), + py::doc(serial_DeserializeEvalBootstrapKey_docs)); m.def("DeserializeEvalBootstrapKey", &DeserializeEvalBootstrapKeyByIndexWrapper<SerType::SERJSON>, - py::arg("filename"), py::arg("sertype"), py::arg("keyTag"), py::arg("indexList")); + py::arg("filename"), py::arg("sertype"), py::arg("keyTag"), py::arg("indexList"), + py::doc(serial_DeserializeEvalBootstrapKeyIndex_docs)); m.def("DeserializeEvalBootstrapKey", &DeserializeEvalBootstrapKeyByIndexWrapper<SerType::SERBINARY>, - py::arg("filename"), py::arg("sertype"), py::arg("keyTag"), py::arg("indexList")); + py::arg("filename"), py::arg("sertype"), py::arg("keyTag"), py::arg("indexList"), + py::doc(serial_DeserializeEvalBootstrapKeyIndex_docs)); // Json Serialization m.def("SerializeToFile", static_cast<bool (*)(const std::string &, const CryptoContextDCRT &, const SerType::SERJSON &)>(&Serial::SerializeToFile<CryptoContextDCRT>), - py::arg("filename"), py::arg("obj"), py::arg("sertype")); + py::arg("filename"), py::arg("obj"), py::arg("sertype"), + py::doc(serial_SerializeToFile_docs)); m.def("DeserializeCryptoContext", static_cast<std::tuple<CryptoContextDCRT, bool> (*)(const std::string &, const SerType::SERJSON &)>(&DeserializeCCWrapper<SerType::SERJSON>), - py::arg("filename"), py::arg("sertype")); + py::arg("filename"), py::arg("sertype"), + py::doc(serial_DeserializeCryptoContext_docs)); m.def("SerializeToFile", static_cast<bool (*)(const std::string &, const PublicKey<DCRTPoly> &, const SerType::SERJSON &)>(&Serial::SerializeToFile<PublicKey<DCRTPoly>>), - py::arg("filename"), py::arg("obj"), py::arg("sertype")); + py::arg("filename"), py::arg("obj"), py::arg("sertype"), + py::doc(serial_SerializeToFile_docs)); m.def("DeserializePublicKey", static_cast<std::tuple<PublicKey<DCRTPoly>,bool> (*)(const std::string&, const SerType::SERJSON&)>(&DeserializeFromFileWrapper<PublicKey<DCRTPoly>, SerType::SERJSON>), - py::arg("filename"), py::arg("sertype")); + py::arg("filename"), py::arg("sertype"), + py::doc(serial_DeserializePublicKey_docs)); m.def("SerializeToFile", static_cast<bool (*)(const std::string&, const PrivateKey<DCRTPoly>&, const SerType::SERJSON&)>(&Serial::SerializeToFile<PrivateKey<DCRTPoly>>), - py::arg("filename"), py::arg("obj"), py::arg("sertype")); + py::arg("filename"), py::arg("obj"), py::arg("sertype"), + py::doc(serial_SerializeToFile_docs)); m.def("DeserializePrivateKey", static_cast<std::tuple<PrivateKey<DCRTPoly>,bool> (*)(const std::string&, const SerType::SERJSON&)>(&DeserializeFromFileWrapper<PrivateKey<DCRTPoly>, SerType::SERJSON>), - py::arg("filename"), py::arg("sertype")); + py::arg("filename"), py::arg("sertype"), + py::doc(serial_DeserializePrivateKey_docs)); m.def("SerializeToFile", static_cast<bool (*)(const std::string&, const Ciphertext<DCRTPoly>&, const SerType::SERJSON&)>(&Serial::SerializeToFile<Ciphertext<DCRTPoly>>), - py::arg("filename"), py::arg("obj"), py::arg("sertype")); + py::arg("filename"), py::arg("obj"), py::arg("sertype"), + py::doc(serial_SerializeToFile_docs)); m.def("DeserializeCiphertext", static_cast<std::tuple<Ciphertext<DCRTPoly>,bool> (*)(const std::string&, const SerType::SERJSON&)>(&DeserializeFromFileWrapper<Ciphertext<DCRTPoly>, SerType::SERJSON>), - py::arg("filename"), py::arg("sertype")); + py::arg("filename"), py::arg("sertype"), + py::doc(serial_DeserializeCiphertext_docs)); m.def("SerializeToFile", static_cast<bool (*)(const std::string&, const EvalKey<DCRTPoly>&, const SerType::SERJSON&)>(&Serial::SerializeToFile<EvalKey<DCRTPoly>>), - py::arg("filename"), py::arg("obj"), py::arg("sertype")); + py::arg("filename"), py::arg("obj"), py::arg("sertype"), + py::doc(serial_SerializeToFile_docs)); m.def("DeserializeEvalKey", static_cast<std::tuple<EvalKey<DCRTPoly>,bool> (*)(const std::string&, const SerType::SERJSON&)>(&DeserializeFromFileWrapper<EvalKey<DCRTPoly>, SerType::SERJSON>), - py::arg("filename"), py::arg("sertype")); + py::arg("filename"), py::arg("sertype"), + py::doc(serial_DeserializeEvalKey_docs)); m.def("SerializeToFile", static_cast<bool (*)(const std::string&, const std::shared_ptr<std::map<uint32_t, EvalKey<DCRTPoly>>>&, const SerType::SERJSON&)>(&Serial::SerializeToFile<std::shared_ptr<std::map<uint32_t, EvalKey<DCRTPoly>>>>), - py::arg("filename"), py::arg("obj"), py::arg("sertype")); + py::arg("filename"), py::arg("obj"), py::arg("sertype"), + py::doc(serial_SerializeToFile_docs)); m.def("DeserializeEvalKeyMap", static_cast<std::tuple<std::shared_ptr<std::map<uint32_t, EvalKey<DCRTPoly>>>, bool> (*)(const std::string&, const SerType::SERJSON&)>(&DeserializeFromFileWrapper<std::shared_ptr<std::map<uint32_t, EvalKey<DCRTPoly>>>, SerType::SERJSON>), - py::arg("filename"), py::arg("sertype")); + py::arg("filename"), py::arg("sertype"), + py::doc(serial_DeserializeEvalKeyMap_docs)); // JSON Serialization to string m.def("Serialize", &SerializeToStringWrapper<CryptoContextDCRT, SerType::SERJSON>, - py::arg("obj"), py::arg("sertype")); + py::arg("obj"), py::arg("sertype"), + py::doc(serial_Serialize_docs)); m.def("DeserializeCryptoContextString", &DeserializeCCFromStringWrapper<SerType::SERJSON>, - py::arg("str"), py::arg("sertype")); + py::arg("str"), py::arg("sertype"), + py::doc(serial_DeserializeCryptoContextString_docs)); m.def("Serialize", &SerializeToStringWrapper<PublicKey<DCRTPoly>, SerType::SERJSON>, - py::arg("obj"), py::arg("sertype")); + py::arg("obj"), py::arg("sertype"), + py::doc(serial_Serialize_docs)); m.def("DeserializePublicKeyString", &DeserializeFromStringWrapper<PublicKey<DCRTPoly>, SerType::SERJSON>, - py::arg("str"), py::arg("sertype")); + py::arg("str"), py::arg("sertype"), + py::doc(serial_DeserializePublicKeyString_docs)); m.def("Serialize", &SerializeToStringWrapper<PrivateKey<DCRTPoly>, SerType::SERJSON>, - py::arg("obj"), py::arg("sertype")); + py::arg("obj"), py::arg("sertype"), + py::doc(serial_Serialize_docs)); m.def("DeserializePrivateKeyString", &DeserializeFromStringWrapper<PrivateKey<DCRTPoly>, SerType::SERJSON>, - py::arg("str"), py::arg("sertype")); + py::arg("str"), py::arg("sertype"), + py::doc(serial_DeserializePrivateKeyString_docs)); m.def("Serialize", &SerializeToStringWrapper<Ciphertext<DCRTPoly>, SerType::SERJSON>, - py::arg("obj"), py::arg("sertype")); + py::arg("obj"), py::arg("sertype"), + py::doc(serial_Serialize_docs)); m.def("DeserializeCiphertextString", &DeserializeFromStringWrapper<Ciphertext<DCRTPoly>, SerType::SERJSON>, - py::arg("str"), py::arg("sertype")); + py::arg("str"), py::arg("sertype"), + py::doc(serial_DeserializeCiphertextString_docs)); m.def("Serialize", &SerializeToStringWrapper<EvalKey<DCRTPoly>, SerType::SERJSON>, - py::arg("obj"), py::arg("sertype")); + py::arg("obj"), py::arg("sertype"), + py::doc(serial_Serialize_docs)); m.def("DeserializeEvalKeyString", &DeserializeFromStringWrapper<EvalKey<DCRTPoly>, SerType::SERJSON>, - py::arg("str"), py::arg("sertype")); + py::arg("str"), py::arg("sertype"), + py::doc(serial_DeserializeEvalKeyString_docs)); m.def("Serialize", &SerializeToBytesWrapper<std::shared_ptr<std::map<uint32_t, EvalKey<DCRTPoly>>>, SerType::SERJSON>, - py::arg("obj"), py::arg("sertype")); + py::arg("obj"), py::arg("sertype"), + py::doc(serial_Serialize_docs)); m.def("DeserializeEvalKeyMapString", &DeserializeFromBytesWrapper<std::shared_ptr<std::map<uint32_t, EvalKey<DCRTPoly>>>, SerType::SERJSON>, - py::arg("str"), py::arg("sertype")); + py::arg("str"), py::arg("sertype"), + py::doc(serial_DeserializeEvalKeyMapString_docs)); m.def("SerializeEvalMultKeyString", &SerializeEvalMultKeyToStringWrapper<SerType::SERJSON>, - py::arg("sertype"), py::arg("keyTag") = ""); + py::arg("sertype"), py::arg("keyTag") = "", + py::doc(serial_SerializeEvalMultKeyString_docs)); m.def("DeserializeEvalMultKeyString", static_cast<void (*)(const std::string&, const SerType::SERJSON&)>(&DeserializeEvalMultKeyFromStringWrapper<SerType::SERJSON>), - py::arg("data"), py::arg("sertype")); + py::arg("data"), py::arg("sertype"), + py::doc(serial_DeserializeEvalMultKeyString_docs)); m.def("SerializeEvalAutomorphismKeyString", &SerializeEvalAutomorphismKeyToStringWrapper<SerType::SERJSON>, - py::arg("sertype"), py::arg("keyTag") = ""); + py::arg("sertype"), py::arg("keyTag") = "", + py::doc(serial_SerializeEvalAutomorphismKeyString_docs)); m.def("DeserializeEvalAutomorphismKeyString", static_cast<void (*)(const std::string&, const SerType::SERJSON&)>(&DeserializeEvalAutomorphismKeyFromStringWrapper<SerType::SERJSON>), - py::arg("data"), py::arg("sertype")); + py::arg("data"), py::arg("sertype"), + py::doc(serial_DeserializeEvalAutomorphismKeyString_docs)); // Binary Serialization m.def("SerializeToFile", static_cast<bool (*)(const std::string&,const CryptoContextDCRT&, const SerType::SERBINARY&)>(&Serial::SerializeToFile<CryptoContextDCRT>), - py::arg("filename"), py::arg("obj"), py::arg("sertype")); + py::arg("filename"), py::arg("obj"), py::arg("sertype"), + py::doc(serial_SerializeToFile_docs)); m.def("DeserializeCryptoContext", static_cast<std::tuple<CryptoContextDCRT,bool> (*)(const std::string&, const SerType::SERBINARY&)>(&DeserializeCCWrapper<SerType::SERBINARY>), - py::arg("filename"), py::arg("sertype")); + py::arg("filename"), py::arg("sertype"), + py::doc(serial_DeserializeCryptoContext_docs)); m.def("SerializeToFile", static_cast<bool (*)(const std::string&, const PublicKey<DCRTPoly>&, const SerType::SERBINARY&)>(&Serial::SerializeToFile<PublicKey<DCRTPoly>>), - py::arg("filename"), py::arg("obj"), py::arg("sertype")); + py::arg("filename"), py::arg("obj"), py::arg("sertype"), + py::doc(serial_SerializeToFile_docs)); m.def("DeserializePublicKey", static_cast<std::tuple<PublicKey<DCRTPoly>,bool> (*)(const std::string&, const SerType::SERBINARY&)>(&DeserializeFromFileWrapper<PublicKey<DCRTPoly>, SerType::SERBINARY>), - py::arg("filename"), py::arg("sertype")); + py::arg("filename"), py::arg("sertype"), + py::doc(serial_DeserializePublicKey_docs)); m.def("SerializeToFile", static_cast<bool (*)(const std::string&, const PrivateKey<DCRTPoly>&, const SerType::SERBINARY&)>(&Serial::SerializeToFile<PrivateKey<DCRTPoly>>), - py::arg("filename"), py::arg("obj"), py::arg("sertype")); + py::arg("filename"), py::arg("obj"), py::arg("sertype"), + py::doc(serial_SerializeToFile_docs)); m.def("DeserializePrivateKey", static_cast<std::tuple<PrivateKey<DCRTPoly>,bool> (*)(const std::string&, const SerType::SERBINARY&)>(&DeserializeFromFileWrapper<PrivateKey<DCRTPoly>, SerType::SERBINARY>), - py::arg("filename"), py::arg("sertype")); + py::arg("filename"), py::arg("sertype"), + py::doc(serial_DeserializePrivateKey_docs)); m.def("SerializeToFile", static_cast<bool (*)(const std::string&, const Ciphertext<DCRTPoly>&, const SerType::SERBINARY&)>(&Serial::SerializeToFile<Ciphertext<DCRTPoly>>), - py::arg("filename"), py::arg("obj"), py::arg("sertype")); + py::arg("filename"), py::arg("obj"), py::arg("sertype"), + py::doc(serial_SerializeToFile_docs)); m.def("DeserializeCiphertext", static_cast<std::tuple<Ciphertext<DCRTPoly>,bool> (*)(const std::string&, const SerType::SERBINARY&)>(&DeserializeFromFileWrapper<Ciphertext<DCRTPoly>, SerType::SERBINARY>), - py::arg("filename"), py::arg("sertype")); + py::arg("filename"), py::arg("sertype"), + py::doc(serial_DeserializeCiphertext_docs)); m.def("SerializeToFile", static_cast<bool (*)(const std::string&, const EvalKey<DCRTPoly>&, const SerType::SERBINARY&)>(&Serial::SerializeToFile<EvalKey<DCRTPoly>>), - py::arg("filename"), py::arg("obj"), py::arg("sertype")); + py::arg("filename"), py::arg("obj"), py::arg("sertype"), + py::doc(serial_SerializeToFile_docs)); m.def("DeserializeEvalKey", static_cast<std::tuple<EvalKey<DCRTPoly>,bool> (*)(const std::string&, const SerType::SERBINARY&)>(&DeserializeFromFileWrapper<EvalKey<DCRTPoly>, SerType::SERBINARY>), - py::arg("filename"), py::arg("sertype")); + py::arg("filename"), py::arg("sertype"), + py::doc(serial_DeserializeEvalKey_docs)); m.def("SerializeToFile", static_cast<bool (*)(const std::string&, const std::shared_ptr<std::map<uint32_t, EvalKey<DCRTPoly>>>&, const SerType::SERBINARY&)>(&Serial::SerializeToFile<std::shared_ptr<std::map<uint32_t, EvalKey<DCRTPoly>>>>), - py::arg("filename"), py::arg("obj"), py::arg("sertype")); + py::arg("filename"), py::arg("obj"), py::arg("sertype"), + py::doc(serial_SerializeToFile_docs)); m.def("DeserializeEvalKeyMap", static_cast<std::tuple<std::shared_ptr<std::map<uint32_t, EvalKey<DCRTPoly>>>, bool> (*)(const std::string&, const SerType::SERBINARY&)>(&DeserializeFromFileWrapper<std::shared_ptr<std::map<uint32_t, EvalKey<DCRTPoly>>>, SerType::SERBINARY>), - py::arg("filename"), py::arg("sertype")); + py::arg("filename"), py::arg("sertype"), + py::doc(serial_DeserializeEvalKeyMap_docs)); // Binary Serialization to bytes m.def("Serialize", &SerializeToBytesWrapper<CryptoContextDCRT, SerType::SERBINARY>, - py::arg("obj"), py::arg("sertype")); + py::arg("obj"), py::arg("sertype"), + py::doc(serial_Serialize_docs)); m.def("DeserializeCryptoContextString", &DeserializeCCFromBytesWrapper<SerType::SERBINARY>, - py::arg("str"), py::arg("sertype")); + py::arg("str"), py::arg("sertype"), + py::doc(serial_DeserializeCryptoContextString_docs)); m.def("Serialize", &SerializeToBytesWrapper<PublicKey<DCRTPoly>, SerType::SERBINARY>, - py::arg("obj"), py::arg("sertype")); + py::arg("obj"), py::arg("sertype"), + py::doc(serial_Serialize_docs)); m.def("DeserializePublicKeyString", &DeserializeFromBytesWrapper<PublicKey<DCRTPoly>, SerType::SERBINARY>, - py::arg("str"), py::arg("sertype")); + py::arg("str"), py::arg("sertype"), + py::doc(serial_DeserializePublicKeyString_docs)); m.def("Serialize", &SerializeToBytesWrapper<PrivateKey<DCRTPoly>, SerType::SERBINARY>, - py::arg("obj"), py::arg("sertype")); + py::arg("obj"), py::arg("sertype"), + py::doc(serial_Serialize_docs)); m.def("DeserializePrivateKeyString", &DeserializeFromBytesWrapper<PrivateKey<DCRTPoly>, SerType::SERBINARY>, - py::arg("str"), py::arg("sertype")); + py::arg("str"), py::arg("sertype"), + py::doc(serial_DeserializePrivateKeyString_docs)); m.def("Serialize", &SerializeToBytesWrapper<Ciphertext<DCRTPoly>, SerType::SERBINARY>, - py::arg("obj"), py::arg("sertype")); + py::arg("obj"), py::arg("sertype"), + py::doc(serial_Serialize_docs)); m.def("DeserializeCiphertextString", &DeserializeFromBytesWrapper<Ciphertext<DCRTPoly>, SerType::SERBINARY>, - py::arg("str"), py::arg("sertype")); + py::arg("str"), py::arg("sertype"), + py::doc(serial_DeserializeCiphertextString_docs)); m.def("Serialize", &SerializeToBytesWrapper<EvalKey<DCRTPoly>, SerType::SERBINARY>, - py::arg("obj"), py::arg("sertype")); + py::arg("obj"), py::arg("sertype"), + py::doc(serial_Serialize_docs)); m.def("DeserializeEvalKeyString", &DeserializeFromBytesWrapper<EvalKey<DCRTPoly>, SerType::SERBINARY>, - py::arg("str"), py::arg("sertype")); + py::arg("str"), py::arg("sertype"), + py::doc(serial_DeserializeEvalKeyString_docs)); m.def("Serialize", &SerializeToBytesWrapper<std::shared_ptr<std::map<uint32_t, EvalKey<DCRTPoly>>>, SerType::SERBINARY>, - py::arg("obj"), py::arg("sertype")); + py::arg("obj"), py::arg("sertype"), + py::doc(serial_Serialize_docs)); m.def("DeserializeEvalKeyMapString", &DeserializeFromBytesWrapper<std::shared_ptr<std::map<uint32_t, EvalKey<DCRTPoly>>>, SerType::SERBINARY>, - py::arg("str"), py::arg("sertype")); + py::arg("str"), py::arg("sertype"), + py::doc(serial_DeserializeEvalKeyMapString_docs)); m.def("Serialize", &SerializeToBytesWrapper<std::vector<EvalKey<DCRTPoly>>, SerType::SERBINARY>, - py::arg("obj"), py::arg("sertype")); + py::arg("obj"), py::arg("sertype"), + py::doc(serial_Serialize_docs)); m.def("DeserializeEvalKeyMapVectorString", &DeserializeFromBytesWrapper<std::vector<EvalKey<DCRTPoly>>, SerType::SERBINARY>, - py::arg("str"), py::arg("sertype")); + py::arg("str"), py::arg("sertype"), + py::doc(serial_DeserializeEvalKeyMapVectorString_docs)); m.def("SerializeEvalMultKeyString", &SerializeEvalMultKeyToBytesWrapper<SerType::SERBINARY>, - py::arg("sertype"), py::arg("keyTag") = ""); + py::arg("sertype"), py::arg("keyTag") = "", + py::doc(serial_SerializeEvalMultKeyString_docs)); m.def("DeserializeEvalMultKeyString", static_cast<void (*)(const py::bytes&, const SerType::SERBINARY&)>(&DeserializeEvalMultKeyFromBytesWrapper<SerType::SERBINARY>), - py::arg("bytes"), py::arg("sertype")); + py::arg("bytes"), py::arg("sertype"), + py::doc(serial_DeserializeEvalMultKeyString_docs)); m.def("SerializeEvalAutomorphismKeyString", &SerializeEvalAutomorphismKeyToBytesWrapper<SerType::SERBINARY>, - py::arg("sertype"), py::arg("keyTag") = ""); + py::arg("sertype"), py::arg("keyTag") = "", + py::doc(serial_SerializeEvalAutomorphismKeyString_docs)); m.def("DeserializeEvalAutomorphismKeyString", static_cast<void (*)(const py::bytes&, const SerType::SERBINARY&)>(&DeserializeEvalAutomorphismKeyFromBytesWrapper<SerType::SERBINARY>), - py::arg("bytes"), py::arg("sertype")); + py::arg("bytes"), py::arg("sertype"), + py::doc(serial_DeserializeEvalAutomorphismKeyString_docs)); }