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 +# "/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 64f98b5..5278095 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_MINOR 6) 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.5.1) +# OpenFHE version can be specified externally (-DOPENFHE_REQUIRED_VERSION=1.6.1) if(NOT DEFINED OPENFHE_REQUIRED_VERSION) - set(OPENFHE_REQUIRED_VERSION "1.5.1" 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) @@ -66,11 +75,14 @@ endif() ### add_executable( test demo-simple-example.cpp ) ### Pybind Modules -pybind11_add_module(openfhe - src/lib/bindings.cpp +add_compile_definitions(OPENFHE_PYTHON_VERSION_STR="${OPENFHE_PYTHON_VERSION}") +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. @@ -97,28 +109,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) @@ -128,5 +126,7 @@ else() endif() message("***** INSTALL IS AT ${Python_Install_Location}; to change, run cmake with -DCMAKE_INSTALL_PREFIX=/your/path") install(TARGETS openfhe LIBRARY DESTINATION ${Python_Install_Location}) +# openfhe-python-packager composes the pip wheel as an openfhe/ package from this +# install tree: without __init__.py setuptools packages nothing and the wheel is empty install(FILES ${CMAKE_SOURCE_DIR}/src/__init__.py DESTINATION ${Python_Install_Location}) diff --git a/README.md b/README.md index 5c2ad4d..331c9fe 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,13 @@ [![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.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. ## Table of Contents +- [Breaking changes in v1.6](#breaking-changes-in-v16) - [Installing using pip](#installing-using-pip) - [Running from Docker](#running-from-docker) - [Building from Source](#building-from-source) @@ -19,6 +20,11 @@ Python wrapper for the main cryptographic capabilities of the OpenFHE C++ librar - [OpenFHE Python Wrapper Documentation](#openfhe-python-wrapper-documentation) - [Contributing Guide](#contributing-guide) +## Breaking changes in v1.6 + +- The `FHECKKSRNS` class is no longer exposed to Python. Its static depth helpers are now static methods of `CryptoContext`: replace `FHECKKSRNS.GetBootstrapDepth(...)` with `CryptoContext.GetBootstrapDepth(...)` (both overloads keep their signatures). +- The `BinFHEContext` attributes `SaveJSON`, `LoadJSON`, `SaveBinary`, `LoadBinary`, `SavePortableBinary`, `LoadPortableBinary`, `GetParams`, `GetLWEScheme`, `GetBinFHEScheme`, and `GetRefreshKey` were removed. They could never be called successfully in v1.5.1 (their argument or return types were not exposed to Python), but `hasattr` checks on them now return `False`. Use the module-level `SerializeToFile`/`Deserialize*` functions for serialization, and `GetN()`/`GetQ()` for the common parameter queries. + ## Installing using pip (for Ubuntu) On Ubuntu, openfhe-python can be installed using pip. All available releases are listed at [Python Package Index OpenFHE Release History](https://pypi.org/project/openfhe/#history). Find the release for your version of Ubuntu and run @@ -42,7 +48,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.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) @@ -63,19 +69,16 @@ make make install # You may have to run sudo make install ``` -At this point the `.so` file has been built. Your exact installation process will depend on your virtual environment. +With the default install prefix, `make install` copies the module directly into the site-packages directory of the Python interpreter cmake found (this also works inside virtual environments), so `import openfhe` works with no further setup. Cmake will automatically find the python installation path, if unwanted, you can specify the python path by adding `-DPYTHON_EXECUTABLE_PATH=/path/to/python` to the cmake command. -If you see an error saying that one of OpenFHE .so files cannot be found when running a Python example (occurs only for some environments), -add the path where the .so files reside to the `PYTHONPATH` environment variable: +If you install to a custom location instead (`cmake .. -DCMAKE_INSTALL_PREFIX=/your/path`), add that location to the `PYTHONPATH` environment variable so Python can find the module: ``` -export PYTHONPATH=(/path/to/installed/openfhe):$PYTHONPATH +export PYTHONPATH=/your/path:$PYTHONPATH ``` -In some environments (this happens rarely), it may also be necessary to add the OpenFHE libraries path to `LD_LIBRARY_PATH`. - -If OpenFHE is not installed in the default location, then both `PYTHONPATH and LD_LIBRARY_PATH` must be set before running any Python example. +Independently of where the Python module is installed, if you see an error saying that one of the OpenFHE .so libraries cannot be found when running a Python example, add the directory containing the OpenFHE C++ libraries to `LD_LIBRARY_PATH`. This is only needed when OpenFHE itself was installed somewhere the dynamic linker does not search by default. #### Conda @@ -127,33 +130,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-Chebyschev.py) - - [Code Example for BFV with 5 parties](examples/pke/threshold-fhe-5p.py) + - [n-party Interactive Bootstrapping after Chebyshev Approximation](examples/pke/tckks-interactive-mp-bootstrapping-Chebyshev.py) ## OpenFHE Python Wrapper Documentation diff --git a/docs/binfhe.rst b/docs/binfhe.rst index d9d0790..1a0702c 100644 --- a/docs/binfhe.rst +++ b/docs/binfhe.rst @@ -18,4 +18,48 @@ LWEPrivateKey .. autoclass:: openfhe.LWEPrivateKey :members: :undoc-members: - :show-inheritance: \ No newline at end of file + :show-inheritance: + +LWEPublicKey +---------------- +.. autoclass:: openfhe.LWEPublicKey + :members: + :undoc-members: + :show-inheritance: + +LWESwitchingKey +---------------- +.. autoclass:: openfhe.LWESwitchingKey + :members: + :undoc-members: + :show-inheritance: + +LWEKeyPair +---------------- +.. autoclass:: openfhe.LWEKeyPair + :members: + :undoc-members: + :show-inheritance: + +RingGSWBTKey +---------------- +.. autoclass:: openfhe.RingGSWBTKey + :members: + :undoc-members: + :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/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..1284d09 --- /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(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) + 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..a51b918 --- /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 + CryptoContext.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..eceac1c --- /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 + CryptoContext.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..0e404e2 --- /dev/null +++ b/examples/pke/ckks-release-memory.py @@ -0,0 +1,88 @@ +# +# 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) + + 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 + CryptoContext.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..2950d13 --- /dev/null +++ b/examples/pke/depth-bgvrns.py @@ -0,0 +1,223 @@ +# +# 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) + # 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) + # 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..e0e0856 --- /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 + CryptoContext.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 + \ + CryptoContext.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 + \ + CryptoContext.GetFBTDepth(lvlb, coeffint_mod, p_digit, order, secret_key_dist) + else: + depth = levels_available_after_bootstrap + \ + CryptoContext.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/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/iterative-ckks-bootstrapping-composite-scaling.py b/examples/pke/iterative-ckks-bootstrapping-composite-scaling.py new file mode 100644 index 0000000..7593b18 --- /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 + CryptoContext.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/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/linearwsum-evaluation.py b/examples/pke/linearwsum-evaluation.py new file mode 100644 index 0000000..c667c3e --- /dev/null +++ b/examples/pke/linearwsum-evaluation.py @@ -0,0 +1,78 @@ +# +# 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) + if get_native_int() == 128: + parameters.SetScalingTechnique(ScalingTechnique.FIXEDAUTO) + else: + 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..c6c73bb --- /dev/null +++ b/examples/pke/scheme-switching-serial.py @@ -0,0 +1,195 @@ +# +# 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) + # 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) + + # 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..4c18800 --- /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 + CryptoContext.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 + CryptoContext.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-ckks-bootstrapping.py b/examples/pke/simple-ckks-bootstrapping.py index 56ceba2..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() @@ -29,7 +30,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 +102,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) @@ -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}") @@ -144,4 +145,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 2defbd2..e3fb9a8 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) @@ -162,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) @@ -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) @@ -233,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/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/__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/include/bindings.h b/src/include/bindings.h index 1f06ba6..cf238c8 100644 --- a/src/include/bindings.h +++ b/src/include/bindings.h @@ -31,16 +31,24 @@ #ifndef __BINDINGS_H__ #define __BINDINGS_H__ +#include "openfhe.h" + #include -void bind_parameters(pybind11::module &m); +#include + +using DCRTCryptoContextClass = pybind11::class_< + lbcrypto::CryptoContextImpl, + std::shared_ptr>>; + void bind_crypto_context(pybind11::module &m); void bind_enums_and_constants(pybind11::module &m); 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(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/include/docstrings/binfhecontext_docs.h b/src/include/docstrings/binfhecontext_docs.h index 94f299c..ba2a0ce 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 @@ -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. @@ -123,6 +151,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 +232,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 large ciphertext modulus Q (public keys are generated for the ring dimension N) + :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 b7f1e71..5f6e4c2 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 @@ -165,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) @@ -182,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) @@ -290,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"; @@ -329,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( @@ -351,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"; @@ -433,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 @@ -507,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( @@ -607,7 +621,6 @@ const char* cc_EvalSquareInPlace_docs = R"pbdoc( :param ciphertext: input ciphertext :type ciphertext: Ciphertext - :return: squared ciphertext )pbdoc"; @@ -813,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"; @@ -942,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"; @@ -951,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"; @@ -974,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( @@ -991,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"; @@ -1004,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"; @@ -1017,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"; @@ -1030,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"; @@ -1043,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"; @@ -1056,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"; @@ -1069,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"; @@ -1082,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"; @@ -1173,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"; @@ -1184,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 @@ -1199,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( @@ -1339,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( @@ -1396,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( @@ -1622,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"; @@ -1634,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"; @@ -1660,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..83d434f --- /dev/null +++ b/src/include/docstrings/fbt_docs.h @@ -0,0 +1,641 @@ +//================================================================================== +// 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_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]. + + :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"; + +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/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/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/include/integer_conversions.h b/src/include/integer_conversions.h new file mode 100644 index 0000000..c2a1b9f --- /dev/null +++ b/src/include/integer_conversions.h @@ -0,0 +1,106 @@ +//================================================================================== +// 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 +#include +#include + +namespace openfhe_python { + +template +struct IsNativeInteger : std::false_type {}; + +template +struct IsNativeInteger> + : std::bool_constant {}; + +// 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); +} + +[[noreturn]] inline void ThrowOverflowError(const std::string& valueStr) { + PyErr_SetString(PyExc_OverflowError, + ("value too large for the target OpenFHE integer type: " + valueStr).c_str()); + throw pybind11::error_already_set(); +} + +// Convert an arbitrary-size Python integer through its decimal representation. +// 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 enforces its +// bound by throwing from the constructor; that throw is remapped below so every +// oversized value surfaces as the same Python OverflowError. +template +Integer PyIntToInteger(const pybind11::int_& value) { + if (pybind11::isinstance(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(result).cast(); + + if (!valueStr.empty() && valueStr[0] == '-') + throw pybind11::value_error("OpenFHE integers cannot be negative, got " + valueStr); + + if constexpr (IsNativeInteger::value) { + using NativeType = typename Integer::Integer; + static const std::string maxValue = Integer(std::numeric_limits::max()).ToString(); + if (valueStr.size() > maxValue.size() || + (valueStr.size() == maxValue.size() && valueStr > maxValue)) { + ThrowOverflowError(valueStr); + } + return Integer(valueStr); + } else { + // the string is digits-only at this point, so running out of capacity is the + // only way the fixed-size BigInteger constructor can throw + try { + return Integer(valueStr); + } catch (const std::exception&) { + ThrowOverflowError(valueStr); + } + } +} + +} // namespace openfhe_python + +#endif // OPENFHE_PYTHON_INTEGER_CONVERSIONS_H 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 b5727e7..aaf3e6e 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" @@ -52,6 +53,12 @@ using namespace lbcrypto; using CC = CryptoContextImpl; namespace py = pybind11; +// PyIntToInteger only range-checks types the IsNativeInteger trait recognizes; if an +// OpenFHE change stops NativeInteger from matching it, oversized Python ints would +// silently wrap again, so turn that into a build error instead. +static_assert(openfhe_python::IsNativeInteger::value, + "IsNativeInteger does not detect NativeInteger; PyIntToInteger would lose its overflow check"); + // disable the PYBIND11 template-based conversion for this type PYBIND11_MAKE_OPAQUE(std::map>); @@ -63,80 +70,148 @@ inline std::shared_ptr GetParamsRNSChecked(const CryptoCont } void bind_DCRTPoly(py::module &m) { - py::class_(m, "DCRTPoly").def(py::init<>()); + py::class_(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 void bind_parameters(py::module &m, const std::string name) { - py::class_>(m, name.c_str()) + py::class_>(m, name.c_str(), ccparams_doc) .def(py::init<>()) // getters - .def("GetPlaintextModulus", &CCParams::GetPlaintextModulus) - .def("GetScheme", &CCParams::GetScheme) - .def("GetDigitSize", &CCParams::GetDigitSize) - .def("GetStandardDeviation", &CCParams::GetStandardDeviation) - .def("GetSecretKeyDist", &CCParams::GetSecretKeyDist) - .def("GetMaxRelinSkDeg", &CCParams::GetMaxRelinSkDeg) - .def("GetPREMode", &CCParams::GetPREMode) - .def("GetMultipartyMode", &CCParams::GetMultipartyMode) - .def("GetExecutionMode", &CCParams::GetExecutionMode) - .def("GetDecryptionNoiseMode", &CCParams::GetDecryptionNoiseMode) - .def("GetNoiseEstimate", &CCParams::GetNoiseEstimate) - .def("GetDesiredPrecision", &CCParams::GetDesiredPrecision) - .def("GetStatisticalSecurity", &CCParams::GetStatisticalSecurity) - .def("GetNumAdversarialQueries", &CCParams::GetNumAdversarialQueries) - //.def("GetThresholdNumOfParties", &CCParams::GetThresholdNumOfParties) - .def("GetKeySwitchTechnique", &CCParams::GetKeySwitchTechnique) - .def("GetScalingTechnique", &CCParams::GetScalingTechnique) - .def("GetBatchSize", &CCParams::GetBatchSize) - .def("GetFirstModSize", &CCParams::GetFirstModSize) - .def("GetNumLargeDigits", &CCParams::GetNumLargeDigits) - .def("GetMultiplicativeDepth", &CCParams::GetMultiplicativeDepth) - .def("GetScalingModSize", &CCParams::GetScalingModSize) - .def("GetSecurityLevel", &CCParams::GetSecurityLevel) - .def("GetRingDim", &CCParams::GetRingDim) - .def("GetEvalAddCount", &CCParams::GetEvalAddCount) - .def("GetKeySwitchCount", &CCParams::GetKeySwitchCount) - .def("GetEncryptionTechnique", &CCParams::GetEncryptionTechnique) - .def("GetMultiplicationTechnique", &CCParams::GetMultiplicationTechnique) - .def("GetPRENumHops", &CCParams::GetPRENumHops) - .def("GetInteractiveBootCompressionLevel", &CCParams::GetInteractiveBootCompressionLevel) - .def("GetCompositeDegree", &CCParams::GetCompositeDegree) - .def("GetRegisterWordSize", &CCParams::GetRegisterWordSize) - .def("GetCKKSDataType", &CCParams::GetCKKSDataType) + .def("GetPlaintextModulus", &CCParams::GetPlaintextModulus, + py::doc("Get the plaintext modulus (used by BGV/BFV).")) + .def("GetScheme", &CCParams::GetScheme, + py::doc("Get the scheme type.")) + .def("GetDigitSize", &CCParams::GetDigitSize, + py::doc("Get the digit size used in BV key switching.")) + .def("GetStandardDeviation", &CCParams::GetStandardDeviation, + py::doc("Get the standard deviation of the Gaussian error distribution.")) + .def("GetSecretKeyDist", &CCParams::GetSecretKeyDist, + py::doc("Get the secret key distribution.")) + .def("GetMaxRelinSkDeg", &CCParams::GetMaxRelinSkDeg, + py::doc("Get the maximum power of the secret key for which relinearization keys are generated.")) + .def("GetPREMode", &CCParams::GetPREMode, + py::doc("Get the proxy re-encryption security mode.")) + .def("GetMultipartyMode", &CCParams::GetMultipartyMode, + py::doc("Get the multiparty security mode.")) + .def("GetExecutionMode", &CCParams::GetExecutionMode, + py::doc("Get the execution mode (evaluation or noise estimation).")) + .def("GetDecryptionNoiseMode", &CCParams::GetDecryptionNoiseMode, + py::doc("Get the decryption noise mode (fixed noise or noise flooding).")) + .def("GetNoiseEstimate", &CCParams::GetNoiseEstimate, + py::doc("Get the noise estimate obtained in the noise-estimation execution mode.")) + .def("GetDesiredPrecision", &CCParams::GetDesiredPrecision, + py::doc("Get the desired precision for 128-bit CKKS.")) + .def("GetStatisticalSecurity", &CCParams::GetStatisticalSecurity, + py::doc("Get the statistical security parameter (bits) used for noise flooding.")) + .def("GetNumAdversarialQueries", &CCParams::GetNumAdversarialQueries, + py::doc("Get the assumed bound on the number of adversarial queries used for noise flooding.")) + .def("GetThresholdNumOfParties", &CCParams::GetThresholdNumOfParties, + py::doc(ccparams_GetThresholdNumOfParties_docs)) + .def("GetKeySwitchTechnique", &CCParams::GetKeySwitchTechnique, + py::doc("Get the key switching technique (BV or HYBRID).")) + .def("GetScalingTechnique", &CCParams::GetScalingTechnique, + py::doc("Get the rescaling/modulus switching technique (CKKS/BGV).")) + .def("GetBatchSize", &CCParams::GetBatchSize, + py::doc("Get the maximum batch size (number of packed slots).")) + .def("GetFirstModSize", &CCParams::GetFirstModSize, + py::doc("Get the bit size of the first ciphertext prime.")) + .def("GetNumLargeDigits", &CCParams::GetNumLargeDigits, + py::doc("Get the number of digits used in HYBRID key switching.")) + .def("GetMultiplicativeDepth", &CCParams::GetMultiplicativeDepth, + py::doc("Get the multiplicative depth the parameters support.")) + .def("GetScalingModSize", &CCParams::GetScalingModSize, + py::doc("Get the bit size of the scaling ciphertext primes.")) + .def("GetSecurityLevel", &CCParams::GetSecurityLevel, + py::doc("Get the security level according to the HE standard.")) + .def("GetRingDim", &CCParams::GetRingDim, + py::doc("Get the polynomial ring dimension.")) + .def("GetEvalAddCount", &CCParams::GetEvalAddCount, + py::doc("Get the maximum number of additions assumed for BGV/BFV noise estimation.")) + .def("GetKeySwitchCount", &CCParams::GetKeySwitchCount, + py::doc("Get the maximum number of key switching operations assumed for BGV/BFV noise estimation.")) + .def("GetEncryptionTechnique", &CCParams::GetEncryptionTechnique, + py::doc("Get the encryption technique (BFV).")) + .def("GetMultiplicationTechnique", &CCParams::GetMultiplicationTechnique, + py::doc("Get the multiplication technique (BFV).")) + .def("GetPRENumHops", &CCParams::GetPRENumHops, + py::doc("Get the number of hops supported for proxy re-encryption.")) + .def("GetInteractiveBootCompressionLevel", &CCParams::GetInteractiveBootCompressionLevel, + py::doc("Get the compression level for interactive multiparty CKKS bootstrapping.")) + .def("GetCompositeDegree", &CCParams::GetCompositeDegree, + py::doc("Get the composite degree used by CKKS composite scaling.")) + .def("GetRegisterWordSize", &CCParams::GetRegisterWordSize, + py::doc("Get the target hardware register word size for CKKS composite scaling.")) + .def("GetCKKSDataType", &CCParams::GetCKKSDataType, + py::doc("Get the CKKS data type (real or complex).")) // 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"), + py::doc("Set the plaintext modulus (used by BGV/BFV).")) + .def("SetDigitSize", &CCParams::SetDigitSize, py::arg("digitSize"), + py::doc("Set the digit size used in BV key switching.")) + .def("SetStandardDeviation", &CCParams::SetStandardDeviation, py::arg("standardDeviation"), + py::doc("Set the standard deviation of the Gaussian error distribution.")) + .def("SetSecretKeyDist", &CCParams::SetSecretKeyDist, py::arg("secretKeyDist"), + py::doc("Set the secret key distribution.")) + .def("SetMaxRelinSkDeg", &CCParams::SetMaxRelinSkDeg, py::arg("maxRelinSkDeg"), + py::doc("Set the maximum power of the secret key for which relinearization keys are generated.")) + .def("SetPREMode", &CCParams::SetPREMode, py::arg("PREMode"), + py::doc("Set the proxy re-encryption security mode.")) + .def("SetMultipartyMode", &CCParams::SetMultipartyMode, py::arg("multipartyMode"), + py::doc("Set the multiparty security mode.")) + .def("SetExecutionMode", &CCParams::SetExecutionMode, py::arg("executionMode"), + py::doc("Set the execution mode (evaluation or noise estimation).")) + .def("SetDecryptionNoiseMode", &CCParams::SetDecryptionNoiseMode, py::arg("decryptionNoiseMode"), + py::doc("Set the decryption noise mode (fixed noise or noise flooding).")) + .def("SetNoiseEstimate", &CCParams::SetNoiseEstimate, py::arg("noiseEstimate"), + py::doc("Set the noise estimate obtained in the noise-estimation execution mode.")) + .def("SetDesiredPrecision", &CCParams::SetDesiredPrecision, py::arg("desiredPrecision"), + py::doc("Set the desired precision for 128-bit CKKS.")) + .def("SetStatisticalSecurity", &CCParams::SetStatisticalSecurity, py::arg("statisticalSecurity"), + py::doc("Set the statistical security parameter (bits) used for noise flooding.")) + .def("SetNumAdversarialQueries", &CCParams::SetNumAdversarialQueries, py::arg("numAdversarialQueries"), + py::doc("Set the assumed bound on the number of adversarial queries used for noise flooding.")) + .def("SetThresholdNumOfParties", &CCParams::SetThresholdNumOfParties, py::arg("thresholdNumOfParties"), + py::doc("Set the number of parties in a threshold application.")) + .def("SetKeySwitchTechnique", &CCParams::SetKeySwitchTechnique, py::arg("keySwitchTechnique"), + py::doc("Set the key switching technique (BV or HYBRID).")) + .def("SetScalingTechnique", &CCParams::SetScalingTechnique, py::arg("scalingTechnique"), + py::doc("Set the rescaling/modulus switching technique (CKKS/BGV).")) + .def("SetBatchSize", &CCParams::SetBatchSize, py::arg("batchSize"), + py::doc("Set the maximum batch size (number of packed slots).")) + .def("SetFirstModSize", &CCParams::SetFirstModSize, py::arg("firstModSize"), + py::doc("Set the bit size of the first ciphertext prime.")) + .def("SetNumLargeDigits", &CCParams::SetNumLargeDigits, py::arg("numLargeDigits"), + py::doc("Set the number of digits used in HYBRID key switching.")) + .def("SetMultiplicativeDepth", &CCParams::SetMultiplicativeDepth, py::arg("multiplicativeDepth"), + py::doc("Set the multiplicative depth the parameters must support.")) + .def("SetScalingModSize", &CCParams::SetScalingModSize, py::arg("scalingModSize"), + py::doc("Set the bit size of the scaling ciphertext primes.")) + .def("SetSecurityLevel", &CCParams::SetSecurityLevel, py::arg("securityLevel"), + py::doc("Set the security level according to the HE standard.")) + .def("SetRingDim", &CCParams::SetRingDim, py::arg("ringDim"), + py::doc("Set the polynomial ring dimension.")) + .def("SetEvalAddCount", &CCParams::SetEvalAddCount, py::arg("evalAddCount"), + py::doc("Set the maximum number of additions assumed for BGV/BFV noise estimation.")) + .def("SetKeySwitchCount", &CCParams::SetKeySwitchCount, py::arg("keySwitchCount"), + py::doc("Set the maximum number of key switching operations assumed for BGV/BFV noise estimation.")) + .def("SetEncryptionTechnique", &CCParams::SetEncryptionTechnique, py::arg("encryptionTechnique"), + py::doc("Set the encryption technique (BFV).")) + .def("SetMultiplicationTechnique", &CCParams::SetMultiplicationTechnique, py::arg("multiplicationTechnique"), + py::doc("Set the multiplication technique (BFV).")) + .def("SetPRENumHops", &CCParams::SetPRENumHops, py::arg("PRENumHops"), + py::doc("Set the number of hops supported for proxy re-encryption.")) + .def("SetInteractiveBootCompressionLevel", &CCParams::SetInteractiveBootCompressionLevel, py::arg("interactiveBootCompressionLevel"), + py::doc("Set the compression level for interactive multiparty CKKS bootstrapping.")) + .def("SetCompositeDegree", &CCParams::SetCompositeDegree, py::arg("compositeDegree"), + py::doc("Set the composite degree used by CKKS composite scaling.")) + .def("SetRegisterWordSize", &CCParams::SetRegisterWordSize, py::arg("registerWordSize"), + py::doc("Set the target hardware register word size for CKKS composite scaling.")) + .def("SetCKKSDataType", &CCParams::SetCKKSDataType, py::arg("ckksDataType"), + py::doc("Set the CKKS data type (real or complex).")) .def("__str__",[](const CCParams ¶ms) { std::stringstream stream; stream << params; @@ -189,7 +264,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&, @@ -197,7 +272,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&, @@ -222,7 +297,30 @@ 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"), + 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::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::doc(cc_EvalPolyWithPrecomp_docs)) + .def("EvalPowers", + static_cast> (CC::*)( + ConstCiphertext&, const std::vector& + ) const>(&CC::EvalPowers), + py::arg("ciphertext"), py::arg("coefficients"), + py::doc(cc_EvalPowers_docs)); } void bind_eval_add_family(py::class_>& cls) { @@ -237,7 +335,7 @@ void bind_eval_add_family(py::class_>& cls) { cls.def("EvalAdd", static_cast (CC::*)(Plaintext&, ConstCiphertext&) 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 void bind_eval_add_scalar_overloads(py::class_>& cls) { @@ -248,7 +346,7 @@ void bind_eval_add_scalar_overloads(py::class_>& cls) { cls.def("EvalAdd", static_cast (CC::*)(ScalarT, ConstCiphertext&) 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_>& cls) { @@ -263,18 +361,18 @@ void bind_eval_addinplace_family(py::class_>& cls) { cls.def("EvalAddInPlace", static_cast&) 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 void bind_eval_addinplace_scalar_overloads(py::class_>& cls) { cls.def("EvalAddInPlace", static_cast&, 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&) 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_>& cls) { @@ -289,7 +387,7 @@ void bind_eval_sub_family(py::class_>& cls) { cls.def("EvalSub", static_cast (CC::*)(Plaintext&, ConstCiphertext&) 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 void bind_eval_sub_scalar_overloads(py::class_>& cls) { @@ -300,7 +398,7 @@ void bind_eval_sub_scalar_overloads(py::class_>& cls) { cls.def("EvalSub", static_cast (CC::*)(ScalarT, ConstCiphertext&) 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_>& cls) { @@ -311,11 +409,11 @@ void bind_eval_subinplace_family(py::class_>& cls) { cls.def("EvalSubInPlace", static_cast&, 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&) 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 void bind_eval_subinplace_scalar_overloads(py::class_>& cls) { @@ -326,7 +424,7 @@ void bind_eval_subinplace_scalar_overloads(py::class_>& cls.def("EvalSubInPlace", static_cast&) 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_>& cls) { @@ -341,7 +439,7 @@ void bind_eval_mult_family(py::class_>& cls) { cls.def("EvalMult", static_cast (CC::*)(ConstPlaintext&, ConstCiphertext&) 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 void bind_eval_mult_scalar_overloads(py::class_>& cls) { @@ -352,33 +450,19 @@ void bind_eval_mult_scalar_overloads(py::class_>& cls) { cls.def("EvalMult", static_cast (CC::*)(ScalarT, ConstCiphertext&) const>(&CC::EvalMult), py::arg("scalar"), py::arg("ciphertext"), - py::doc("")); // TODO (dsuponit): replace this with an actual docstring + py::doc(cc_EvalMultfloat_docs)); } -// 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("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), + py::doc("In-place homomorphic multiplication by a scalar: ciphertext *= scalar.")); + cls.def("EvalMultInPlace", static_cast&) 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) { @@ -386,17 +470,42 @@ 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", + "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()); }, + 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).")); - auto cc_class = py::class_, std::shared_ptr>>(m, "CryptoContext"); + py::class_, std::shared_ptr>>(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_, std::shared_ptr>>(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, + 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::doc(cc_ComposedEvalMult_docs)) .def("GetKeyGenLevel", &CryptoContextImpl::GetKeyGenLevel, cc_GetKeyGenLevel_docs) .def("SetKeyGenLevel", &CryptoContextImpl::SetKeyGenLevel, py::arg("level"), py::doc(cc_SetKeyGenLevel_docs)) .def("get_ptr",[](const CryptoContext &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::GetRingDimension, cc_GetRingDimension_docs) .def("GetPlaintextModulus", [](CryptoContext& self) { @@ -406,7 +515,8 @@ void bind_crypto_context(py::module &m) { .def("GetBatchSize", [](CryptoContext& self) { return self->GetCryptoParameters()->GetBatchSize(); - }) + }, + py::doc("Get the maximum batch size (number of packed slots).")) .def("GetModulus", [](CryptoContext& self) { return self->GetCryptoParameters() @@ -421,7 +531,8 @@ void bind_crypto_context(py::module &m) { if (!cryptoParams) OPENFHE_THROW("std::dynamic_pointer_cast() failed"); return cryptoParams->GetElementParams()->GetParams()[0]->GetModulus().ConvertToInt(); - }) + }, + py::doc("Get the first RNS modulus q_0 of a CKKS crypto context.")) .def("GetScalingFactorReal", [](CryptoContext& self, uint32_t level) { auto cryptoParams = std::dynamic_pointer_cast(self->GetCryptoParameters()); @@ -437,63 +548,107 @@ void bind_crypto_context(py::module &m) { if (!cryptoParams) OPENFHE_THROW("std::dynamic_pointer_cast() failed"); return cryptoParams->GetScalingTechnique(); - }) + }, + py::doc("Get the rescaling/modulus switching technique (CKKS/BGV).")) .def("GetDigitSize", [](CryptoContext& self) { return self->GetCryptoParameters()->GetDigitSize(); - }) + }, + py::doc("Get the digit size used in BV key switching.")) .def("GetCyclotomicOrder", &CryptoContextImpl::GetCyclotomicOrder, cc_GetCyclotomicOrder_docs) - .def("GetCKKSDataType", &CryptoContextImpl::GetCKKSDataType) - .def("GetCKKSBootCorrectionFactor", &CC::GetCKKSBootCorrectionFactor) - .def("SetCKKSBootCorrectionFactor", &CC::SetCKKSBootCorrectionFactor, py::arg("cf")) + .def("GetEncodingParams", &CC::GetEncodingParams, + py::doc(cc_GetEncodingParams_docs)) + .def("GetElementParams", + [](const CC& self) { + return self.GetCryptoParameters()->GetElementParams(); + }, + py::doc(cc_GetElementParams_docs)) + .def("GetRootOfUnity", [](const CC& self) { + return openfhe_python::IntegerToPyInt(self.GetRootOfUnity()); + }, + py::doc(cc_GetRootOfUnity_docs)) + .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, + 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& self) { return GetParamsRNSChecked(self, "GetNoiseEstimate")->GetNoiseEstimate(); - }) + }, + py::doc("Get the noise estimate used for CKKS noise flooding (RNS schemes only).")) .def("SetNoiseEstimate", [](CryptoContext& 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& self) { return GetParamsRNSChecked(self, "GetMultiplicativeDepth")->GetMultiplicativeDepth(); - }) + }, + py::doc("Get the multiplicative depth supported by the crypto parameters (RNS schemes only).")) .def("SetMultiplicativeDepth", [](CryptoContext& 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& 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& 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& 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& 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& self) { return GetParamsRNSChecked(self, "GetPRENumHops")->GetPRENumHops(); - }) + }, + py::doc("Get the number of hops supported for proxy re-encryption (RNS schemes only).")) .def("SetPRENumHops", [](CryptoContext& 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& 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& self) { return GetParamsRNSChecked(self, "GetCompositeDegree")->GetCompositeDegree(); - }) + }, + py::doc("Get the composite degree used by CKKS composite scaling (RNS schemes only).")) .def("GetKeySwitchTechnique", [](CryptoContext& self) { return GetParamsRNSChecked(self, "GetKeySwitchTechnique")->GetKeySwitchTechnique(); - }) + }, + py::doc("Get the key switching technique (BV or HYBRID; RNS schemes only).")) .def("Enable", py::overload_cast(&CryptoContextImpl::Enable), py::arg("feature"), py::doc(cc_Enable_docs)) .def("KeyGen", &CryptoContextImpl::KeyGen, cc_KeyGen_docs) + .def("SparseKeyGen", &CC::SparseKeyGen, + py::doc(cc_SparseKeyGen_docs)) .def("EvalMultKeyGen", &CryptoContextImpl::EvalMultKeyGen, py::arg("privateKey"), py::doc(cc_EvalMultKeyGen_docs)) @@ -514,7 +669,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 @@ -550,7 +705,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"), @@ -641,6 +796,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"), + 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"), @@ -654,7 +819,7 @@ void bind_crypto_context(py::module &m) { .def("EvalAddMutable", py::overload_cast&>(&CryptoContextImpl::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::EvalAddMutableInPlace, py::arg("ciphertext1"), py::arg("ciphertext2"), @@ -673,7 +838,8 @@ void bind_crypto_context(py::module &m) { py::overload_cast&>(&CryptoContextImpl::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::EvalSubMutableInPlace, py::arg("ciphertext1"), py::arg("ciphertext2"), @@ -692,7 +858,7 @@ void bind_crypto_context(py::module &m) { py::overload_cast&>(&CryptoContextImpl::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::EvalMultMutableInPlace, py::arg("ciphertext1"), py::arg("ciphertext2"), @@ -710,6 +876,17 @@ 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"), + 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::doc(cc_EvalMultNoCheck_docs)) + .def("EvalAddInPlaceNoCheck", &CC::EvalAddInPlaceNoCheck, + py::arg("ciphertext1"), py::arg("ciphertext2"), + py::doc(cc_EvalAddInPlaceNoCheck_docs)) .def("Relinearize", &CryptoContextImpl::Relinearize, py::arg("ciphertext"), py::doc(cc_Relinearize_docs)) @@ -720,6 +897,12 @@ 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, + py::doc(cc_LevelReduce_docs)) + .def("LevelReduceInPlace", &CC::LevelReduceInPlace, + 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)) @@ -841,6 +1024,12 @@ 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::doc(cc_ShareKeys_docs)) + .def("RecoverSharedKey", &CC::RecoverSharedKey, + 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"), @@ -859,6 +1048,9 @@ 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") = "", + py::doc(cc_MultiEvalAutomorphismKeyGen_docs)) .def("MultiEvalSumKeyGen", &CryptoContextImpl::MultiEvalSumKeyGen, py::arg("privateKey"), py::arg("evalKeyMap"), @@ -907,14 +1099,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"), @@ -984,11 +1168,19 @@ 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, + 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::doc(cc_EvalBootstrapStCFirst_docs)) + .def_static("ClearStaticMapsAndVectors", &CryptoContextImpl::ClearStaticMapsAndVectors, + py::doc("Clear various static caches within the library (evaluation-key maps and the " + "context registry).")) .def("EvalCKKStoFHEWSetup", &CryptoContextImpl::EvalCKKStoFHEWSetup, py::arg("schswchparams"), py::doc(cc_EvalCKKStoFHEWSetup_docs)) @@ -1028,6 +1220,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"), + 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"), @@ -1080,21 +1280,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"), @@ -1103,13 +1288,23 @@ void bind_crypto_context(py::module &m) { .def("Compress", &CryptoContextImpl::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::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::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::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& self, int32_t idx) { return self.FindAutomorphismIndex(static_cast(idx)); }, @@ -1127,7 +1322,8 @@ void bind_crypto_context(py::module &m) { }, py::arg("keyTag"), py::doc(cc_GetEvalSumKeyMap_docs)) - .def("GetBinCCForSchemeSwitch", &CryptoContextImpl::GetBinCCForSchemeSwitch) + .def("GetBinCCForSchemeSwitch", &CryptoContextImpl::GetBinCCForSchemeSwitch, + py::doc("Get the BinFHE context used for CKKS <-> FHEW scheme switching.")) .def_static("InsertEvalSumKey", &CryptoContextImpl::InsertEvalSumKey, py::arg("evalKeyMap"), py::arg("keyTag") = "", @@ -1159,8 +1355,96 @@ 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; + }, + 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::doc(cc_GetEvalAutomorphismNoKeyIndices_docs)) + .def_static("GetUniqueValues", &CC::GetUniqueValues, + 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, + 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); + 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") = "", + 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); + 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") = "", + 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) { + 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"), + 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); + 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"), + 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::SerializeEvalMultKey(outfile, sertype, keyTag); outfile.close(); return res; @@ -1171,6 +1455,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::SerializeEvalMultKey(outfile, sertype, keyTag); outfile.close(); return res; @@ -1181,6 +1469,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::SerializeEvalAutomorphismKey(outfile, sertype, keyTag); outfile.close(); return res; @@ -1191,6 +1483,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::SerializeEvalAutomorphismKey(outfile, sertype, keyTag); outfile.close(); return res; @@ -1199,13 +1495,31 @@ 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"), + 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()) { + std::cerr << "I cannot read serialization from " << filename << std::endl; + return false; + } + return CC::DeserializeEvalSumKey(infile, 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()) { 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"), @@ -1214,9 +1528,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"), @@ -1225,9 +1539,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"), @@ -1236,14 +1550,16 @@ 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"), 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); @@ -1273,17 +1589,26 @@ void bind_crypto_context(py::module &m) { // Generator Functions m.def("GenCryptoContext", &GenCryptoContext, - py::arg("params")); + py::arg("params"), + py::doc("Generate a BFV RNS crypto context from the given CCParamsBFVRNS parameters.")); m.def("GenCryptoContext", &GenCryptoContext, - py::arg("params")); + py::arg("params"), + py::doc("Generate a BGV RNS crypto context from the given CCParamsBGVRNS parameters.")); m.def("GenCryptoContext", &GenCryptoContext, - py::arg("params")); + py::arg("params"), + py::doc("Generate a CKKS RNS crypto context from the given CCParamsCKKSRNS parameters.")); - m.def("GetAllContexts", &CryptoContextFactory::GetAllContexts); + m.def("GetAllContexts", &CryptoContextFactory::GetAllContexts, + py::doc("Return all crypto contexts registered in the library-wide context registry.")); - m.def("ReleaseAllContexts", &CryptoContextFactory::ReleaseAllContexts); + m.def("ReleaseAllContexts", &CryptoContextFactory::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(&CryptoContextImpl::ClearEvalMultKeys)); + m.def("ClearEvalMultKeys", static_cast(&CryptoContextImpl::ClearEvalMultKeys), + py::doc("Clear the entire static EvalMult key cache.")); + m.def("ClearEvalSumKeys", static_cast(&CryptoContextImpl::ClearEvalSumKeys), + py::doc("Clear the entire static EvalSum key cache.")); } int get_native_int() { @@ -1303,11 +1628,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") @@ -1318,25 +1640,28 @@ 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) + .export_values(); // Serialization Types - py::class_(m, "SERJSON"); - py::class_(m, "SERBINARY"); + py::class_(m, "SERJSON", + "Serialization format tag for human-readable JSON; pass JSON (its instance) as the " + "sertype argument of the (de)serialization functions."); + py::class_(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); @@ -1349,100 +1674,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 @@ -1453,38 +1753,54 @@ 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); + 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_, std::shared_ptr>>(m, "PublicKey") + py::class_, std::shared_ptr>>(m, "PublicKey", + "Public encryption key of a DCRTPoly-based scheme, generated by CryptoContext.KeyGen().") .def(py::init<>()) - .def("GetKeyTag", &PublicKeyImpl::GetKeyTag) - .def("SetKeyTag", &PublicKeyImpl::SetKeyTag); - py::class_, std::shared_ptr>>(m, "PrivateKey") + .def("GetCryptoContext", &PublicKeyImpl::GetCryptoContext, + py::doc(key_GetCryptoContext_docs)) + .def("GetKeyTag", &PublicKeyImpl::GetKeyTag, + py::doc("Get the tag identifying the secret key this public key belongs to.")) + .def("SetKeyTag", &PublicKeyImpl::SetKeyTag, py::arg("keyTag"), + py::doc("Set the tag identifying the secret key this public key belongs to.")); + py::class_, std::shared_ptr>>(m, "PrivateKey", + "Secret decryption key of a DCRTPoly-based scheme, generated by CryptoContext.KeyGen().") .def(py::init<>()) - .def("GetCryptoContext", &PrivateKeyImpl::GetCryptoContext) - .def("GetKeyTag", &PrivateKeyImpl::GetKeyTag) - .def("SetKeyTag", &PrivateKeyImpl::SetKeyTag); - py::class_>(m, "KeyPair") - .def_readwrite("publicKey", &KeyPair::publicKey) - .def_readwrite("secretKey", &KeyPair::secretKey) + // a key created without a CryptoContext cannot be passed to RecoverSharedKey() + .def(py::init&>(), py::arg("cryptoContext")) + .def("GetCryptoContext", &PrivateKeyImpl::GetCryptoContext, + py::doc("Get the CryptoContext this key was generated for.")) + .def("GetKeyTag", &PrivateKeyImpl::GetKeyTag, + py::doc("Get the tag identifying this secret key.")) + .def("SetKeyTag", &PrivateKeyImpl::SetKeyTag, py::arg("keyTag"), + py::doc("Set the tag identifying this secret key.")); + py::class_>(m, "KeyPair", + "Pair of a public key and the matching secret key, as returned by CryptoContext.KeyGen().") + .def_readwrite("publicKey", &KeyPair::publicKey, + "The public key of the pair.") + .def_readwrite("secretKey", &KeyPair::secretKey, + "The secret key of the pair.") .def("good", &KeyPair::good,kp_good_docs); - py::class_, std::shared_ptr>>(m, "EvalKey") + py::class_, std::shared_ptr>>(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::GetKeyTag) - .def("SetKeyTag", &EvalKeyImpl::SetKeyTag); - py::class_>, std::shared_ptr>>>(m, "EvalKeyMap") + .def("GetKeyTag", &EvalKeyImpl::GetKeyTag, + py::doc("Get the tag identifying the secret key this evaluation key belongs to.")) + .def("SetKeyTag", &EvalKeyImpl::SetKeyTag, py::arg("keyTag"), + py::doc("Set the tag identifying the secret key this evaluation key belongs to.")); + py::class_>, std::shared_ptr>>>(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<>()); } @@ -1567,12 +1883,47 @@ class PlaintextImpl_helper : public PlaintextImpl { }; void bind_encodings(py::module &m) { - py::class_, PlaintextImpl_helper>(m, "Plaintext") + py::class_(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, + py::doc(encparams_GetBatchSize_docs)) + .def("GetPlaintextGenerator", &EncodingParamsImpl::GetPlaintextGenerator, + py::doc(encparams_GetPlaintextGenerator_docs)); + + py::class_, 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()); + }, + py::doc(ptx_GetScalingFactorInt_docs)) + .def("SetScalingFactorInt", [](PlaintextImpl& self, const py::int_& sf) { + self.SetScalingFactorInt(openfhe_python::PyIntToInteger(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, + 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"), @@ -1581,28 +1932,44 @@ 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, 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) - .def("GetNoiseScaleDeg", &PlaintextImpl::GetNoiseScaleDeg) - .def("SetNoiseScaleDeg", &PlaintextImpl::SetNoiseScaleDeg) - .def("GetSlots", &PlaintextImpl::GetSlots) - .def("SetSlots", &PlaintextImpl::SetSlots) - .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) - .def("SetIntVectorValue", &PlaintextImpl::SetIntVectorValue) - .def("GetFormattedValues", &PlaintextImpl::GetFormattedValues) + .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 << ""; @@ -1616,12 +1983,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) @@ -1629,97 +2000,166 @@ 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, + py::doc(ctx_NumberCiphertextElements_docs)) .def("RemoveElement", [](Ciphertext<DCRTPoly>& self, uint32_t index) { self->GetElements().erase(self->GetElements().begin() + index); }, py::arg("index"), py::doc(cc_RemoveElement_docs)) - // .def("GetHopLevel", &CiphertextImpl<DCRTPoly>::GetHopLevel) - // .def("SetHopLevel", &CiphertextImpl<DCRTPoly>::SetHopLevel) - .def("GetScalingFactor", &CiphertextImpl<DCRTPoly>::GetScalingFactor) - .def("SetScalingFactor", &CiphertextImpl<DCRTPoly>::SetScalingFactor) - .def("GetSlots", &CiphertextImpl<DCRTPoly>::GetSlots) - .def("SetSlots", &CiphertextImpl<DCRTPoly>::SetSlots) - .def("GetNoiseScaleDeg", &CiphertextImpl<DCRTPoly>::GetNoiseScaleDeg) - .def("SetNoiseScaleDeg", &CiphertextImpl<DCRTPoly>::SetNoiseScaleDeg) - .def("GetCryptoContext", &CiphertextImpl<DCRTPoly>::GetCryptoContext) - .def("GetKeyTag", &CiphertextImpl<DCRTPoly>::GetKeyTag) - .def("GetEncodingType", &CiphertextImpl<DCRTPoly>::GetEncodingType) + .def("GetHopLevel", &CiphertextImpl<DCRTPoly>::GetHopLevel, + py::doc(ctx_GetHopLevel_docs)) + .def("SetHopLevel", &CiphertextImpl<DCRTPoly>::SetHopLevel, py::arg("hopLevel"), + py::doc(ctx_SetHopLevel_docs)) + .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()); + }, + py::doc(ctx_GetScalingFactorInt_docs)) + .def("SetScalingFactorInt", [](CiphertextImpl<DCRTPoly>& self, const py::int_& sf) { + self.SetScalingFactorInt(openfhe_python::PyIntToInteger<NativeInteger>(sf)); + }, py::arg("sf"), + py::doc(ctx_SetScalingFactorInt_docs)) + .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"), + py::doc(ctx_SetKeyTag_docs)) .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::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)); - }); -} - -void bind_schemes(py::module &m) { - // Bind schemes specific functionalities like bootstrapping functions and multiparty - py::class_<FHECKKSRNS>(m, "FHECKKSRNS") - .def(py::init<>()) - .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")) - .def_static("GetBootstrapDepth", - py::overload_cast<const std::vector<uint32_t>&, SecretKeyDist>(&FHECKKSRNS::GetBootstrapDepth), - py::arg("levelBudget"), - py::arg("keyDist")) - ; + }, + 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) - .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("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) { std::stringstream stream; stream << params; @@ -1729,9 +2169,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) { @@ -1739,24 +2179,37 @@ 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 + #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); 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<CryptoContextBFVRNS>(m,"CCParamsBFVRNS"); @@ -1767,8 +2220,8 @@ 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); bind_free_functions(m); } 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 eb141b5..cdf6148 100644 --- a/src/lib/binfhe_bindings.cpp +++ b/src/lib/binfhe_bindings.cpp @@ -29,16 +29,21 @@ // 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 <pybind11/stl.h> #include <pybind11/operators.h> -#include "cereal/archives/binary.hpp" +#include <map> +#include <string> +#include <tuple> using namespace lbcrypto; namespace py = pybind11; @@ -68,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) @@ -130,38 +104,108 @@ 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); - 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); + .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) + .export_values(); } void bind_binfhe_keys(py::module &m) { - py::class_<LWEPrivateKeyImpl, std::shared_ptr<LWEPrivateKeyImpl>>(m, "LWEPrivateKey") - .def(py::init<>()) - .def("GetLength", &LWEPrivateKeyImpl::GetLength) + // 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", + "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()); + }, + py::doc(binfhe_LWEPrivateKeyGetModulus_docs)) + .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", + "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()); + }, + py::doc(binfhe_LWEPublicKeyGetModulus_docs)) .def(py::self == py::self) .def(py::self != py::self); + + 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", + "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", + "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", - [](LWECiphertext& self) { - return self->GetModulus().ConvertToInt<uint64_t>(); - }) + [](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()); + }, + py::doc(binfhe_LWECiphertextGetptModulus_docs)) + .def("SetModulus", + [](LWECiphertextImpl& self, const py::int_& modulus) { + self.SetModulus(openfhe_python::PyIntToInteger<NativeInteger>(modulus)); + }, + py::arg("modulus"), + py::doc(binfhe_LWECiphertextSetModulus_docs)) + .def("SetptModulus", + [](LWECiphertextImpl& self, const py::int_& plaintextModulus) { + self.SetptModulus(openfhe_python::PyIntToInteger<NativeInteger>(plaintextModulus)); + }, + py::arg("plaintextModulus"), + py::doc(binfhe_LWECiphertextSetptModulus_docs)) .def(py::self == py::self) .def(py::self != py::self); } 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), @@ -181,12 +225,26 @@ 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"), + py::doc(binfhe_KeySwitchGen_docs)) + .def("PubKeyGen", &BinFHEContext::PubKeyGen, + py::arg("sk"), + py::doc(binfhe_PubKeyGen_docs)) + .def("SwitchCTtoqn", &BinFHEContext::SwitchCTtoqn, + py::arg("ksk"), + py::arg("ct"), + py::doc(binfhe_SwitchCTtoqn_docs)) .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 +255,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_EncryptPubKey_docs)) .def("Decrypt", [](BinFHEContext& self, ConstLWEPrivateKey sk, ConstLWECiphertext ct, LWEPlaintextModulus p) { LWEPlaintext result; @@ -225,19 +293,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")) @@ -265,23 +337,110 @@ void bind_binfhe_context(py::module &m) { binfhe_EvalSign_docs, py::arg("ct"), py::arg("schemeSwitch") = false) - .def("EvalConstant", &BinFHEContext::EvalConstant) - .def("ClearBTKeys", &BinFHEContext::ClearBTKeys) - .def("Bootstrap", &BinFHEContext::Bootstrap, py::arg("ct"), py::arg("extended") = false) + .def("EvalConstant", &BinFHEContext::EvalConstant, + 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, + 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, 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>) - .def("GetPublicKey", &BinFHEContext::GetPublicKey) - .def("GetSwitchKey", &BinFHEContext::GetSwitchKey) - .def("GetRefreshKey", &BinFHEContext::GetRefreshKey) - .def("GetBinFHEScheme", &BinFHEContext::GetBinFHEScheme) - .def("GetLWEScheme", &BinFHEContext::GetLWEScheme) - .def("GetParams", &BinFHEContext::GetParams); + // 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, + py::doc("Get the LWE public key held by the context, populated only by BTKeyGen with PUB_ENCRYPT " + "(PubKeyGen returns a key without storing it in the context, leaving this None).")) + .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>(); + }, + 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::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::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 + +template <typename T, typename ST> +static std::tuple<T, bool> BinFHEDeserializeFromFileWrapper(const std::string& filename, const ST& sertype) { + T newob; + bool result = Serial::DeserializeFromFile<T>(filename, newob, sertype); + return std::make_tuple(newob, result); +} + +template <typename ST> +static void bind_binfhe_serialization_for_sertype(py::module &m) { + m.def("SerializeToFile", + static_cast<bool (*)(const std::string&, const BinFHEContext&, const ST&)>( + &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::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::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::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::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::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) { + bind_binfhe_serialization_for_sertype<SerType::SERJSON>(m); + bind_binfhe_serialization_for_sertype<SerType::SERBINARY>(m); } diff --git a/src/lib/pke/fbt.cpp b/src/lib/pke/fbt.cpp new file mode 100644 index 0000000..0f3222b --- /dev/null +++ b/src/lib/pke/fbt.cpp @@ -0,0 +1,412 @@ +//================================================================================== +// 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 "docstrings/fbt_docs.h" +#include "integer_conversions.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 <pybind11/complex.h> +#include <pybind11/functional.h> +#include <pybind11/stl.h> + +#include <complex> +#include <cstdint> +#include <functional> +#include <memory> +#include <string> +#include <vector> + +using namespace lbcrypto; +using CC = CryptoContextImpl<DCRTPoly>; +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<Poly>); + +using ElementParams = ILDCRTParams<DCRTPoly::Integer>; + +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 + // 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<uint32_t, const std::vector<uint32_t>&, SecretKeyDist>(&FHECKKSRNS::GetBootstrapDepth), + 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::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) { + return FHECKKSRNS::GetFBTDepth( + levelBudget, coefficients, openfhe_python::PyIntToInteger<BigInteger>(PInput), order, skd, + firstModSize); + }, + py::arg("levelBudget"), py::arg("coefficients"), py::arg("PInput"), py::arg("order"), py::arg("skd"), + 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) { + return FHECKKSRNS::GetFBTDepth( + levelBudget, coefficients, openfhe_python::PyIntToInteger<BigInteger>(PInput), order, skd, + firstModSize); + }, + py::arg("levelBudget"), py::arg("coefficients"), py::arg("PInput"), py::arg("order"), py::arg("skd"), + 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::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, + const std::vector<uint32_t>& dim1, const std::vector<uint32_t>& levelBudget, uint32_t lvlsAfterBoot, + uint32_t depthLeveledComputation, size_t order) { + self.EvalFBTSetup(coeffs, numSlots, openfhe_python::PyIntToInteger<BigInteger>(PIn), + openfhe_python::PyIntToInteger<BigInteger>(POut), + openfhe_python::PyIntToInteger<BigInteger>(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, + 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, + const std::vector<uint32_t>& dim1, const std::vector<uint32_t>& levelBudget, uint32_t lvlsAfterBoot, + uint32_t depthLeveledComputation, size_t order) { + self.EvalFBTSetup(coeffs, numSlots, openfhe_python::PyIntToInteger<BigInteger>(PIn), + openfhe_python::PyIntToInteger<BigInteger>(POut), + openfhe_python::PyIntToInteger<BigInteger>(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, + 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, + size_t order) { + return self.EvalFBT(ciphertext, coeffs, digitBitSize, + openfhe_python::PyIntToInteger<BigInteger>(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, + 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, + size_t order) { + return self.EvalFBT(ciphertext, coeffs, digitBitSize, + openfhe_python::PyIntToInteger<BigInteger>(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, + 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) { + return self.EvalFBTNoDecoding(ciphertext, coeffs, digitBitSize, + 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::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) { + return self.EvalFBTNoDecoding(ciphertext, coeffs, digitBitSize, + 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::doc(cc_EvalFBTNoDecoding_docs)) + .def("EvalHomDecoding", &CryptoContextImpl<DCRTPoly>::EvalHomDecoding, + 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) { + return self.EvalMVBPrecompute(ciphertext, coeffs, digitBitSize, + 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::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) { + return self.EvalMVBPrecompute(ciphertext, coeffs, digitBitSize, + 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::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::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::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::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::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, + py::doc(cc_EvalHermiteTrigSeries_docs)) + .def("EvalHermiteTrigSeries", &CryptoContextImpl<DCRTPoly>::EvalHermiteTrigSeries<std::complex<double>>, + py::arg("ciphertext"), py::arg("coefficientsCheb"), py::arg("a"), py::arg("b"), + py::arg("coefficientsHerm"), py::arg("precomp") = (size_t)0, + py::doc(cc_EvalHermiteTrigSeries_docs)) + // FE functional bootstrapping + .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::doc(cc_EvalFEFuncBootstrapSetup_docs)) + .def("EvalFEFuncBootstrap", &CryptoContextImpl<DCRTPoly>::EvalFEFuncBootstrap, + py::arg("ciphertext"), py::arg("coefficients"), + py::doc(cc_EvalFEFuncBootstrap_docs)) + .def("EvalFEFuncBootstrapPrecompute", &CryptoContextImpl<DCRTPoly>::EvalFEFuncBootstrapPrecompute, + py::arg("ciphertext"), py::arg("coefficients"), + py::doc(cc_EvalFEFuncBootstrapPrecompute_docs)) + .def("EvalFEFuncBootstrapWithPrecomp", &CryptoContextImpl<DCRTPoly>::EvalFEFuncBootstrapWithPrecomp, + 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", + fbt_SeriesPowers_docs); + + // opaque holder for an RLWE ciphertext (a vector of Poly) used by SchemeletRLWEMP + py::class_<std::vector<Poly>>(m, "PolyVector", fbt_PolyVector_docs) + .def(py::init<>()) + .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) { + auto mod = openfhe_python::PyIntToInteger<BigInteger>(modulus); + for (auto& poly : self) + poly.SwitchModulus(mod, 1, 0, 0); + }, + 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) { + if (self.size() != other.size()) + OPENFHE_THROW("PolyVector sizes do not match"); + std::vector<Poly> result(self); + 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::doc(fbt_GetMultiplicativeDepthByCoeffVector_docs)); + m.def("GetMultiplicativeDepthByCoeffVector", &GetMultiplicativeDepthByCoeffVector<std::complex<double>>, + 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::doc(fbt_GetHermiteTrigCoefficients_docs)); + + // Conversions between an RLWE scheme and CKKS sharing the same secret key + py::class_<SchemeletRLWEMP>(m, "SchemeletRLWEMP", fbt_SchemeletRLWEMP_docs) + .def_static("GetElementParams", + [](const PrivateKey<DCRTPoly>& privateKey, uint32_t level) { + // the key is dereferenced in C++, so a None argument would segfault without this check + if (!privateKey) + OPENFHE_THROW("privateKey is nullptr"); + return SchemeletRLWEMP::GetElementParams(privateKey, level); + }, + 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, + bool bitReverse) { + // both pointers are dereferenced in C++, so None arguments would segfault without these checks + if (!privateKey) + OPENFHE_THROW("privateKey is nullptr"); + if (!elementParams) + OPENFHE_THROW("elementParams is nullptr"); + return SchemeletRLWEMP::EncryptCoeff(input, openfhe_python::PyIntToInteger<BigInteger>(Q), + openfhe_python::PyIntToInteger<BigInteger>(p), privateKey, + elementParams, bitReverse); + }, + py::arg("input"), py::arg("Q"), py::arg("p"), py::arg("privateKey"), py::arg("elementParams"), + 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, + uint32_t numSlots, uint32_t length, bool bitReverse) { + // the pointers are dereferenced and input[0]/input[1] are indexed in C++, + // so None arguments or a short vector would segfault without these checks + if (!privateKey) + OPENFHE_THROW("privateKey is nullptr"); + if (!elementParams) + OPENFHE_THROW("elementParams is nullptr"); + if (input.size() < 2) + OPENFHE_THROW("input must hold the 2 RLWE ciphertext polynomials {b, a}"); + return SchemeletRLWEMP::DecryptCoeff(input, openfhe_python::PyIntToInteger<BigInteger>(Q), + openfhe_python::PyIntToInteger<BigInteger>(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, + py::doc(schemelet_DecryptCoeff_docs)) + .def_static("ModSwitch", + [](std::vector<Poly>& input, const py::int_& Q1, const py::int_& Q2) { + // the C++ implementation indexes input[0] and input[1], so a short vector + // would segfault without this check + if (input.size() < 2) + OPENFHE_THROW("input must hold the 2 RLWE ciphertext polynomials {b, a}"); + SchemeletRLWEMP::ModSwitch(input, openfhe_python::PyIntToInteger<BigInteger>(Q1), + openfhe_python::PyIntToInteger<BigInteger>(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) { + // cc is dereferenced and coeffs[0]/coeffs[1] are indexed in C++, so a None + // context or a short vector would segfault without these checks + if (!cc) + OPENFHE_THROW("cc is nullptr"); + if (coeffs.size() < 2) + OPENFHE_THROW("coeffs must hold the 2 RLWE ciphertext polynomials {b, a}"); + return SchemeletRLWEMP::ConvertRLWEToCKKS( + *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::doc(schemelet_ConvertRLWEToCKKS_docs)) + .def_static("ConvertCKKSToRLWE", + [](ConstCiphertext<DCRTPoly> ctxt, const py::int_& Q) { + // the ciphertext is dereferenced in C++, so a None argument would segfault without this check + if (!ctxt) + OPENFHE_THROW("ctxt is nullptr"); + return SchemeletRLWEMP::ConvertCKKSToRLWE( + ctxt, openfhe_python::PyIntToInteger<BigInteger>(Q)); + }, + py::arg("ctxt"), py::arg("Q"), + py::doc(schemelet_ConvertCKKSToRLWE_docs)) + .def_static("GetQPrime", + [](const PublicKey<DCRTPoly>& pubKey, uint32_t lvls) { + // the key is dereferenced in C++, so a None argument would segfault without this check + if (!pubKey) + OPENFHE_THROW("pubKey is nullptr"); + return openfhe_python::IntegerToPyInt(SchemeletRLWEMP::GetQPrime(pubKey, 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", + 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, + 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"), + 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", + fbt_SchemeSwitchingDataDeserializer_docs) + .def(py::init<>()) + .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/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 <cstdint> +#include <fstream> +#include <string> +#include <vector> + +#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<uint32_t> 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<uint32_t> 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..87b0b1a 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 @@ -39,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; @@ -48,29 +49,38 @@ PYBIND11_MAKE_OPAQUE(std::map<uint32_t, EvalKey<DCRTPoly>>); template <typename ST> -bool SerializeEvalMultKeyWrapper(const std::string& filename, const ST& sertype, std::string keyTag) { +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<DCRTPoly>::SerializeEvalMultKey<ST>(outfile, sertype, keyTag); + 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; } 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; +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<DCRTPoly>::DeserializeEvalBootstrapKey(keyfile, sertype, cc, keyTag, slots); } 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()) { +bool DeserializeEvalBootstrapKeyByIndexWrapper(const std::string& filename, const ST& sertype, + const std::string& keyTag, const std::vector<uint32_t>& 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; } - bool res = CryptoContextImpl<DCRTPoly>::DeserializeEvalMultKey<ST>(emkeys, sertype); - return res; + return CryptoContextImpl<DCRTPoly>::DeserializeEvalBootstrapKey(keyfile, sertype, keyTag, indexList); } template <typename T, typename ST> @@ -229,133 +239,211 @@ 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::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::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::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::doc(serial_DeserializeEvalBootstrapKey_docs)); + m.def("DeserializeEvalBootstrapKey", &DeserializeEvalBootstrapKeyByIndexWrapper<SerType::SERJSON>, + 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::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)); } diff --git a/tests/test_api_coverage.py b/tests/test_api_coverage.py new file mode 100644 index 0000000..5f34f59 --- /dev/null +++ b/tests/test_api_coverage.py @@ -0,0 +1,445 @@ +import re +from pathlib import Path + +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) + + # 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, unusable_path): + """An unopenable file returns False instead of surfacing a cereal stream error.""" + cc, keys = ckks_context + + 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, unusable_path): + """An unopenable file returns False instead of deserializing from a closed stream.""" + assert fhe.CryptoContext.DeserializeEvalMultKey(unusable_path, sertype) is False + assert fhe.CryptoContext.DeserializeEvalAutomorphismKey(unusable_path, 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_schemelet_null_arguments_raise_instead_of_crashing(ckks_context): + """A None key/params/ciphertext, or an empty vector where C++ indexes the two RLWE + polynomials, used to be dereferenced in C++ and segfault.""" + cc, keys = ckks_context + params = fhe.SchemeletRLWEMP.GetElementParams(keys.secretKey) + + with pytest.raises(RuntimeError): + fhe.SchemeletRLWEMP.DecryptCoeff(fhe.PolyVector(), 65537, 17, keys.secretKey, params, 8) + with pytest.raises(RuntimeError): + fhe.SchemeletRLWEMP.ConvertRLWEToCKKS(cc, fhe.PolyVector(), keys.publicKey, 1, 1, 0) + + with pytest.raises(RuntimeError): + fhe.SchemeletRLWEMP.GetElementParams(None) + with pytest.raises(RuntimeError): + fhe.SchemeletRLWEMP.EncryptCoeff([1], 65537, 17, None, params) + with pytest.raises(RuntimeError): + fhe.SchemeletRLWEMP.EncryptCoeff([1], 65537, 17, keys.secretKey, None) + with pytest.raises(RuntimeError): + fhe.SchemeletRLWEMP.DecryptCoeff(fhe.PolyVector(), 65537, 17, None, params, 8) + with pytest.raises(RuntimeError): + fhe.SchemeletRLWEMP.DecryptCoeff(fhe.PolyVector(), 65537, 17, keys.secretKey, None, 8) + with pytest.raises(RuntimeError): + fhe.SchemeletRLWEMP.ModSwitch(fhe.PolyVector(), 65537, 17) + with pytest.raises(RuntimeError): + fhe.SchemeletRLWEMP.ConvertCKKSToRLWE(None, 65537) + with pytest.raises(RuntimeError): + fhe.SchemeletRLWEMP.GetQPrime(None, 1) + + +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]) + native_bits = fhe.get_native_int() + native_max = (1 << native_bits) - 1 + + with pytest.raises(ValueError): + fhe.PolyVector().SwitchModulus(-1) + # 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) + + 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) + + # The fixed-size BigInteger backend (3500/8000 bits) must report overflow as + # OverflowError like the native path, not RuntimeError; the dynamic backend + # has no fixed capacity and accepts any size. + try: + fhe.PolyVector().SwitchModulus(2**8192) + except OverflowError: + pass + + +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() + 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() + + 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) 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 new file mode 100644 index 0000000..9450b9a --- /dev/null +++ b/tests/test_binfhe_api_coverage.py @@ -0,0 +1,148 @@ +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_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.""" + # 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, fhe.GINX) + p = cc.GetMaxPlaintextSpace() + + 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_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, fhe.GINX) + p = cc.GetMaxPlaintextSpace() + 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) + + 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(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(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_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) diff --git a/tests/test_examples.py b/tests/test_examples.py index 8d8093a..d22e05f 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 @@ -11,15 +11,49 @@ 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 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. + """ + 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("-", "_") + 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", "") + # 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( @@ -39,22 +73,109 @@ 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): - 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, +# 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", + [ + 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): + run_example(EXAMPLES_SCRIPTS_PATH, raw_modulename, require_main=True) + + +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): + run_example(BINFHE_EXAMPLES_SCRIPTS_PATH, raw_modulename, require_main=False) + + +def _parametrized_example_names(test_function): + for mark in test_function.pytestmark: + if mark.name == "parametrize" and mark.args[0] == "raw_modulename": + # 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") + + +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