mirror of
https://github.com/JHUAPL/openfhe-python-bindings.git
synced 2026-01-06 21:13:52 -05:00
Initial commit
This commit is contained in:
90
CMakeLists.txt
Normal file
90
CMakeLists.txt
Normal file
@@ -0,0 +1,90 @@
|
||||
#
|
||||
# (c) 2021-2023 The Johns Hopkins University Applied Physics
|
||||
# Laboratory LLC (JHU/APL).
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# 3. Neither the name of the copyright holder nor the names of its
|
||||
# contributors may be used to endorse or promote products derived
|
||||
# from this software without specific prior written permission.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
cmake_minimum_required (VERSION 3.21.1)
|
||||
|
||||
# set the project name
|
||||
project(OpenFHEPythonBindings VERSION 1.0)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
find_package(OpenFHE)
|
||||
|
||||
set (Python_FIND_VIRTUALENV FIRST)
|
||||
|
||||
|
||||
find_package(fmt REQUIRED)
|
||||
find_package (Python3 COMPONENTS Interpreter Development)
|
||||
find_package (Boost COMPONENTS numpy3 REQUIRED)
|
||||
|
||||
set( CMAKE_CXX_FLAGS "${OpenFHE_CXX_FLAGS} -DBOOST_NO_AUTO_PTR -Wno-error=deprecated-declarations -Wno-error=unused-variable -Wno-error" )
|
||||
set( CMAKE_EXE_LINKER_FLAGS ${OpenFHE_EXE_LINKER_FLAGS} )
|
||||
|
||||
message(STATUS "OpenFHE directory: " ${OpenFHE_INCLUDE})
|
||||
|
||||
include_directories(include/)
|
||||
include_directories( ${OPENMP_INCLUDES} )
|
||||
include_directories( ${OpenFHE_INCLUDE} )
|
||||
include_directories( ${OpenFHE_INCLUDE}/third-party/include )
|
||||
include_directories( ${OpenFHE_INCLUDE}/core )
|
||||
include_directories( ${OpenFHE_INCLUDE}/pke )
|
||||
include_directories( ${OpenFHE_INCLUDE}/binfhe )
|
||||
include_directories(/openFHE/fmt/include/)
|
||||
|
||||
include_directories(${Python3_INCLUDE_DIRS})
|
||||
include_directories(${Python3_ROOT_DIR}/include/python3.10)
|
||||
include_directories(${Python3_ROOT_DIR}/include)
|
||||
include_directories(${Boost_INCLUDE_DIRS})
|
||||
|
||||
link_directories( ${Python3_ROOT_DIR}/lib )
|
||||
link_directories( ${OpenFHE_LIBDIR} )
|
||||
link_directories( ${OPENMP_LIBRARIES} )
|
||||
|
||||
link_libraries( ${OpenFHE_SHARED_LIBRARIES} )
|
||||
|
||||
file (GLOB_RECURSE PYTHON_SRC_FILES CONFIGURE_DEPENDS src/*.cpp)
|
||||
|
||||
add_library (pythonobj OBJECT ${PYTHON_SRC_FILES})
|
||||
set_property(TARGET pythonobj PROPERTY POSITION_INDEPENDENT_CODE 1)
|
||||
|
||||
add_library (pyOpenFHE SHARED $<TARGET_OBJECTS:pythonobj>)
|
||||
set_property(TARGET pyOpenFHE PROPERTY VERSION 1)
|
||||
set_property(TARGET pyOpenFHE PROPERTY SOVERSION 1)
|
||||
set_property(TARGET pyOpenFHE PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/)
|
||||
set_property(TARGET pyOpenFHE PROPERTY PREFIX "")
|
||||
install(TARGETS pyOpenFHE DESTINATION lib)
|
||||
|
||||
target_link_directories(pyOpenFHE PUBLIC ${pyOpenFHE_DIR}/lib)
|
||||
|
||||
target_link_libraries (pyOpenFHE PUBLIC ${Boost_LIBRARIES} ${THIRDPARTYLIBS} ${OpenMP_CXX_FLAGS} fmt::fmt)
|
||||
64
Dockerfile
Normal file
64
Dockerfile
Normal file
@@ -0,0 +1,64 @@
|
||||
FROM quay.io/pypa/manylinux2014_x86_64
|
||||
|
||||
RUN yum update -y && yum install -y curl wget
|
||||
|
||||
ENV PATH /opt/python/cp310-cp310/bin:${PATH}
|
||||
ENV CMAKE_MODULE_PATH /opt/python/cp310-cp310/lib/cmake:/usr/local/lib64/cmake/
|
||||
|
||||
#
|
||||
# install dependencies
|
||||
#
|
||||
|
||||
|
||||
RUN yum groupinstall -y 'Development Tools' && \
|
||||
yum install -y autoconf git
|
||||
|
||||
RUN pip install cmake && ln -s /opt/python/cp310-cp310/bin/cmake /usr/bin/cmake
|
||||
|
||||
# install openFHE
|
||||
# Pull from the early-release version with bootstrapping
|
||||
WORKDIR /openFHE
|
||||
|
||||
RUN git clone --recursive \
|
||||
https://github.com/openfheorg/openfhe-development.git/ && \
|
||||
cd openfhe-development && \
|
||||
mkdir openFHE-build && \
|
||||
cd openFHE-build && \
|
||||
cmake ../ && \
|
||||
make -j4 && \
|
||||
make install
|
||||
|
||||
|
||||
# string formatting
|
||||
RUN git clone https://github.com/fmtlib/fmt.git && \
|
||||
cd fmt && \
|
||||
mkdir _build && cd _build && \
|
||||
cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE .. && \
|
||||
make -j4 && \
|
||||
make install
|
||||
|
||||
# Install numpy
|
||||
RUN pip install "numpy<2.0.0"
|
||||
|
||||
# Install boost
|
||||
RUN set -ex; \
|
||||
wget --ca-certificate=/etc/pki/ca-trust/extracted/openssl/ca-bundle.trust.crt \
|
||||
https://boostorg.jfrog.io/artifactory/main/release/1.84.0//source/boost_1_84_0.tar.gz; \
|
||||
tar xzf ./boost_1_84_0.tar.gz; \
|
||||
cd boost_1_84_0; \
|
||||
./bootstrap.sh; \
|
||||
./b2 install --with-python --prefix=/opt/python/cp310-cp310 -j 4
|
||||
|
||||
# openFHE is installed, now build the python packages
|
||||
RUN mkdir openFHE-python
|
||||
COPY . openFHE-python
|
||||
RUN set -ex; \
|
||||
pip install -U ninja wheel setuptools
|
||||
CMD set -ex; \
|
||||
pip wheel -e /openFHE/openFHE-python \
|
||||
-w /wheelhouse/tmp/ \
|
||||
--no-deps; \
|
||||
cd /; \
|
||||
auditwheel repair /wheelhouse/tmp/OpenFHE-*.whl; \
|
||||
chmod -R 777 ./wheelhouse
|
||||
|
||||
31
LICENSE
Normal file
31
LICENSE
Normal file
@@ -0,0 +1,31 @@
|
||||
(c) 2021-2023 The Johns Hopkins University Applied Physics
|
||||
Laboratory LLC (JHU/APL).
|
||||
|
||||
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.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
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.
|
||||
23
LICENSES/Boost/LICENSE
Normal file
23
LICENSES/Boost/LICENSE
Normal file
@@ -0,0 +1,23 @@
|
||||
Boost Software License - Version 1.0 - August 17th, 2003
|
||||
|
||||
Permission is hereby granted, free of charge, to any person or organization
|
||||
obtaining a copy of the software and accompanying documentation covered by
|
||||
this license (the "Software") to use, reproduce, display, distribute,
|
||||
execute, and transmit the Software, and to prepare derivative works of the
|
||||
Software, and to permit third-parties to whom the Software is furnished to
|
||||
do so, all subject to the following:
|
||||
|
||||
The copyright notices in the Software and this entire statement, including
|
||||
the above license grant, this restriction and the following disclaimer,
|
||||
must be included in all copies of the Software, in whole or in part, and
|
||||
all derivative works of the Software, unless such copies or derivative
|
||||
works are solely in the form of machine-executable object code generated by
|
||||
a source language processor.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
30
LICENSES/NumPy/LICENSE
Normal file
30
LICENSES/NumPy/LICENSE
Normal file
@@ -0,0 +1,30 @@
|
||||
Copyright (c) 2005-2024, NumPy Developers.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* 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.
|
||||
|
||||
* Neither the name of the NumPy Developers nor the names of any
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
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
|
||||
OWNER 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.
|
||||
26
LICENSES/OpenFHE/LICENSE
Normal file
26
LICENSES/OpenFHE/LICENSE
Normal file
@@ -0,0 +1,26 @@
|
||||
BSD 2-Clause License
|
||||
|
||||
Copyright (c) 2022, OpenFHE
|
||||
|
||||
All rights reserved.
|
||||
|
||||
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.
|
||||
27
LICENSES/fmt/LICENSE
Normal file
27
LICENSES/fmt/LICENSE
Normal file
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
--- Optional exception to the license ---
|
||||
|
||||
As an exception, if, as a result of your compiling your source code, portions
|
||||
of this Software are embedded into a machine-executable object form of such
|
||||
source code, you may redistribute such embedded portions in such object form
|
||||
without including the above copyright and permission notices.
|
||||
36
README.md
Normal file
36
README.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# OpenFHE Python bindings
|
||||
|
||||
Python bindings for OpenFHE's implementation of CKKS and BGV encryption schemes, including arithmetic operations, bootstraping, and neural network operations. This README has instructions for building a Python wheel file, which can be imported as a Python module for use in Python codebases. These bindings are a dependency for code developed that accompanies the manuscript: [High-Resolution Convolutional Neural Networks on Homomorphically Encrypted Data via Sharding Ciphertexts
|
||||
](https://arxiv.org/abs/2306.09189).
|
||||
|
||||
## Requirements
|
||||
|
||||
Bindings are built as a Python wheel file within a Docker container, so Docker is required to build the bindings. All dependencies, including OpenFHE, Boost, fmt, and numpy, are downloaded and installed when building the Docker image. Running the Docker container only builds bindings for x86 architectures; the build process is untested for other architectures like ARM.
|
||||
|
||||
Code was developed and tested on Ubuntu 20.04. While it should run on Windows platforms as well, this has not been explicitly tested.
|
||||
|
||||
## Build Instructions
|
||||
|
||||
Run the following command to build the Docker image:
|
||||
|
||||
```bash
|
||||
docker build . --tag openfhe-python-build
|
||||
```
|
||||
|
||||
Then run the following to run a container from the image. This will mount a volume and write a Python wheel file to it so that the wheel file is accessible on the host machine.
|
||||
|
||||
```bash
|
||||
docker run -v ${PWD}:/openFHE/openFHE-python -v ${PWD}/wheelhouse:/wheelhouse openfhe-python-build
|
||||
```
|
||||
|
||||
The resulting wheel file can be installed in a Python environment as with any other wheel, for example with `pip` as:
|
||||
|
||||
```bash
|
||||
pip install example_wheel.whl
|
||||
```
|
||||
|
||||
Once installed, the bindings can be imported with `import pyOpenFHE`. See the SHIELD repository for examples on how it is used.
|
||||
|
||||
## Citation and Acknowledgements
|
||||
|
||||
Please cite this work if using it on other projects. In addition to the authors on the supporting manuscript (Vivian Maloney, Freddy Obrect, Vikram Saraph, Prathibha Rama, and Kate Tallaksen), Aaron Pendergrass and Charlie Schneider also made significant contributions to this work.
|
||||
39
examples/ckks/basic_usage.py
Normal file
39
examples/ckks/basic_usage.py
Normal file
@@ -0,0 +1,39 @@
|
||||
import numpy as np
|
||||
from pyOpenFHE import CKKS
|
||||
from pyOpenFHE import enums as pal
|
||||
from math import log2
|
||||
|
||||
np.set_printoptions(formatter={'float': lambda x: "{0:0.3f}".format(x)})
|
||||
|
||||
mult_depth = 5
|
||||
scale_factor_bits = 59
|
||||
batch_size = 16
|
||||
|
||||
# Pass the non-default parameters
|
||||
cc = CKKS.genCryptoContextCKKS(
|
||||
mult_depth,
|
||||
scale_factor_bits,
|
||||
batch_size
|
||||
)
|
||||
|
||||
# enable features
|
||||
cc.enable(pal.PKESchemeFeature.PKE)
|
||||
cc.enable(pal.PKESchemeFeature.KEYSWITCH)
|
||||
cc.enable(pal.PKESchemeFeature.LEVELEDSHE)
|
||||
cc.enable(pal.PKESchemeFeature.ADVANCEDSHE)
|
||||
|
||||
# generate keys
|
||||
keys = cc.keyGen()
|
||||
cc.evalMultKeyGen(keys.secretKey)
|
||||
cc.evalPowerOf2RotationKeyGen(keys.secretKey)
|
||||
|
||||
|
||||
|
||||
x1 = np.random.random(batch_size)
|
||||
x2 = np.random.random(batch_size)
|
||||
|
||||
c1 = cc.encrypt(keys.publicKey, x1)
|
||||
c2 = cc.encrypt(keys.publicKey, x2)
|
||||
|
||||
y1 = cc.decrypt(keys.secretKey, c1)
|
||||
print(y1)
|
||||
53
examples/ckks/bootstrapping_usage.py
Normal file
53
examples/ckks/bootstrapping_usage.py
Normal file
@@ -0,0 +1,53 @@
|
||||
import numpy as np
|
||||
from pyOpenFHE import CKKS
|
||||
from pyOpenFHE import enums as pal
|
||||
from math import log2
|
||||
|
||||
np.set_printoptions(formatter={'float': lambda x: "{0:0.3f}".format(x)})
|
||||
|
||||
mult_depth = 26
|
||||
scale_factor_bits = 59
|
||||
batch_size = 64
|
||||
|
||||
# Pass the non-default parameters
|
||||
cc = CKKS.genCryptoContextCKKS(
|
||||
mult_depth,
|
||||
scale_factor_bits,
|
||||
batch_size,
|
||||
stdLevel=pal.SecurityLevel.HEStd_NotSet, ringDim=128
|
||||
)
|
||||
|
||||
# enable features
|
||||
cc.enable(pal.PKESchemeFeature.PKE)
|
||||
cc.enable(pal.PKESchemeFeature.KEYSWITCH)
|
||||
cc.enable(pal.PKESchemeFeature.LEVELEDSHE)
|
||||
cc.enable(pal.PKESchemeFeature.ADVANCEDSHE)
|
||||
cc.enable(pal.PKESchemeFeature.FHE)
|
||||
|
||||
# generate keys
|
||||
keys = cc.keyGen()
|
||||
cc.evalMultKeyGen(keys.secretKey)
|
||||
cc.evalPowerOf2RotationKeyGen(keys.secretKey)
|
||||
|
||||
cc.evalBootstrapSetup()
|
||||
cc.evalBootstrapKeyGen(keys.secretKey)
|
||||
|
||||
x1 = np.random.random(batch_size)
|
||||
x2 = np.random.random(batch_size)
|
||||
|
||||
c1 = cc.encrypt(keys.publicKey, x1)
|
||||
c2 = cc.encrypt(keys.publicKey, x2)
|
||||
|
||||
for i in range(23):
|
||||
c1 = c1 * 1.0 + 1. - c2
|
||||
|
||||
c3 = c1
|
||||
for j in range(10):
|
||||
c3 = cc.evalBootstrap(c3)
|
||||
for i in range(3):
|
||||
print(c3.getTowersRemaining())
|
||||
c3 = c3 * 1.0 + 1. - c2
|
||||
|
||||
|
||||
y3 = cc.decrypt(keys.secretKey, c3)
|
||||
print(y3)
|
||||
220
include/bgv/BGV_ciphertext_extension.hpp
Normal file
220
include/bgv/BGV_ciphertext_extension.hpp
Normal file
@@ -0,0 +1,220 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
#ifndef BGV_OPENFHE_PYTHON_CIPHERTEXT_H
|
||||
#define BGV_OPENFHE_PYTHON_CIPHERTEXT_H
|
||||
|
||||
#include <complex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/numpy.hpp>
|
||||
|
||||
#include "openfhe.h"
|
||||
|
||||
#include "bgv/BGV_key_operations.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#include "constants.h"
|
||||
#include "encoding/encodingparams.h"
|
||||
#include "scheme/scheme-utils.h"
|
||||
#include "utils/exceptions.hpp"
|
||||
|
||||
using namespace lbcrypto;
|
||||
|
||||
namespace pyOpenFHE_BGV {
|
||||
|
||||
class BGVCryptoContext; // forward declaration so we can declare
|
||||
// getCryptoContext()
|
||||
|
||||
class BGVCiphertext {
|
||||
|
||||
public:
|
||||
// I kind of want to make this private and hide behind a getter but maybe
|
||||
// that's just Java brain
|
||||
Ciphertext<DCRTPoly> cipher;
|
||||
|
||||
// empty constructor
|
||||
BGVCiphertext(){};
|
||||
|
||||
// wrapper constructor
|
||||
BGVCiphertext(Ciphertext<DCRTPoly> cipher) : cipher(cipher){};
|
||||
|
||||
// copy constructor
|
||||
BGVCiphertext(const BGVCiphertext &ctxt) {
|
||||
cipher = Ciphertext<DCRTPoly>(ctxt.cipher);
|
||||
};
|
||||
|
||||
BGVCiphertext array_ufunc(const boost::python::object ufunc,
|
||||
const boost::python::str method,
|
||||
const boost::python::numpy::ndarray &vals,
|
||||
const BGVCiphertext &cipher);
|
||||
|
||||
uint64_t getPlaintextModulus(void) const {
|
||||
return cipher->GetCryptoContext()
|
||||
->GetEncodingParams()
|
||||
->GetPlaintextModulus();
|
||||
};
|
||||
|
||||
BGVCryptoContext getCryptoContext(void) const;
|
||||
|
||||
double getScalingFactor(void) const {
|
||||
// TODO
|
||||
// const auto cryptoParams =
|
||||
// std::dynamic_pointer_cast<CryptoParametersRNS>(cipher->GetCryptoParameters());
|
||||
// double scFactor = cryptoParams->GetScalingFactorInt(cipher->GetLevel());
|
||||
// return scFactor;
|
||||
return cipher->GetScalingFactor();
|
||||
};
|
||||
|
||||
uint64_t getBatchSize(void) const {
|
||||
return cipher->GetCryptoContext()->GetEncodingParams()->GetBatchSize();
|
||||
};
|
||||
|
||||
/*
|
||||
* multiplicative depth performed & remaining.
|
||||
* GetLevel() return holds the number of rescalings performed
|
||||
* before getting this ciphertext - initially 0,
|
||||
* which is backwards for our purposes.
|
||||
*/
|
||||
uint64_t getMultLevel(void) const { return cipher->GetLevel(); };
|
||||
|
||||
uint64_t getTowersRemaining(void) const {
|
||||
const std::vector<DCRTPoly> &cv = cipher->GetElements();
|
||||
usint sizeQl = cv[0].GetNumOfElements();
|
||||
return sizeQl;
|
||||
}
|
||||
|
||||
// TODO broken
|
||||
// uint64_t getMultsRemaining(void) const {
|
||||
// CryptoContext<DCRTPoly> cc = cipher->GetCryptoContext();
|
||||
|
||||
// std::cout << "BGV scheme is using ring dimension = " <<
|
||||
// cc->GetRingDimension() << std::endl; std::cout << "batch size = " <<
|
||||
// cc->GetEncodingParams()->GetBatchSize() << std::endl; std::cout <<
|
||||
// "mult depth = " << cc->GetEncodingParams()->GetMultDepth() <<
|
||||
// std::endl;
|
||||
|
||||
// // uint64_t mults_used = cipher->GetLevel();
|
||||
// // return max_depth - mults_used;
|
||||
|
||||
// return 0;
|
||||
// }
|
||||
|
||||
// an alternative to rescale?
|
||||
// lowers TowersRemaining to towersLeft
|
||||
// I don't really know what happens if towersLeft >= TowersRemaining so I'll
|
||||
// just not do that
|
||||
BGVCiphertext compress(size_t towersLeft) const {
|
||||
if (getTowersRemaining() <= towersLeft) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("Cannot compress to {} towers, towers remaining = {}",
|
||||
towersLeft, getTowersRemaining()));
|
||||
}
|
||||
auto cipher2 =
|
||||
cipher->GetCryptoContext()->GetScheme()->Compress(cipher, towersLeft);
|
||||
return BGVCiphertext(cipher2);
|
||||
}
|
||||
|
||||
// TODO broken
|
||||
BGVCiphertext rescale(size_t levels = 1) const {
|
||||
if (getTowersRemaining() <= 1 + levels) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("Insufficient number of towers remaining = {} to perform "
|
||||
"{} rescalings",
|
||||
getTowersRemaining(), levels));
|
||||
}
|
||||
// ModReduce will only work if ScalingTechnique is FIXEDMANUAL
|
||||
// default technique is FLEXIBLEAUTOEXT, which is useful
|
||||
// so this won't work
|
||||
// auto algo = cipher->GetCryptoContext()->GetScheme();
|
||||
// auto cipher2 = algo->ModReduce(cipher, levels);
|
||||
// compression *may* do what we want?
|
||||
// but it has to be inverted
|
||||
auto cipher2 = compress(getTowersRemaining() - levels);
|
||||
return BGVCiphertext(cipher2);
|
||||
}
|
||||
};
|
||||
|
||||
// a whole load of operators
|
||||
// we need to specify ALL of these, and then specify them again in the
|
||||
// bindings...
|
||||
|
||||
bool operator==(const BGVCiphertext &c1, const BGVCiphertext &c2);
|
||||
bool operator!=(const BGVCiphertext &c1, const BGVCiphertext &c2);
|
||||
BGVCiphertext operator+=(BGVCiphertext &c1, const BGVCiphertext &c2);
|
||||
BGVCiphertext operator+(BGVCiphertext c1, const BGVCiphertext &c2);
|
||||
BGVCiphertext operator-=(BGVCiphertext &c1, const BGVCiphertext &c2);
|
||||
BGVCiphertext operator-(BGVCiphertext c1, const BGVCiphertext &c2);
|
||||
BGVCiphertext operator*=(BGVCiphertext &c1, const BGVCiphertext &c2);
|
||||
BGVCiphertext operator*(BGVCiphertext c1, const BGVCiphertext &c2);
|
||||
BGVCiphertext operator<<=(BGVCiphertext &ctxt, int r);
|
||||
BGVCiphertext BGVRotateEvalAtIndex(BGVCiphertext ctxt, int r);
|
||||
boost::python::list BGVHoistedRotations(const BGVCiphertext &ctxt,
|
||||
const boost::python::list &pylist);
|
||||
BGVCiphertext BGVMultiplySingletonDirect(BGVCiphertext ctxt, int64_t val);
|
||||
BGVCiphertext BGVMultiplySingletonIntAndAdd(const BGVCiphertext &ctxt,
|
||||
int64_t val);
|
||||
BGVCiphertext operator>>=(BGVCiphertext &ctxt, int r);
|
||||
BGVCiphertext operator<<(BGVCiphertext ctxt, int r);
|
||||
BGVCiphertext operator>>(BGVCiphertext ctxt, int r);
|
||||
BGVCiphertext operator+=(BGVCiphertext &ctxt, int64_t val);
|
||||
BGVCiphertext operator+(BGVCiphertext ctxt, int64_t val);
|
||||
BGVCiphertext operator+(int64_t val, BGVCiphertext ctxt);
|
||||
BGVCiphertext operator+=(BGVCiphertext &ctxt, std::vector<int64_t> vals);
|
||||
BGVCiphertext operator+=(BGVCiphertext &ctxt,
|
||||
const boost::python::list &pyvals);
|
||||
BGVCiphertext operator+=(BGVCiphertext &ctxt,
|
||||
const boost::python::numpy::ndarray &pyvals);
|
||||
BGVCiphertext operator+(BGVCiphertext ctxt, const std::vector<int64_t> &vals);
|
||||
BGVCiphertext operator+(const std::vector<int64_t> &vals, BGVCiphertext ctxt);
|
||||
BGVCiphertext operator+(BGVCiphertext ctxt, const boost::python::list &vals);
|
||||
BGVCiphertext operator+(BGVCiphertext ctxt,
|
||||
const boost::python::numpy::ndarray &vals);
|
||||
BGVCiphertext operator+(const boost::python::list &vals, BGVCiphertext ctxt);
|
||||
BGVCiphertext operator+(const boost::python::numpy::ndarray &vals,
|
||||
BGVCiphertext ctxt);
|
||||
BGVCiphertext operator-=(BGVCiphertext &ctxt, std::vector<int64_t> vals);
|
||||
BGVCiphertext operator-=(BGVCiphertext &ctxt,
|
||||
const boost::python::list &pyvals);
|
||||
BGVCiphertext operator-=(BGVCiphertext &ctxt,
|
||||
const boost::python::numpy::ndarray &pyvals);
|
||||
BGVCiphertext operator-(BGVCiphertext ctxt, const boost::python::list &vals);
|
||||
BGVCiphertext operator-(BGVCiphertext ctxt,
|
||||
const boost::python::numpy::ndarray &vals);
|
||||
BGVCiphertext operator-(const BGVCiphertext &ctxt);
|
||||
BGVCiphertext operator-=(BGVCiphertext &ctxt, int64_t val);
|
||||
BGVCiphertext operator-(BGVCiphertext ctxt, int64_t val);
|
||||
BGVCiphertext operator-(int64_t val, const BGVCiphertext &ctxt);
|
||||
BGVCiphertext operator-(BGVCiphertext ctxt, const std::vector<int64_t> &vals);
|
||||
BGVCiphertext operator-(const std::vector<int64_t> &vals, BGVCiphertext ctxt);
|
||||
BGVCiphertext operator-(const boost::python::list &vals,
|
||||
const BGVCiphertext &ctxt);
|
||||
BGVCiphertext operator-(const boost::python::numpy::ndarray &vals,
|
||||
const BGVCiphertext &ctxt);
|
||||
BGVCiphertext operator*=(BGVCiphertext &ctxt, std::vector<int64_t> vals);
|
||||
BGVCiphertext operator*=(BGVCiphertext &ctxt, int64_t val);
|
||||
BGVCiphertext operator*=(BGVCiphertext &ctxt, int64_t val);
|
||||
BGVCiphertext operator*=(BGVCiphertext &ctxt,
|
||||
const boost::python::list &pyvals);
|
||||
BGVCiphertext operator*=(BGVCiphertext &ctxt,
|
||||
const boost::python::numpy::ndarray &pyvals);
|
||||
BGVCiphertext operator*(BGVCiphertext ctxt, int64_t val);
|
||||
BGVCiphertext operator*(int64_t val, BGVCiphertext ctxt);
|
||||
BGVCiphertext operator*(BGVCiphertext ctxt, int64_t val);
|
||||
BGVCiphertext operator*(int64_t val, BGVCiphertext ctxt);
|
||||
BGVCiphertext operator*(BGVCiphertext ctxt, const std::vector<int64_t> &vals);
|
||||
BGVCiphertext operator*(const std::vector<int64_t> &vals, BGVCiphertext ctxt);
|
||||
BGVCiphertext operator*(BGVCiphertext ctxt, const boost::python::list &vals);
|
||||
BGVCiphertext operator*(const boost::python::list &vals, BGVCiphertext ctxt);
|
||||
BGVCiphertext operator*(BGVCiphertext ctxt,
|
||||
const boost::python::numpy::ndarray &vals);
|
||||
BGVCiphertext operator*(const boost::python::numpy::ndarray &vals,
|
||||
BGVCiphertext ctxt);
|
||||
|
||||
} // namespace pyOpenFHE_BGV
|
||||
|
||||
#endif /* BGV_OPENFHE_PYTHON_CIPHERTEXT_H */
|
||||
111
include/bgv/BGV_key_operations.hpp
Normal file
111
include/bgv/BGV_key_operations.hpp
Normal file
@@ -0,0 +1,111 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
// encrypt, decrypt, keygeneration, and the like
|
||||
|
||||
#ifndef BGV_ENCRYPTION_OPENFHE_PYTHON_BINDINGS_H
|
||||
#define BGV_ENCRYPTION_OPENFHE_PYTHON_BINDINGS_H
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/numpy.hpp>
|
||||
|
||||
#include "bgv/BGV_ciphertext_extension.hpp"
|
||||
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
using namespace boost::python;
|
||||
using namespace boost::python::numpy;
|
||||
using namespace lbcrypto;
|
||||
|
||||
namespace pyOpenFHE_BGV {
|
||||
class BGVCiphertext;
|
||||
|
||||
// BGV-specific crypto context wrapper
|
||||
class BGVCryptoContext {
|
||||
|
||||
public:
|
||||
// reminder to self that CryptoContext = shared_ptr<CryptoContextImpl>
|
||||
CryptoContext<DCRTPoly> context;
|
||||
|
||||
// default for pickling
|
||||
BGVCryptoContext() {};
|
||||
|
||||
BGVCryptoContext(CryptoContext<DCRTPoly> cc) : context(cc){};
|
||||
|
||||
BGVCryptoContext(const BGVCryptoContext &cc) { context = cc.context; };
|
||||
|
||||
void enable(PKESchemeFeature m) { context->Enable(m); };
|
||||
|
||||
KeyPair<DCRTPoly> keyGen() { return context->KeyGen(); };
|
||||
|
||||
void evalMultKeyGen(const PrivateKey<DCRTPoly> privateKey) {
|
||||
context->EvalMultKeyGen(privateKey);
|
||||
};
|
||||
void evalMultKeysGen(const PrivateKey<DCRTPoly> privateKey) {
|
||||
context->EvalMultKeysGen(privateKey);
|
||||
};
|
||||
|
||||
EvalKey<DCRTPoly> keySwitchGen(const PrivateKey<DCRTPoly> oldPrivateKey,
|
||||
const PrivateKey<DCRTPoly> newPrivateKey) {
|
||||
return context->KeySwitchGen(oldPrivateKey, newPrivateKey);
|
||||
};
|
||||
|
||||
SCHEME getSchemeId() { return context->getSchemeId(); }
|
||||
|
||||
void evalAtIndexKeyGen1(const PrivateKey<DCRTPoly> privateKey,
|
||||
const list &index_list) {
|
||||
context->EvalAtIndexKeyGen(
|
||||
privateKey, pyOpenFHE::pythonListToCppIntVector(index_list), nullptr);
|
||||
};
|
||||
|
||||
void evalAtIndexKeyGen2(const PrivateKey<DCRTPoly> privateKey,
|
||||
const ndarray &index_list) {
|
||||
context->EvalAtIndexKeyGen(
|
||||
privateKey, pyOpenFHE::numpyListToCppIntVector(index_list), nullptr);
|
||||
};
|
||||
|
||||
void evalPowerOf2RotationKeyGen(const PrivateKey<DCRTPoly> &);
|
||||
|
||||
void evalBootstrapSetup();
|
||||
void evalBootstrapKeyGen(const PrivateKey<DCRTPoly> &);
|
||||
list evalBootstrapList(list);
|
||||
pyOpenFHE_BGV::BGVCiphertext evalBootstrap(pyOpenFHE_BGV::BGVCiphertext);
|
||||
|
||||
Plaintext encode(std::vector<int64_t>);
|
||||
|
||||
pyOpenFHE_BGV::BGVCiphertext encryptPrivate(const PrivateKey<DCRTPoly> &,
|
||||
const list &);
|
||||
pyOpenFHE_BGV::BGVCiphertext encryptPrivate2(const PrivateKey<DCRTPoly> &,
|
||||
const ndarray &);
|
||||
|
||||
pyOpenFHE_BGV::BGVCiphertext encryptPublic(const PublicKey<DCRTPoly> &,
|
||||
const list &);
|
||||
pyOpenFHE_BGV::BGVCiphertext encryptPublic2(const PublicKey<DCRTPoly> &,
|
||||
const ndarray &);
|
||||
|
||||
ndarray decrypt(const PrivateKey<DCRTPoly> &, pyOpenFHE_BGV::BGVCiphertext &);
|
||||
|
||||
usint getBatchSize() { return context->GetEncodingParams()->GetBatchSize(); };
|
||||
|
||||
usint getRingDimension() { return context->GetRingDimension(); };
|
||||
|
||||
usint getPlaintextModulus() {
|
||||
return context->GetEncodingParams()->GetPlaintextModulus();
|
||||
};
|
||||
|
||||
ndarray zeroPadToBatchSize(std::vector<int64_t>);
|
||||
ndarray zeroPadToBatchSizeList(const list &);
|
||||
ndarray zeroPadToBatchSizeNumpy(const ndarray &);
|
||||
|
||||
template <class Archive> void serialize(Archive &ar) { ar(context); };
|
||||
};
|
||||
|
||||
// not entirely sure what the BGV crypto context is like
|
||||
// TODO: verify this is the same
|
||||
BGVCryptoContext genBGVContext(usint multiplicativeDepth, usint batchSize,
|
||||
usint plaintextModulus,
|
||||
SecurityLevel stdLevel = HEStd_128_classic,
|
||||
usint ringDim = 0);
|
||||
|
||||
} // namespace pyOpenFHE_BGV
|
||||
|
||||
#endif /* BGV_ENCRYPTION_OPENFHE_PYTHON_BINDINGS_H */
|
||||
34
include/bgv/BGV_pickle.hpp
Normal file
34
include/bgv/BGV_pickle.hpp
Normal file
@@ -0,0 +1,34 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
#ifndef BGV_PICKLE_OPENFHE_PYTHON_BINDINGS_H
|
||||
#define BGV_PICKLE_OPENFHE_PYTHON_BINDINGS_H
|
||||
|
||||
#include <complex>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/numpy.hpp>
|
||||
|
||||
#include "openfhe.h"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#include "bgv/BGV_ciphertext_extension.hpp"
|
||||
#include "bgv/BGV_key_operations.hpp"
|
||||
#include "bgv/serialization.hpp"
|
||||
|
||||
using namespace lbcrypto;
|
||||
|
||||
namespace pyOpenFHE_BGV {
|
||||
|
||||
struct BGVCiphertext_pickle_suite : boost::python::pickle_suite {
|
||||
static boost::python::tuple
|
||||
getinitargs(const pyOpenFHE_BGV::BGVCiphertext &w);
|
||||
static boost::python::tuple getstate(const pyOpenFHE_BGV::BGVCiphertext &w);
|
||||
static void setstate(pyOpenFHE_BGV::BGVCiphertext &w,
|
||||
boost::python::tuple state);
|
||||
};
|
||||
|
||||
} // namespace pyOpenFHE_BGV
|
||||
|
||||
#endif /* BGV_PICKLE_OPENFHE_PYTHON_BINDINGS_H */
|
||||
14
include/bgv/bindings.hpp
Normal file
14
include/bgv/bindings.hpp
Normal file
@@ -0,0 +1,14 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
#ifndef BGV_PALISADE_PYTHON_BINDINGS_H
|
||||
#define BGV_PALISADE_PYTHON_BINDINGS_H
|
||||
|
||||
namespace pyOpenFHE_BGV {
|
||||
|
||||
void export_BGV_Ciphertext_boost();
|
||||
void export_BGV_CryptoContext_boost();
|
||||
void export_BGV_serialization_boost();
|
||||
|
||||
} // namespace pyOpenFHE_BGV
|
||||
|
||||
#endif
|
||||
94
include/bgv/serialization.hpp
Normal file
94
include/bgv/serialization.hpp
Normal file
@@ -0,0 +1,94 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
#ifndef BGV_SERIALIZATION_OPENFHE_PYTHON_BINDINGS_H
|
||||
#define BGV_SERIALIZATION_OPENFHE_PYTHON_BINDINGS_H
|
||||
|
||||
#include <complex>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/numpy.hpp>
|
||||
|
||||
#include "openfhe.h"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#include "bgv/BGV_ciphertext_extension.hpp"
|
||||
#include "bgv/BGV_key_operations.hpp"
|
||||
|
||||
using namespace lbcrypto;
|
||||
|
||||
namespace pyOpenFHE_BGV {
|
||||
|
||||
// We turned the Serial::SerType into an enum
|
||||
enum class SerType { BINARY, JSON };
|
||||
|
||||
PyObject *SerializeToBytes_EvalMultKey_CryptoContext(
|
||||
BGVCryptoContext &self, const pyOpenFHE_BGV::SerType sertype);
|
||||
bool DeserializeFromBytes_EvalMultKey_CryptoContext(
|
||||
BGVCryptoContext &self, boost::python::object py_buffer,
|
||||
const pyOpenFHE_BGV::SerType sertype);
|
||||
PyObject *SerializeToBytes_EvalAutomorphismKey_CryptoContext(
|
||||
BGVCryptoContext &self, const pyOpenFHE_BGV::SerType sertype);
|
||||
bool DeserializeFromBytes_EvalAutomorphismKey_CryptoContext(
|
||||
BGVCryptoContext &self, boost::python::object py_buffer,
|
||||
const pyOpenFHE_BGV::SerType sertype);
|
||||
|
||||
PyObject *SerializeToBytes_Ciphertext(const pyOpenFHE_BGV::BGVCiphertext &obj,
|
||||
const pyOpenFHE_BGV::SerType sertype);
|
||||
pyOpenFHE_BGV::BGVCiphertext
|
||||
DeserializeFromBytes_Ciphertext(boost::python::object py_buffer,
|
||||
const pyOpenFHE_BGV::SerType sertype);
|
||||
PyObject *SerializeToBytes_PublicKey(const PublicKey<DCRTPoly> &obj,
|
||||
const pyOpenFHE_BGV::SerType sertype);
|
||||
PublicKey<DCRTPoly>
|
||||
DeserializeFromBytes_PublicKey(boost::python::object py_buffer,
|
||||
const pyOpenFHE_BGV::SerType sertype);
|
||||
PyObject *SerializeToBytes_PrivateKey(const PrivateKey<DCRTPoly> &obj,
|
||||
const pyOpenFHE_BGV::SerType sertype);
|
||||
PrivateKey<DCRTPoly>
|
||||
DeserializeFromBytes_PrivateKey(boost::python::object py_buffer,
|
||||
const pyOpenFHE_BGV::SerType sertype);
|
||||
|
||||
bool SerializeToFile_CryptoContext(const std::string &filename,
|
||||
const BGVCryptoContext &obj,
|
||||
const pyOpenFHE_BGV::SerType sertype);
|
||||
CryptoContext<DCRTPoly>
|
||||
DeserializeFromFile_CryptoContext(const std::string &filename,
|
||||
const pyOpenFHE_BGV::SerType sertype);
|
||||
|
||||
bool SerializeToFile_Ciphertext(const std::string &filename,
|
||||
const pyOpenFHE_BGV::BGVCiphertext &obj,
|
||||
const pyOpenFHE_BGV::SerType sertype);
|
||||
bool SerializeToFile_EvalMultKey_CryptoContext(
|
||||
BGVCryptoContext &self, const std::string &filename,
|
||||
const pyOpenFHE_BGV::SerType sertype);
|
||||
bool SerializeToFile_EvalAutomorphismKey_CryptoContext(
|
||||
BGVCryptoContext &self, const std::string &filename,
|
||||
const pyOpenFHE_BGV::SerType sertype);
|
||||
bool SerializeToFile_PublicKey(const std::string &filename,
|
||||
const PublicKey<DCRTPoly> &obj,
|
||||
const pyOpenFHE_BGV::SerType sertype);
|
||||
bool SerializeToFile_PrivateKey(const std::string &filename,
|
||||
const PrivateKey<DCRTPoly> &obj,
|
||||
const pyOpenFHE_BGV::SerType sertype);
|
||||
|
||||
pyOpenFHE_BGV::BGVCiphertext
|
||||
DeserializeFromFile_Ciphertext(const std::string &filename,
|
||||
const pyOpenFHE_BGV::SerType sertype);
|
||||
PublicKey<DCRTPoly>
|
||||
DeserializeFromFile_PublicKey(const std::string &filename,
|
||||
const pyOpenFHE_BGV::SerType sertype);
|
||||
PrivateKey<DCRTPoly>
|
||||
DeserializeFromFile_PrivateKey(const std::string &filename,
|
||||
const pyOpenFHE_BGV::SerType sertype);
|
||||
bool DeserializeFromFile_EvalMultKey_CryptoContext(
|
||||
BGVCryptoContext &self, const std::string &filename,
|
||||
const pyOpenFHE_BGV::SerType sertype);
|
||||
bool DeserializeFromFile_EvalAutomorphismKey_CryptoContext(
|
||||
BGVCryptoContext &self, const std::string &filename,
|
||||
const pyOpenFHE_BGV::SerType sertype);
|
||||
|
||||
} // namespace pyOpenFHE_BGV
|
||||
|
||||
#endif /* BGV_SERIALIZATION_OPENFHE_PYTHON_BINDINGS_H */
|
||||
206
include/ckks/CKKS_ciphertext_extension.hpp
Normal file
206
include/ckks/CKKS_ciphertext_extension.hpp
Normal file
@@ -0,0 +1,206 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
#ifndef CKKS_PALISADE_PYTHON_CIPHERTEXT_H
|
||||
#define CKKS_PALISADE_PYTHON_CIPHERTEXT_H
|
||||
|
||||
#include <complex>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/numpy.hpp>
|
||||
|
||||
#include "openfhe.h"
|
||||
|
||||
#include "ckks/CKKS_key_operations.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#include "constants.h"
|
||||
#include "encoding/encodingparams.h"
|
||||
#include "scheme/scheme-utils.h"
|
||||
#include "utils/exceptions.hpp"
|
||||
|
||||
using namespace lbcrypto;
|
||||
|
||||
namespace pyOpenFHE_CKKS {
|
||||
|
||||
class CKKSCryptoContext;
|
||||
|
||||
class CKKSCiphertext {
|
||||
|
||||
public:
|
||||
Ciphertext<DCRTPoly> cipher;
|
||||
|
||||
// empty constructor
|
||||
CKKSCiphertext(){};
|
||||
|
||||
// wrapper constructor
|
||||
CKKSCiphertext(Ciphertext<DCRTPoly> cipher) : cipher(cipher){};
|
||||
|
||||
// copy constructor
|
||||
CKKSCiphertext(const CKKSCiphertext &ctxt) {
|
||||
cipher = Ciphertext<DCRTPoly>(ctxt.cipher);
|
||||
};
|
||||
|
||||
CKKSCiphertext array_ufunc(const boost::python::object ufunc,
|
||||
const boost::python::str method,
|
||||
const boost::python::numpy::ndarray &vals,
|
||||
const CKKSCiphertext &cipher);
|
||||
|
||||
uint64_t getPlaintextModulus(void) const {
|
||||
return cipher->GetCryptoContext()
|
||||
->GetEncodingParams()
|
||||
->GetPlaintextModulus();
|
||||
};
|
||||
|
||||
CKKSCryptoContext getCryptoContext(void) const;
|
||||
|
||||
double getScalingFactor(void) const {
|
||||
// TODO
|
||||
// const auto cryptoParams =
|
||||
// std::dynamic_pointer_cast<CryptoParametersRNS>(cipher->GetCryptoParameters());
|
||||
// double scFactor = cryptoParams->GetScalingFactorInt(cipher->GetLevel());
|
||||
// return scFactor;
|
||||
return cipher->GetScalingFactor();
|
||||
};
|
||||
|
||||
uint64_t getBatchSize(void) const {
|
||||
return cipher->GetCryptoContext()->GetEncodingParams()->GetBatchSize();
|
||||
};
|
||||
|
||||
/*
|
||||
* multiplicative depth performed & remaining.
|
||||
* GetLevel() return holds the number of rescalings performed
|
||||
* before getting this ciphertext - initially 0,
|
||||
* which is backwards for our purposes.
|
||||
*/
|
||||
uint64_t getMultLevel(void) const { return cipher->GetLevel(); };
|
||||
|
||||
uint64_t getTowersRemaining(void) const {
|
||||
const std::vector<DCRTPoly> &cv = cipher->GetElements();
|
||||
usint sizeQl = cv[0].GetNumOfElements();
|
||||
return sizeQl;
|
||||
}
|
||||
|
||||
// TODO broken
|
||||
// uint64_t getMultsRemaining(void) const {
|
||||
// CryptoContext<DCRTPoly> cc = cipher->GetCryptoContext();
|
||||
|
||||
// std::cout << "CKKS scheme is using ring dimension = " <<
|
||||
// cc->GetRingDimension() << std::endl; std::cout << "batch size = " <<
|
||||
// cc->GetEncodingParams()->GetBatchSize() << std::endl; std::cout <<
|
||||
// "mult depth = " << cc->GetEncodingParams()->GetMultDepth() <<
|
||||
// std::endl;
|
||||
|
||||
// // uint64_t mults_used = cipher->GetLevel();
|
||||
// // return max_depth - mults_used;
|
||||
|
||||
// return 0;
|
||||
// }
|
||||
|
||||
// an alternative to rescale?
|
||||
// lowers TowersRemaining to towersLeft
|
||||
// I don't really know what happens if towersLeft >= TowersRemaining so I'll just not do that
|
||||
CKKSCiphertext compress(size_t towersLeft) const {
|
||||
if (getTowersRemaining() <= towersLeft) {
|
||||
throw std::runtime_error(fmt::format("Cannot compress to {} towers, towers remaining = {}", towersLeft, getTowersRemaining()));
|
||||
}
|
||||
auto cipher2 = cipher->GetCryptoContext()->GetScheme()->Compress(cipher, towersLeft);
|
||||
return CKKSCiphertext(cipher2);
|
||||
}
|
||||
|
||||
|
||||
// TODO broken
|
||||
CKKSCiphertext Rescale(size_t levels = 1) const {
|
||||
if (getTowersRemaining() <= 1 + levels) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("Insufficient number of towers remaining = {} to perform "
|
||||
"{} rescalings",
|
||||
getTowersRemaining(), levels));
|
||||
}
|
||||
auto algo = cipher->GetCryptoContext()->GetScheme();
|
||||
auto cipher2 = algo->ModReduce(cipher, levels);
|
||||
return CKKSCiphertext(cipher2);
|
||||
}
|
||||
};
|
||||
|
||||
// a whole load of operators
|
||||
// we need to specify ALL of these, and then specify them again in the
|
||||
// bindings...
|
||||
|
||||
bool operator==(const CKKSCiphertext &c1, const CKKSCiphertext &c2);
|
||||
bool operator!=(const CKKSCiphertext &c1, const CKKSCiphertext &c2);
|
||||
CKKSCiphertext operator+=(CKKSCiphertext &c1, const CKKSCiphertext &c2);
|
||||
CKKSCiphertext operator+(CKKSCiphertext c1, const CKKSCiphertext &c2);
|
||||
CKKSCiphertext operator-=(CKKSCiphertext &c1, const CKKSCiphertext &c2);
|
||||
CKKSCiphertext operator-(CKKSCiphertext c1, const CKKSCiphertext &c2);
|
||||
CKKSCiphertext operator*=(CKKSCiphertext &c1, const CKKSCiphertext &c2);
|
||||
CKKSCiphertext operator*(CKKSCiphertext c1, const CKKSCiphertext &c2);
|
||||
CKKSCiphertext operator<<=(CKKSCiphertext &ctxt, int r);
|
||||
CKKSCiphertext CKKSRotateEvalAtIndex(CKKSCiphertext ctxt, int r);
|
||||
boost::python::list CKKSHoistedRotations(const CKKSCiphertext &ctxt,
|
||||
const boost::python::list &pylist);
|
||||
CKKSCiphertext CKKSMultiplySingletonDirect(CKKSCiphertext ctxt, double val);
|
||||
CKKSCiphertext CKKSMultiplySingletonIntDoubleAndAdd(const CKKSCiphertext &ctxt,
|
||||
long int val);
|
||||
CKKSCiphertext operator>>=(CKKSCiphertext &ctxt, double r);
|
||||
CKKSCiphertext operator<<(CKKSCiphertext ctxt, double r);
|
||||
CKKSCiphertext operator>>(CKKSCiphertext ctxt, double r);
|
||||
CKKSCiphertext operator+=(CKKSCiphertext &ctxt, double val);
|
||||
CKKSCiphertext operator+(CKKSCiphertext ctxt, double val);
|
||||
CKKSCiphertext operator+(double val, CKKSCiphertext ctxt);
|
||||
CKKSCiphertext operator+=(CKKSCiphertext &ctxt, std::vector<double> vals);
|
||||
CKKSCiphertext operator+=(CKKSCiphertext &ctxt,
|
||||
const boost::python::list &pyvals);
|
||||
CKKSCiphertext operator+=(CKKSCiphertext &ctxt,
|
||||
const boost::python::numpy::ndarray &pyvals);
|
||||
CKKSCiphertext operator+(CKKSCiphertext ctxt, const std::vector<double> &vals);
|
||||
CKKSCiphertext operator+(const std::vector<double> &vals, CKKSCiphertext ctxt);
|
||||
CKKSCiphertext operator+(CKKSCiphertext ctxt, const boost::python::list &vals);
|
||||
CKKSCiphertext operator+(CKKSCiphertext ctxt,
|
||||
const boost::python::numpy::ndarray &vals);
|
||||
CKKSCiphertext operator+(const boost::python::list &vals, CKKSCiphertext ctxt);
|
||||
CKKSCiphertext operator+(const boost::python::numpy::ndarray &vals,
|
||||
CKKSCiphertext ctxt);
|
||||
CKKSCiphertext operator-=(CKKSCiphertext &ctxt, std::vector<double> vals);
|
||||
CKKSCiphertext operator-=(CKKSCiphertext &ctxt,
|
||||
const boost::python::list &pyvals);
|
||||
CKKSCiphertext operator-=(CKKSCiphertext &ctxt,
|
||||
const boost::python::numpy::ndarray &pyvals);
|
||||
CKKSCiphertext operator-(CKKSCiphertext ctxt, const boost::python::list &vals);
|
||||
CKKSCiphertext operator-(CKKSCiphertext ctxt,
|
||||
const boost::python::numpy::ndarray &vals);
|
||||
CKKSCiphertext operator-(const CKKSCiphertext &ctxt);
|
||||
CKKSCiphertext operator-=(CKKSCiphertext &ctxt, double val);
|
||||
CKKSCiphertext operator-(CKKSCiphertext ctxt, double val);
|
||||
CKKSCiphertext operator-(double val, const CKKSCiphertext &ctxt);
|
||||
CKKSCiphertext operator-(CKKSCiphertext ctxt, const std::vector<double> &vals);
|
||||
CKKSCiphertext operator-(const std::vector<double> &vals, CKKSCiphertext ctxt);
|
||||
CKKSCiphertext operator-(const boost::python::list &vals,
|
||||
const CKKSCiphertext &ctxt);
|
||||
CKKSCiphertext operator-(const boost::python::numpy::ndarray &vals,
|
||||
const CKKSCiphertext &ctxt);
|
||||
CKKSCiphertext operator*=(CKKSCiphertext &ctxt, std::vector<double> vals);
|
||||
CKKSCiphertext operator*=(CKKSCiphertext &ctxt, double val);
|
||||
CKKSCiphertext operator*=(CKKSCiphertext &ctxt, long int val);
|
||||
CKKSCiphertext operator*=(CKKSCiphertext &ctxt,
|
||||
const boost::python::list &pyvals);
|
||||
CKKSCiphertext operator*=(CKKSCiphertext &ctxt,
|
||||
const boost::python::numpy::ndarray &pyvals);
|
||||
CKKSCiphertext operator*(CKKSCiphertext ctxt, double val);
|
||||
CKKSCiphertext operator*(double val, CKKSCiphertext ctxt);
|
||||
CKKSCiphertext operator*(CKKSCiphertext ctxt, long int val);
|
||||
CKKSCiphertext operator*(long int val, CKKSCiphertext ctxt);
|
||||
CKKSCiphertext operator*(CKKSCiphertext ctxt, const std::vector<double> &vals);
|
||||
CKKSCiphertext operator*(const std::vector<double> &vals, CKKSCiphertext ctxt);
|
||||
CKKSCiphertext operator*(CKKSCiphertext ctxt, const boost::python::list &vals);
|
||||
CKKSCiphertext operator*(const boost::python::list &vals, CKKSCiphertext ctxt);
|
||||
CKKSCiphertext operator*(CKKSCiphertext ctxt,
|
||||
const boost::python::numpy::ndarray &vals);
|
||||
CKKSCiphertext operator*(const boost::python::numpy::ndarray &vals,
|
||||
CKKSCiphertext ctxt);
|
||||
|
||||
} // namespace pyOpenFHE_CKKS
|
||||
|
||||
#endif /* CKKS_PALISADE_PYTHON_CIPHERTEXT_H */
|
||||
105
include/ckks/CKKS_key_operations.hpp
Normal file
105
include/ckks/CKKS_key_operations.hpp
Normal file
@@ -0,0 +1,105 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
// encrypt, decrypt, keygeneration, and the like
|
||||
|
||||
#ifndef CKKS_ENCRYPTION_PALISADE_PYTHON_BINDINGS_H
|
||||
#define CKKS_ENCRYPTION_PALISADE_PYTHON_BINDINGS_H
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/numpy.hpp>
|
||||
|
||||
#include "ckks/CKKS_ciphertext_extension.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
using namespace boost::python;
|
||||
using namespace boost::python::numpy;
|
||||
using namespace lbcrypto;
|
||||
|
||||
namespace pyOpenFHE_CKKS {
|
||||
class CKKSCiphertext;
|
||||
|
||||
// CKKS-specific crypto context wrapper
|
||||
class CKKSCryptoContext {
|
||||
|
||||
public:
|
||||
// reminder to self that CryptoContext = shared_ptr<CryptoContextImpl>
|
||||
CryptoContext<DCRTPoly> context;
|
||||
|
||||
CKKSCryptoContext(CryptoContext<DCRTPoly> cc) : context(cc){};
|
||||
|
||||
CKKSCryptoContext(const CKKSCryptoContext &cc) { context = cc.context; };
|
||||
|
||||
void enable(PKESchemeFeature m) { context->Enable(m); };
|
||||
|
||||
KeyPair<DCRTPoly> keyGen() { return context->KeyGen(); };
|
||||
|
||||
void evalMultKeyGen(const PrivateKey<DCRTPoly> privateKey) {
|
||||
context->EvalMultKeyGen(privateKey);
|
||||
};
|
||||
void evalMultKeysGen(const PrivateKey<DCRTPoly> privateKey) {
|
||||
context->EvalMultKeysGen(privateKey);
|
||||
};
|
||||
|
||||
EvalKey<DCRTPoly> keySwitchGen(const PrivateKey<DCRTPoly> oldPrivateKey,
|
||||
const PrivateKey<DCRTPoly> newPrivateKey) {
|
||||
return context->KeySwitchGen(oldPrivateKey, newPrivateKey);
|
||||
};
|
||||
|
||||
SCHEME getSchemeId() { return context->getSchemeId(); }
|
||||
|
||||
void evalAtIndexKeyGen1(const PrivateKey<DCRTPoly> privateKey,
|
||||
const list &index_list) {
|
||||
context->EvalAtIndexKeyGen(
|
||||
privateKey, pyOpenFHE::pythonListToCppIntVector(index_list), nullptr);
|
||||
};
|
||||
|
||||
void evalAtIndexKeyGen2(const PrivateKey<DCRTPoly> privateKey,
|
||||
const ndarray &index_list) {
|
||||
context->EvalAtIndexKeyGen(
|
||||
privateKey, pyOpenFHE::numpyListToCppIntVector(index_list), nullptr);
|
||||
};
|
||||
|
||||
void evalPowerOf2RotationKeyGen(const PrivateKey<DCRTPoly> &);
|
||||
|
||||
void evalBootstrapSetup();
|
||||
void evalBootstrapKeyGen(const PrivateKey<DCRTPoly> &);
|
||||
list evalBootstrapList(list);
|
||||
pyOpenFHE_CKKS::CKKSCiphertext evalBootstrap(pyOpenFHE_CKKS::CKKSCiphertext);
|
||||
list evalMetaBootstrapList(list);
|
||||
pyOpenFHE_CKKS::CKKSCiphertext evalMetaBootstrap(pyOpenFHE_CKKS::CKKSCiphertext);
|
||||
|
||||
Plaintext encode(std::vector<double>);
|
||||
|
||||
pyOpenFHE_CKKS::CKKSCiphertext encryptPrivate(const PrivateKey<DCRTPoly> &,
|
||||
const list &);
|
||||
pyOpenFHE_CKKS::CKKSCiphertext encryptPrivate2(const PrivateKey<DCRTPoly> &,
|
||||
const ndarray &);
|
||||
|
||||
pyOpenFHE_CKKS::CKKSCiphertext encryptPublic(const PublicKey<DCRTPoly> &,
|
||||
const list &);
|
||||
pyOpenFHE_CKKS::CKKSCiphertext encryptPublic2(const PublicKey<DCRTPoly> &,
|
||||
const ndarray &);
|
||||
|
||||
ndarray decrypt(const PrivateKey<DCRTPoly> &,
|
||||
pyOpenFHE_CKKS::CKKSCiphertext &);
|
||||
|
||||
size_t getBatchSize() {
|
||||
return context->GetEncodingParams()->GetBatchSize();
|
||||
};
|
||||
|
||||
size_t getRingDimension() { return context->GetRingDimension(); };
|
||||
|
||||
ndarray zeroPadToBatchSize(std::vector<double>);
|
||||
ndarray zeroPadToBatchSizeList(const list &);
|
||||
ndarray zeroPadToBatchSizeNumpy(const ndarray &);
|
||||
|
||||
template <class Archive> void serialize(Archive &ar) { ar(context); };
|
||||
};
|
||||
|
||||
CKKSCryptoContext genCKKSContext(usint multiplicativeDepth,
|
||||
usint scalingFactorBits, usint batchSize,
|
||||
SecurityLevel stdLevel = HEStd_128_classic,
|
||||
usint ringDim = 0);
|
||||
} // namespace pyOpenFHE_CKKS
|
||||
|
||||
#endif
|
||||
34
include/ckks/CKKS_pickle.hpp
Normal file
34
include/ckks/CKKS_pickle.hpp
Normal file
@@ -0,0 +1,34 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
#ifndef CKKS_PICKLE_OPENFHE_PYTHON_BINDINGS_H
|
||||
#define CKKS_PICKLE_OPENFHE_PYTHON_BINDINGS_H
|
||||
|
||||
#include <vector>
|
||||
#include <complex>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/numpy.hpp>
|
||||
|
||||
#include "openfhe.h"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#include "ckks/CKKS_key_operations.hpp"
|
||||
#include "ckks/CKKS_ciphertext_extension.hpp"
|
||||
#include "ckks/serialization.hpp"
|
||||
|
||||
|
||||
using namespace lbcrypto;
|
||||
|
||||
namespace pyOpenFHE_CKKS {
|
||||
|
||||
struct CKKSCiphertext_pickle_suite : boost::python::pickle_suite
|
||||
{
|
||||
static boost::python::tuple getinitargs(const pyOpenFHE_CKKS::CKKSCiphertext& w);
|
||||
static boost::python::tuple getstate(const pyOpenFHE_CKKS::CKKSCiphertext& w);
|
||||
static void setstate(pyOpenFHE_CKKS::CKKSCiphertext& w, boost::python::tuple state);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* CKKS_PICKLE_OPENFHE_PYTHON_BINDINGS_H */
|
||||
15
include/ckks/bindings.hpp
Normal file
15
include/ckks/bindings.hpp
Normal file
@@ -0,0 +1,15 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
#ifndef CKKS_PALISADE_PYTHON_BINDINGS_H
|
||||
#define CKKS_PALISADE_PYTHON_BINDINGS_H
|
||||
|
||||
namespace pyOpenFHE_CKKS {
|
||||
|
||||
void export_CKKS_Ciphertext_boost();
|
||||
void export_CKKS_CryptoContext_boost();
|
||||
void export_CKKS_serialization_boost();
|
||||
void export_he_cnn_functions_boost();
|
||||
|
||||
} // namespace pyOpenFHE_CKKS
|
||||
|
||||
#endif
|
||||
26
include/ckks/cnn/conv.hpp
Normal file
26
include/ckks/cnn/conv.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
#ifndef HE_CNN_CONV_H
|
||||
#define HE_CNN_CONV_H
|
||||
|
||||
#include <vector>
|
||||
#include <complex>
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/numpy.hpp>
|
||||
|
||||
#include "boost/multi_array.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
using namespace pyOpenFHE;
|
||||
using namespace pyOpenFHE_CKKS;
|
||||
using namespace boost::python;
|
||||
using namespace boost::python::numpy;
|
||||
|
||||
namespace pyOpenFHE_CKKS {
|
||||
|
||||
boost::python::list conv2d(const boost::python::list &py_shards, const ndarray &npfilters, int mtx_size, const ndarray &permutation);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
30
include/ckks/cnn/he_cnn.hpp
Normal file
30
include/ckks/cnn/he_cnn.hpp
Normal file
@@ -0,0 +1,30 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
#ifndef HE_CNN_PYTHON_BINDINGS_H
|
||||
#define HE_CNN_PYTHON_BINDINGS_H
|
||||
|
||||
// functions for the homomorphic CNN
|
||||
|
||||
#include <vector>
|
||||
#include <complex>
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/numpy.hpp>
|
||||
|
||||
#include "boost/multi_array.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
using namespace pyOpenFHE;
|
||||
using namespace pyOpenFHE_CKKS;
|
||||
using namespace boost::python;
|
||||
using namespace boost::python::numpy;
|
||||
|
||||
namespace pyOpenFHE_CKKS {
|
||||
|
||||
// should probably put this inside the OpenFHE namespace
|
||||
typedef typename boost::multi_array<pyOpenFHE_CKKS::CKKSCiphertext, 2> ciphertext_array2d;
|
||||
typedef typename boost::multi_array<pyOpenFHE_CKKS::CKKSCiphertext, 4> ciphertext_array4d;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
28
include/ckks/cnn/linear.hpp
Normal file
28
include/ckks/cnn/linear.hpp
Normal file
@@ -0,0 +1,28 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
#ifndef HE_CNN_LINEAR_H
|
||||
#define HE_CNN_LINEAR_H
|
||||
|
||||
// pooling stuff
|
||||
|
||||
#include <vector>
|
||||
#include <complex>
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/numpy.hpp>
|
||||
|
||||
#include "boost/multi_array.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
using namespace pyOpenFHE;
|
||||
using namespace pyOpenFHE_CKKS;
|
||||
using namespace boost::python;
|
||||
using namespace boost::python::numpy;
|
||||
|
||||
namespace pyOpenFHE_CKKS {
|
||||
|
||||
pyOpenFHE_CKKS::CKKSCiphertext linear(const boost::python::list &py_shards, const ndarray &npweights, const int mtx_size, const ndarray &permutation, const int pool_factor);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
24
include/ckks/cnn/poly.hpp
Normal file
24
include/ckks/cnn/poly.hpp
Normal file
@@ -0,0 +1,24 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
#ifndef HE_CNN_POLY_H
|
||||
#define HE_CNN_POLY_H
|
||||
|
||||
#include <vector>
|
||||
#include <complex>
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/numpy.hpp>
|
||||
|
||||
#include "boost/multi_array.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
using namespace pyOpenFHE;
|
||||
using namespace pyOpenFHE_CKKS;
|
||||
using namespace boost::python;
|
||||
using namespace boost::python::numpy;
|
||||
|
||||
namespace pyOpenFHE_CKKS {
|
||||
boost::python::list fhe_gelu(const boost::python::list &py_shards, int degree, double bound);
|
||||
}
|
||||
|
||||
#endif
|
||||
25
include/ckks/cnn/pool.hpp
Normal file
25
include/ckks/cnn/pool.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
#ifndef HE_CNN_POOL_H
|
||||
#define HE_CNN_POOL_H
|
||||
|
||||
#include <vector>
|
||||
#include <complex>
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/numpy.hpp>
|
||||
|
||||
#include "boost/multi_array.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "ckks/CKKS_ciphertext_extension.hpp"
|
||||
|
||||
using namespace pyOpenFHE;
|
||||
using namespace boost::python;
|
||||
using namespace boost::python::numpy;
|
||||
|
||||
namespace pyOpenFHE_CKKS {
|
||||
|
||||
boost::python::list pool(const boost::python::list &py_shards, int mtx_size, bool conv);
|
||||
}
|
||||
|
||||
#endif
|
||||
27
include/ckks/cnn/upsample.hpp
Normal file
27
include/ckks/cnn/upsample.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
#ifndef HE_CNN_UPSAMPLE_H
|
||||
#define HE_CNN_UPSAMPLE_H
|
||||
|
||||
#include <vector>
|
||||
#include <complex>
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/numpy.hpp>
|
||||
|
||||
#include "boost/multi_array.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
using namespace pyOpenFHE;
|
||||
using namespace pyOpenFHE_CKKS;
|
||||
using namespace boost::python;
|
||||
using namespace boost::python::numpy;
|
||||
|
||||
namespace pyOpenFHE_CKKS {
|
||||
|
||||
boost::python::list upsample(const boost::python::list &py_shards, const int mtx_size, const ndarray &permutation, int upsample_type);
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
94
include/ckks/serialization.hpp
Normal file
94
include/ckks/serialization.hpp
Normal file
@@ -0,0 +1,94 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
#ifndef PALISADE_PYTHON_SERIALIZATION_H
|
||||
#define PALISADE_PYTHON_SERIALIZATION_H
|
||||
|
||||
#include <complex>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/numpy.hpp>
|
||||
|
||||
#include "openfhe.h"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#include "ckks/CKKS_ciphertext_extension.hpp"
|
||||
#include "ckks/CKKS_key_operations.hpp"
|
||||
|
||||
using namespace lbcrypto;
|
||||
|
||||
namespace pyOpenFHE_CKKS {
|
||||
|
||||
// We turned the Serial::SerType into an enum
|
||||
enum class SerType { BINARY, JSON };
|
||||
|
||||
PyObject *SerializeToBytes_EvalMultKey_CryptoContext(
|
||||
CKKSCryptoContext &self, const pyOpenFHE_CKKS::SerType sertype);
|
||||
bool DeserializeFromBytes_EvalMultKey_CryptoContext(
|
||||
CKKSCryptoContext &self, boost::python::object py_buffer,
|
||||
const pyOpenFHE_CKKS::SerType sertype);
|
||||
PyObject *SerializeToBytes_EvalAutomorphismKey_CryptoContext(
|
||||
CKKSCryptoContext &self, const pyOpenFHE_CKKS::SerType sertype);
|
||||
bool DeserializeFromBytes_EvalAutomorphismKey_CryptoContext(
|
||||
CKKSCryptoContext &self, boost::python::object py_buffer,
|
||||
const pyOpenFHE_CKKS::SerType sertype);
|
||||
|
||||
PyObject *SerializeToBytes_Ciphertext(const pyOpenFHE_CKKS::CKKSCiphertext &obj,
|
||||
const pyOpenFHE_CKKS::SerType sertype);
|
||||
pyOpenFHE_CKKS::CKKSCiphertext
|
||||
DeserializeFromBytes_Ciphertext(boost::python::object py_buffer,
|
||||
const pyOpenFHE_CKKS::SerType sertype);
|
||||
PyObject *SerializeToBytes_PublicKey(const PublicKey<DCRTPoly> &obj,
|
||||
const pyOpenFHE_CKKS::SerType sertype);
|
||||
PublicKey<DCRTPoly>
|
||||
DeserializeFromBytes_PublicKey(boost::python::object py_buffer,
|
||||
const pyOpenFHE_CKKS::SerType sertype);
|
||||
PyObject *SerializeToBytes_PrivateKey(const PrivateKey<DCRTPoly> &obj,
|
||||
const pyOpenFHE_CKKS::SerType sertype);
|
||||
PrivateKey<DCRTPoly>
|
||||
DeserializeFromBytes_PrivateKey(boost::python::object py_buffer,
|
||||
const pyOpenFHE_CKKS::SerType sertype);
|
||||
|
||||
bool SerializeToFile_CryptoContext(const std::string &filename,
|
||||
const CKKSCryptoContext &obj,
|
||||
const pyOpenFHE_CKKS::SerType sertype);
|
||||
CryptoContext<DCRTPoly>
|
||||
DeserializeFromFile_CryptoContext(const std::string &filename,
|
||||
const pyOpenFHE_CKKS::SerType sertype);
|
||||
|
||||
bool SerializeToFile_Ciphertext(const std::string &filename,
|
||||
const pyOpenFHE_CKKS::CKKSCiphertext &obj,
|
||||
const pyOpenFHE_CKKS::SerType sertype);
|
||||
bool SerializeToFile_EvalMultKey_CryptoContext(
|
||||
CKKSCryptoContext &self, const std::string &filename,
|
||||
const pyOpenFHE_CKKS::SerType sertype);
|
||||
bool SerializeToFile_EvalAutomorphismKey_CryptoContext(
|
||||
CKKSCryptoContext &self, const std::string &filename,
|
||||
const pyOpenFHE_CKKS::SerType sertype);
|
||||
bool SerializeToFile_PublicKey(const std::string &filename,
|
||||
const PublicKey<DCRTPoly> &obj,
|
||||
const pyOpenFHE_CKKS::SerType sertype);
|
||||
bool SerializeToFile_PrivateKey(const std::string &filename,
|
||||
const PrivateKey<DCRTPoly> &obj,
|
||||
const pyOpenFHE_CKKS::SerType sertype);
|
||||
|
||||
pyOpenFHE_CKKS::CKKSCiphertext
|
||||
DeserializeFromFile_Ciphertext(const std::string &filename,
|
||||
const pyOpenFHE_CKKS::SerType sertype);
|
||||
PublicKey<DCRTPoly>
|
||||
DeserializeFromFile_PublicKey(const std::string &filename,
|
||||
const pyOpenFHE_CKKS::SerType sertype);
|
||||
PrivateKey<DCRTPoly>
|
||||
DeserializeFromFile_PrivateKey(const std::string &filename,
|
||||
const pyOpenFHE_CKKS::SerType sertype);
|
||||
bool DeserializeFromFile_EvalMultKey_CryptoContext(
|
||||
CKKSCryptoContext &self, const std::string &filename,
|
||||
const pyOpenFHE_CKKS::SerType sertype);
|
||||
bool DeserializeFromFile_EvalAutomorphismKey_CryptoContext(
|
||||
CKKSCryptoContext &self, const std::string &filename,
|
||||
const pyOpenFHE_CKKS::SerType sertype);
|
||||
|
||||
} // namespace pyOpenFHE_CKKS
|
||||
|
||||
#endif /* PALISADE_PYTHON_SERIALIZATION_H */
|
||||
26
include/ckks/utils.hpp
Normal file
26
include/ckks/utils.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
#ifndef CKKS_UTILS_H
|
||||
#define CKKS_UTILS_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
int kernel_index_to_shift(int i, int ker_size);
|
||||
int shift_to_kernel_index(int shift, int ker_size);
|
||||
|
||||
std::vector<int> generate_up_mask(int mtx_num_rows, int mtx_num_cols, int num_shift_up);
|
||||
std::vector<int> generate_down_mask(int mtx_num_rows, int mtx_num_cols, int num_shift_down);
|
||||
std::vector<int> generate_left_mask(int mtx_num_rows, int mtx_num_cols, int num_shift_left);
|
||||
std::vector<int> generate_right_mask(int mtx_num_rows, int mtx_num_cols, int num_shift_right);
|
||||
std::vector<int> generate_ud_mask(int mtx_num_rows, int mtx_num_cols, int num_shift_up);
|
||||
std::vector<int> generate_lr_mask(int mtx_num_rows, int mtx_num_cols, int num_shift_left);
|
||||
std::vector<int> make_shift_mask_image_sharded(int num_mtxs, int mtx_num_rows, int mtx_num_cols, int num_shift_up, int num_shift_left);
|
||||
std::vector<int> make_shift_mask_channel_shard(int num_rows, int num_cols, int num_shift_up, int num_shift_left);
|
||||
std::vector<int> make_shift_mask_bleed_channel_shard(int num_rows, int num_cols, int num_shift_up, int num_shift_left);
|
||||
|
||||
void print_vector(std::vector<int> vec);
|
||||
void print_vector(std::vector<double> vec);
|
||||
void print_mask(std::vector<int> vec, int num_rows, int num_cols);
|
||||
void print_mask(std::vector<double> vec, int num_rows, int num_cols);
|
||||
|
||||
#endif
|
||||
12
include/utils/enums_binding.hpp
Normal file
12
include/utils/enums_binding.hpp
Normal file
@@ -0,0 +1,12 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
#include "openfhe.h"
|
||||
|
||||
using namespace boost::python;
|
||||
using namespace boost::python::numpy;
|
||||
using namespace lbcrypto;
|
||||
|
||||
namespace pyOpenFHE {
|
||||
|
||||
void export_enums_boost();
|
||||
}
|
||||
37
include/utils/exceptions.hpp
Normal file
37
include/utils/exceptions.hpp
Normal file
@@ -0,0 +1,37 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
#ifndef OpenFHE_PYTHON_EXCEPTIONS_H
|
||||
#define OpenFHE_PYTHON_EXCEPTIONS_H
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <boost/python.hpp>
|
||||
|
||||
using namespace boost::python;
|
||||
|
||||
namespace pyOpenFHE {
|
||||
|
||||
// we usually just throw RuntimeExceptions
|
||||
// but certain exceptions are meaningful
|
||||
// e.g. numpy will respond to NotImplementedError differently than TypeError
|
||||
// so we need to define a custom exception on the C++ side and define a simple
|
||||
// translator
|
||||
|
||||
// we inherit from logic_error because it has a string constructor, unlike
|
||||
// exception
|
||||
class not_implemented_exception : public std::logic_error {
|
||||
|
||||
// constructor inheritance, thanks C++11
|
||||
using std::logic_error::logic_error;
|
||||
};
|
||||
|
||||
class type_exception : public std::logic_error {
|
||||
using std::logic_error::logic_error;
|
||||
};
|
||||
|
||||
void translate_not_implemented(not_implemented_exception const &e);
|
||||
void translate_type_exception(type_exception const &e);
|
||||
|
||||
} // namespace pyOpenFHE
|
||||
|
||||
#endif /* OpenFHE_PYTHON_EXCEPTIONS_H */
|
||||
6
include/utils/rotate_utils.hpp
Normal file
6
include/utils/rotate_utils.hpp
Normal file
@@ -0,0 +1,6 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
#include <vector>
|
||||
|
||||
std::vector<int> sumOfPo2s(int num);
|
||||
std::vector<int> po2Decompose(int num);
|
||||
72
include/utils/utils.hpp
Normal file
72
include/utils/utils.hpp
Normal file
@@ -0,0 +1,72 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
#ifndef OpenFHE_PYTHON_UTILS_H
|
||||
#define OpenFHE_PYTHON_UTILS_H
|
||||
|
||||
#include <complex>
|
||||
#include <vector>
|
||||
|
||||
#include "boost/multi_array.hpp"
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/numpy.hpp>
|
||||
|
||||
using namespace boost::python;
|
||||
using namespace boost::python::numpy;
|
||||
|
||||
namespace pyOpenFHE {
|
||||
|
||||
// TODO do these really belong in utils.hpp rather than he-cnn.hpp?
|
||||
// for kernel slicing
|
||||
typedef typename boost::multi_array<double, 2> boost_vector2d;
|
||||
typedef typename boost::multi_array<double, 4> boost_vector4d;
|
||||
typedef boost::multi_array_types::index_range srange;
|
||||
typedef typename boost_vector4d::array_view<4>::type boost_vector4d_slice;
|
||||
|
||||
boost::python::list
|
||||
make_list(const std::size_t n,
|
||||
boost::python::object item = boost::python::object() /* none */);
|
||||
|
||||
// converts std::vector to python list, O(n) time
|
||||
list cppVectorToPythonList(const std::vector<double> &);
|
||||
|
||||
list cppLongIntVectorToPythonList(const std::vector<int64_t> &vector);
|
||||
|
||||
// O(n) std::vector to numpy array
|
||||
// could be faster if we didn't have use complex<double>
|
||||
// in that case we could just copy the vector contents to the numpy array
|
||||
// that's probably optimized or something, better than iteration
|
||||
ndarray cppDoubleVectorToNumpyList(const std::vector<double> &);
|
||||
|
||||
ndarray cppLongIntVectorToNumpyList(const std::vector<int64_t> &vector);
|
||||
|
||||
// these two are used for converting lists of indices for EvalAtIndexKeyGen
|
||||
// since they have to be ints, and we're on the static typing side of things
|
||||
std::vector<int> pythonListToCppIntVector(const list &);
|
||||
|
||||
std::vector<int> numpyListToCppIntVector(const ndarray &);
|
||||
|
||||
boost_vector4d numpyArrayToCppArray4D(const ndarray &nplist);
|
||||
boost_vector2d numpyArrayToCppArray2D(const ndarray &nplist);
|
||||
|
||||
// conversion for complex<double>
|
||||
// for numpy arrays, the dtype must be specified to float/double or else C++
|
||||
// will throw a fit
|
||||
std::vector<double> numpyListToCppDoubleVector(const ndarray &);
|
||||
|
||||
std::vector<double> pythonListToCppDoubleVector(const list &);
|
||||
|
||||
std::vector<int64_t> pythonListToCppLongIntVector(const list &pylist);
|
||||
|
||||
std::vector<int64_t> numpyListToCppLongIntVector(const ndarray &nplist);
|
||||
|
||||
} // namespace pyOpenFHE
|
||||
|
||||
std::vector<int> sumOfPo2s(int);
|
||||
|
||||
void tileVector(std::vector<double> &vals, unsigned int n);
|
||||
void tileVector(std::vector<int64_t> &vals, unsigned int n);
|
||||
void tileVector(std::vector<int> &vals, unsigned int n);
|
||||
|
||||
template <typename T> void print_vector(std::vector<T> vec);
|
||||
|
||||
#endif /* OpenFHE_PYTHON_UTILS_H */
|
||||
129
setup.py
Normal file
129
setup.py
Normal file
@@ -0,0 +1,129 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# Almost taken verbatim from https://github.com/pybind/cmake_example
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
from setuptools import setup, Extension
|
||||
from setuptools.command.build_ext import build_ext
|
||||
|
||||
# Convert distutils Windows platform specifiers to CMake -A arguments
|
||||
PLAT_TO_CMAKE = {
|
||||
"win32": "Win32",
|
||||
"win-amd64": "x64",
|
||||
"win-arm32": "ARM",
|
||||
"win-arm64": "ARM64",
|
||||
}
|
||||
|
||||
|
||||
# A CMakeExtension needs a sourcedir instead of a file list.
|
||||
# The name must be the _single_ output extension from the CMake build.
|
||||
# If you need multiple extensions, see scikit-build.
|
||||
class CMakeExtension(Extension):
|
||||
def __init__(self, name, sourcedir=""):
|
||||
Extension.__init__(self, name, sources=[])
|
||||
self.sourcedir = os.path.abspath(sourcedir)
|
||||
|
||||
|
||||
class CMakeBuild(build_ext):
|
||||
def build_extension(self, ext):
|
||||
extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name)))
|
||||
|
||||
# required for auto-detection of auxiliary "native" libs
|
||||
if not extdir.endswith(os.path.sep):
|
||||
extdir += os.path.sep
|
||||
|
||||
cfg = "Debug" if self.debug else "Release"
|
||||
|
||||
# CMake lets you override the generator - we need to check this.
|
||||
# Can be set with Conda-Build, for example.
|
||||
cmake_generator = os.environ.get("CMAKE_GENERATOR", "")
|
||||
|
||||
# Set Python_EXECUTABLE instead if you use PYBIND11_FINDPYTHON
|
||||
# EXAMPLE_VERSION_INFO shows you how to pass a value into the C++ code
|
||||
# from Python.
|
||||
cmake_args = [
|
||||
"-DPython3_ROOT_DIR={}".format(sys.prefix),
|
||||
"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={}".format(extdir),
|
||||
"-DPYTHON_EXECUTABLE={}".format(sys.executable),
|
||||
"-DPACKAGE_VERSION_INFO={}".format(self.distribution.get_version()),
|
||||
"-DCMAKE_BUILD_TYPE={}".format(cfg), # not used on MSVC, but no harm
|
||||
]
|
||||
build_args = []
|
||||
|
||||
if self.compiler.compiler_type != "msvc":
|
||||
# Using Ninja-build since it a) is available as a wheel and b)
|
||||
# multithreads automatically. MSVC would require all variables be
|
||||
# exported for Ninja to pick it up, which is a little tricky to do.
|
||||
# Users can override the generator with CMAKE_GENERATOR in CMake
|
||||
# 3.15+.
|
||||
if not cmake_generator:
|
||||
cmake_args += ["-GNinja"]
|
||||
|
||||
else:
|
||||
|
||||
# Single config generators are handled "normally"
|
||||
single_config = any(x in cmake_generator for x in {"NMake", "Ninja"})
|
||||
|
||||
# CMake allows an arch-in-generator style for backward compatibility
|
||||
contains_arch = any(x in cmake_generator for x in {"ARM", "Win64"})
|
||||
|
||||
# Specify the arch if using MSVC generator, but only if it doesn't
|
||||
# contain a backward-compatibility arch spec already in the
|
||||
# generator name.
|
||||
if not single_config and not contains_arch:
|
||||
cmake_args += ["-A", PLAT_TO_CMAKE[self.plat_name]]
|
||||
|
||||
# Multi-config generators have a different way to specify configs
|
||||
if not single_config:
|
||||
cmake_args += [
|
||||
"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}".format(cfg.upper(), extdir)
|
||||
]
|
||||
build_args += ["--config", cfg]
|
||||
|
||||
# Set CMAKE_BUILD_PARALLEL_LEVEL to control the parallel build level
|
||||
# across all generators.
|
||||
if "CMAKE_BUILD_PARALLEL_LEVEL" not in os.environ:
|
||||
# self.parallel is a Python 3 only way to set parallel jobs by hand
|
||||
# using -j in the build_ext call, not supported by pip or PyPA-build.
|
||||
if hasattr(self, "parallel") and self.parallel:
|
||||
# CMake 3.12+ only.
|
||||
build_args += ["-j{}".format(self.parallel)]
|
||||
|
||||
if not os.path.exists(self.build_temp):
|
||||
os.makedirs(self.build_temp)
|
||||
|
||||
subprocess.check_call(
|
||||
["cmake", ext.sourcedir] + cmake_args, cwd=self.build_temp
|
||||
)
|
||||
subprocess.check_call(
|
||||
["cmake", "--build", "."] + build_args, cwd=self.build_temp
|
||||
)
|
||||
|
||||
# Next package version with a0 alpha suffix.
|
||||
# Release script automatically removes suffix for release.
|
||||
if os.getenv("CI_COMMIT_TAG"):
|
||||
VERSION = os.getenv("CI_COMMIT_TAG")
|
||||
else:
|
||||
VERSION = "1.0.5a0"
|
||||
pass
|
||||
|
||||
# The information here can also be placed in setup.cfg - better separation of
|
||||
# logic and declaration, and simpler if you include description/version in a file.
|
||||
setup(
|
||||
name="OpenFHE",
|
||||
version=VERSION,
|
||||
python_requires=">=3.10",
|
||||
description="Python wrapper for OpenFHE",
|
||||
ext_modules=[
|
||||
CMakeExtension("OpenFHE"),
|
||||
],
|
||||
cmdclass={"build_ext": CMakeBuild},
|
||||
install_requires=["numpy"], # numpy required for pybind buffer translations
|
||||
setup_requires=["setuptools>=42", "wheel",
|
||||
"ninja; sys_platform != 'win32'",
|
||||
"cmake>=3.12"],
|
||||
# TODO: move to PEP 517?
|
||||
zip_safe=False,
|
||||
)
|
||||
75
src/bgv/BGV_CryptoContext_bindings.cpp
Normal file
75
src/bgv/BGV_CryptoContext_bindings.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
// bindings for the CryptoContext class
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/numpy.hpp>
|
||||
|
||||
#include "openfhe.h"
|
||||
|
||||
#include "bgv/BGV_key_operations.hpp"
|
||||
|
||||
using namespace boost::python;
|
||||
using namespace boost::python::numpy;
|
||||
using namespace lbcrypto;
|
||||
using namespace pyOpenFHE;
|
||||
|
||||
namespace boost {
|
||||
template <typename T> T *get_pointer(std::shared_ptr<T> p) { return p.get(); }
|
||||
} // namespace boost
|
||||
|
||||
namespace pyOpenFHE_BGV {
|
||||
|
||||
// boost has trouble with overloaded methods
|
||||
// best option is to define these function pointers for different method
|
||||
// signatures then pass them later when def'ing the module other option: use
|
||||
// lambda function pointers that basically do the same thing but in-line
|
||||
|
||||
// Minimum number of arguments is 4, maximum is 6 for genBGVContext
|
||||
BOOST_PYTHON_FUNCTION_OVERLOADS(BGV_factory_overloads, genBGVContext, 3, 5)
|
||||
|
||||
void export_BGV_CryptoContext_boost() {
|
||||
|
||||
class_<pyOpenFHE_BGV::BGVCryptoContext>("BGVCryptoContext",
|
||||
init<CryptoContext<DCRTPoly>>())
|
||||
.def("enable", &BGVCryptoContext::enable)
|
||||
.def("keyGen", &BGVCryptoContext::keyGen)
|
||||
.def("evalMultKeyGen", &BGVCryptoContext::evalMultKeyGen)
|
||||
.def("evalMultKeysGen", &BGVCryptoContext::evalMultKeysGen)
|
||||
.def("keySwitchGen", &BGVCryptoContext::keySwitchGen)
|
||||
.def("getSchemeID", &BGVCryptoContext::getSchemeId)
|
||||
|
||||
/*
|
||||
boost doesn't accept lambdas as member functions
|
||||
but it *does* accept function pointers
|
||||
and the "+" turns this lambda into a function pointer
|
||||
this is only possible because it doesn't capture any values
|
||||
*/
|
||||
.def("evalAtIndexKeyGen", &BGVCryptoContext::evalAtIndexKeyGen1)
|
||||
.def("evalAtIndexKeyGen", &BGVCryptoContext::evalAtIndexKeyGen2)
|
||||
.def("evalPowerOf2RotationKeyGen",
|
||||
&BGVCryptoContext::evalPowerOf2RotationKeyGen)
|
||||
.def("evalBootstrapSetup", &BGVCryptoContext::evalBootstrapSetup)
|
||||
.def("evalBootstrapKeyGen", &BGVCryptoContext::evalBootstrapKeyGen)
|
||||
.def("evalBootstrap", &BGVCryptoContext::evalBootstrap)
|
||||
.def("evalBootstrap", &BGVCryptoContext::evalBootstrapList)
|
||||
.def("encrypt", &BGVCryptoContext::encryptPublic)
|
||||
.def("encrypt", &BGVCryptoContext::encryptPrivate)
|
||||
.def("encrypt", &BGVCryptoContext::encryptPublic2)
|
||||
.def("encrypt", &BGVCryptoContext::encryptPrivate2)
|
||||
.def("decrypt", &BGVCryptoContext::decrypt)
|
||||
.def("getRingDimension", &BGVCryptoContext::getRingDimension)
|
||||
.def("getBatchSize", &BGVCryptoContext::getBatchSize)
|
||||
.def("getPlaintextModulus", &BGVCryptoContext::getPlaintextModulus)
|
||||
|
||||
.def("zeroPadToBatchSize", &BGVCryptoContext::zeroPadToBatchSizeList)
|
||||
.def("zeroPadToBatchSize", &BGVCryptoContext::zeroPadToBatchSizeNumpy);
|
||||
|
||||
def("genCryptoContextBGV", &genBGVContext,
|
||||
BGV_factory_overloads((arg("multiplicativeDepth"), arg("batchSize"),
|
||||
arg("plaintextModulus"),
|
||||
arg("stdLevel") = SecurityLevel::HEStd_128_classic,
|
||||
arg("ringDim") = 0)));
|
||||
}
|
||||
|
||||
} // namespace pyOpenFHE_BGV
|
||||
105
src/bgv/BGV_bindings.cpp
Normal file
105
src/bgv/BGV_bindings.cpp
Normal file
@@ -0,0 +1,105 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
#include <complex>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
// string formatting for exceptions
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/numpy.hpp>
|
||||
|
||||
#include "openfhe.h"
|
||||
|
||||
#include "bgv/BGV_ciphertext_extension.hpp"
|
||||
#include "bgv/BGV_pickle.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
using namespace boost::python;
|
||||
using namespace boost::python::numpy;
|
||||
using namespace lbcrypto;
|
||||
|
||||
namespace pyOpenFHE_BGV {
|
||||
|
||||
BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(BGV_Rescale_overloads,
|
||||
pyOpenFHE_BGV::BGVCiphertext::rescale, 0,
|
||||
1)
|
||||
|
||||
void export_BGV_Ciphertext_boost() {
|
||||
|
||||
class_<pyOpenFHE_BGV::BGVCiphertext>("BGVCiphertext",
|
||||
init<Ciphertext<DCRTPoly>>())
|
||||
.def(init<const pyOpenFHE_BGV::BGVCiphertext &>())
|
||||
// need to expose default constructor for pickling
|
||||
.def(init<>())
|
||||
.def("getPlaintextModulus",
|
||||
&pyOpenFHE_BGV::BGVCiphertext::getPlaintextModulus)
|
||||
.def("getScalingFactor", &pyOpenFHE_BGV::BGVCiphertext::getScalingFactor)
|
||||
.def("getBatchSize", &pyOpenFHE_BGV::BGVCiphertext::getBatchSize)
|
||||
.def("getMultLevel", &pyOpenFHE_BGV::BGVCiphertext::getMultLevel)
|
||||
.def("getTowersRemaining",
|
||||
&pyOpenFHE_BGV::BGVCiphertext::getTowersRemaining)
|
||||
.def("getCryptoContext", &pyOpenFHE_BGV::BGVCiphertext::getCryptoContext)
|
||||
.def("rescale", &pyOpenFHE_BGV::BGVCiphertext::rescale,
|
||||
BGV_Rescale_overloads((arg("levels") = 1)))
|
||||
.def("compress", &pyOpenFHE_BGV::BGVCiphertext::compress)
|
||||
|
||||
.def("RotateEvalAtIndex", &pyOpenFHE_BGV::BGVRotateEvalAtIndex)
|
||||
.def("HoistedRotations", &pyOpenFHE_BGV::BGVHoistedRotations)
|
||||
|
||||
.def("__copy__",
|
||||
+[](pyOpenFHE_BGV::BGVCiphertext &self) {
|
||||
return pyOpenFHE_BGV::BGVCiphertext(self);
|
||||
})
|
||||
// here we specify every possible type interaction:
|
||||
.def(self == self)
|
||||
.def(self != self)
|
||||
.def(self += self)
|
||||
.def(self + self)
|
||||
.def(self -= self)
|
||||
.def(self - self)
|
||||
.def(self *= self)
|
||||
.def(self * self)
|
||||
.def(self <<= int())
|
||||
.def(self >>= int())
|
||||
.def(self << int())
|
||||
.def(self >> int())
|
||||
.def(self += int64_t())
|
||||
.def(self + int64_t())
|
||||
.def(int64_t() + self)
|
||||
.def(self += other<list>())
|
||||
.def(self += other<ndarray>())
|
||||
.def(self + other<list>())
|
||||
.def(self + other<ndarray>())
|
||||
.def(other<list>() + self)
|
||||
.def(other<ndarray>() + self)
|
||||
.def(self -= other<list>())
|
||||
.def(self -= other<ndarray>())
|
||||
.def(self - other<list>())
|
||||
.def(self - other<ndarray>())
|
||||
.def(-self)
|
||||
.def(other<list>() - self)
|
||||
.def(other<ndarray>() - self)
|
||||
.def(self -= int64_t())
|
||||
.def(self - int64_t())
|
||||
.def(int64_t() - self)
|
||||
.def(self *= int64_t())
|
||||
.def(self * int64_t())
|
||||
.def(int64_t() * self)
|
||||
.def(self *= other<list>())
|
||||
.def(self *= other<ndarray>())
|
||||
.def(self * other<list>())
|
||||
.def(self * other<ndarray>())
|
||||
.def(other<list>() * self)
|
||||
.def(other<ndarray>() * self)
|
||||
|
||||
// should prevent weird numpy broadcasting
|
||||
.def("__array_ufunc__", &pyOpenFHE_BGV::BGVCiphertext::array_ufunc)
|
||||
|
||||
// attempt to support pickling
|
||||
.def_pickle(BGVCiphertext_pickle_suite())
|
||||
.attr("__module__") = "pyOpenFHE.BGV";
|
||||
}
|
||||
|
||||
} // namespace pyOpenFHE_BGV
|
||||
426
src/bgv/BGV_ciphertext_extension.cpp
Normal file
426
src/bgv/BGV_ciphertext_extension.cpp
Normal file
@@ -0,0 +1,426 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
#include "openfhe.h"
|
||||
|
||||
#include <complex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// string formatting for exceptions
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/numpy.hpp>
|
||||
|
||||
#include "bgv/BGV_ciphertext_extension.hpp"
|
||||
#include "bgv/BGV_key_operations.hpp"
|
||||
#include "utils/rotate_utils.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
using namespace lbcrypto;
|
||||
using namespace boost::python;
|
||||
using namespace boost::python::numpy;
|
||||
using namespace pyOpenFHE;
|
||||
|
||||
namespace pyOpenFHE_BGV {
|
||||
|
||||
BGVCryptoContext BGVCiphertext::getCryptoContext(void) const {
|
||||
return BGVCryptoContext(cipher->GetCryptoContext());
|
||||
}
|
||||
|
||||
BGVCiphertext BGVCiphertext::array_ufunc(
|
||||
const boost::python::object ufunc, const boost::python::str method,
|
||||
const boost::python::numpy::ndarray &vals, const BGVCiphertext &cipher) {
|
||||
// this is a little rough but I can't think of a better way
|
||||
// supposedly if you set __array_ufunc__ to None, numpy will default to the
|
||||
// __rmul__ and __radd__ methods, but it doesn't seem to work with C++
|
||||
// extensions or something so we define this to override the default ufunc
|
||||
// behavior because this is defined, numpy will call into this instead of
|
||||
// ufunc
|
||||
std::string op =
|
||||
boost::python::extract<std::string>(ufunc.attr("__name__"))();
|
||||
|
||||
if (op == "multiply") {
|
||||
return cipher * vals;
|
||||
} else if (op == "add") {
|
||||
return cipher + vals;
|
||||
} else {
|
||||
throw pyOpenFHE::not_implemented_exception(
|
||||
fmt::format("operator {} between ndarray and BGVCiphertext", op));
|
||||
}
|
||||
}
|
||||
|
||||
// a whole load of operators
|
||||
// we need to specify ALL of these, and then specify them again in the
|
||||
// bindings...
|
||||
|
||||
bool operator==(const BGVCiphertext &c1, const BGVCiphertext &c2) {
|
||||
return c1.cipher == c2.cipher;
|
||||
}
|
||||
|
||||
bool operator!=(const BGVCiphertext &c1, const BGVCiphertext &c2) {
|
||||
return !(c1 == c2);
|
||||
}
|
||||
|
||||
BGVCiphertext operator+=(BGVCiphertext &c1, const BGVCiphertext &c2) {
|
||||
c1.cipher += c2.cipher;
|
||||
return c1;
|
||||
}
|
||||
|
||||
BGVCiphertext operator+(BGVCiphertext c1, const BGVCiphertext &c2) {
|
||||
return c1 += c2;
|
||||
}
|
||||
|
||||
BGVCiphertext operator-=(BGVCiphertext &c1, const BGVCiphertext &c2) {
|
||||
c1.cipher -= c2.cipher;
|
||||
return c1;
|
||||
}
|
||||
|
||||
BGVCiphertext operator-(BGVCiphertext c1, const BGVCiphertext &c2) {
|
||||
return c1 -= c2;
|
||||
}
|
||||
|
||||
BGVCiphertext operator*=(BGVCiphertext &c1, const BGVCiphertext &c2) {
|
||||
// TODO: verify this condition is still the same for BGV
|
||||
if ((c1.getTowersRemaining() <= 2) || (c2.getTowersRemaining() <= 2)) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("Insufficient number of towers remaining to perform a "
|
||||
"multiplication = {}, {}",
|
||||
c1.getTowersRemaining(), c2.getTowersRemaining()));
|
||||
}
|
||||
c1.cipher *= c2.cipher;
|
||||
return c1;
|
||||
}
|
||||
|
||||
BGVCiphertext operator*(BGVCiphertext c1, const BGVCiphertext &c2) {
|
||||
return c1 *= c2;
|
||||
}
|
||||
|
||||
/*
|
||||
uses the positive and negative power-of-2 decomposition
|
||||
e.g. decompose(15) = {16, -1}
|
||||
*/
|
||||
BGVCiphertext EvalRotatePositiveNegativePow2(BGVCiphertext &ctxt, int r) {
|
||||
if (r == 0) {
|
||||
// no change
|
||||
return ctxt;
|
||||
} else {
|
||||
// cyclic packing
|
||||
int N = ctxt.cipher->GetEncodingParameters()->GetBatchSize();
|
||||
|
||||
if (abs(r) > N) {
|
||||
throw std::runtime_error(fmt::format(
|
||||
"rotation value = {} is too large compared to batch size = {}", r,
|
||||
N));
|
||||
}
|
||||
|
||||
std::vector<int> po2s = po2Decompose(r);
|
||||
|
||||
for (int i : po2s) {
|
||||
ctxt.cipher =
|
||||
ctxt.cipher->GetCryptoContext()->EvalAtIndex(ctxt.cipher, i);
|
||||
}
|
||||
return ctxt;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
uses the positive power-of-2 decomposition
|
||||
e.g. decompose(15) = {8, 4, 2, 1}
|
||||
*/
|
||||
BGVCiphertext EvalRotatePositivePow2(BGVCiphertext &ctxt, int r) {
|
||||
if (r == 0) {
|
||||
// no change
|
||||
return ctxt;
|
||||
} else {
|
||||
// cyclic packing
|
||||
int N = ctxt.cipher->GetEncodingParameters()->GetBatchSize();
|
||||
|
||||
if (abs(r) > N) {
|
||||
throw std::runtime_error(fmt::format(
|
||||
"rotation value = {} is too large compared to batch size = {}", r,
|
||||
N));
|
||||
}
|
||||
|
||||
int mult = (r > 0) ? 1 : -1;
|
||||
std::vector<int> po2s = sumOfPo2s(abs(r));
|
||||
|
||||
for (int i : po2s) {
|
||||
ctxt.cipher = ctxt.cipher->GetCryptoContext()->EvalAtIndex(
|
||||
ctxt.cipher, mult * (1 << i));
|
||||
}
|
||||
return ctxt;
|
||||
}
|
||||
}
|
||||
|
||||
BGVCiphertext BGVRotateEvalAtIndex(BGVCiphertext ctxt, int r) {
|
||||
ctxt.cipher = ctxt.cipher->GetCryptoContext()->EvalAtIndex(ctxt.cipher, r);
|
||||
return ctxt;
|
||||
}
|
||||
|
||||
BGVCiphertext operator<<=(BGVCiphertext &ctxt, int r) {
|
||||
return EvalRotatePositiveNegativePow2(ctxt, r);
|
||||
}
|
||||
|
||||
/*
|
||||
TODO
|
||||
need error checking to see if that rotation key actually exists.
|
||||
Also this function should support integer numpy arrays as well
|
||||
*/
|
||||
boost::python::list BGVHoistedRotations(const BGVCiphertext &ctxt,
|
||||
const boost::python::list &pylist) {
|
||||
std::vector<int> rotations = pyOpenFHE::pythonListToCppIntVector(pylist);
|
||||
|
||||
auto cc = ctxt.cipher->GetCryptoContext();
|
||||
auto cPrecomp = cc->EvalFastRotationPrecompute(ctxt.cipher);
|
||||
|
||||
// M is the cyclotomic order and we need it to call EvalFastRotation
|
||||
uint32_t N = cc->GetRingDimension();
|
||||
uint32_t M = 2 * N;
|
||||
|
||||
// initialize list to correct length
|
||||
boost::python::object empty_item = boost::python::object(); // None
|
||||
boost::python::list result;
|
||||
result.append(empty_item);
|
||||
result *= rotations.size();
|
||||
|
||||
for (unsigned int i = 0; i < rotations.size(); i++) {
|
||||
result[i] = pyOpenFHE_BGV::BGVCiphertext(
|
||||
cc->EvalFastRotation(ctxt.cipher, rotations[i], M, cPrecomp));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
BGVCiphertext operator>>=(BGVCiphertext &ctxt, int r) { return ctxt <<= (-r); }
|
||||
|
||||
BGVCiphertext operator<<(BGVCiphertext ctxt, int r) { return ctxt <<= r; }
|
||||
|
||||
BGVCiphertext operator>>(BGVCiphertext ctxt, int r) { return ctxt >>= r; }
|
||||
|
||||
BGVCiphertext operator+=(BGVCiphertext &ctxt, int64_t val) {
|
||||
std::vector<int64_t> vals = {val};
|
||||
// size_t final_size = ctxt.cipher->GetEncodingParameters()->GetBatchSize();
|
||||
size_t final_size = ctxt.cipher->GetCryptoContext()->GetRingDimension() / 2;
|
||||
tileVector(vals, final_size);
|
||||
auto ptxt = ctxt.cipher->GetCryptoContext()->MakePackedPlaintext(vals);
|
||||
ctxt.cipher = ctxt.cipher->GetCryptoContext()->EvalAdd(ctxt.cipher, ptxt);
|
||||
return ctxt;
|
||||
}
|
||||
|
||||
BGVCiphertext operator+(BGVCiphertext ctxt, int64_t val) { return ctxt += val; }
|
||||
|
||||
BGVCiphertext operator+(int64_t val, BGVCiphertext ctxt) { return ctxt += val; }
|
||||
|
||||
BGVCiphertext operator+=(BGVCiphertext &ctxt, std::vector<int64_t> vals) {
|
||||
size_t N = ctxt.cipher->GetEncodingParameters()->GetBatchSize();
|
||||
if (vals.size() != N) {
|
||||
std::string s = fmt::format("Provided vector has length = {}, but the "
|
||||
"CryptoContext batch size = {}",
|
||||
vals.size(), N);
|
||||
throw std::runtime_error(s);
|
||||
}
|
||||
size_t final_size = ctxt.cipher->GetCryptoContext()->GetRingDimension() / 2;
|
||||
tileVector(vals, final_size);
|
||||
auto ptxt = ctxt.cipher->GetCryptoContext()->MakePackedPlaintext(vals);
|
||||
ctxt.cipher = ctxt.cipher->GetCryptoContext()->EvalAdd(ctxt.cipher, ptxt);
|
||||
return ctxt;
|
||||
}
|
||||
|
||||
BGVCiphertext operator+=(BGVCiphertext &ctxt,
|
||||
const boost::python::list &pyvals) {
|
||||
std::vector<int64_t> vals = pythonListToCppLongIntVector(pyvals);
|
||||
return ctxt += vals;
|
||||
}
|
||||
|
||||
BGVCiphertext operator+=(BGVCiphertext &ctxt,
|
||||
const boost::python::numpy::ndarray &pyvals) {
|
||||
std::vector<int64_t> vals = numpyListToCppLongIntVector(pyvals);
|
||||
return ctxt += vals;
|
||||
}
|
||||
|
||||
BGVCiphertext operator+(BGVCiphertext ctxt, const std::vector<int64_t> &vals) {
|
||||
return ctxt += vals;
|
||||
}
|
||||
|
||||
BGVCiphertext operator+(BGVCiphertext ctxt, const boost::python::list &vals) {
|
||||
return ctxt += vals;
|
||||
}
|
||||
|
||||
BGVCiphertext operator+(BGVCiphertext ctxt,
|
||||
const boost::python::numpy::ndarray &vals) {
|
||||
return ctxt += vals;
|
||||
}
|
||||
|
||||
BGVCiphertext operator+(const std::vector<int64_t> &vals, BGVCiphertext ctxt) {
|
||||
return ctxt += vals;
|
||||
}
|
||||
|
||||
BGVCiphertext operator+(const boost::python::list &vals, BGVCiphertext ctxt) {
|
||||
return ctxt += vals;
|
||||
}
|
||||
|
||||
BGVCiphertext operator+(const boost::python::numpy::ndarray &vals,
|
||||
BGVCiphertext ctxt) {
|
||||
return ctxt += vals;
|
||||
}
|
||||
|
||||
BGVCiphertext operator-=(BGVCiphertext &ctxt, std::vector<int64_t> vals) {
|
||||
size_t N = ctxt.cipher->GetEncodingParameters()->GetBatchSize();
|
||||
if (vals.size() != N) {
|
||||
std::string s = fmt::format("Provided vector has length = {}, but the "
|
||||
"CryptoContext batch size = {}",
|
||||
vals.size(), N);
|
||||
throw std::runtime_error(s);
|
||||
}
|
||||
size_t final_size = ctxt.cipher->GetCryptoContext()->GetRingDimension() / 2;
|
||||
tileVector(vals, final_size);
|
||||
auto ptxt = ctxt.cipher->GetCryptoContext()->MakePackedPlaintext(vals);
|
||||
ctxt.cipher = ctxt.cipher->GetCryptoContext()->EvalSub(ctxt.cipher, ptxt);
|
||||
return ctxt;
|
||||
}
|
||||
|
||||
BGVCiphertext operator-=(BGVCiphertext &ctxt,
|
||||
const boost::python::list &pyvals) {
|
||||
std::vector<int64_t> vals = pythonListToCppLongIntVector(pyvals);
|
||||
return ctxt -= vals;
|
||||
}
|
||||
|
||||
BGVCiphertext operator-=(BGVCiphertext &ctxt,
|
||||
const boost::python::numpy::ndarray &pyvals) {
|
||||
std::vector<int64_t> vals = numpyListToCppLongIntVector(pyvals);
|
||||
return ctxt -= vals;
|
||||
}
|
||||
|
||||
BGVCiphertext operator-(BGVCiphertext ctxt, const boost::python::list &vals) {
|
||||
return ctxt -= vals;
|
||||
}
|
||||
|
||||
BGVCiphertext operator-(BGVCiphertext ctxt,
|
||||
const boost::python::numpy::ndarray &vals) {
|
||||
return ctxt -= vals;
|
||||
}
|
||||
|
||||
BGVCiphertext operator-(const BGVCiphertext &ctxt) {
|
||||
return ctxt.cipher->GetCryptoContext()->EvalNegate(ctxt.cipher);
|
||||
}
|
||||
|
||||
BGVCiphertext operator-=(BGVCiphertext &ctxt, int64_t val) {
|
||||
ctxt += -val;
|
||||
return ctxt;
|
||||
}
|
||||
|
||||
BGVCiphertext operator-(BGVCiphertext ctxt, int64_t val) { return ctxt -= val; }
|
||||
|
||||
BGVCiphertext operator-(int64_t val, const BGVCiphertext &ctxt) {
|
||||
auto ncipher = -ctxt;
|
||||
return ncipher += val;
|
||||
}
|
||||
|
||||
BGVCiphertext operator-(const std::vector<int64_t> &vals, BGVCiphertext ctxt) {
|
||||
return ctxt -= vals;
|
||||
}
|
||||
|
||||
BGVCiphertext operator-(const boost::python::list &vals,
|
||||
const BGVCiphertext &ctxt) {
|
||||
auto ncipher = -ctxt;
|
||||
return ncipher += vals;
|
||||
}
|
||||
|
||||
BGVCiphertext operator-(const boost::python::numpy::ndarray &vals,
|
||||
const BGVCiphertext &ctxt) {
|
||||
auto ncipher = -ctxt;
|
||||
return ncipher -= vals;
|
||||
}
|
||||
|
||||
// TODO: is this still needed? is it any different for BGV/int operations?
|
||||
BGVCiphertext BGVMultiplySingletonIntAndAdd(const BGVCiphertext &ctxt,
|
||||
int64_t val) {
|
||||
if (val == 0) {
|
||||
return (ctxt - ctxt); // zero
|
||||
}
|
||||
if (val < 0) {
|
||||
return BGVMultiplySingletonIntAndAdd(-ctxt, -val);
|
||||
}
|
||||
|
||||
BGVCiphertext doubles = ctxt;
|
||||
BGVCiphertext result = (ctxt - ctxt); // zero
|
||||
while (val > 0) {
|
||||
if (val & 1) {
|
||||
result += doubles;
|
||||
}
|
||||
doubles = doubles + doubles;
|
||||
val >>= 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
BGVCiphertext operator*=(BGVCiphertext &ctxt, int64_t val) {
|
||||
ctxt = BGVMultiplySingletonIntAndAdd(ctxt, val);
|
||||
return ctxt;
|
||||
}
|
||||
|
||||
BGVCiphertext operator*=(BGVCiphertext &ctxt, std::vector<int64_t> vals) {
|
||||
size_t N = ctxt.cipher->GetEncodingParameters()->GetBatchSize();
|
||||
if (vals.size() != N) {
|
||||
std::string s = fmt::format("Provided vector has length = {}, but the "
|
||||
"CryptoContext batch size = {}",
|
||||
vals.size(), N);
|
||||
throw std::runtime_error(s);
|
||||
}
|
||||
if (ctxt.getTowersRemaining() <= 2) {
|
||||
// TODO: verify if this condition is the same for BGV
|
||||
throw std::runtime_error(
|
||||
fmt::format("Insufficient number of towers remaining to perform a "
|
||||
"multiplication = {}",
|
||||
ctxt.getTowersRemaining()));
|
||||
}
|
||||
size_t final_size = ctxt.cipher->GetCryptoContext()->GetRingDimension() / 2;
|
||||
tileVector(vals, final_size);
|
||||
auto ptxt = ctxt.cipher->GetCryptoContext()->MakePackedPlaintext(vals);
|
||||
ctxt.cipher = ctxt.cipher->GetCryptoContext()->EvalMult(ctxt.cipher, ptxt);
|
||||
return ctxt;
|
||||
}
|
||||
|
||||
BGVCiphertext operator*=(BGVCiphertext &ctxt,
|
||||
const boost::python::list &pyvals) {
|
||||
std::vector<int64_t> vals = pythonListToCppLongIntVector(pyvals);
|
||||
return ctxt *= vals;
|
||||
}
|
||||
|
||||
BGVCiphertext operator*=(BGVCiphertext &ctxt,
|
||||
const boost::python::numpy::ndarray &pyvals) {
|
||||
std::vector<int64_t> vals = numpyListToCppLongIntVector(pyvals);
|
||||
return ctxt *= vals;
|
||||
}
|
||||
|
||||
BGVCiphertext operator*(BGVCiphertext ctxt, int64_t val) { return ctxt *= val; }
|
||||
|
||||
BGVCiphertext operator*(int64_t val, BGVCiphertext ctxt) { return ctxt *= val; }
|
||||
|
||||
BGVCiphertext operator*(const std::vector<int64_t> &vals, BGVCiphertext ctxt) {
|
||||
return ctxt *= vals;
|
||||
}
|
||||
|
||||
BGVCiphertext operator*(BGVCiphertext ctxt, const std::vector<int64_t> &vals) {
|
||||
return ctxt *= vals;
|
||||
}
|
||||
|
||||
BGVCiphertext operator*(BGVCiphertext ctxt, const boost::python::list &vals) {
|
||||
return ctxt *= vals;
|
||||
}
|
||||
|
||||
BGVCiphertext operator*(const boost::python::list &vals, BGVCiphertext ctxt) {
|
||||
return ctxt *= vals;
|
||||
}
|
||||
|
||||
BGVCiphertext operator*(BGVCiphertext ctxt,
|
||||
const boost::python::numpy::ndarray &vals) {
|
||||
return ctxt *= vals;
|
||||
}
|
||||
|
||||
BGVCiphertext operator*(const boost::python::numpy::ndarray &vals,
|
||||
BGVCiphertext ctxt) {
|
||||
return ctxt *= vals;
|
||||
}
|
||||
|
||||
} // namespace pyOpenFHE_BGV
|
||||
214
src/bgv/BGV_key_operations.cpp
Normal file
214
src/bgv/BGV_key_operations.cpp
Normal file
@@ -0,0 +1,214 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
// encrypt, decrypt, keygeneration, and the like
|
||||
|
||||
#include <complex>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
// string formatting for exceptions
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/numpy.hpp>
|
||||
|
||||
#include "bgv/BGV_ciphertext_extension.hpp"
|
||||
#include "bgv/BGV_key_operations.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
using namespace boost::python;
|
||||
using namespace boost::python::numpy;
|
||||
using namespace lbcrypto;
|
||||
using namespace pyOpenFHE;
|
||||
|
||||
namespace pyOpenFHE_BGV {
|
||||
|
||||
BGVCryptoContext genBGVContext(usint multiplicativeDepth, usint batchSize,
|
||||
usint plaintextModulus, SecurityLevel stdLevel,
|
||||
usint ringDim) {
|
||||
CCParams<CryptoContextBGVRNS> parameters;
|
||||
parameters.SetMultiplicativeDepth(multiplicativeDepth);
|
||||
parameters.SetBatchSize(batchSize);
|
||||
parameters.SetPlaintextModulus(plaintextModulus);
|
||||
parameters.SetSecurityLevel(stdLevel);
|
||||
parameters.SetRingDim(ringDim);
|
||||
|
||||
// trying this out, this may fix ModRescale
|
||||
// parameters.SetScalingTechnique(FIXEDMANUAL);
|
||||
|
||||
CryptoContext<DCRTPoly> native_cc = GenCryptoContext(parameters);
|
||||
|
||||
// convert to BGVCryptoContext
|
||||
BGVCryptoContext cc = BGVCryptoContext(native_cc);
|
||||
std::cout << "BGV scheme is using ring dimension = " << cc.getRingDimension()
|
||||
<< std::endl;
|
||||
std::cout << "batch size = " << cc.getBatchSize() << std::endl;
|
||||
std::cout << "plaintext modulus = " << cc.getPlaintextModulus() << std::endl;
|
||||
|
||||
std::cout << std::endl;
|
||||
|
||||
return cc;
|
||||
}
|
||||
|
||||
// Encode a C++ vector into an OpenFHE Plaintext object
|
||||
Plaintext BGVCryptoContext::encode(std::vector<int64_t> vals) {
|
||||
if (vals.size() != context->GetEncodingParams()->GetBatchSize()) {
|
||||
std::string s =
|
||||
fmt::format("Provided vector has length = {}, but the CryptoContext "
|
||||
"batch size = {}",
|
||||
vals.size(), context->GetEncodingParams()->GetBatchSize());
|
||||
throw std::runtime_error(s);
|
||||
}
|
||||
// cyclically duplicate the input
|
||||
size_t final_size = context->GetRingDimension() / 2;
|
||||
tileVector(vals, final_size);
|
||||
// vanilla MakePackedPlaintext just takes an int64 vector
|
||||
Plaintext ptxt = context->MakePackedPlaintext(vals);
|
||||
return ptxt;
|
||||
}
|
||||
|
||||
// Encrypt with: private key and python list
|
||||
pyOpenFHE_BGV::BGVCiphertext
|
||||
BGVCryptoContext::encryptPrivate(const PrivateKey<DCRTPoly> &privateKey,
|
||||
const list &pyvals) {
|
||||
std::vector<int64_t> vals = pyOpenFHE::pythonListToCppLongIntVector(pyvals);
|
||||
auto ptxt = encode(vals);
|
||||
return pyOpenFHE_BGV::BGVCiphertext(context->Encrypt(privateKey, ptxt));
|
||||
}
|
||||
|
||||
// public key and python list
|
||||
pyOpenFHE_BGV::BGVCiphertext
|
||||
BGVCryptoContext::encryptPublic(const PublicKey<DCRTPoly> &publicKey,
|
||||
const list &pyvals) {
|
||||
std::vector<int64_t> vals = pyOpenFHE::pythonListToCppLongIntVector(pyvals);
|
||||
auto ptxt = encode(vals);
|
||||
return pyOpenFHE_BGV::BGVCiphertext(context->Encrypt(publicKey, ptxt));
|
||||
}
|
||||
|
||||
// private key and numpy array
|
||||
pyOpenFHE_BGV::BGVCiphertext
|
||||
BGVCryptoContext::encryptPrivate2(const PrivateKey<DCRTPoly> &privateKey,
|
||||
const ndarray &pyvals) {
|
||||
std::vector<int64_t> vals = pyOpenFHE::numpyListToCppLongIntVector(pyvals);
|
||||
auto ptxt = encode(vals);
|
||||
return pyOpenFHE_BGV::BGVCiphertext(context->Encrypt(privateKey, ptxt));
|
||||
}
|
||||
|
||||
// public key and numpy array
|
||||
pyOpenFHE_BGV::BGVCiphertext
|
||||
BGVCryptoContext::encryptPublic2(const PublicKey<DCRTPoly> &publicKey,
|
||||
const ndarray &pyvals) {
|
||||
std::vector<int64_t> vals = pyOpenFHE::numpyListToCppLongIntVector(pyvals);
|
||||
// std::cout << "hello 1" << std::endl;
|
||||
// std::cout << "vals size " << vals.size() << std::endl;
|
||||
// std::cout << "slots " << self.size << std::endl;
|
||||
auto ptxt = encode(vals);
|
||||
// std::cout << "hello 2" << std::endl;
|
||||
return pyOpenFHE_BGV::BGVCiphertext(context->Encrypt(publicKey, ptxt));
|
||||
}
|
||||
|
||||
ndarray BGVCryptoContext::zeroPadToBatchSize(std::vector<int64_t> vals) {
|
||||
size_t batch_size = context->GetEncodingParams()->GetBatchSize();
|
||||
if (vals.size() > batch_size) {
|
||||
std::string s =
|
||||
fmt::format("Provided vector has length = {}, but the CryptoContext "
|
||||
"batch size = {}",
|
||||
vals.size(), context->GetEncodingParams()->GetBatchSize());
|
||||
throw std::runtime_error(s);
|
||||
}
|
||||
vals.resize(batch_size, 0);
|
||||
return pyOpenFHE::cppLongIntVectorToNumpyList(vals);
|
||||
}
|
||||
|
||||
// python list
|
||||
ndarray BGVCryptoContext::zeroPadToBatchSizeList(const list &pyvals) {
|
||||
std::vector<int64_t> vals = pyOpenFHE::pythonListToCppLongIntVector(pyvals);
|
||||
return zeroPadToBatchSize(vals);
|
||||
}
|
||||
|
||||
// numpy array
|
||||
ndarray BGVCryptoContext::zeroPadToBatchSizeNumpy(const ndarray &pyvals) {
|
||||
std::vector<int64_t> vals = pyOpenFHE::numpyListToCppLongIntVector(pyvals);
|
||||
return zeroPadToBatchSize(vals);
|
||||
}
|
||||
|
||||
// again, usually decrypt takes a key, Ciphertext, and Plaintext reference
|
||||
// and fills in the plaintext ref and returns whether or not the decryption was
|
||||
// valid I want to give a key, BGVCiphertext wrapper, and receive a numpy array
|
||||
// this does nothing with the return value of decrypt, but maybe there could be
|
||||
// an error check here...
|
||||
ndarray BGVCryptoContext::decrypt(const PrivateKey<DCRTPoly> &privateKey,
|
||||
pyOpenFHE_BGV::BGVCiphertext &ctxt) {
|
||||
Plaintext ptxt;
|
||||
// level reduce to level2 before decrypting
|
||||
auto algo = ctxt.cipher->GetCryptoContext()->GetScheme();
|
||||
auto ctxt2 = algo->Compress(ctxt.cipher, 2);
|
||||
context->Decrypt(privateKey, ctxt2, &ptxt);
|
||||
ptxt->SetLength(ctxt.cipher->GetEncodingParameters()->GetBatchSize());
|
||||
// we can skip the weird copy step, there could be an optimization here...
|
||||
auto vals = ptxt->GetPackedValue();
|
||||
return pyOpenFHE::cppLongIntVectorToNumpyList(vals);
|
||||
}
|
||||
|
||||
/*
|
||||
BGV Bootstrapping functions
|
||||
*/
|
||||
void BGVCryptoContext::evalBootstrapSetup() {
|
||||
std::vector<uint32_t> bsgsDim = {0, 0};
|
||||
std::vector<uint32_t> levelBudget = {4, 4};
|
||||
|
||||
usint slots = context->GetEncodingParams()->GetBatchSize();
|
||||
context->EvalBootstrapSetup(levelBudget, bsgsDim, slots);
|
||||
}
|
||||
|
||||
void BGVCryptoContext::evalBootstrapKeyGen(
|
||||
const PrivateKey<DCRTPoly> &privateKey) {
|
||||
usint slots = context->GetEncodingParams()->GetBatchSize();
|
||||
// usint n = self.GetRingDimension();
|
||||
|
||||
context->EvalBootstrapKeyGen(privateKey, slots);
|
||||
}
|
||||
|
||||
boost::python::list
|
||||
BGVCryptoContext::evalBootstrapList(boost::python::list ctxts) {
|
||||
|
||||
std::vector<pyOpenFHE_BGV::BGVCiphertext> input_ctxts(len(ctxts));
|
||||
auto output_ctxts = pyOpenFHE::make_list(len(ctxts));
|
||||
|
||||
#pragma omp parallel for
|
||||
for (int i = 0; i < len(ctxts); ++i) {
|
||||
input_ctxts[i] = extract<pyOpenFHE_BGV::BGVCiphertext>(ctxts[i]);
|
||||
input_ctxts[i].cipher = context->EvalBootstrap(input_ctxts[i].cipher);
|
||||
output_ctxts[i] = input_ctxts[i];
|
||||
}
|
||||
|
||||
return output_ctxts;
|
||||
}
|
||||
|
||||
pyOpenFHE_BGV::BGVCiphertext
|
||||
BGVCryptoContext::evalBootstrap(pyOpenFHE_BGV::BGVCiphertext ctxt) {
|
||||
ctxt.cipher = context->EvalBootstrap(ctxt.cipher);
|
||||
return ctxt;
|
||||
}
|
||||
|
||||
// make rotation keys for all of the +/- powers-of-2
|
||||
// we should probably try and put all the scheme-agnostic functions somewhere
|
||||
// neutral reduce code duplication and C++ won't complain about it if we ever
|
||||
// link these libraries
|
||||
// TODO: that ^
|
||||
void BGVCryptoContext::evalPowerOf2RotationKeyGen(
|
||||
const PrivateKey<DCRTPoly> &privateKey) {
|
||||
int N = context->GetEncodingParams()->GetBatchSize();
|
||||
int M = context->GetRingDimension();
|
||||
N = std::min(N, M / 2);
|
||||
std::vector<int> index_list;
|
||||
int r = 1;
|
||||
while (r <= N) {
|
||||
index_list.push_back(r);
|
||||
index_list.push_back(-r);
|
||||
r *= 2;
|
||||
}
|
||||
context->EvalAtIndexKeyGen(privateKey, index_list, nullptr);
|
||||
}
|
||||
|
||||
} // namespace pyOpenFHE_BGV
|
||||
62
src/bgv/BGV_pickle.cpp
Normal file
62
src/bgv/BGV_pickle.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
/*
|
||||
define all the serialization functions which operate on our wrapped ciphertext,
|
||||
crypto context, and keys here
|
||||
*/
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/numpy.hpp>
|
||||
#include <boost/python/tuple.hpp>
|
||||
|
||||
#include "bgv/BGV_ciphertext_extension.hpp"
|
||||
#include "bgv/BGV_key_operations.hpp"
|
||||
#include "bgv/BGV_pickle.hpp"
|
||||
#include "bgv/serialization.hpp"
|
||||
#include "utils/enums_binding.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
// header files needed for serialization
|
||||
#include "ciphertext-ser.h"
|
||||
#include "cryptocontext-ser.h"
|
||||
#include "key/key-ser.h"
|
||||
#include "openfhe.h"
|
||||
#include "scheme/bgvrns/bgvrns-ser.h"
|
||||
|
||||
using namespace lbcrypto;
|
||||
|
||||
namespace pyOpenFHE_BGV {
|
||||
|
||||
boost::python::tuple
|
||||
BGVCiphertext_pickle_suite::getinitargs(const pyOpenFHE_BGV::BGVCiphertext &w) {
|
||||
return boost::python::make_tuple();
|
||||
}
|
||||
|
||||
boost::python::tuple
|
||||
BGVCiphertext_pickle_suite::getstate(const pyOpenFHE_BGV::BGVCiphertext &w) {
|
||||
PyObject *py_buffer =
|
||||
SerializeToBytes_Ciphertext(w, pyOpenFHE_BGV::SerType::JSON);
|
||||
boost::python::handle<> handle(py_buffer);
|
||||
auto object = boost::python::object(handle);
|
||||
return boost::python::make_tuple(object);
|
||||
}
|
||||
|
||||
void BGVCiphertext_pickle_suite::setstate(pyOpenFHE_BGV::BGVCiphertext &w,
|
||||
boost::python::tuple state) {
|
||||
using namespace boost::python;
|
||||
if (len(state) != 1) {
|
||||
PyErr_SetObject(
|
||||
PyExc_ValueError,
|
||||
("expected 1-item tuple in call to __setstate__; got %s" % state)
|
||||
.ptr());
|
||||
throw_error_already_set();
|
||||
}
|
||||
|
||||
auto ctxt =
|
||||
DeserializeFromBytes_Ciphertext(state[0], pyOpenFHE_BGV::SerType::JSON);
|
||||
w.cipher = ctxt.cipher;
|
||||
}
|
||||
|
||||
} // namespace pyOpenFHE_BGV
|
||||
525
src/bgv/BGV_serialization.cpp
Normal file
525
src/bgv/BGV_serialization.cpp
Normal file
@@ -0,0 +1,525 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
/*
|
||||
define all the serialization functions which operate on our wrapped ciphertext,
|
||||
crypto context, and keys here
|
||||
*/
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/numpy.hpp>
|
||||
|
||||
#include "bgv/BGV_ciphertext_extension.hpp"
|
||||
#include "bgv/BGV_key_operations.hpp"
|
||||
#include "bgv/serialization.hpp"
|
||||
#include "utils/enums_binding.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
// header files needed for serialization
|
||||
#include "ciphertext-ser.h"
|
||||
#include "cryptocontext-ser.h"
|
||||
#include "key/key-ser.h"
|
||||
#include "openfhe.h"
|
||||
#include "scheme/bgvrns/bgvrns-ser.h"
|
||||
|
||||
using namespace lbcrypto;
|
||||
|
||||
namespace pyOpenFHE_BGV {
|
||||
|
||||
PyObject *SerializeToBytes_Ciphertext(const pyOpenFHE_BGV::BGVCiphertext &obj,
|
||||
const pyOpenFHE_BGV::SerType sertype) {
|
||||
std::stringstream ss;
|
||||
|
||||
if (sertype == pyOpenFHE_BGV::SerType::BINARY) {
|
||||
Serial::Serialize(obj.cipher, ss, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_BGV::SerType::JSON) {
|
||||
Serial::Serialize(obj.cipher, ss, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
std::string result = ss.str();
|
||||
PyObject *pymemview = PyMemoryView_FromMemory((char *)result.c_str(),
|
||||
result.length(), PyBUF_READ);
|
||||
return PyBytes_FromObject(pymemview);
|
||||
}
|
||||
|
||||
pyOpenFHE_BGV::BGVCiphertext
|
||||
DeserializeFromBytes_Ciphertext(boost::python::object py_buffer,
|
||||
const pyOpenFHE_BGV::SerType sertype) {
|
||||
std::string object_classname = boost::python::extract<std::string>(
|
||||
py_buffer.attr("__class__").attr("__name__"));
|
||||
if (object_classname != "bytes") {
|
||||
throw std::runtime_error(
|
||||
"expected object of type bytes, instead received type: " +
|
||||
object_classname);
|
||||
}
|
||||
|
||||
std::string buffer = boost::python::extract<std::string>(py_buffer);
|
||||
std::stringstream ss(buffer);
|
||||
|
||||
Ciphertext<DCRTPoly> obj;
|
||||
|
||||
if (sertype == pyOpenFHE_BGV::SerType::BINARY) {
|
||||
Serial::Deserialize(obj, ss, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_BGV::SerType::JSON) {
|
||||
Serial::Deserialize(obj, ss, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
return pyOpenFHE_BGV::BGVCiphertext(obj);
|
||||
}
|
||||
|
||||
PyObject *SerializeToBytes_PublicKey(const PublicKey<DCRTPoly> &obj,
|
||||
const pyOpenFHE_BGV::SerType sertype) {
|
||||
std::stringstream ss;
|
||||
|
||||
if (sertype == pyOpenFHE_BGV::SerType::BINARY) {
|
||||
Serial::Serialize(obj, ss, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_BGV::SerType::JSON) {
|
||||
Serial::Serialize(obj, ss, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
std::string result = ss.str();
|
||||
PyObject *pymemview = PyMemoryView_FromMemory((char *)result.c_str(),
|
||||
result.length(), PyBUF_READ);
|
||||
return PyBytes_FromObject(pymemview);
|
||||
}
|
||||
|
||||
PublicKey<DCRTPoly>
|
||||
DeserializeFromBytes_PublicKey(boost::python::object py_buffer,
|
||||
const pyOpenFHE_BGV::SerType sertype) {
|
||||
std::string object_classname = boost::python::extract<std::string>(
|
||||
py_buffer.attr("__class__").attr("__name__"));
|
||||
if (object_classname != "bytes") {
|
||||
throw std::runtime_error(
|
||||
"expected object of type bytes, instead received type: " +
|
||||
object_classname);
|
||||
}
|
||||
|
||||
std::string buffer = boost::python::extract<std::string>(py_buffer);
|
||||
std::stringstream ss(buffer);
|
||||
|
||||
PublicKey<DCRTPoly> obj;
|
||||
|
||||
if (sertype == pyOpenFHE_BGV::SerType::BINARY) {
|
||||
Serial::Deserialize(obj, ss, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_BGV::SerType::JSON) {
|
||||
Serial::Deserialize(obj, ss, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
PyObject *SerializeToBytes_PrivateKey(const PrivateKey<DCRTPoly> &obj,
|
||||
const pyOpenFHE_BGV::SerType sertype) {
|
||||
std::stringstream ss;
|
||||
|
||||
if (sertype == pyOpenFHE_BGV::SerType::BINARY) {
|
||||
Serial::Serialize(obj, ss, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_BGV::SerType::JSON) {
|
||||
Serial::Serialize(obj, ss, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
std::string result = ss.str();
|
||||
PyObject *pymemview = PyMemoryView_FromMemory((char *)result.c_str(),
|
||||
result.length(), PyBUF_READ);
|
||||
return PyBytes_FromObject(pymemview);
|
||||
}
|
||||
|
||||
PrivateKey<DCRTPoly>
|
||||
DeserializeFromBytes_PrivateKey(boost::python::object py_buffer,
|
||||
const pyOpenFHE_BGV::SerType sertype) {
|
||||
std::string object_classname = boost::python::extract<std::string>(
|
||||
py_buffer.attr("__class__").attr("__name__"));
|
||||
if (object_classname != "bytes") {
|
||||
throw std::runtime_error(
|
||||
"expected object of type bytes, instead received type: " +
|
||||
object_classname);
|
||||
}
|
||||
|
||||
std::string buffer = boost::python::extract<std::string>(py_buffer);
|
||||
std::stringstream ss(buffer);
|
||||
|
||||
PrivateKey<DCRTPoly> obj;
|
||||
|
||||
if (sertype == pyOpenFHE_BGV::SerType::BINARY) {
|
||||
Serial::Deserialize(obj, ss, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_BGV::SerType::JSON) {
|
||||
Serial::Deserialize(obj, ss, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
PyObject *SerializeToBytes_EvalMultKey_CryptoContext(
|
||||
BGVCryptoContext &self, const pyOpenFHE_BGV::SerType sertype) {
|
||||
std::stringstream ss;
|
||||
|
||||
if (sertype == pyOpenFHE_BGV::SerType::BINARY) {
|
||||
self.context->SerializeEvalMultKey(ss, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_BGV::SerType::JSON) {
|
||||
self.context->SerializeEvalMultKey(ss, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
std::string result = ss.str();
|
||||
PyObject *pymemview = PyMemoryView_FromMemory((char *)result.c_str(),
|
||||
result.length(), PyBUF_READ);
|
||||
return PyBytes_FromObject(pymemview);
|
||||
}
|
||||
|
||||
bool DeserializeFromBytes_EvalMultKey_CryptoContext(
|
||||
BGVCryptoContext &self, boost::python::object py_buffer,
|
||||
const pyOpenFHE_BGV::SerType sertype) {
|
||||
std::string object_classname = boost::python::extract<std::string>(
|
||||
py_buffer.attr("__class__").attr("__name__"));
|
||||
if (object_classname != "bytes") {
|
||||
throw std::runtime_error(
|
||||
"expected object of type bytes, instead received type: " +
|
||||
object_classname);
|
||||
}
|
||||
|
||||
std::string buffer = boost::python::extract<std::string>(py_buffer);
|
||||
std::stringstream ss(buffer);
|
||||
|
||||
if (sertype == pyOpenFHE_BGV::SerType::BINARY) {
|
||||
self.context->DeserializeEvalMultKey(ss, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_BGV::SerType::JSON) {
|
||||
self.context->DeserializeEvalMultKey(ss, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
PyObject *SerializeToBytes_EvalAutomorphismKey_CryptoContext(
|
||||
BGVCryptoContext &self, const pyOpenFHE_BGV::SerType sertype) {
|
||||
std::stringstream ss;
|
||||
|
||||
if (sertype == pyOpenFHE_BGV::SerType::BINARY) {
|
||||
self.context->SerializeEvalAutomorphismKey(ss, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_BGV::SerType::JSON) {
|
||||
self.context->SerializeEvalAutomorphismKey(ss, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
std::string result = ss.str();
|
||||
PyObject *pymemview = PyMemoryView_FromMemory((char *)result.c_str(),
|
||||
result.length(), PyBUF_READ);
|
||||
return PyBytes_FromObject(pymemview);
|
||||
}
|
||||
|
||||
bool DeserializeFromBytes_EvalAutomorphismKey_CryptoContext(
|
||||
BGVCryptoContext &self, boost::python::object py_buffer,
|
||||
const pyOpenFHE_BGV::SerType sertype) {
|
||||
std::string object_classname = boost::python::extract<std::string>(
|
||||
py_buffer.attr("__class__").attr("__name__"));
|
||||
if (object_classname != "bytes") {
|
||||
throw std::runtime_error(
|
||||
"expected object of type bytes, instead received type: " +
|
||||
object_classname);
|
||||
}
|
||||
|
||||
std::string buffer = boost::python::extract<std::string>(py_buffer);
|
||||
std::stringstream ss(buffer);
|
||||
|
||||
if (sertype == pyOpenFHE_BGV::SerType::BINARY) {
|
||||
self.context->DeserializeEvalAutomorphismKey(ss, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_BGV::SerType::JSON) {
|
||||
self.context->DeserializeEvalAutomorphismKey(ss, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SerializeToFile_Ciphertext(const std::string &filename,
|
||||
const pyOpenFHE_BGV::BGVCiphertext &obj,
|
||||
const pyOpenFHE_BGV::SerType sertype) {
|
||||
bool success = false;
|
||||
if (sertype == pyOpenFHE_BGV::SerType::BINARY) {
|
||||
success = Serial::SerializeToFile(filename, obj.cipher,
|
||||
lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_BGV::SerType::JSON) {
|
||||
success =
|
||||
Serial::SerializeToFile(filename, obj.cipher, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
throw std::runtime_error(
|
||||
"Could not write serialized BGVCiphertext to file: ");
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool SerializeToFile_CryptoContext(const std::string &filename,
|
||||
const BGVCryptoContext &obj,
|
||||
const pyOpenFHE_BGV::SerType sertype) {
|
||||
// throw std::runtime_error("This function is disabled as CryptoContext
|
||||
// Deserialization is broken.");
|
||||
|
||||
bool success = false;
|
||||
if (sertype == pyOpenFHE_BGV::SerType::BINARY) {
|
||||
success = Serial::SerializeToFile(filename, obj, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_BGV::SerType::JSON) {
|
||||
success = Serial::SerializeToFile(filename, obj, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
throw std::runtime_error(
|
||||
"Could not write serialized CryptoContext to file: " + filename);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool SerializeToFile_EvalMultKey_CryptoContext(
|
||||
BGVCryptoContext &self, const std::string &filename,
|
||||
const pyOpenFHE_BGV::SerType sertype) {
|
||||
std::ofstream multKeyFile(filename, std::ios::out | std::ios::binary);
|
||||
bool success = false;
|
||||
|
||||
if (!multKeyFile.is_open()) {
|
||||
throw std::runtime_error(
|
||||
"Could not write serialized EvalMult / relinearization keys to file: " +
|
||||
filename);
|
||||
}
|
||||
|
||||
if (sertype == pyOpenFHE_BGV::SerType::BINARY) {
|
||||
success = self.context->SerializeEvalMultKey(multKeyFile,
|
||||
lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_BGV::SerType::JSON) {
|
||||
success = self.context->SerializeEvalMultKey(multKeyFile,
|
||||
lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
multKeyFile.close();
|
||||
|
||||
if (!success) {
|
||||
throw std::runtime_error(
|
||||
"Could not write serialized EvalMult / relinearization keys to file: " +
|
||||
filename);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool SerializeToFile_EvalAutomorphismKey_CryptoContext(
|
||||
BGVCryptoContext &self, const std::string &filename,
|
||||
const pyOpenFHE_BGV::SerType sertype) {
|
||||
std::ofstream multKeyFile(filename, std::ios::out | std::ios::binary);
|
||||
bool success = false;
|
||||
|
||||
if (!multKeyFile.is_open()) {
|
||||
throw std::runtime_error("Could not write serialized EvalAutomorphism / "
|
||||
"rotation keys to file: " +
|
||||
filename);
|
||||
}
|
||||
|
||||
if (sertype == pyOpenFHE_BGV::SerType::BINARY) {
|
||||
success = self.context->SerializeEvalAutomorphismKey(
|
||||
multKeyFile, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_BGV::SerType::JSON) {
|
||||
success = self.context->SerializeEvalAutomorphismKey(
|
||||
multKeyFile, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
multKeyFile.close();
|
||||
|
||||
if (!success) {
|
||||
throw std::runtime_error("Could not write serialized EvalAutomorphism / "
|
||||
"rotation keys to file: " +
|
||||
filename);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool SerializeToFile_PublicKey(const std::string &filename,
|
||||
const PublicKey<DCRTPoly> &obj,
|
||||
const pyOpenFHE_BGV::SerType sertype) {
|
||||
bool success = false;
|
||||
if (sertype == pyOpenFHE_BGV::SerType::BINARY) {
|
||||
success = Serial::SerializeToFile(filename, obj, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_BGV::SerType::JSON) {
|
||||
success = Serial::SerializeToFile(filename, obj, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
throw std::runtime_error("Could not write serialized PublicKey to file: " +
|
||||
filename);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool SerializeToFile_PrivateKey(const std::string &filename,
|
||||
const PrivateKey<DCRTPoly> &obj,
|
||||
const pyOpenFHE_BGV::SerType sertype) {
|
||||
bool success = false;
|
||||
if (sertype == pyOpenFHE_BGV::SerType::BINARY) {
|
||||
success = Serial::SerializeToFile(filename, obj, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_BGV::SerType::JSON) {
|
||||
success = Serial::SerializeToFile(filename, obj, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
throw std::runtime_error("Could not write serialized PrivateKey to file: " +
|
||||
filename);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
pyOpenFHE_BGV::BGVCiphertext
|
||||
DeserializeFromFile_Ciphertext(const std::string &filename,
|
||||
const pyOpenFHE_BGV::SerType sertype) {
|
||||
bool success = false;
|
||||
Ciphertext<DCRTPoly> obj;
|
||||
|
||||
if (sertype == pyOpenFHE_BGV::SerType::BINARY) {
|
||||
success =
|
||||
Serial::DeserializeFromFile(filename, obj, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_BGV::SerType::JSON) {
|
||||
success =
|
||||
Serial::DeserializeFromFile(filename, obj, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
throw std::runtime_error("Could not read serialized data from file: " +
|
||||
filename);
|
||||
}
|
||||
|
||||
return pyOpenFHE_BGV::BGVCiphertext(obj);
|
||||
}
|
||||
|
||||
CryptoContext<DCRTPoly>
|
||||
DeserializeFromFile_CryptoContext(const std::string &filename,
|
||||
const pyOpenFHE_BGV::SerType sertype) {
|
||||
throw std::runtime_error(
|
||||
"This function is disabled as CryptoContext Deserialization is broken.");
|
||||
|
||||
std::cout << "hello we started the deserialization function" << std::endl;
|
||||
|
||||
lbcrypto::CryptoContextFactory<lbcrypto::DCRTPoly>::ReleaseAllContexts();
|
||||
|
||||
std::cout << "contexts are released" << std::endl;
|
||||
|
||||
CryptoContext<DCRTPoly> obj;
|
||||
bool success = false;
|
||||
|
||||
std::cout << "new contexts is created" << std::endl;
|
||||
|
||||
// obj->ClearEvalMultKeys();
|
||||
// obj->ClearEvalAutomorphismKeys();
|
||||
|
||||
if (sertype == pyOpenFHE_BGV::SerType::BINARY) {
|
||||
success =
|
||||
Serial::DeserializeFromFile(filename, obj, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_BGV::SerType::JSON) {
|
||||
success =
|
||||
Serial::DeserializeFromFile(filename, obj, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
throw std::runtime_error("Could not read serialized data from file: " +
|
||||
filename);
|
||||
}
|
||||
|
||||
std::cout << "deserialization maybe happened" << std::endl;
|
||||
|
||||
auto cc = obj;
|
||||
std::cout << "BGV scheme is using ring dimension = " << cc->GetRingDimension()
|
||||
<< std::endl;
|
||||
std::cout << "batch size = " << cc->GetEncodingParams()->GetBatchSize()
|
||||
<< std::endl;
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
PublicKey<DCRTPoly>
|
||||
DeserializeFromFile_PublicKey(const std::string &filename,
|
||||
const pyOpenFHE_BGV::SerType sertype) {
|
||||
PublicKey<DCRTPoly> obj;
|
||||
bool success = false;
|
||||
|
||||
if (sertype == pyOpenFHE_BGV::SerType::BINARY) {
|
||||
success =
|
||||
Serial::DeserializeFromFile(filename, obj, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_BGV::SerType::JSON) {
|
||||
success =
|
||||
Serial::DeserializeFromFile(filename, obj, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
throw std::runtime_error("Could not read serialized data from file: " +
|
||||
filename);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
PrivateKey<DCRTPoly>
|
||||
DeserializeFromFile_PrivateKey(const std::string &filename,
|
||||
const pyOpenFHE_BGV::SerType sertype) {
|
||||
PrivateKey<DCRTPoly> obj;
|
||||
bool success = false;
|
||||
|
||||
if (sertype == pyOpenFHE_BGV::SerType::BINARY) {
|
||||
success =
|
||||
Serial::DeserializeFromFile(filename, obj, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_BGV::SerType::JSON) {
|
||||
success =
|
||||
Serial::DeserializeFromFile(filename, obj, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
throw std::runtime_error("Could not read serialized data from file: " +
|
||||
filename);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
bool DeserializeFromFile_EvalMultKey_CryptoContext(
|
||||
BGVCryptoContext &self, const std::string &filename,
|
||||
const pyOpenFHE_BGV::SerType sertype) {
|
||||
std::ifstream multKeyFile(filename, std::ios::in | std::ios::binary);
|
||||
bool success = false;
|
||||
|
||||
if (!multKeyFile.is_open()) {
|
||||
throw std::runtime_error(
|
||||
"Error reading EvalMult / relinearization keys from file: " + filename);
|
||||
}
|
||||
|
||||
if (sertype == pyOpenFHE_BGV::SerType::BINARY) {
|
||||
success = self.context->DeserializeEvalMultKey(multKeyFile,
|
||||
lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_BGV::SerType::JSON) {
|
||||
success = self.context->DeserializeEvalMultKey(multKeyFile,
|
||||
lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
multKeyFile.close();
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool DeserializeFromFile_EvalAutomorphismKey_CryptoContext(
|
||||
BGVCryptoContext &self, const std::string &filename,
|
||||
const pyOpenFHE_BGV::SerType sertype) {
|
||||
std::ifstream multKeyFile(filename, std::ios::in | std::ios::binary);
|
||||
bool success = false;
|
||||
|
||||
if (!multKeyFile.is_open()) {
|
||||
throw std::runtime_error(
|
||||
"Error reading EvalAutomorphism / rotation keys from file: " +
|
||||
filename);
|
||||
}
|
||||
|
||||
if (sertype == pyOpenFHE_BGV::SerType::BINARY) {
|
||||
success = self.context->DeserializeEvalAutomorphismKey(
|
||||
multKeyFile, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_BGV::SerType::JSON) {
|
||||
success = self.context->DeserializeEvalAutomorphismKey(
|
||||
multKeyFile, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
multKeyFile.close();
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
} // namespace pyOpenFHE_BGV
|
||||
69
src/bgv/BGV_serialization_bindings.cpp
Normal file
69
src/bgv/BGV_serialization_bindings.cpp
Normal file
@@ -0,0 +1,69 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
// bindings for the serialization functions
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/numpy.hpp>
|
||||
|
||||
#include "openfhe.h"
|
||||
|
||||
#include "bgv/serialization.hpp"
|
||||
|
||||
namespace pyOpenFHE_BGV {
|
||||
|
||||
void export_BGV_serialization_boost() {
|
||||
|
||||
enum_<pyOpenFHE_BGV::SerType>("SerType")
|
||||
.value("BINARY", pyOpenFHE_BGV::SerType::BINARY)
|
||||
.value("JSON", pyOpenFHE_BGV::SerType::JSON);
|
||||
|
||||
/*
|
||||
TODO: is there a better way to handle the Deserialize operation?
|
||||
*/
|
||||
def("SerializeToBytes", SerializeToBytes_Ciphertext);
|
||||
def("SerializeToBytes", SerializeToBytes_PublicKey);
|
||||
def("SerializeToBytes", SerializeToBytes_PrivateKey);
|
||||
|
||||
def("SerializeToFile", SerializeToFile_Ciphertext);
|
||||
def("SerializeToFile", SerializeToFile_PublicKey);
|
||||
def("SerializeToFile", SerializeToFile_PrivateKey);
|
||||
|
||||
def("DeserializeFromBytes_Ciphertext", DeserializeFromBytes_Ciphertext);
|
||||
def("DeserializeFromBytes_PublicKey", DeserializeFromBytes_PublicKey);
|
||||
def("DeserializeFromBytes_PrivateKey", DeserializeFromBytes_PrivateKey);
|
||||
|
||||
def("DeserializeFromFile_Ciphertext", DeserializeFromFile_Ciphertext);
|
||||
def("DeserializeFromFile_PublicKey", DeserializeFromFile_PublicKey);
|
||||
def("DeserializeFromFile_PrivateKey", DeserializeFromFile_PrivateKey);
|
||||
|
||||
/* TODO: disabled */
|
||||
def("DeserializeFromFile_CryptoContext", &DeserializeFromFile_CryptoContext);
|
||||
|
||||
/*
|
||||
The difference is naming between these and the above functions is unfortunate,
|
||||
but hard to avoid given how PALISADE works in C++.
|
||||
Deserializing these keys requires being passed a CryptoContext object,
|
||||
while the above keys do not.
|
||||
*/
|
||||
def("SerializeToFile_EvalMultKey_CryptoContext",
|
||||
&SerializeToFile_EvalMultKey_CryptoContext);
|
||||
def("SerializeToFile_EvalAutomorphismKey_CryptoContext",
|
||||
&SerializeToFile_EvalAutomorphismKey_CryptoContext);
|
||||
|
||||
def("DeserializeFromFile_EvalMultKey_CryptoContext",
|
||||
&DeserializeFromFile_EvalMultKey_CryptoContext);
|
||||
def("DeserializeFromFile_EvalAutomorphismKey_CryptoContext",
|
||||
&DeserializeFromFile_EvalAutomorphismKey_CryptoContext);
|
||||
|
||||
def("SerializeToBytes_EvalMultKey_CryptoContext",
|
||||
&SerializeToBytes_EvalMultKey_CryptoContext);
|
||||
def("SerializeToBytes_EvalAutomorphismKey_CryptoContext",
|
||||
&SerializeToBytes_EvalAutomorphismKey_CryptoContext);
|
||||
|
||||
def("DeserializeFromBytes_EvalMultKey_CryptoContext",
|
||||
&DeserializeFromBytes_EvalMultKey_CryptoContext);
|
||||
def("DeserializeFromBytes_EvalAutomorphismKey_CryptoContext",
|
||||
&DeserializeFromBytes_EvalAutomorphismKey_CryptoContext);
|
||||
}
|
||||
|
||||
} // namespace pyOpenFHE_BGV
|
||||
77
src/ckks/CKKS_CryptoContext_bindings.cpp
Normal file
77
src/ckks/CKKS_CryptoContext_bindings.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
// bindings for the CryptoContext class
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/numpy.hpp>
|
||||
|
||||
#include "openfhe.h"
|
||||
|
||||
#include "ckks/CKKS_key_operations.hpp"
|
||||
|
||||
using namespace boost::python;
|
||||
using namespace boost::python::numpy;
|
||||
using namespace lbcrypto;
|
||||
|
||||
namespace boost {
|
||||
template <typename T> T *get_pointer(std::shared_ptr<T> p) { return p.get(); }
|
||||
} // namespace boost
|
||||
|
||||
namespace pyOpenFHE_CKKS {
|
||||
|
||||
// boost has trouble with overloaded methods
|
||||
// best option is to define these function pointers for different method
|
||||
// signatures then pass them later when def'ing the module other option: use
|
||||
// lambda function pointers that basically do the same thing but in-line
|
||||
void (CryptoContextImpl<DCRTPoly>::*Enable1)(PKESchemeFeature) =
|
||||
&CryptoContextImpl<DCRTPoly>::Enable;
|
||||
|
||||
// Minimum number of arguments is 3, maximum is 5 for genCKKSContext
|
||||
BOOST_PYTHON_FUNCTION_OVERLOADS(CKKS_factory_overloads, genCKKSContext, 3, 5)
|
||||
|
||||
void export_CKKS_CryptoContext_boost() {
|
||||
|
||||
class_<pyOpenFHE_CKKS::CKKSCryptoContext>("CKKSCryptoContext",
|
||||
init<CryptoContext<DCRTPoly>>())
|
||||
.def("enable", &CKKSCryptoContext::enable)
|
||||
.def("keyGen", &CKKSCryptoContext::keyGen)
|
||||
.def("evalMultKeyGen", &CKKSCryptoContext::evalMultKeyGen)
|
||||
.def("evalMultKeysGen", &CKKSCryptoContext::evalMultKeysGen)
|
||||
.def("keySwitchGen", &CKKSCryptoContext::keySwitchGen)
|
||||
.def("getSchemeID", &CKKSCryptoContext::getSchemeId)
|
||||
|
||||
/*
|
||||
boost doesn't accept lambdas as member functions
|
||||
but it *does* accept function pointers
|
||||
and the "+" turns this lambda into a function pointer
|
||||
this is only possible because it doesn't capture any values
|
||||
*/
|
||||
.def("evalAtIndexKeyGen", &CKKSCryptoContext::evalAtIndexKeyGen1)
|
||||
.def("evalAtIndexKeyGen", &CKKSCryptoContext::evalAtIndexKeyGen2)
|
||||
.def("evalPowerOf2RotationKeyGen",
|
||||
&CKKSCryptoContext::evalPowerOf2RotationKeyGen)
|
||||
.def("evalBootstrapSetup", &CKKSCryptoContext::evalBootstrapSetup)
|
||||
.def("evalBootstrapKeyGen", &CKKSCryptoContext::evalBootstrapKeyGen)
|
||||
.def("evalBootstrap", &CKKSCryptoContext::evalBootstrap)
|
||||
.def("evalBootstrap", &CKKSCryptoContext::evalBootstrapList)
|
||||
.def("evalMetaBootstrap", &CKKSCryptoContext::evalMetaBootstrap)
|
||||
.def("evalMetaBootstrap", &CKKSCryptoContext::evalMetaBootstrapList)
|
||||
.def("encrypt", &CKKSCryptoContext::encryptPublic)
|
||||
.def("encrypt", &CKKSCryptoContext::encryptPrivate)
|
||||
.def("encrypt", &CKKSCryptoContext::encryptPublic2)
|
||||
.def("encrypt", &CKKSCryptoContext::encryptPrivate2)
|
||||
.def("decrypt", &CKKSCryptoContext::decrypt)
|
||||
.def("getRingDimension", &CKKSCryptoContext::getRingDimension)
|
||||
.def("getBatchSize", &CKKSCryptoContext::getBatchSize)
|
||||
|
||||
.def("zeroPadToBatchSize", &CKKSCryptoContext::zeroPadToBatchSizeList)
|
||||
.def("zeroPadToBatchSize", &CKKSCryptoContext::zeroPadToBatchSizeNumpy);
|
||||
|
||||
def("genCryptoContextCKKS", &genCKKSContext,
|
||||
CKKS_factory_overloads(
|
||||
(arg("multiplicativeDepth"), arg("scalingFactorBits"),
|
||||
arg("batchSize"), arg("stdLevel") = SecurityLevel::HEStd_128_classic,
|
||||
arg("ringDim") = 0)));
|
||||
}
|
||||
|
||||
} // namespace pyOpenFHE_CKKS
|
||||
114
src/ckks/CKKS_bindings.cpp
Normal file
114
src/ckks/CKKS_bindings.cpp
Normal file
@@ -0,0 +1,114 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
// python bindings for PALISADE's CKKS functionality
|
||||
|
||||
#include <complex>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
// string formatting for exceptions
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/numpy.hpp>
|
||||
|
||||
#include "openfhe.h"
|
||||
|
||||
#include "ckks/CKKS_ciphertext_extension.hpp"
|
||||
#include "ckks/CKKS_pickle.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
using namespace boost::python;
|
||||
using namespace boost::python::numpy;
|
||||
using namespace lbcrypto;
|
||||
|
||||
namespace pyOpenFHE_CKKS {
|
||||
|
||||
BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(CKKS_Rescale_overloads,
|
||||
pyOpenFHE_CKKS::CKKSCiphertext::Rescale,
|
||||
0, 1)
|
||||
|
||||
void export_CKKS_Ciphertext_boost() {
|
||||
|
||||
class_<pyOpenFHE_CKKS::CKKSCiphertext>("CKKSCiphertext",
|
||||
init<Ciphertext<DCRTPoly>>())
|
||||
.def(init<const pyOpenFHE_CKKS::CKKSCiphertext &>())
|
||||
.def(init<>()) // for pickle
|
||||
.def("getPlaintextModulus",
|
||||
&pyOpenFHE_CKKS::CKKSCiphertext::getPlaintextModulus)
|
||||
.def("getScalingFactor",
|
||||
&pyOpenFHE_CKKS::CKKSCiphertext::getScalingFactor)
|
||||
.def("getBatchSize", &pyOpenFHE_CKKS::CKKSCiphertext::getBatchSize)
|
||||
.def("getMultLevel", &pyOpenFHE_CKKS::CKKSCiphertext::getMultLevel)
|
||||
.def("getTowersRemaining",
|
||||
&pyOpenFHE_CKKS::CKKSCiphertext::getTowersRemaining)
|
||||
.def("getCryptoContext",
|
||||
&pyOpenFHE_CKKS::CKKSCiphertext::getCryptoContext)
|
||||
.def("Rescale", &pyOpenFHE_CKKS::CKKSCiphertext::Rescale,
|
||||
CKKS_Rescale_overloads((arg("levels") = 1)))
|
||||
.def("compress", &pyOpenFHE_CKKS::CKKSCiphertext::compress)
|
||||
|
||||
.def("RotateEvalAtIndex", &pyOpenFHE_CKKS::CKKSRotateEvalAtIndex)
|
||||
.def("HoistedRotations", &pyOpenFHE_CKKS::CKKSHoistedRotations)
|
||||
.def("MultiplySingletonDirect",
|
||||
&pyOpenFHE_CKKS::CKKSMultiplySingletonDirect)
|
||||
.def("MultiplySingletonIntDoubleAndAdd",
|
||||
&pyOpenFHE_CKKS::CKKSMultiplySingletonIntDoubleAndAdd)
|
||||
|
||||
.def("__copy__",
|
||||
+[](pyOpenFHE_CKKS::CKKSCiphertext &self) {
|
||||
return pyOpenFHE_CKKS::CKKSCiphertext(self);
|
||||
})
|
||||
// here we specify every possible type interaction:
|
||||
.def(self == self)
|
||||
.def(self != self)
|
||||
.def(self += self)
|
||||
.def(self + self)
|
||||
.def(self -= self)
|
||||
.def(self - self)
|
||||
.def(self *= self)
|
||||
.def(self * self)
|
||||
.def(self <<= int())
|
||||
.def(self >>= int())
|
||||
.def(self << int())
|
||||
.def(self >> int())
|
||||
.def(self += double())
|
||||
.def(self + double())
|
||||
.def(double() + self)
|
||||
.def(self += other<list>())
|
||||
.def(self += other<ndarray>())
|
||||
.def(self + other<list>())
|
||||
.def(self + other<ndarray>())
|
||||
.def(other<list>() + self)
|
||||
.def(other<ndarray>() + self)
|
||||
.def(self -= other<list>())
|
||||
.def(self -= other<ndarray>())
|
||||
.def(self - other<list>())
|
||||
.def(self - other<ndarray>())
|
||||
.def(-self)
|
||||
.def(other<list>() - self)
|
||||
.def(other<ndarray>() - self)
|
||||
.def(self -= double())
|
||||
.def(self - double())
|
||||
.def(double() - self)
|
||||
// .def(self *= int())
|
||||
// .def(self * int())
|
||||
// .def(int() * self)
|
||||
.def(self *= double())
|
||||
.def(self * double())
|
||||
.def(double() * self)
|
||||
.def(self *= other<list>())
|
||||
.def(self *= other<ndarray>())
|
||||
.def(self * other<list>())
|
||||
.def(self * other<ndarray>())
|
||||
.def(other<list>() * self)
|
||||
.def(other<ndarray>() * self)
|
||||
|
||||
// prevents weird numpy broadcasting
|
||||
.def("__array_ufunc__", &pyOpenFHE_CKKS::CKKSCiphertext::array_ufunc)
|
||||
|
||||
.def_pickle(CKKSCiphertext_pickle_suite())
|
||||
.attr("__module__") = "pyOpenFHE.CKKS";
|
||||
}
|
||||
|
||||
} // namespace pyOpenFHE_CKKS
|
||||
471
src/ckks/CKKS_ciphertext_extension.cpp
Normal file
471
src/ckks/CKKS_ciphertext_extension.cpp
Normal file
@@ -0,0 +1,471 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
#include "openfhe.h"
|
||||
|
||||
#include <complex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// string formatting for exceptions
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/numpy.hpp>
|
||||
|
||||
#include "ckks/CKKS_ciphertext_extension.hpp"
|
||||
#include "ckks/CKKS_key_operations.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/rotate_utils.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
using namespace lbcrypto;
|
||||
using namespace boost::python;
|
||||
using namespace boost::python::numpy;
|
||||
using namespace pyOpenFHE;
|
||||
|
||||
namespace pyOpenFHE_CKKS {
|
||||
|
||||
CKKSCryptoContext CKKSCiphertext::getCryptoContext(void) const {
|
||||
return CKKSCryptoContext(cipher->GetCryptoContext());
|
||||
}
|
||||
|
||||
CKKSCiphertext CKKSCiphertext::array_ufunc(
|
||||
const boost::python::object ufunc, const boost::python::str method,
|
||||
const boost::python::numpy::ndarray &vals, const CKKSCiphertext &cipher) {
|
||||
// because this is defined, numpy will call into this instead of ufunc
|
||||
std::string op =
|
||||
boost::python::extract<std::string>(ufunc.attr("__name__"))();
|
||||
|
||||
if (op == "multiply") {
|
||||
return cipher * vals;
|
||||
} else if (op == "add") {
|
||||
return cipher + vals;
|
||||
} else {
|
||||
throw pyOpenFHE::not_implemented_exception(
|
||||
fmt::format("operator {} between ndarray and CKKSCiphertext", op));
|
||||
}
|
||||
}
|
||||
|
||||
// a whole load of operators
|
||||
// unfortunately C++'s operator lookup isn't smart enough to infer double +
|
||||
// CKKSCiphertext from CKKSCiphertext + double or I guess it's more that it
|
||||
// doesn't assume commutativity either way, we need to specify ALL of these, and
|
||||
// then specify them again in the bindings...
|
||||
|
||||
bool operator==(const CKKSCiphertext &c1, const CKKSCiphertext &c2) {
|
||||
return c1.cipher == c2.cipher;
|
||||
}
|
||||
|
||||
bool operator!=(const CKKSCiphertext &c1, const CKKSCiphertext &c2) {
|
||||
return !(c1 == c2);
|
||||
}
|
||||
|
||||
CKKSCiphertext operator+=(CKKSCiphertext &c1, const CKKSCiphertext &c2) {
|
||||
c1.cipher += c2.cipher;
|
||||
return c1;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator+(CKKSCiphertext c1, const CKKSCiphertext &c2) {
|
||||
return c1 += c2;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator-=(CKKSCiphertext &c1, const CKKSCiphertext &c2) {
|
||||
c1.cipher -= c2.cipher;
|
||||
return c1;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator-(CKKSCiphertext c1, const CKKSCiphertext &c2) {
|
||||
return c1 -= c2;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator*=(CKKSCiphertext &c1, const CKKSCiphertext &c2) {
|
||||
if ((c1.getTowersRemaining() <= 2) || (c2.getTowersRemaining() <= 2)) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("Insufficient number of towers remaining to perform a "
|
||||
"multiplication = {}, {}",
|
||||
c1.getTowersRemaining(), c2.getTowersRemaining()));
|
||||
}
|
||||
c1.cipher *= c2.cipher;
|
||||
return c1;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator*(CKKSCiphertext c1, const CKKSCiphertext &c2) {
|
||||
return c1 *= c2;
|
||||
}
|
||||
|
||||
/*
|
||||
uses the positive and negative power-of-2 decomposition
|
||||
e.g. decompose(15) = {16, -1}
|
||||
*/
|
||||
CKKSCiphertext EvalRotatePositiveNegativePow2(CKKSCiphertext &ctxt, int r) {
|
||||
if (r == 0) {
|
||||
// no change
|
||||
return ctxt;
|
||||
} else {
|
||||
// cyclic packing
|
||||
int N = ctxt.cipher->GetEncodingParameters()->GetBatchSize();
|
||||
|
||||
if (abs(r) > N) {
|
||||
throw std::runtime_error(fmt::format(
|
||||
"rotation value = {} is too large compared to batch size = {}", r,
|
||||
N));
|
||||
}
|
||||
|
||||
std::vector<int> po2s = po2Decompose(r);
|
||||
|
||||
for (int i : po2s) {
|
||||
ctxt.cipher =
|
||||
ctxt.cipher->GetCryptoContext()->EvalAtIndex(ctxt.cipher, i);
|
||||
}
|
||||
return ctxt;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
uses the positive power-of-2 decomposition
|
||||
e.g. decompose(15) = {8, 4, 2, 1}
|
||||
*/
|
||||
CKKSCiphertext EvalRotatePositivePow2(CKKSCiphertext &ctxt, int r) {
|
||||
if (r == 0) {
|
||||
// no change
|
||||
return ctxt;
|
||||
} else {
|
||||
// cyclic packing
|
||||
int N = ctxt.cipher->GetEncodingParameters()->GetBatchSize();
|
||||
|
||||
if (abs(r) > N) {
|
||||
throw std::runtime_error(fmt::format(
|
||||
"rotation value = {} is too large compared to batch size = {}", r,
|
||||
N));
|
||||
}
|
||||
|
||||
int mult = (r > 0) ? 1 : -1;
|
||||
std::vector<int> po2s = sumOfPo2s(abs(r));
|
||||
|
||||
for (int i : po2s) {
|
||||
ctxt.cipher = ctxt.cipher->GetCryptoContext()->EvalAtIndex(
|
||||
ctxt.cipher, mult * (1 << i));
|
||||
}
|
||||
return ctxt;
|
||||
}
|
||||
}
|
||||
|
||||
CKKSCiphertext CKKSRotateEvalAtIndex(CKKSCiphertext ctxt, int r) {
|
||||
ctxt.cipher = ctxt.cipher->GetCryptoContext()->EvalAtIndex(ctxt.cipher, r);
|
||||
return ctxt;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator<<=(CKKSCiphertext &ctxt, int r) {
|
||||
return EvalRotatePositiveNegativePow2(ctxt, r);
|
||||
}
|
||||
|
||||
/*
|
||||
TODO
|
||||
need error checking to see if that rotation key actually exists.
|
||||
Also this function should support integer numpy arrays as well
|
||||
*/
|
||||
boost::python::list CKKSHoistedRotations(const CKKSCiphertext &ctxt,
|
||||
const boost::python::list &pylist) {
|
||||
std::vector<int32_t> rotations = pyOpenFHE::pythonListToCppIntVector(pylist);
|
||||
|
||||
auto cc = ctxt.cipher->GetCryptoContext();
|
||||
auto cPrecomp = cc->EvalFastRotationPrecompute(ctxt.cipher);
|
||||
|
||||
// M is the cyclotomic order and we need it to call EvalFastRotation
|
||||
uint32_t N = cc->GetRingDimension();
|
||||
uint32_t M = 2 * N;
|
||||
|
||||
// initialize list to correct length
|
||||
boost::python::object empty_item = boost::python::object(); // None
|
||||
boost::python::list result;
|
||||
result.append(empty_item);
|
||||
result *= rotations.size();
|
||||
|
||||
for (unsigned int i = 0; i < rotations.size(); i++) {
|
||||
result[i] = pyOpenFHE_CKKS::CKKSCiphertext(
|
||||
cc->EvalFastRotation(ctxt.cipher, rotations[i], M, cPrecomp));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator>>=(CKKSCiphertext &ctxt, double r) {
|
||||
return ctxt <<= (-r);
|
||||
}
|
||||
|
||||
CKKSCiphertext operator<<(CKKSCiphertext ctxt, double r) { return ctxt <<= r; }
|
||||
|
||||
CKKSCiphertext operator>>(CKKSCiphertext ctxt, double r) { return ctxt >>= r; }
|
||||
|
||||
CKKSCiphertext operator+=(CKKSCiphertext &ctxt, double val) {
|
||||
std::vector<double> vals = {val};
|
||||
size_t dn = ctxt.cipher->GetEncodingParameters()->GetBatchSize();
|
||||
tileVector(vals, dn);
|
||||
auto ptxt = ctxt.cipher->GetCryptoContext()->MakeCKKSPackedPlaintext(vals);
|
||||
ctxt.cipher = ctxt.cipher->GetCryptoContext()->EvalAdd(ctxt.cipher, ptxt);
|
||||
return ctxt;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator+(CKKSCiphertext ctxt, double val) {
|
||||
return ctxt += val;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator+(double val, CKKSCiphertext ctxt) {
|
||||
return ctxt += val;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator+=(CKKSCiphertext &ctxt, std::vector<double> vals) {
|
||||
size_t N = ctxt.cipher->GetEncodingParameters()->GetBatchSize();
|
||||
if (vals.size() != N) {
|
||||
std::string s = fmt::format("Provided vector has length = {}, but the "
|
||||
"CryptoContext batch size = {}",
|
||||
vals.size(), N);
|
||||
throw std::runtime_error(s);
|
||||
}
|
||||
// size_t final_size = ctxt.cipher->GetCryptoContext()->GetRingDimension() /
|
||||
// 2; tileVector(vals, final_size);
|
||||
auto ptxt = ctxt.cipher->GetCryptoContext()->MakeCKKSPackedPlaintext(vals);
|
||||
ctxt.cipher = ctxt.cipher->GetCryptoContext()->EvalAdd(ctxt.cipher, ptxt);
|
||||
return ctxt;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator+=(CKKSCiphertext &ctxt,
|
||||
const boost::python::list &pyvals) {
|
||||
std::vector<double> vals = pythonListToCppDoubleVector(pyvals);
|
||||
return ctxt += vals;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator+=(CKKSCiphertext &ctxt,
|
||||
const boost::python::numpy::ndarray &pyvals) {
|
||||
std::vector<double> vals = numpyListToCppDoubleVector(pyvals);
|
||||
return ctxt += vals;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator+(CKKSCiphertext ctxt, const std::vector<double> &vals) {
|
||||
return ctxt += vals;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator+(CKKSCiphertext ctxt, const boost::python::list &vals) {
|
||||
return ctxt += vals;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator+(CKKSCiphertext ctxt,
|
||||
const boost::python::numpy::ndarray &vals) {
|
||||
return ctxt += vals;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator+(const std::vector<double> &vals, CKKSCiphertext ctxt) {
|
||||
return ctxt += vals;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator+(const boost::python::list &vals, CKKSCiphertext ctxt) {
|
||||
return ctxt += vals;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator+(const boost::python::numpy::ndarray &vals,
|
||||
CKKSCiphertext ctxt) {
|
||||
return ctxt += vals;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator-=(CKKSCiphertext &ctxt, std::vector<double> vals) {
|
||||
size_t N = ctxt.cipher->GetEncodingParameters()->GetBatchSize();
|
||||
if (vals.size() != N) {
|
||||
std::string s = fmt::format("Provided vector has length = {}, but the "
|
||||
"CryptoContext batch size = {}",
|
||||
vals.size(), N);
|
||||
throw std::runtime_error(s);
|
||||
}
|
||||
// size_t final_size = ctxt.cipher->GetCryptoContext()->GetRingDimension() /
|
||||
// 2; tileVector(vals, final_size);
|
||||
auto ptxt = ctxt.cipher->GetCryptoContext()->MakeCKKSPackedPlaintext(vals);
|
||||
ctxt.cipher = ctxt.cipher->GetCryptoContext()->EvalSub(ctxt.cipher, ptxt);
|
||||
return ctxt;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator-=(CKKSCiphertext &ctxt,
|
||||
const boost::python::list &pyvals) {
|
||||
std::vector<double> vals = pythonListToCppDoubleVector(pyvals);
|
||||
return ctxt -= vals;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator-=(CKKSCiphertext &ctxt,
|
||||
const boost::python::numpy::ndarray &pyvals) {
|
||||
std::vector<double> vals = numpyListToCppDoubleVector(pyvals);
|
||||
return ctxt -= vals;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator-(CKKSCiphertext ctxt, const boost::python::list &vals) {
|
||||
return ctxt -= vals;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator-(CKKSCiphertext ctxt,
|
||||
const boost::python::numpy::ndarray &vals) {
|
||||
return ctxt -= vals;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator-(const CKKSCiphertext &ctxt) {
|
||||
return ctxt.cipher->GetCryptoContext()->EvalNegate(ctxt.cipher);
|
||||
}
|
||||
|
||||
CKKSCiphertext operator-=(CKKSCiphertext &ctxt, double val) {
|
||||
ctxt += -val;
|
||||
return ctxt;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator-(CKKSCiphertext ctxt, double val) {
|
||||
return ctxt -= val;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator-(double val, const CKKSCiphertext &ctxt) {
|
||||
auto ncipher = -ctxt;
|
||||
return ncipher += val;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator-(const std::vector<double> &vals, CKKSCiphertext ctxt) {
|
||||
return ctxt -= vals;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator-(const boost::python::list &vals,
|
||||
const CKKSCiphertext &ctxt) {
|
||||
auto ncipher = -ctxt;
|
||||
return ncipher += vals;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator-(const boost::python::numpy::ndarray &vals,
|
||||
const CKKSCiphertext &ctxt) {
|
||||
auto ncipher = -ctxt;
|
||||
return ncipher -= vals;
|
||||
}
|
||||
|
||||
CKKSCiphertext CKKSMultiplySingletonIntDoubleAndAdd(const CKKSCiphertext &ctxt,
|
||||
long int val) {
|
||||
if (val == 0) {
|
||||
return (ctxt - ctxt); // zero
|
||||
}
|
||||
if (val < 0) {
|
||||
return CKKSMultiplySingletonIntDoubleAndAdd(-ctxt, -val);
|
||||
}
|
||||
|
||||
CKKSCiphertext doubles = ctxt;
|
||||
CKKSCiphertext result = (ctxt - ctxt); // zero
|
||||
while (val > 0) {
|
||||
if (val & 1) {
|
||||
result += doubles;
|
||||
}
|
||||
doubles = doubles + doubles;
|
||||
val >>= 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// pass-by-value, so it makes a copy of ctxt
|
||||
CKKSCiphertext CKKSMultiplySingletonDirect(CKKSCiphertext ctxt, double val) {
|
||||
if (ctxt.getTowersRemaining() <= 2) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("Insufficient number of towers remaining to perform a "
|
||||
"multiplication = {}",
|
||||
ctxt.getTowersRemaining()));
|
||||
}
|
||||
std::vector<double> vals = {val};
|
||||
size_t dn = ctxt.cipher->GetEncodingParameters()->GetBatchSize();
|
||||
tileVector(vals, dn);
|
||||
auto ptxt = ctxt.cipher->GetCryptoContext()->MakeCKKSPackedPlaintext(vals);
|
||||
ctxt.cipher = ctxt.cipher->GetCryptoContext()->EvalMult(ctxt.cipher, ptxt);
|
||||
return ctxt;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator*=(CKKSCiphertext &ctxt, long int val) {
|
||||
if (abs(val) <= 256) {
|
||||
ctxt = CKKSMultiplySingletonIntDoubleAndAdd(ctxt, val);
|
||||
} else {
|
||||
ctxt = CKKSMultiplySingletonDirect(ctxt, (double)val);
|
||||
}
|
||||
return ctxt;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator*=(CKKSCiphertext &ctxt, double val) {
|
||||
if (ctxt.getTowersRemaining() <= 2) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("Insufficient number of towers remaining to perform a "
|
||||
"multiplication = {}",
|
||||
ctxt.getTowersRemaining()));
|
||||
}
|
||||
ctxt = CKKSMultiplySingletonDirect(ctxt, val);
|
||||
return ctxt;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator*=(CKKSCiphertext &ctxt, std::vector<double> vals) {
|
||||
size_t N = ctxt.cipher->GetEncodingParameters()->GetBatchSize();
|
||||
if (vals.size() != N) {
|
||||
std::string s = fmt::format("Provided vector has length = {}, but the "
|
||||
"CryptoContext batch size = {}",
|
||||
vals.size(), N);
|
||||
throw std::runtime_error(s);
|
||||
}
|
||||
if (ctxt.getTowersRemaining() <= 2) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("Insufficient number of towers remaining to perform a "
|
||||
"multiplication = {}",
|
||||
ctxt.getTowersRemaining()));
|
||||
}
|
||||
// size_t final_size = ctxt.cipher->GetCryptoContext()->GetRingDimension() /
|
||||
// 2; tileVector(vals, final_size);
|
||||
auto ptxt = ctxt.cipher->GetCryptoContext()->MakeCKKSPackedPlaintext(vals);
|
||||
ctxt.cipher = ctxt.cipher->GetCryptoContext()->EvalMult(ctxt.cipher, ptxt);
|
||||
return ctxt;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator*=(CKKSCiphertext &ctxt,
|
||||
const boost::python::list &pyvals) {
|
||||
std::vector<double> vals = pythonListToCppDoubleVector(pyvals);
|
||||
return ctxt *= vals;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator*=(CKKSCiphertext &ctxt,
|
||||
const boost::python::numpy::ndarray &pyvals) {
|
||||
std::vector<double> vals = numpyListToCppDoubleVector(pyvals);
|
||||
return ctxt *= vals;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator*(CKKSCiphertext ctxt, double val) {
|
||||
return ctxt *= val;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator*(double val, CKKSCiphertext ctxt) {
|
||||
return ctxt *= val;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator*(CKKSCiphertext ctxt, long int val) {
|
||||
return ctxt *= val;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator*(long int val, CKKSCiphertext ctxt) {
|
||||
return ctxt *= val;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator*(const std::vector<double> &vals, CKKSCiphertext ctxt) {
|
||||
return ctxt *= vals;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator*(CKKSCiphertext ctxt, const std::vector<double> &vals) {
|
||||
return ctxt *= vals;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator*(CKKSCiphertext ctxt, const boost::python::list &vals) {
|
||||
return ctxt *= vals;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator*(const boost::python::list &vals, CKKSCiphertext ctxt) {
|
||||
return ctxt *= vals;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator*(CKKSCiphertext ctxt,
|
||||
const boost::python::numpy::ndarray &vals) {
|
||||
return ctxt *= vals;
|
||||
}
|
||||
|
||||
CKKSCiphertext operator*(const boost::python::numpy::ndarray &vals,
|
||||
CKKSCiphertext ctxt) {
|
||||
return ctxt *= vals;
|
||||
}
|
||||
|
||||
} // namespace pyOpenFHE_CKKS
|
||||
242
src/ckks/CKKS_key_operations.cpp
Normal file
242
src/ckks/CKKS_key_operations.cpp
Normal file
@@ -0,0 +1,242 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
// encrypt, decrypt, keygeneration, and the like
|
||||
|
||||
#include <complex>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
// string formatting for exceptions
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/numpy.hpp>
|
||||
|
||||
#include "ckks/CKKS_ciphertext_extension.hpp"
|
||||
#include "ckks/CKKS_key_operations.hpp"
|
||||
#include "ckks/serialization.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
using namespace boost::python;
|
||||
using namespace boost::python::numpy;
|
||||
using namespace lbcrypto;
|
||||
using namespace pyOpenFHE;
|
||||
|
||||
namespace pyOpenFHE_CKKS {
|
||||
|
||||
CKKSCryptoContext genCKKSContext(usint multiplicativeDepth,
|
||||
usint scalingFactorBits, usint batchSize,
|
||||
SecurityLevel stdLevel, usint ringDim) {
|
||||
CCParams<CryptoContextCKKSRNS> parameters;
|
||||
parameters.SetMultiplicativeDepth(multiplicativeDepth);
|
||||
parameters.SetScalingModSize(scalingFactorBits);
|
||||
parameters.SetBatchSize(batchSize);
|
||||
parameters.SetSecurityLevel(stdLevel);
|
||||
if(ringDim != 0) {
|
||||
parameters.SetRingDim(ringDim);
|
||||
}
|
||||
|
||||
CryptoContext<DCRTPoly> native_cc = GenCryptoContext(parameters);
|
||||
|
||||
// convert to CKKSCryptoContext
|
||||
CKKSCryptoContext cc = CKKSCryptoContext(native_cc);
|
||||
|
||||
|
||||
return cc;
|
||||
}
|
||||
|
||||
// Encode a C++ vector into an OpenFHE Plaintext object
|
||||
Plaintext CKKSCryptoContext::encode(std::vector<double> vals) {
|
||||
if (vals.size() != context->GetEncodingParams()->GetBatchSize()) {
|
||||
std::string s =
|
||||
fmt::format("Provided vector has length = {}, but the CryptoContext "
|
||||
"batch size = {}",
|
||||
vals.size(), context->GetEncodingParams()->GetBatchSize());
|
||||
throw std::runtime_error(s);
|
||||
}
|
||||
|
||||
std::vector<std::complex<double>> cvals(vals.size());
|
||||
for (unsigned int i = 0; i < cvals.size(); i++) {
|
||||
cvals[i] = vals[i];
|
||||
}
|
||||
|
||||
Plaintext ptxt = context->MakeCKKSPackedPlaintext(cvals);
|
||||
return ptxt;
|
||||
}
|
||||
|
||||
// Encrypt with: private key and python list
|
||||
pyOpenFHE_CKKS::CKKSCiphertext
|
||||
CKKSCryptoContext::encryptPrivate(const PrivateKey<DCRTPoly> &privateKey,
|
||||
const list &pyvals) {
|
||||
std::vector<double> vals = pyOpenFHE::pythonListToCppDoubleVector(pyvals);
|
||||
auto ptxt = encode(vals);
|
||||
return pyOpenFHE_CKKS::CKKSCiphertext(context->Encrypt(privateKey, ptxt));
|
||||
}
|
||||
|
||||
// public key and python list
|
||||
pyOpenFHE_CKKS::CKKSCiphertext
|
||||
CKKSCryptoContext::encryptPublic(const PublicKey<DCRTPoly> &publicKey,
|
||||
const list &pyvals) {
|
||||
std::vector<double> vals = pyOpenFHE::pythonListToCppDoubleVector(pyvals);
|
||||
auto ptxt = encode(vals);
|
||||
return pyOpenFHE_CKKS::CKKSCiphertext(context->Encrypt(publicKey, ptxt));
|
||||
}
|
||||
|
||||
// private key and numpy array
|
||||
pyOpenFHE_CKKS::CKKSCiphertext
|
||||
CKKSCryptoContext::encryptPrivate2(const PrivateKey<DCRTPoly> &privateKey,
|
||||
const ndarray &pyvals) {
|
||||
std::vector<double> vals = pyOpenFHE::numpyListToCppDoubleVector(pyvals);
|
||||
auto ptxt = encode(vals);
|
||||
return pyOpenFHE_CKKS::CKKSCiphertext(context->Encrypt(privateKey, ptxt));
|
||||
}
|
||||
|
||||
// public key and numpy array
|
||||
pyOpenFHE_CKKS::CKKSCiphertext
|
||||
CKKSCryptoContext::encryptPublic2(const PublicKey<DCRTPoly> &publicKey,
|
||||
const ndarray &pyvals) {
|
||||
std::vector<double> vals = pyOpenFHE::numpyListToCppDoubleVector(pyvals);
|
||||
// std::cout << "hello 1" << std::endl;
|
||||
// std::cout << "vals size " << vals.size() << std::endl;
|
||||
// std::cout << "slots " << self.size << std::endl;
|
||||
auto ptxt = encode(vals);
|
||||
// std::cout << "hello 2" << std::endl;
|
||||
return pyOpenFHE_CKKS::CKKSCiphertext(context->Encrypt(publicKey, ptxt));
|
||||
}
|
||||
|
||||
ndarray CKKSCryptoContext::zeroPadToBatchSize(std::vector<double> vals) {
|
||||
size_t batch_size = context->GetEncodingParams()->GetBatchSize();
|
||||
if (vals.size() > batch_size) {
|
||||
std::string s =
|
||||
fmt::format("Provided vector has length = {}, but the CryptoContext "
|
||||
"batch size = {}",
|
||||
vals.size(), context->GetEncodingParams()->GetBatchSize());
|
||||
throw std::runtime_error(s);
|
||||
}
|
||||
vals.resize(batch_size, 0);
|
||||
return pyOpenFHE::cppDoubleVectorToNumpyList(vals);
|
||||
}
|
||||
|
||||
// python list
|
||||
ndarray CKKSCryptoContext::zeroPadToBatchSizeList(const list &pyvals) {
|
||||
std::vector<double> vals = pyOpenFHE::pythonListToCppDoubleVector(pyvals);
|
||||
return zeroPadToBatchSize(vals);
|
||||
}
|
||||
|
||||
// numpy array
|
||||
ndarray CKKSCryptoContext::zeroPadToBatchSizeNumpy(const ndarray &pyvals) {
|
||||
std::vector<double> vals = pyOpenFHE::numpyListToCppDoubleVector(pyvals);
|
||||
return zeroPadToBatchSize(vals);
|
||||
}
|
||||
|
||||
// again, usually decrypt takes a key, Ciphertext, and Plaintext reference
|
||||
// and fills in the plaintext ref and returns whether or not the decryption was
|
||||
// valid I want to give a key, CKKSCiphertext wrapper, and receive a numpy array
|
||||
// this does nothing with the return value of decrypt, but maybe there could be
|
||||
// an error check here...
|
||||
ndarray CKKSCryptoContext::decrypt(const PrivateKey<DCRTPoly> &privateKey,
|
||||
pyOpenFHE_CKKS::CKKSCiphertext &ctxt) {
|
||||
Plaintext ptxt;
|
||||
// level reduce to level2 before decrypting
|
||||
auto algo = ctxt.cipher->GetCryptoContext()->GetScheme();
|
||||
auto ctxt2 = algo->Compress(ctxt.cipher, 2);
|
||||
context->Decrypt(privateKey, ctxt2, &ptxt);
|
||||
ptxt->SetLength(ctxt.cipher->GetEncodingParameters()->GetBatchSize());
|
||||
auto cvals = ptxt->GetRealPackedValue();
|
||||
std::vector<double> vals(cvals.size());
|
||||
for (unsigned int i = 0; i < cvals.size(); i++) {
|
||||
vals[i] = std::real(cvals[i]);
|
||||
}
|
||||
return pyOpenFHE::cppDoubleVectorToNumpyList(vals);
|
||||
}
|
||||
|
||||
/*
|
||||
CKKS Bootstrapping functions
|
||||
*/
|
||||
void CKKSCryptoContext::evalBootstrapSetup() {
|
||||
std::vector<uint32_t> bsgsDim = {0, 0};
|
||||
std::vector<uint32_t> levelBudget = {4, 4};
|
||||
|
||||
usint slots = context->GetEncodingParams()->GetBatchSize();
|
||||
context->EvalBootstrapSetup(levelBudget, bsgsDim, slots);
|
||||
}
|
||||
|
||||
void CKKSCryptoContext::evalBootstrapKeyGen(
|
||||
const PrivateKey<DCRTPoly> &privateKey) {
|
||||
usint slots = context->GetEncodingParams()->GetBatchSize();
|
||||
// usint n = self.GetRingDimension();
|
||||
|
||||
context->EvalBootstrapKeyGen(privateKey, slots);
|
||||
}
|
||||
|
||||
boost::python::list CKKSCryptoContext::evalBootstrapList(boost::python::list ctxts) {
|
||||
|
||||
std::vector<pyOpenFHE_CKKS::CKKSCiphertext> input_ctxts(len(ctxts));
|
||||
auto output_ctxts = pyOpenFHE::make_list(len(ctxts));
|
||||
|
||||
#pragma omp parallel for
|
||||
for (int i = 0; i < len(ctxts); ++i) {
|
||||
input_ctxts[i] = extract<pyOpenFHE_CKKS::CKKSCiphertext>(ctxts[i]);
|
||||
input_ctxts[i].cipher = context->EvalBootstrap(input_ctxts[i].cipher);
|
||||
output_ctxts[i] = input_ctxts[i];
|
||||
}
|
||||
|
||||
return output_ctxts;
|
||||
}
|
||||
|
||||
pyOpenFHE_CKKS::CKKSCiphertext CKKSCryptoContext::evalMetaBootstrap(pyOpenFHE_CKKS::CKKSCiphertext ctxt) {
|
||||
double error_scale = 1e-3;
|
||||
auto c2 = pyOpenFHE_CKKS::CKKSCiphertext(context->EvalBootstrap(ctxt.cipher));
|
||||
auto e1 = (ctxt - c2) * (1/error_scale);
|
||||
auto e2 = pyOpenFHE_CKKS::CKKSCiphertext(context->EvalBootstrap(e1.cipher)) * error_scale;
|
||||
auto c3 = c2 + e2;
|
||||
return c3;
|
||||
}
|
||||
|
||||
boost::python::list CKKSCryptoContext::evalMetaBootstrapList(boost::python::list ctxts) {
|
||||
std::vector<pyOpenFHE_CKKS::CKKSCiphertext> input_ctxts(len(ctxts));
|
||||
auto output_ctxts = pyOpenFHE::make_list(len(ctxts));
|
||||
|
||||
for(int i = 0; i < len(ctxts); ++i) {
|
||||
input_ctxts[i] = extract<pyOpenFHE_CKKS::CKKSCiphertext>(ctxts[i]);
|
||||
}
|
||||
|
||||
#pragma omp parallel for
|
||||
for(int i = 0; i < len(ctxts); ++i) {
|
||||
input_ctxts[i] = evalMetaBootstrap(input_ctxts[i]);
|
||||
}
|
||||
|
||||
for(int i = 0; i < len(ctxts); ++i) {
|
||||
output_ctxts[i] = input_ctxts[i];
|
||||
}
|
||||
|
||||
return output_ctxts;
|
||||
}
|
||||
|
||||
pyOpenFHE_CKKS::CKKSCiphertext
|
||||
CKKSCryptoContext::evalBootstrap(pyOpenFHE_CKKS::CKKSCiphertext ctxt) {
|
||||
ctxt.cipher = context->EvalBootstrap(ctxt.cipher);
|
||||
return ctxt;
|
||||
}
|
||||
|
||||
// make rotation keys for all of the +/- powers-of-2
|
||||
// we should probably try and put all the scheme-agnostic functions somewhere
|
||||
// neutral reduce code duplication and C++ won't complain about it if we ever
|
||||
// link these libraries
|
||||
// TODO: that ^
|
||||
void CKKSCryptoContext::evalPowerOf2RotationKeyGen(
|
||||
const PrivateKey<DCRTPoly> &privateKey) {
|
||||
int N = context->GetEncodingParams()->GetBatchSize();
|
||||
int M = context->GetRingDimension();
|
||||
N = std::min(N, M / 2);
|
||||
std::vector<int> index_list;
|
||||
int r = 1;
|
||||
while (r <= N) {
|
||||
index_list.push_back(r);
|
||||
index_list.push_back(-r);
|
||||
r *= 2;
|
||||
}
|
||||
context->EvalAtIndexKeyGen(privateKey, index_list, nullptr);
|
||||
}
|
||||
|
||||
} // namespace pyOpenFHE_CKKS
|
||||
56
src/ckks/CKKS_pickle.cpp
Normal file
56
src/ckks/CKKS_pickle.cpp
Normal file
@@ -0,0 +1,56 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
/*
|
||||
define all the serialization functions which operate on our wrapped ciphertext, crypto context, and keys here
|
||||
*/
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/numpy.hpp>
|
||||
#include <boost/python/tuple.hpp>
|
||||
|
||||
#include "ckks/CKKS_ciphertext_extension.hpp"
|
||||
#include "ckks/CKKS_key_operations.hpp"
|
||||
#include "ckks/CKKS_pickle.hpp"
|
||||
#include "ckks/serialization.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "utils/enums_binding.hpp"
|
||||
|
||||
// header files needed for serialization
|
||||
#include "openfhe.h"
|
||||
#include "cryptocontext-ser.h"
|
||||
#include "ciphertext-ser.h"
|
||||
#include "key/key-ser.h"
|
||||
#include "scheme/ckksrns/ckksrns-ser.h"
|
||||
|
||||
using namespace lbcrypto;
|
||||
|
||||
namespace pyOpenFHE_CKKS {
|
||||
|
||||
boost::python::tuple CKKSCiphertext_pickle_suite::getinitargs(const pyOpenFHE_CKKS::CKKSCiphertext& w) {
|
||||
return boost::python::make_tuple();
|
||||
}
|
||||
|
||||
boost::python::tuple CKKSCiphertext_pickle_suite::getstate(const pyOpenFHE_CKKS::CKKSCiphertext& w) {
|
||||
PyObject * py_buffer = SerializeToBytes_Ciphertext(w, pyOpenFHE_CKKS::SerType::JSON);
|
||||
boost::python::handle<> handle(py_buffer);
|
||||
auto object = boost::python::object(handle);
|
||||
return boost::python::make_tuple(object);
|
||||
}
|
||||
|
||||
void CKKSCiphertext_pickle_suite::setstate(pyOpenFHE_CKKS::CKKSCiphertext& w, boost::python::tuple state) {
|
||||
using namespace boost::python;
|
||||
if (len(state) != 1) {
|
||||
PyErr_SetObject(
|
||||
PyExc_ValueError,
|
||||
("expected 1-item tuple in call to __setstate__; got %s" % state).ptr()
|
||||
);
|
||||
throw_error_already_set();
|
||||
}
|
||||
|
||||
auto ctxt = DeserializeFromBytes_Ciphertext(state[0], pyOpenFHE_CKKS::SerType::JSON);
|
||||
w.cipher = ctxt.cipher;
|
||||
}
|
||||
|
||||
}
|
||||
525
src/ckks/CKKS_serialization.cpp
Normal file
525
src/ckks/CKKS_serialization.cpp
Normal file
@@ -0,0 +1,525 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
/*
|
||||
define all the serialization functions which operate on our wrapped ciphertext,
|
||||
crypto context, and keys here
|
||||
*/
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/numpy.hpp>
|
||||
|
||||
#include "ckks/CKKS_ciphertext_extension.hpp"
|
||||
#include "ckks/CKKS_key_operations.hpp"
|
||||
#include "ckks/serialization.hpp"
|
||||
#include "utils/enums_binding.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
// header files needed for serialization
|
||||
#include "ciphertext-ser.h"
|
||||
#include "cryptocontext-ser.h"
|
||||
#include "key/key-ser.h"
|
||||
#include "openfhe.h"
|
||||
#include "scheme/ckksrns/ckksrns-ser.h"
|
||||
|
||||
using namespace lbcrypto;
|
||||
|
||||
namespace pyOpenFHE_CKKS {
|
||||
|
||||
PyObject *SerializeToBytes_Ciphertext(const pyOpenFHE_CKKS::CKKSCiphertext &obj,
|
||||
const pyOpenFHE_CKKS::SerType sertype) {
|
||||
std::stringstream ss;
|
||||
|
||||
if (sertype == pyOpenFHE_CKKS::SerType::BINARY) {
|
||||
Serial::Serialize(obj.cipher, ss, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_CKKS::SerType::JSON) {
|
||||
Serial::Serialize(obj.cipher, ss, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
std::string result = ss.str();
|
||||
PyObject *pymemview = PyMemoryView_FromMemory((char *)result.c_str(),
|
||||
result.length(), PyBUF_READ);
|
||||
return PyBytes_FromObject(pymemview);
|
||||
}
|
||||
|
||||
pyOpenFHE_CKKS::CKKSCiphertext
|
||||
DeserializeFromBytes_Ciphertext(boost::python::object py_buffer,
|
||||
const pyOpenFHE_CKKS::SerType sertype) {
|
||||
std::string object_classname = boost::python::extract<std::string>(
|
||||
py_buffer.attr("__class__").attr("__name__"));
|
||||
if (object_classname != "bytes") {
|
||||
throw std::runtime_error(
|
||||
"expected object of type bytes, instead received type: " +
|
||||
object_classname);
|
||||
}
|
||||
|
||||
std::string buffer = boost::python::extract<std::string>(py_buffer);
|
||||
std::stringstream ss(buffer);
|
||||
|
||||
Ciphertext<DCRTPoly> obj;
|
||||
|
||||
if (sertype == pyOpenFHE_CKKS::SerType::BINARY) {
|
||||
Serial::Deserialize(obj, ss, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_CKKS::SerType::JSON) {
|
||||
Serial::Deserialize(obj, ss, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
return pyOpenFHE_CKKS::CKKSCiphertext(obj);
|
||||
}
|
||||
|
||||
PyObject *SerializeToBytes_PublicKey(const PublicKey<DCRTPoly> &obj,
|
||||
const pyOpenFHE_CKKS::SerType sertype) {
|
||||
std::stringstream ss;
|
||||
|
||||
if (sertype == pyOpenFHE_CKKS::SerType::BINARY) {
|
||||
Serial::Serialize(obj, ss, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_CKKS::SerType::JSON) {
|
||||
Serial::Serialize(obj, ss, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
std::string result = ss.str();
|
||||
PyObject *pymemview = PyMemoryView_FromMemory((char *)result.c_str(),
|
||||
result.length(), PyBUF_READ);
|
||||
return PyBytes_FromObject(pymemview);
|
||||
}
|
||||
|
||||
PublicKey<DCRTPoly>
|
||||
DeserializeFromBytes_PublicKey(boost::python::object py_buffer,
|
||||
const pyOpenFHE_CKKS::SerType sertype) {
|
||||
std::string object_classname = boost::python::extract<std::string>(
|
||||
py_buffer.attr("__class__").attr("__name__"));
|
||||
if (object_classname != "bytes") {
|
||||
throw std::runtime_error(
|
||||
"expected object of type bytes, instead received type: " +
|
||||
object_classname);
|
||||
}
|
||||
|
||||
std::string buffer = boost::python::extract<std::string>(py_buffer);
|
||||
std::stringstream ss(buffer);
|
||||
|
||||
PublicKey<DCRTPoly> obj;
|
||||
|
||||
if (sertype == pyOpenFHE_CKKS::SerType::BINARY) {
|
||||
Serial::Deserialize(obj, ss, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_CKKS::SerType::JSON) {
|
||||
Serial::Deserialize(obj, ss, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
PyObject *SerializeToBytes_PrivateKey(const PrivateKey<DCRTPoly> &obj,
|
||||
const pyOpenFHE_CKKS::SerType sertype) {
|
||||
std::stringstream ss;
|
||||
|
||||
if (sertype == pyOpenFHE_CKKS::SerType::BINARY) {
|
||||
Serial::Serialize(obj, ss, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_CKKS::SerType::JSON) {
|
||||
Serial::Serialize(obj, ss, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
std::string result = ss.str();
|
||||
PyObject *pymemview = PyMemoryView_FromMemory((char *)result.c_str(),
|
||||
result.length(), PyBUF_READ);
|
||||
return PyBytes_FromObject(pymemview);
|
||||
}
|
||||
|
||||
PrivateKey<DCRTPoly>
|
||||
DeserializeFromBytes_PrivateKey(boost::python::object py_buffer,
|
||||
const pyOpenFHE_CKKS::SerType sertype) {
|
||||
std::string object_classname = boost::python::extract<std::string>(
|
||||
py_buffer.attr("__class__").attr("__name__"));
|
||||
if (object_classname != "bytes") {
|
||||
throw std::runtime_error(
|
||||
"expected object of type bytes, instead received type: " +
|
||||
object_classname);
|
||||
}
|
||||
|
||||
std::string buffer = boost::python::extract<std::string>(py_buffer);
|
||||
std::stringstream ss(buffer);
|
||||
|
||||
PrivateKey<DCRTPoly> obj;
|
||||
|
||||
if (sertype == pyOpenFHE_CKKS::SerType::BINARY) {
|
||||
Serial::Deserialize(obj, ss, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_CKKS::SerType::JSON) {
|
||||
Serial::Deserialize(obj, ss, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
PyObject *SerializeToBytes_EvalMultKey_CryptoContext(
|
||||
CKKSCryptoContext &self, const pyOpenFHE_CKKS::SerType sertype) {
|
||||
std::stringstream ss;
|
||||
|
||||
if (sertype == pyOpenFHE_CKKS::SerType::BINARY) {
|
||||
self.context->SerializeEvalMultKey(ss, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_CKKS::SerType::JSON) {
|
||||
self.context->SerializeEvalMultKey(ss, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
std::string result = ss.str();
|
||||
PyObject *pymemview = PyMemoryView_FromMemory((char *)result.c_str(),
|
||||
result.length(), PyBUF_READ);
|
||||
return PyBytes_FromObject(pymemview);
|
||||
}
|
||||
|
||||
bool DeserializeFromBytes_EvalMultKey_CryptoContext(
|
||||
CKKSCryptoContext &self, boost::python::object py_buffer,
|
||||
const pyOpenFHE_CKKS::SerType sertype) {
|
||||
std::string object_classname = boost::python::extract<std::string>(
|
||||
py_buffer.attr("__class__").attr("__name__"));
|
||||
if (object_classname != "bytes") {
|
||||
throw std::runtime_error(
|
||||
"expected object of type bytes, instead received type: " +
|
||||
object_classname);
|
||||
}
|
||||
|
||||
std::string buffer = boost::python::extract<std::string>(py_buffer);
|
||||
std::stringstream ss(buffer);
|
||||
|
||||
if (sertype == pyOpenFHE_CKKS::SerType::BINARY) {
|
||||
self.context->DeserializeEvalMultKey(ss, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_CKKS::SerType::JSON) {
|
||||
self.context->DeserializeEvalMultKey(ss, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
PyObject *SerializeToBytes_EvalAutomorphismKey_CryptoContext(
|
||||
CKKSCryptoContext &self, const pyOpenFHE_CKKS::SerType sertype) {
|
||||
std::stringstream ss;
|
||||
|
||||
if (sertype == pyOpenFHE_CKKS::SerType::BINARY) {
|
||||
self.context->SerializeEvalAutomorphismKey(ss, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_CKKS::SerType::JSON) {
|
||||
self.context->SerializeEvalAutomorphismKey(ss, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
std::string result = ss.str();
|
||||
PyObject *pymemview = PyMemoryView_FromMemory((char *)result.c_str(),
|
||||
result.length(), PyBUF_READ);
|
||||
return PyBytes_FromObject(pymemview);
|
||||
}
|
||||
|
||||
bool DeserializeFromBytes_EvalAutomorphismKey_CryptoContext(
|
||||
CKKSCryptoContext &self, boost::python::object py_buffer,
|
||||
const pyOpenFHE_CKKS::SerType sertype) {
|
||||
std::string object_classname = boost::python::extract<std::string>(
|
||||
py_buffer.attr("__class__").attr("__name__"));
|
||||
if (object_classname != "bytes") {
|
||||
throw std::runtime_error(
|
||||
"expected object of type bytes, instead received type: " +
|
||||
object_classname);
|
||||
}
|
||||
|
||||
std::string buffer = boost::python::extract<std::string>(py_buffer);
|
||||
std::stringstream ss(buffer);
|
||||
|
||||
if (sertype == pyOpenFHE_CKKS::SerType::BINARY) {
|
||||
self.context->DeserializeEvalAutomorphismKey(ss, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_CKKS::SerType::JSON) {
|
||||
self.context->DeserializeEvalAutomorphismKey(ss, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SerializeToFile_Ciphertext(const std::string &filename,
|
||||
const pyOpenFHE_CKKS::CKKSCiphertext &obj,
|
||||
const pyOpenFHE_CKKS::SerType sertype) {
|
||||
bool success = false;
|
||||
if (sertype == pyOpenFHE_CKKS::SerType::BINARY) {
|
||||
success = Serial::SerializeToFile(filename, obj.cipher,
|
||||
lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_CKKS::SerType::JSON) {
|
||||
success =
|
||||
Serial::SerializeToFile(filename, obj.cipher, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
throw std::runtime_error(
|
||||
"Could not write serialized CKKSCiphertext to file: ");
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool SerializeToFile_CryptoContext(const std::string &filename,
|
||||
const CKKSCryptoContext &obj,
|
||||
const pyOpenFHE_CKKS::SerType sertype) {
|
||||
// throw std::runtime_error("This function is disabled as CryptoContext
|
||||
// Deserialization is broken.");
|
||||
|
||||
bool success = false;
|
||||
if (sertype == pyOpenFHE_CKKS::SerType::BINARY) {
|
||||
success = Serial::SerializeToFile(filename, obj, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_CKKS::SerType::JSON) {
|
||||
success = Serial::SerializeToFile(filename, obj, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
throw std::runtime_error(
|
||||
"Could not write serialized CryptoContext to file: " + filename);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool SerializeToFile_EvalMultKey_CryptoContext(
|
||||
CKKSCryptoContext &self, const std::string &filename,
|
||||
const pyOpenFHE_CKKS::SerType sertype) {
|
||||
std::ofstream multKeyFile(filename, std::ios::out | std::ios::binary);
|
||||
bool success = false;
|
||||
|
||||
if (!multKeyFile.is_open()) {
|
||||
throw std::runtime_error(
|
||||
"Could not write serialized EvalMult / relinearization keys to file: " +
|
||||
filename);
|
||||
}
|
||||
|
||||
if (sertype == pyOpenFHE_CKKS::SerType::BINARY) {
|
||||
success = self.context->SerializeEvalMultKey(multKeyFile,
|
||||
lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_CKKS::SerType::JSON) {
|
||||
success = self.context->SerializeEvalMultKey(multKeyFile,
|
||||
lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
multKeyFile.close();
|
||||
|
||||
if (!success) {
|
||||
throw std::runtime_error(
|
||||
"Could not write serialized EvalMult / relinearization keys to file: " +
|
||||
filename);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool SerializeToFile_EvalAutomorphismKey_CryptoContext(
|
||||
CKKSCryptoContext &self, const std::string &filename,
|
||||
const pyOpenFHE_CKKS::SerType sertype) {
|
||||
std::ofstream multKeyFile(filename, std::ios::out | std::ios::binary);
|
||||
bool success = false;
|
||||
|
||||
if (!multKeyFile.is_open()) {
|
||||
throw std::runtime_error("Could not write serialized EvalAutomorphism / "
|
||||
"rotation keys to file: " +
|
||||
filename);
|
||||
}
|
||||
|
||||
if (sertype == pyOpenFHE_CKKS::SerType::BINARY) {
|
||||
success = self.context->SerializeEvalAutomorphismKey(
|
||||
multKeyFile, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_CKKS::SerType::JSON) {
|
||||
success = self.context->SerializeEvalAutomorphismKey(
|
||||
multKeyFile, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
multKeyFile.close();
|
||||
|
||||
if (!success) {
|
||||
throw std::runtime_error("Could not write serialized EvalAutomorphism / "
|
||||
"rotation keys to file: " +
|
||||
filename);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool SerializeToFile_PublicKey(const std::string &filename,
|
||||
const PublicKey<DCRTPoly> &obj,
|
||||
const pyOpenFHE_CKKS::SerType sertype) {
|
||||
bool success = false;
|
||||
if (sertype == pyOpenFHE_CKKS::SerType::BINARY) {
|
||||
success = Serial::SerializeToFile(filename, obj, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_CKKS::SerType::JSON) {
|
||||
success = Serial::SerializeToFile(filename, obj, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
throw std::runtime_error("Could not write serialized PublicKey to file: " +
|
||||
filename);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool SerializeToFile_PrivateKey(const std::string &filename,
|
||||
const PrivateKey<DCRTPoly> &obj,
|
||||
const pyOpenFHE_CKKS::SerType sertype) {
|
||||
bool success = false;
|
||||
if (sertype == pyOpenFHE_CKKS::SerType::BINARY) {
|
||||
success = Serial::SerializeToFile(filename, obj, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_CKKS::SerType::JSON) {
|
||||
success = Serial::SerializeToFile(filename, obj, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
throw std::runtime_error("Could not write serialized PrivateKey to file: " +
|
||||
filename);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
pyOpenFHE_CKKS::CKKSCiphertext
|
||||
DeserializeFromFile_Ciphertext(const std::string &filename,
|
||||
const pyOpenFHE_CKKS::SerType sertype) {
|
||||
bool success = false;
|
||||
Ciphertext<DCRTPoly> obj;
|
||||
|
||||
if (sertype == pyOpenFHE_CKKS::SerType::BINARY) {
|
||||
success =
|
||||
Serial::DeserializeFromFile(filename, obj, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_CKKS::SerType::JSON) {
|
||||
success =
|
||||
Serial::DeserializeFromFile(filename, obj, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
throw std::runtime_error("Could not read serialized data from file: " +
|
||||
filename);
|
||||
}
|
||||
|
||||
return pyOpenFHE_CKKS::CKKSCiphertext(obj);
|
||||
}
|
||||
|
||||
CryptoContext<DCRTPoly>
|
||||
DeserializeFromFile_CryptoContext(const std::string &filename,
|
||||
const pyOpenFHE_CKKS::SerType sertype) {
|
||||
throw std::runtime_error(
|
||||
"This function is disabled as CryptoContext Deserialization is broken.");
|
||||
|
||||
std::cout << "hello we started the deserialization function" << std::endl;
|
||||
|
||||
lbcrypto::CryptoContextFactory<lbcrypto::DCRTPoly>::ReleaseAllContexts();
|
||||
|
||||
std::cout << "contexts are released" << std::endl;
|
||||
|
||||
CryptoContext<DCRTPoly> obj;
|
||||
bool success = false;
|
||||
|
||||
std::cout << "new contexts is created" << std::endl;
|
||||
|
||||
// obj->ClearEvalMultKeys();
|
||||
// obj->ClearEvalAutomorphismKeys();
|
||||
|
||||
if (sertype == pyOpenFHE_CKKS::SerType::BINARY) {
|
||||
success =
|
||||
Serial::DeserializeFromFile(filename, obj, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_CKKS::SerType::JSON) {
|
||||
success =
|
||||
Serial::DeserializeFromFile(filename, obj, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
throw std::runtime_error("Could not read serialized data from file: " +
|
||||
filename);
|
||||
}
|
||||
|
||||
std::cout << "deserialization maybe happened" << std::endl;
|
||||
|
||||
auto cc = obj;
|
||||
std::cout << "CKKS scheme is using ring dimension = "
|
||||
<< cc->GetRingDimension() << std::endl;
|
||||
std::cout << "batch size = " << cc->GetEncodingParams()->GetBatchSize()
|
||||
<< std::endl;
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
PublicKey<DCRTPoly>
|
||||
DeserializeFromFile_PublicKey(const std::string &filename,
|
||||
const pyOpenFHE_CKKS::SerType sertype) {
|
||||
PublicKey<DCRTPoly> obj;
|
||||
bool success = false;
|
||||
|
||||
if (sertype == pyOpenFHE_CKKS::SerType::BINARY) {
|
||||
success =
|
||||
Serial::DeserializeFromFile(filename, obj, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_CKKS::SerType::JSON) {
|
||||
success =
|
||||
Serial::DeserializeFromFile(filename, obj, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
throw std::runtime_error("Could not read serialized data from file: " +
|
||||
filename);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
PrivateKey<DCRTPoly>
|
||||
DeserializeFromFile_PrivateKey(const std::string &filename,
|
||||
const pyOpenFHE_CKKS::SerType sertype) {
|
||||
PrivateKey<DCRTPoly> obj;
|
||||
bool success = false;
|
||||
|
||||
if (sertype == pyOpenFHE_CKKS::SerType::BINARY) {
|
||||
success =
|
||||
Serial::DeserializeFromFile(filename, obj, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_CKKS::SerType::JSON) {
|
||||
success =
|
||||
Serial::DeserializeFromFile(filename, obj, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
throw std::runtime_error("Could not read serialized data from file: " +
|
||||
filename);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
bool DeserializeFromFile_EvalMultKey_CryptoContext(
|
||||
CKKSCryptoContext &self, const std::string &filename,
|
||||
const pyOpenFHE_CKKS::SerType sertype) {
|
||||
std::ifstream multKeyFile(filename, std::ios::in | std::ios::binary);
|
||||
bool success = false;
|
||||
|
||||
if (!multKeyFile.is_open()) {
|
||||
throw std::runtime_error(
|
||||
"Error reading EvalMult / relinearization keys from file: " + filename);
|
||||
}
|
||||
|
||||
if (sertype == pyOpenFHE_CKKS::SerType::BINARY) {
|
||||
success = self.context->DeserializeEvalMultKey(multKeyFile,
|
||||
lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_CKKS::SerType::JSON) {
|
||||
success = self.context->DeserializeEvalMultKey(multKeyFile,
|
||||
lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
multKeyFile.close();
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool DeserializeFromFile_EvalAutomorphismKey_CryptoContext(
|
||||
CKKSCryptoContext &self, const std::string &filename,
|
||||
const pyOpenFHE_CKKS::SerType sertype) {
|
||||
std::ifstream multKeyFile(filename, std::ios::in | std::ios::binary);
|
||||
bool success = false;
|
||||
|
||||
if (!multKeyFile.is_open()) {
|
||||
throw std::runtime_error(
|
||||
"Error reading EvalAutomorphism / rotation keys from file: " +
|
||||
filename);
|
||||
}
|
||||
|
||||
if (sertype == pyOpenFHE_CKKS::SerType::BINARY) {
|
||||
success = self.context->DeserializeEvalAutomorphismKey(
|
||||
multKeyFile, lbcrypto::SerType::BINARY);
|
||||
} else if (sertype == pyOpenFHE_CKKS::SerType::JSON) {
|
||||
success = self.context->DeserializeEvalAutomorphismKey(
|
||||
multKeyFile, lbcrypto::SerType::JSON);
|
||||
}
|
||||
|
||||
multKeyFile.close();
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
} // namespace pyOpenFHE_CKKS
|
||||
71
src/ckks/CKKS_serialization_bindings.cpp
Normal file
71
src/ckks/CKKS_serialization_bindings.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
// bindings for the serialization functions
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/numpy.hpp>
|
||||
|
||||
#include "openfhe.h"
|
||||
|
||||
#include "ckks/serialization.hpp"
|
||||
|
||||
namespace pyOpenFHE_CKKS {
|
||||
|
||||
void export_CKKS_serialization_boost() {
|
||||
|
||||
enum_<pyOpenFHE_CKKS::SerType>("SerType")
|
||||
.value("BINARY", pyOpenFHE_CKKS::SerType::BINARY)
|
||||
.value("JSON", pyOpenFHE_CKKS::SerType::JSON);
|
||||
|
||||
/*
|
||||
TODO: is there a better way to handle the Deserialize operation?
|
||||
*/
|
||||
def("SerializeToBytes", SerializeToBytes_Ciphertext);
|
||||
def("SerializeToBytes", SerializeToBytes_PublicKey);
|
||||
def("SerializeToBytes", SerializeToBytes_PrivateKey);
|
||||
|
||||
def("SerializeToFile", SerializeToFile_Ciphertext);
|
||||
def("SerializeToFile", SerializeToFile_PublicKey);
|
||||
def("SerializeToFile", SerializeToFile_PrivateKey);
|
||||
|
||||
def("DeserializeFromBytes_Ciphertext", DeserializeFromBytes_Ciphertext);
|
||||
def("DeserializeFromBytes_PublicKey", DeserializeFromBytes_PublicKey);
|
||||
def("DeserializeFromBytes_PrivateKey", DeserializeFromBytes_PrivateKey);
|
||||
|
||||
def("DeserializeFromFile_Ciphertext", DeserializeFromFile_Ciphertext);
|
||||
def("DeserializeFromFile_PublicKey", DeserializeFromFile_PublicKey);
|
||||
def("DeserializeFromFile_PrivateKey", DeserializeFromFile_PrivateKey);
|
||||
|
||||
/* TODO: disabled */
|
||||
def("SerializeToFile_EvalMultKey_CryptoContext",
|
||||
&SerializeToFile_EvalMultKey_CryptoContext);
|
||||
def("DeserializeFromFile_CryptoContext", &DeserializeFromFile_CryptoContext);
|
||||
|
||||
/*
|
||||
The difference is naming between these and the above functions is unfortunate,
|
||||
but hard to avoid given how PALISADE works in C++.
|
||||
Deserializing these keys requires being passed a CryptoContext object,
|
||||
while the above keys do not.
|
||||
*/
|
||||
def("SerializeToFile_EvalMultKey_CryptoContext",
|
||||
&SerializeToFile_EvalMultKey_CryptoContext);
|
||||
def("SerializeToFile_EvalAutomorphismKey_CryptoContext",
|
||||
&SerializeToFile_EvalAutomorphismKey_CryptoContext);
|
||||
|
||||
def("DeserializeFromFile_EvalMultKey_CryptoContext",
|
||||
&DeserializeFromFile_EvalMultKey_CryptoContext);
|
||||
def("DeserializeFromFile_EvalAutomorphismKey_CryptoContext",
|
||||
&DeserializeFromFile_EvalAutomorphismKey_CryptoContext);
|
||||
|
||||
def("SerializeToBytes_EvalMultKey_CryptoContext",
|
||||
&SerializeToBytes_EvalMultKey_CryptoContext);
|
||||
def("SerializeToBytes_EvalAutomorphismKey_CryptoContext",
|
||||
&SerializeToBytes_EvalAutomorphismKey_CryptoContext);
|
||||
|
||||
def("DeserializeFromBytes_EvalMultKey_CryptoContext",
|
||||
&DeserializeFromBytes_EvalMultKey_CryptoContext);
|
||||
def("DeserializeFromBytes_EvalAutomorphismKey_CryptoContext",
|
||||
&DeserializeFromBytes_EvalAutomorphismKey_CryptoContext);
|
||||
}
|
||||
|
||||
} // namespace pyOpenFHE_CKKS
|
||||
379
src/ckks/cnn/conv.cpp
Normal file
379
src/ckks/cnn/conv.cpp
Normal file
@@ -0,0 +1,379 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
#include "ckks/CKKS_ciphertext_extension.hpp"
|
||||
#include "ckks/cnn/he_cnn.hpp"
|
||||
#include "ckks/cnn/conv.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "ckks/utils.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/numpy.hpp>
|
||||
#include <boost/python/scope.hpp>
|
||||
#include <omp.h>
|
||||
#include <cstdlib>
|
||||
|
||||
using namespace pyOpenFHE;
|
||||
using namespace pyOpenFHE_CKKS;
|
||||
using namespace boost::python;
|
||||
using namespace boost::python::numpy;
|
||||
|
||||
pyOpenFHE_CKKS::ciphertext_array2d get_all_rotations_image_sharded(pyOpenFHE_CKKS::CKKSCiphertext &ciphertext, int mtx_size, int ker_size) {
|
||||
pyOpenFHE_CKKS::ciphertext_array2d rotations(boost::extents[ker_size][ker_size]);
|
||||
|
||||
int center = shift_to_kernel_index(0, ker_size);
|
||||
|
||||
// fill out one row
|
||||
for (int j = 0; j < ker_size; j++) {
|
||||
int shift = kernel_index_to_shift(j, ker_size);
|
||||
rotations[center][j] = ciphertext << shift;
|
||||
}
|
||||
|
||||
// fill out the rest
|
||||
for (int i = center - 1; i >= 0; i--) {
|
||||
for (int j = 0; j < ker_size; j++) {
|
||||
rotations[i][j] = rotations[i+1][j] >> mtx_size;
|
||||
}
|
||||
}
|
||||
for (int i = center + 1; i < ker_size; i++) {
|
||||
for (int j = 0; j < ker_size; j++) {
|
||||
rotations[i][j] = rotations[i-1][j] << mtx_size;
|
||||
}
|
||||
}
|
||||
|
||||
return rotations;
|
||||
}
|
||||
|
||||
pyOpenFHE_CKKS::ciphertext_array4d get_all_rotations_channel_sharded(std::vector<pyOpenFHE_CKKS::CKKSCiphertext>& all_shards, int shards_per_channel, int mtx_size, int ker_size) {
|
||||
int num_channels = all_shards.size() / shards_per_channel;
|
||||
pyOpenFHE_CKKS::ciphertext_array4d rotations(boost::extents[num_channels][shards_per_channel][ker_size][ker_size]);
|
||||
|
||||
for (int channel_index = 0; channel_index < num_channels; channel_index++) {
|
||||
int channel_offset = channel_index * shards_per_channel;
|
||||
for (int shard_index = 0; shard_index < shards_per_channel; shard_index++) {
|
||||
int center = shift_to_kernel_index(0, ker_size);
|
||||
|
||||
// fill out one row
|
||||
for (int j = 0; j < ker_size; j++) {
|
||||
int shift = kernel_index_to_shift(j, ker_size);
|
||||
rotations[channel_index][shard_index][center][j] = all_shards[channel_offset + shard_index] << shift;
|
||||
}
|
||||
|
||||
// fill out the rest
|
||||
for (int i = center - 1; i >= 0; i--) {
|
||||
for (int j = 0; j < ker_size; j++) {
|
||||
rotations[channel_index][shard_index][i][j] = rotations[channel_index][shard_index][i+1][j] >> mtx_size;
|
||||
}
|
||||
}
|
||||
for (int i = center + 1; i < ker_size; i++) {
|
||||
for (int j = 0; j < ker_size; j++) {
|
||||
rotations[channel_index][shard_index][i][j] = rotations[channel_index][shard_index][i-1][j] << mtx_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rotations;
|
||||
}
|
||||
|
||||
// looks and dimensions of 4d filters multiarray to determine duplication factors
|
||||
pyOpenFHE_CKKS::CKKSCiphertext convolution_helper_image_sharded(const pyOpenFHE_CKKS::ciphertext_array2d &ciphertext_rotations,
|
||||
boost_vector4d &filters,
|
||||
int mtx_size,
|
||||
int r,
|
||||
int num_in_channels_per_shard,
|
||||
int num_out_channels_per_shard,
|
||||
int fragment_offset,
|
||||
int shard_offset,
|
||||
std::vector<long int> &sigma) {
|
||||
// math!
|
||||
auto ciphertext = ciphertext_rotations[0][0];
|
||||
int shard_size = ciphertext.getBatchSize();
|
||||
int ker_size = filters.shape()[2]; // assuming square kernels
|
||||
int channel_size = mtx_size * mtx_size;
|
||||
int num_physical_channels = shard_size / channel_size;
|
||||
int input_dup_factor = shard_size / (num_in_channels_per_shard * channel_size);
|
||||
int output_dup_factor = shard_size / (num_out_channels_per_shard * channel_size);
|
||||
|
||||
auto enc_sum = ciphertext - ciphertext; // zero
|
||||
|
||||
std::vector<double> kernel_elements(num_physical_channels);
|
||||
std::vector<double> masked_kernel_elements(shard_size);
|
||||
|
||||
// iterate over all rotations
|
||||
for (int ki = 0; ki < ker_size; ki++) {
|
||||
int num_shift_ud = kernel_index_to_shift(ki, ker_size);
|
||||
for (int kj = 0; kj < ker_size; kj++) {
|
||||
int num_shift_lr = kernel_index_to_shift(kj, ker_size);
|
||||
auto mask = make_shift_mask_image_sharded(num_physical_channels, mtx_size, mtx_size, num_shift_ud, num_shift_lr);
|
||||
|
||||
// extract elements from filters...
|
||||
for (int idx = 0; idx < num_physical_channels; idx++) {
|
||||
int i = (num_in_channels_per_shard * fragment_offset) + (idx / input_dup_factor + r) % num_in_channels_per_shard;
|
||||
int sigma_i = (int)sigma[i];
|
||||
int adjustment = (input_dup_factor / output_dup_factor);
|
||||
if(adjustment == 0) adjustment = 1;
|
||||
int j = (num_out_channels_per_shard * shard_offset) + (idx / output_dup_factor);
|
||||
kernel_elements[idx] = filters[sigma_i][j][ki][kj];
|
||||
}
|
||||
|
||||
// and mask them, possibly with repetition
|
||||
for (int i = 0; i < shard_size; i++) {
|
||||
int idx = ((i / channel_size) - (r * input_dup_factor) + num_physical_channels) % num_physical_channels;
|
||||
masked_kernel_elements[i] = mask[i] * kernel_elements[idx];
|
||||
}
|
||||
|
||||
enc_sum += ciphertext_rotations[ki][kj] * masked_kernel_elements;
|
||||
}
|
||||
}
|
||||
|
||||
enc_sum <<= (r * mtx_size * mtx_size * input_dup_factor);
|
||||
|
||||
return enc_sum;
|
||||
}
|
||||
|
||||
pyOpenFHE_CKKS::CKKSCiphertext convolution_helper_channel_sharded(pyOpenFHE_CKKS::ciphertext_array4d& rotations,
|
||||
boost_vector4d &filters,
|
||||
int mtx_size,
|
||||
int channel_index,
|
||||
int channel_shard_index,
|
||||
int output_channel_index) {
|
||||
auto first_shard = rotations[0][0][0][0];
|
||||
int shard_size = first_shard.getBatchSize();
|
||||
int channel_size = mtx_size * mtx_size;
|
||||
int shards_per_channel = channel_size / shard_size;
|
||||
int ker_size = filters.shape()[2];
|
||||
|
||||
int num_rows = mtx_size / shards_per_channel;
|
||||
int num_cols = mtx_size;
|
||||
|
||||
auto enc_sum = first_shard - first_shard; // zero
|
||||
|
||||
std::vector<double> masked_kernel_elements(shard_size);
|
||||
std::vector<double> bleed_masked_kernel_elements(shard_size);
|
||||
|
||||
// iterate over all shifts
|
||||
for (int ki = 0; ki < ker_size; ki++) {
|
||||
int num_shift_ud = kernel_index_to_shift(ki, ker_size);
|
||||
|
||||
// TODO move this computation into a separate function
|
||||
int bleed_shard_index;
|
||||
if (num_shift_ud > 0) {
|
||||
if (channel_shard_index + 1 == shards_per_channel) {
|
||||
bleed_shard_index = -1;
|
||||
} else {
|
||||
bleed_shard_index = channel_shard_index + 1;
|
||||
}
|
||||
} else if (num_shift_ud < 0) {
|
||||
if (channel_shard_index == 0) {
|
||||
bleed_shard_index = -1;
|
||||
} else {
|
||||
bleed_shard_index = channel_shard_index - 1;
|
||||
}
|
||||
} else {
|
||||
bleed_shard_index = -1;
|
||||
}
|
||||
|
||||
for (int kj = 0; kj < ker_size; kj++) {
|
||||
int num_shift_lr = kernel_index_to_shift(kj, ker_size);
|
||||
|
||||
auto mask = make_shift_mask_channel_shard(num_rows, num_cols, num_shift_ud, num_shift_lr);
|
||||
auto bleed_mask = make_shift_mask_bleed_channel_shard(num_rows, num_cols, num_shift_ud, num_shift_lr);
|
||||
auto kernel_element = filters[channel_index][output_channel_index][ki][kj];
|
||||
|
||||
// create masked kernel elements
|
||||
for (int i = 0; i < shard_size; i++) {
|
||||
masked_kernel_elements[i] = mask[i] * kernel_element;
|
||||
bleed_masked_kernel_elements[i] = bleed_mask[i] * kernel_element;
|
||||
}
|
||||
|
||||
enc_sum += rotations[channel_index][channel_shard_index][ki][kj] * masked_kernel_elements;
|
||||
if (bleed_shard_index >= 0) {
|
||||
enc_sum += rotations[channel_index][bleed_shard_index][ki][kj] * bleed_masked_kernel_elements;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return enc_sum;
|
||||
}
|
||||
|
||||
// or not we have channel shards or not (can pretty easily do this by mathing it out, as below).
|
||||
boost::python::list conv2d_image_sharded(std::vector<pyOpenFHE_CKKS::CKKSCiphertext> & shards, const ndarray &npfilters, int mtx_size, const ndarray &permutation) {
|
||||
// convert to boost multiarray
|
||||
auto filters = numpyArrayToCppArray4D(npfilters);
|
||||
auto sigma = numpyListToCppLongIntVector(permutation);
|
||||
|
||||
auto first_shard = shards[0];
|
||||
|
||||
// do some math
|
||||
// filter dims: input channels, output channels, kernel size x, kernel size y
|
||||
int num_input_shards = shards.size();
|
||||
int shard_size = first_shard.getBatchSize();
|
||||
int channel_size = mtx_size * mtx_size; // assuming square matrices, may want to change this assumption later though
|
||||
int num_physical_channels_per_shard = shard_size / channel_size;
|
||||
int num_output_channels = filters.shape()[1];
|
||||
int ker_size = filters.shape()[2];
|
||||
int num_output_shards = num_output_channels / num_physical_channels_per_shard;
|
||||
|
||||
// if this is true, output_dup_factor > 1
|
||||
if (num_output_shards == 0) {
|
||||
num_output_shards = 1;
|
||||
}
|
||||
|
||||
// these are needed to slice filters correctly
|
||||
// TODO can probably simplify this logic
|
||||
int num_in_channels_per_shard;
|
||||
if (num_input_shards > 1) {
|
||||
// no duplication
|
||||
num_in_channels_per_shard = num_physical_channels_per_shard;
|
||||
} else {
|
||||
// number of channels in the single shard is the total number of channels,
|
||||
// can be determined by looking at dimension of filters
|
||||
num_in_channels_per_shard = filters.shape()[0];
|
||||
}
|
||||
int num_out_channels_per_shard;
|
||||
if (num_output_shards > 1) {
|
||||
num_out_channels_per_shard = num_physical_channels_per_shard;
|
||||
} else {
|
||||
num_out_channels_per_shard = filters.shape()[1];
|
||||
}
|
||||
|
||||
// using convolution_helper, compute one partial output shard at a time
|
||||
std::vector<pyOpenFHE_CKKS::CKKSCiphertext> partial_convolutions(num_in_channels_per_shard * num_output_shards * num_input_shards);
|
||||
|
||||
boost::multi_array<pyOpenFHE_CKKS::CKKSCiphertext, 3> all_ciphertext_rotations(boost::extents[num_input_shards][ker_size][ker_size]);
|
||||
#pragma omp parallel for
|
||||
for (int f = 0; f < num_input_shards; f++) {
|
||||
all_ciphertext_rotations[f] = get_all_rotations_image_sharded(shards[f], mtx_size, ker_size);
|
||||
}
|
||||
|
||||
#pragma omp parallel for collapse(3)
|
||||
for (int s = 0; s < num_output_shards; s++) {
|
||||
for (int f = 0; f < num_input_shards; f++) {
|
||||
for(int r = 0; r < num_in_channels_per_shard; ++r) {
|
||||
int idx = s * (num_in_channels_per_shard * num_input_shards) + f * num_in_channels_per_shard + r;
|
||||
partial_convolutions[idx] = convolution_helper_image_sharded(
|
||||
all_ciphertext_rotations[f],
|
||||
filters,
|
||||
mtx_size,
|
||||
r,
|
||||
num_in_channels_per_shard,
|
||||
num_out_channels_per_shard,
|
||||
f,
|
||||
s,
|
||||
sigma
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<pyOpenFHE_CKKS::CKKSCiphertext> output_shards(num_output_shards);
|
||||
#pragma omp parallel for
|
||||
for (int s = 0; s < num_output_shards; s++) {
|
||||
int idx = s * (num_in_channels_per_shard * num_input_shards);
|
||||
auto ctxt = partial_convolutions[idx];
|
||||
for(int offset = 1; offset < num_in_channels_per_shard * num_input_shards; ++offset) {
|
||||
ctxt += partial_convolutions[idx + offset];
|
||||
}
|
||||
output_shards[s] = ctxt;
|
||||
}
|
||||
|
||||
boost::python::list res = pyOpenFHE::make_list(num_output_shards);
|
||||
for(int s = 0 ; s < num_output_shards; ++s) {
|
||||
res[s] = output_shards[s];
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// entry point for channel sharding
|
||||
boost::python::list conv2d_channel_sharded(std::vector<pyOpenFHE_CKKS::CKKSCiphertext> &shards, const ndarray &npfilters, int mtx_size) {
|
||||
auto filters = numpyArrayToCppArray4D(npfilters);
|
||||
auto first_shard = shards[0];
|
||||
|
||||
// math!
|
||||
int num_input_shards = shards.size();
|
||||
int shard_size = first_shard.getBatchSize();
|
||||
int channel_size = mtx_size * mtx_size;
|
||||
int shards_per_channel = channel_size / shard_size;
|
||||
int num_input_channels = num_input_shards / shards_per_channel;
|
||||
int num_output_channels = filters.shape()[1];
|
||||
int ker_size = filters.shape()[2];
|
||||
int num_output_shards = num_output_channels * shards_per_channel;
|
||||
|
||||
// cache partial computations here
|
||||
std::vector<pyOpenFHE_CKKS::CKKSCiphertext> partial_convolutions(num_input_channels * shards_per_channel * num_output_channels);
|
||||
|
||||
auto channel_shard_rotations = get_all_rotations_channel_sharded(shards, shards_per_channel, mtx_size, ker_size);
|
||||
|
||||
// quintuply nested loop!
|
||||
#pragma omp parallel for collapse(3)
|
||||
for (int input_channel_index = 0; input_channel_index < num_input_channels; input_channel_index++) {
|
||||
for (int channel_shard_index = 0; channel_shard_index < shards_per_channel; channel_shard_index++) {
|
||||
for (int output_channel_index = 0; output_channel_index < num_output_channels; output_channel_index++) {
|
||||
int idx = input_channel_index * (shards_per_channel * num_output_channels) + output_channel_index * shards_per_channel + channel_shard_index;
|
||||
|
||||
// just pass in all shards since we'll need to reference adjacent ones
|
||||
partial_convolutions[idx] = convolution_helper_channel_sharded(
|
||||
channel_shard_rotations,
|
||||
filters,
|
||||
mtx_size,
|
||||
input_channel_index,
|
||||
channel_shard_index,
|
||||
output_channel_index
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sum up the partial_convolutions
|
||||
std::vector<pyOpenFHE_CKKS::CKKSCiphertext> output_shards(num_output_shards);
|
||||
|
||||
for (int output_channel_index = 0; output_channel_index < num_output_channels; output_channel_index++) {
|
||||
|
||||
// Loop over shards
|
||||
for (int shard_index = 0; shard_index < shards_per_channel; shard_index++) {
|
||||
|
||||
auto enc_sum = first_shard - first_shard; // zero
|
||||
|
||||
// Loop over input channels
|
||||
for (int input_channel_index = 0; input_channel_index < num_input_channels; input_channel_index++) {
|
||||
|
||||
// Calculate offset
|
||||
int idx = input_channel_index * (shards_per_channel * num_output_channels) + output_channel_index * shards_per_channel + shard_index;
|
||||
enc_sum += partial_convolutions[idx];
|
||||
}
|
||||
|
||||
int output_idx = output_channel_index * shards_per_channel + shard_index;
|
||||
output_shards[output_idx] = enc_sum;
|
||||
}
|
||||
}
|
||||
|
||||
boost::python::list res = pyOpenFHE::make_list(num_output_shards);
|
||||
for(int s = 0 ; s < num_output_shards; ++s) {
|
||||
res[s] = output_shards[s];
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
boost::python::list pyOpenFHE_CKKS::conv2d(const boost::python::list &py_shards, const ndarray &npfilters, int mtx_size, const ndarray &permutation) {
|
||||
// extract objects
|
||||
int num_input_shards = len(py_shards);
|
||||
std::vector<pyOpenFHE_CKKS::CKKSCiphertext> shards(num_input_shards);
|
||||
for (int i = 0 ; i < num_input_shards; ++i) {
|
||||
shards[i] = extract<pyOpenFHE_CKKS::CKKSCiphertext>(py_shards[i]);
|
||||
}
|
||||
|
||||
// based on channel size vs shard size, invoke corresponding function
|
||||
int shard_size = shards[0].getBatchSize();
|
||||
int channel_size = mtx_size * mtx_size;
|
||||
|
||||
if (shard_size >= channel_size) {
|
||||
return conv2d_image_sharded(shards, npfilters, mtx_size, permutation);
|
||||
} else {
|
||||
// A conv on a channel-sharded image won't have permuted channels, so ignore the permutation
|
||||
return conv2d_channel_sharded(shards, npfilters, mtx_size);
|
||||
}
|
||||
}
|
||||
30
src/ckks/cnn/he_cnn.cpp
Normal file
30
src/ckks/cnn/he_cnn.cpp
Normal file
@@ -0,0 +1,30 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
#include "ckks/CKKS_ciphertext_extension.hpp"
|
||||
#include "ckks/bindings.hpp"
|
||||
#include "ckks/cnn/he_cnn.hpp"
|
||||
#include "ckks/cnn/pool.hpp"
|
||||
#include "ckks/cnn/upsample.hpp"
|
||||
#include "ckks/cnn/conv.hpp"
|
||||
#include "ckks/cnn/linear.hpp"
|
||||
#include "ckks/cnn/poly.hpp"
|
||||
#include <boost/python/scope.hpp>
|
||||
#include <omp.h>
|
||||
|
||||
using namespace pyOpenFHE;
|
||||
using namespace pyOpenFHE_CKKS;
|
||||
using namespace boost::python;
|
||||
using namespace boost::python::numpy;
|
||||
|
||||
class boost_CNN {};
|
||||
|
||||
void pyOpenFHE_CKKS::export_he_cnn_functions_boost() {
|
||||
def("conv2d", conv2d);
|
||||
def("linear", linear);
|
||||
def("pool", pool);
|
||||
def("upsample", upsample);
|
||||
def("fhe_gelu", fhe_gelu);
|
||||
def("omp_set_num_threads", omp_set_num_threads);
|
||||
def("omp_set_nested", omp_set_nested);
|
||||
def("omp_set_dynamic", omp_set_dynamic);
|
||||
}
|
||||
83
src/ckks/cnn/linear.cpp
Normal file
83
src/ckks/cnn/linear.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
#include "ckks/CKKS_ciphertext_extension.hpp"
|
||||
#include "ckks/cnn/linear.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/numpy.hpp>
|
||||
#include <boost/python/scope.hpp>
|
||||
#include <omp.h>
|
||||
#include <cstdlib>
|
||||
|
||||
pyOpenFHE_CKKS::CKKSCiphertext pyOpenFHE_CKKS::linear(const boost::python::list &py_shards, const ndarray &npweights, const int mtx_size, const ndarray &permutation, const int pool_factor) {
|
||||
auto sigma = numpyListToCppLongIntVector(permutation);
|
||||
auto weights = numpyArrayToCppArray2D(npweights);
|
||||
|
||||
int num_shards = len(py_shards);
|
||||
std::vector<pyOpenFHE_CKKS::CKKSCiphertext> shards(num_shards);
|
||||
|
||||
// #pragma omp parallel for
|
||||
for(int i = 0 ; i < num_shards; ++i) {
|
||||
shards[i] = extract<pyOpenFHE_CKKS::CKKSCiphertext>(py_shards[i]);
|
||||
}
|
||||
|
||||
// do some math
|
||||
auto first_shard = shards[0];
|
||||
|
||||
int num_outputs = weights.shape()[0];
|
||||
int num_inputs = weights.shape()[1];
|
||||
int shard_size = first_shard.getBatchSize();
|
||||
int channel_size = mtx_size * mtx_size;
|
||||
int num_physical_channels_per_shard = shard_size / channel_size;
|
||||
|
||||
int duplication_factor = 1;
|
||||
if (num_shards == 1) {
|
||||
duplication_factor = shard_size / num_inputs;
|
||||
}
|
||||
|
||||
std::vector<pyOpenFHE_CKKS::CKKSCiphertext> partial_output(num_outputs * num_shards);
|
||||
|
||||
#pragma omp parallel for collapse(2)
|
||||
for (int r = 0; r < num_outputs; ++r) {
|
||||
for (int s = 0; s < num_shards; s++) {
|
||||
std::vector<double> v(shard_size, 0.0);
|
||||
for (int i = 0; i < shard_size; ++i) {
|
||||
int physical_channel_idx = i / channel_size + s * num_physical_channels_per_shard;
|
||||
int logical_channel_idx = sigma[physical_channel_idx / duplication_factor];
|
||||
int channel_offset = i % channel_size;
|
||||
int idx = logical_channel_idx * channel_size + channel_offset;
|
||||
|
||||
v[i] = weights[r][idx];
|
||||
}
|
||||
auto res = shards[s] * v;
|
||||
|
||||
/*
|
||||
power-of-two add and rotate algorithm
|
||||
Note: we should be able to exit early if there is duplication,
|
||||
rather than going all the way down
|
||||
and then dividing by the duplication_factor afterwards.
|
||||
*/
|
||||
int shift = shard_size / 2;
|
||||
while (shift > 0) {
|
||||
res = res + (res >> shift);
|
||||
shift /= 2;
|
||||
}
|
||||
|
||||
// only keep a single output value
|
||||
std::vector<double> activ_mask(shard_size, 0.0);
|
||||
activ_mask[r] = 1.0 / (duplication_factor * pool_factor * pool_factor);
|
||||
res *= activ_mask;
|
||||
|
||||
partial_output[r * num_shards + s] = res;
|
||||
}
|
||||
}
|
||||
|
||||
auto enc_sum = partial_output[0];
|
||||
for(int r = 1; r < num_outputs * num_shards; ++r) {
|
||||
enc_sum += partial_output[r];
|
||||
}
|
||||
|
||||
return enc_sum;
|
||||
}
|
||||
71
src/ckks/cnn/poly.cpp
Normal file
71
src/ckks/cnn/poly.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
#include "ckks/CKKS_ciphertext_extension.hpp"
|
||||
#include "ckks/cnn/poly.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/numpy.hpp>
|
||||
#include <boost/python/scope.hpp>
|
||||
#include <omp.h>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "math/chebyshev.h"
|
||||
|
||||
|
||||
double normalCDF(double value) {
|
||||
return 0.5 * erfc(-value * M_SQRT1_2);
|
||||
}
|
||||
|
||||
double cpp_gelu(double x) {
|
||||
return x * normalCDF(x);
|
||||
}
|
||||
|
||||
double cpp_gelu_scaled(double x, double bound=1.0) {
|
||||
return cpp_gelu(x * bound);
|
||||
}
|
||||
|
||||
double cpp_relu(double x) {
|
||||
if(x < 0) return 0;
|
||||
return x;
|
||||
}
|
||||
|
||||
boost::python::list pyOpenFHE_CKKS::fhe_gelu(const boost::python::list &py_shards, int degree, double bound) {
|
||||
|
||||
int num_input_shards = len(py_shards);
|
||||
std::vector<pyOpenFHE_CKKS::CKKSCiphertext> shards(num_input_shards);
|
||||
for(int i = 0 ; i < num_input_shards; ++i) {
|
||||
shards[i] = extract<pyOpenFHE_CKKS::CKKSCiphertext>(py_shards[i]);
|
||||
}
|
||||
|
||||
int level = shards[0].getTowersRemaining() - 2;
|
||||
if(
|
||||
(level <= 2) ||
|
||||
((degree <= 5) && (level < 3)) ||
|
||||
((degree <= 13) && (degree >= 6) && (level < 4)) ||
|
||||
((degree <= 27) && (degree >= 14) && (level < 5)) ||
|
||||
((degree <= 59) && (degree >= 28) && (level < 6)) ||
|
||||
((degree <= 119) && (degree >= 60) && (level < 7)) ||
|
||||
((degree <= 200) && (degree >= 120) && (level < 8))
|
||||
) {
|
||||
throw std::runtime_error(fmt::format("Insufficient number of towers remaining = {} to evaluate this Chebyshev series of degree = {}", level + 2, degree));
|
||||
}
|
||||
|
||||
std::vector<double> coefficients = EvalChebyshevCoefficients([bound](double x) -> double { return cpp_gelu_scaled(x, bound); }, -1, 1, degree);
|
||||
|
||||
#pragma omp parallel for
|
||||
for(int i = 0 ; i < num_input_shards; ++i) {
|
||||
auto cc = shards[i].cipher->GetCryptoContext();
|
||||
shards[i].cipher = cc->EvalChebyshevSeries(shards[i].cipher, coefficients, -1.0, 1.0);
|
||||
}
|
||||
|
||||
boost::python::list res = pyOpenFHE::make_list(num_input_shards);
|
||||
for(int i = 0 ; i < num_input_shards; ++i) {
|
||||
res[i] = shards[i];
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
}
|
||||
323
src/ckks/cnn/pool.cpp
Normal file
323
src/ckks/cnn/pool.cpp
Normal file
@@ -0,0 +1,323 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
#include "ckks/cnn/pool.hpp"
|
||||
#include "ckks/CKKS_ciphertext_extension.hpp"
|
||||
|
||||
using namespace pyOpenFHE;
|
||||
using namespace pyOpenFHE_CKKS;
|
||||
using namespace boost::python;
|
||||
using namespace boost::python::numpy;
|
||||
|
||||
/*
|
||||
* Applies a stride-1 convolution with a kernel of 1s, and cyclic rotations (instead of logical).
|
||||
* The masking and dividing by 4 is accomplished by the downsample masks later,
|
||||
* so we can get away with a very simple convolution here.
|
||||
*/
|
||||
void pool_pre_convolution(std::vector<pyOpenFHE_CKKS::CKKSCiphertext>& shards, int num_cols) {
|
||||
int num_input_shards = shards.size();
|
||||
|
||||
std::vector<pyOpenFHE_CKKS::CKKSCiphertext> shifts(num_input_shards * 4);
|
||||
int shift_vals[] = {0, 1, num_cols, num_cols + 1};
|
||||
|
||||
#pragma omp parallel for collapse(2)
|
||||
for (int i = 0 ; i < num_input_shards; ++i) {
|
||||
for (int j = 0; j < 4; ++j) {
|
||||
shifts[i * 4 + j] = shards[i] << shift_vals[j];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma omp parallel for
|
||||
for (int i = 0; i < num_input_shards; ++i) {
|
||||
shards[i] = shifts[i * 4];
|
||||
for (int j = 1; j < 4; ++j) {
|
||||
shards[i] += shifts[i * 4 + j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pool_horizontal_reduce(std::vector<pyOpenFHE_CKKS::CKKSCiphertext>& shards, int num_rows, int num_cols, int num_physical_channels_per_shard, double fill_value) {
|
||||
int num_input_shards = shards.size();
|
||||
int shard_size = shards[0].getBatchSize();
|
||||
|
||||
const int half_num_cols = num_cols / 2;
|
||||
const int channel_size = num_rows * num_cols;
|
||||
|
||||
std::vector<std::vector<double>> horizontal_masks(half_num_cols, std::vector<double>(shard_size));
|
||||
#pragma omp parallel for
|
||||
for (int i = 0 ; i < half_num_cols; ++i) { // column index, also vector index
|
||||
for (int j = 0; j < num_rows; ++j) { // row index
|
||||
for (int k = 0; k < num_physical_channels_per_shard; ++k) { // channel index
|
||||
horizontal_masks[i][i + j * num_cols + k * channel_size] = fill_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<pyOpenFHE_CKKS::CKKSCiphertext> horizontal_reductions(half_num_cols * num_input_shards);
|
||||
#pragma omp parallel for collapse(2)
|
||||
for (int s = 0 ; s < num_input_shards; ++s) {
|
||||
for (int i = 0 ; i < half_num_cols; ++i) {
|
||||
// if performed sequentially, we could have a loop with `shards[s] <<= 1`
|
||||
horizontal_reductions[i + s * half_num_cols] = (shards[s] << i) * horizontal_masks[i];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma omp parallel for
|
||||
for (int s = 0 ; s < num_input_shards; ++s) {
|
||||
auto ctxt = horizontal_reductions[s * half_num_cols];
|
||||
for (int i = 1 ; i < half_num_cols; ++i) {
|
||||
ctxt += horizontal_reductions[i + s * half_num_cols];
|
||||
}
|
||||
shards[s] = ctxt;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void pool_vertical_reduce_image_sharded(std::vector<pyOpenFHE_CKKS::CKKSCiphertext>& shards, int num_rows, int num_cols, int num_physical_channels_per_shard, double fill_value) {
|
||||
int num_input_shards = shards.size();
|
||||
int shard_size = shards[0].getBatchSize();
|
||||
|
||||
const int half_num_rows = num_rows / 2;
|
||||
const int channel_size = num_rows * num_cols;
|
||||
|
||||
std::vector<std::vector<double>> vertical_masks(half_num_rows, std::vector<double>(shard_size));
|
||||
#pragma omp parallel for
|
||||
for (int i = 0; i < half_num_rows; ++i) { // row index, also vector index
|
||||
for (int j = 0; j < num_cols; ++j) { // column index
|
||||
for (int k = 0; k < num_physical_channels_per_shard; ++k) { // channel index
|
||||
vertical_masks[i][i * half_num_rows + j + k * channel_size] = fill_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<pyOpenFHE_CKKS::CKKSCiphertext> vertical_reductions(half_num_rows * num_input_shards);
|
||||
#pragma omp parallel for collapse(2)
|
||||
for (int s = 0 ; s < num_input_shards; ++s) {
|
||||
for (int i = 0 ; i < half_num_rows; ++i) {
|
||||
// if performed sequentially, we could have a loop with `shards[s] <<= 3 * half_num_rows`
|
||||
vertical_reductions[i + s * half_num_rows] = (shards[s] << (i * half_num_rows * 3)) * vertical_masks[i];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma omp parallel for
|
||||
for (int s = 0 ; s < num_input_shards; ++s) {
|
||||
auto ctxt = vertical_reductions[s * half_num_rows];
|
||||
for (int i = 1 ; i < half_num_rows; ++i) {
|
||||
ctxt += vertical_reductions[i + s * half_num_rows];
|
||||
}
|
||||
shards[s] = ctxt;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void pool_vertical_reduce_channel_sharded(std::vector<pyOpenFHE_CKKS::CKKSCiphertext>& shards, int num_rows, int num_cols) {
|
||||
int num_input_shards = shards.size();
|
||||
int shard_size = shards[0].getBatchSize();
|
||||
|
||||
const int half_num_rows = num_rows / 2;
|
||||
const int half_num_cols = num_cols / 2;
|
||||
|
||||
std::vector<std::vector<double>> vertical_masks(half_num_rows, std::vector<double>(shard_size));
|
||||
#pragma omp parallel for
|
||||
for (int i = 0; i < half_num_rows; ++i) { // row index, also vector index
|
||||
for (int j = 0; j < half_num_cols; ++j) { // column index
|
||||
vertical_masks[i][i * half_num_cols + j] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<pyOpenFHE_CKKS::CKKSCiphertext> vertical_reductions(half_num_rows * num_input_shards);
|
||||
#pragma omp parallel for collapse(2)
|
||||
for (int s = 0 ; s < num_input_shards; ++s) {
|
||||
for (int i = 0 ; i < half_num_rows; ++i) {
|
||||
// if performed sequentially, we could have a loop with `shards[s] <<= 3 * half_num_rows`
|
||||
vertical_reductions[i + s * half_num_rows] = (shards[s] << (i * half_num_cols * 3)) * vertical_masks[i];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma omp parallel for
|
||||
for (int s = 0 ; s < num_input_shards; ++s) {
|
||||
auto ctxt = vertical_reductions[s * half_num_rows];
|
||||
for (int i = 1 ; i < half_num_rows; ++i) {
|
||||
ctxt += vertical_reductions[i + s * half_num_rows];
|
||||
}
|
||||
shards[s] = ctxt;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<pyOpenFHE_CKKS::CKKSCiphertext> pool_consolidate_and_duplicate_image_sharded(std::vector<pyOpenFHE_CKKS::CKKSCiphertext>& shards, int num_rows, int num_cols, int num_physical_channels_per_shard) {
|
||||
int num_input_shards = shards.size();
|
||||
|
||||
const int half_num_rows = num_rows / 2;
|
||||
const int half_num_cols = num_cols / 2;
|
||||
|
||||
int num_output_shards;
|
||||
int new_duplication_ratio; // how many times will we re-duplicate the data?
|
||||
switch (num_input_shards) {
|
||||
case 1:
|
||||
num_output_shards = 1;
|
||||
new_duplication_ratio = 4;
|
||||
break;
|
||||
case 2:
|
||||
num_output_shards = 1;
|
||||
new_duplication_ratio = 2;
|
||||
break;
|
||||
default:
|
||||
num_output_shards = num_input_shards / 4;
|
||||
new_duplication_ratio = 1;
|
||||
}
|
||||
|
||||
std::vector<pyOpenFHE_CKKS::CKKSCiphertext> output_shards(num_output_shards);
|
||||
|
||||
if (new_duplication_ratio == 1) {
|
||||
#pragma omp parallel for
|
||||
for (int s = 0; s < num_input_shards; ++s) {
|
||||
int r = (s % 4) * half_num_rows * half_num_cols;
|
||||
shards[s] >>= r;
|
||||
}
|
||||
|
||||
#pragma omp parallel for
|
||||
for (int s = 0; s < num_input_shards; s += 4) {
|
||||
for (int j = 1; j < 4; ++j) {
|
||||
shards[s] += shards[s+j];
|
||||
}
|
||||
output_shards[s / 4] = shards[s];
|
||||
}
|
||||
|
||||
// no duplication step.
|
||||
|
||||
}
|
||||
if (new_duplication_ratio == 2) {
|
||||
// this is simplified because we know we must have num_input_shards = 2 .
|
||||
int r = half_num_rows * half_num_cols;
|
||||
|
||||
shards[1] >>= (2 * r);
|
||||
shards[0] += shards[1];
|
||||
shards[0] += shards[0] >> r;
|
||||
|
||||
output_shards[0] = shards[0];
|
||||
}
|
||||
if (new_duplication_ratio == 4) {
|
||||
// this is simplified because we know we must have num_input_shards = 1 .
|
||||
int r = half_num_rows * half_num_cols;
|
||||
shards[0] += shards[0] >> r;
|
||||
shards[0] += shards[0] >> (2 * r);
|
||||
|
||||
output_shards[0] = shards[0];
|
||||
}
|
||||
|
||||
return output_shards;
|
||||
}
|
||||
|
||||
std::vector<pyOpenFHE_CKKS::CKKSCiphertext> pool_consolidate_and_duplicate_channel_sharded(std::vector<pyOpenFHE_CKKS::CKKSCiphertext>& shards) {
|
||||
int num_input_shards = shards.size();
|
||||
int shard_size = shards[0].getBatchSize();
|
||||
|
||||
int num_output_shards;
|
||||
int duplication_ratio;
|
||||
if (num_input_shards > 2) {
|
||||
num_output_shards = num_input_shards / 4;
|
||||
duplication_ratio = 1;
|
||||
} else {
|
||||
// Degenerate case we have only one channel consisting of only 2 shards.
|
||||
// We're then forced to duplicate to fill up the resulting single shard.
|
||||
num_output_shards = 1;
|
||||
duplication_ratio = 2;
|
||||
}
|
||||
|
||||
std::vector<pyOpenFHE_CKKS::CKKSCiphertext> output_shards(num_output_shards);
|
||||
|
||||
if (duplication_ratio == 1) {
|
||||
int rotation_factor = shard_size / 4;
|
||||
for (int output_shard_index = 0; output_shard_index < num_output_shards; output_shard_index++) {
|
||||
output_shards[output_shard_index] = shards[output_shard_index * 4];
|
||||
// each new shard is four old shards
|
||||
for (int i = 1; i < 4; i++) {
|
||||
output_shards[output_shard_index] += shards[i + output_shard_index * 4] >> (i * rotation_factor);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int rotation_factor = shard_size / 4;
|
||||
output_shards[0] = shards[0] + (shards[1] >> rotation_factor);
|
||||
|
||||
// duplication step
|
||||
output_shards[0] = output_shards[0] + (output_shards[0] >> (rotation_factor * 2));
|
||||
}
|
||||
|
||||
return output_shards;
|
||||
}
|
||||
|
||||
boost::python::list pool_image_sharded(std::vector<pyOpenFHE_CKKS::CKKSCiphertext>& shards, int mtx_size, bool conv) {
|
||||
int shard_size = shards[0].getBatchSize();
|
||||
int channel_size = mtx_size * mtx_size; // assuming square matrices, may want to change this assumption later though
|
||||
int num_physical_channels_per_shard = shard_size / channel_size;
|
||||
|
||||
// this toggles whether we actually do a pooling or just the downsample, deshard, and duplicate steps
|
||||
double fill_value = 1.0;
|
||||
if (conv) {
|
||||
pool_pre_convolution(shards, mtx_size);
|
||||
fill_value = 0.25;
|
||||
}
|
||||
|
||||
pool_horizontal_reduce(shards, mtx_size, mtx_size, num_physical_channels_per_shard, fill_value);
|
||||
pool_vertical_reduce_image_sharded(shards, mtx_size, mtx_size, num_physical_channels_per_shard, 1.0);
|
||||
|
||||
auto output_shards = pool_consolidate_and_duplicate_image_sharded(shards, mtx_size, mtx_size, num_physical_channels_per_shard);
|
||||
int num_output_shards = output_shards.size();
|
||||
|
||||
boost::python::list res = pyOpenFHE::make_list(num_output_shards);
|
||||
// #pragma omp parallel for
|
||||
for (int s = 0 ; s < num_output_shards; ++s) {
|
||||
res[s] = output_shards[s];
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
}
|
||||
|
||||
boost::python::list pool_channel_sharded(std::vector<pyOpenFHE_CKKS::CKKSCiphertext>& shards, int mtx_size, bool conv) {
|
||||
int shard_size = shards[0].getBatchSize();
|
||||
int channel_size = mtx_size * mtx_size; // assuming square matrices, may want to change this assumption later though
|
||||
int shards_per_channel = channel_size / shard_size;
|
||||
int num_rows_per_shard = mtx_size / shards_per_channel;
|
||||
int num_cols_per_shard = mtx_size;
|
||||
|
||||
// this toggles whether we actually do a pooling or just the downsample, consolidate, and duplicate steps
|
||||
double fill_value = 1.0;
|
||||
if (conv) {
|
||||
pool_pre_convolution(shards, num_cols_per_shard);
|
||||
fill_value = 0.25;
|
||||
}
|
||||
|
||||
pool_horizontal_reduce(shards, num_rows_per_shard, num_cols_per_shard, 1, fill_value);
|
||||
pool_vertical_reduce_channel_sharded(shards, num_rows_per_shard, num_cols_per_shard);
|
||||
|
||||
auto output_shards = pool_consolidate_and_duplicate_channel_sharded(shards);
|
||||
int num_output_shards = output_shards.size();
|
||||
|
||||
boost::python::list res = pyOpenFHE::make_list(num_output_shards);
|
||||
// #pragma omp parallel for
|
||||
for (int s = 0 ; s < num_output_shards; ++s) {
|
||||
res[s] = output_shards[s];
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
}
|
||||
|
||||
boost::python::list pyOpenFHE_CKKS::pool(const boost::python::list &py_shards, int mtx_size, bool conv) {
|
||||
int num_input_shards = len(py_shards);
|
||||
|
||||
std::vector<pyOpenFHE_CKKS::CKKSCiphertext> shards(num_input_shards);
|
||||
|
||||
for (int i = 0 ; i < num_input_shards; ++i) {
|
||||
shards[i] = extract<pyOpenFHE_CKKS::CKKSCiphertext>(py_shards[i]);
|
||||
}
|
||||
|
||||
int shard_size = shards[0].getBatchSize();
|
||||
int channel_size = mtx_size * mtx_size; // assuming square matrices, may want to change this assumption later though
|
||||
|
||||
if (shard_size >= channel_size) {
|
||||
return pool_image_sharded(shards, mtx_size, conv);
|
||||
} else {
|
||||
return pool_channel_sharded(shards, mtx_size, conv);
|
||||
}
|
||||
}
|
||||
285
src/ckks/cnn/upsample.cpp
Normal file
285
src/ckks/cnn/upsample.cpp
Normal file
@@ -0,0 +1,285 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
#include "ckks/CKKS_ciphertext_extension.hpp"
|
||||
#include "ckks/cnn/upsample.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/numpy.hpp>
|
||||
#include <boost/python/scope.hpp>
|
||||
#include <omp.h>
|
||||
#include <cstdlib>
|
||||
|
||||
|
||||
std::vector<pyOpenFHE_CKKS::CKKSCiphertext> upsample_vertical_expand(const std::vector<pyOpenFHE_CKKS::CKKSCiphertext>& shards, int num_rows, int num_cols, int num_physical_channels_per_shard, int duplication_ratio, double fill_value) {
|
||||
int num_input_shards = shards.size();
|
||||
int shard_size = shards[0].getBatchSize();
|
||||
|
||||
const int channel_size = num_rows * num_cols;
|
||||
const int channel_size_after_upsample = channel_size * 4;
|
||||
/*
|
||||
distance_to_next_subchannel:
|
||||
if output is small shards and have no duplication, this is the start of the 2nd physical channel.
|
||||
if have duplication, this is the start of either the 2nd or 4th physical channel.
|
||||
if output is big shards, this is either 1/4 or 1/2 way through the 1st channel.
|
||||
if input is big shards, this is 1/4 of the way through the first shard.
|
||||
|
||||
num_rows_per_shard_after_upsample:
|
||||
this counts the number of pre-upsample rows per channel that fit into a single ctxt,
|
||||
i.e. we don't count the zero-filled rows that we're inserting.
|
||||
it is only really used if we output big shards.
|
||||
if we output small shards then it equals num_rows
|
||||
*/
|
||||
int num_rows_per_shard_after_upsample = num_rows;
|
||||
int distance_to_next_subchannel = channel_size;
|
||||
int num_shifts_per_shard = 4;
|
||||
if(channel_size_after_upsample > shard_size) {
|
||||
num_rows_per_shard_after_upsample = num_rows / (channel_size_after_upsample / shard_size);
|
||||
distance_to_next_subchannel = channel_size / (channel_size_after_upsample / shard_size);
|
||||
|
||||
// complete edge case, for when you have exactly one shard duplicated twice of half the shard size
|
||||
if(duplication_ratio == 2) {
|
||||
num_shifts_per_shard = 2;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(duplication_ratio == 2) {
|
||||
distance_to_next_subchannel = 2 * channel_size;
|
||||
num_shifts_per_shard = 2;
|
||||
}
|
||||
if(duplication_ratio > 2) {
|
||||
distance_to_next_subchannel = 4 * channel_size;
|
||||
num_shifts_per_shard = 1;
|
||||
}
|
||||
}
|
||||
|
||||
int num_expanded_shards = num_input_shards * num_shifts_per_shard;
|
||||
|
||||
if(num_rows_per_shard_after_upsample < 1) { // not a typo.
|
||||
std::string s = fmt::format("Shards must be able to store at least two rows");
|
||||
throw std::runtime_error(s);
|
||||
}
|
||||
|
||||
std::vector<std::vector<double>> vertical_masks(num_rows_per_shard_after_upsample, std::vector<double>(shard_size));
|
||||
#pragma omp parallel for
|
||||
for (int j = 0; j < num_rows_per_shard_after_upsample; ++j) { // row index
|
||||
for (int i = 0 ; i < num_cols; ++i) { // column index
|
||||
for (int k = 0; k < num_physical_channels_per_shard; k += 4) { // channel index
|
||||
vertical_masks[j][i + j * num_cols * 4 + k * channel_size] = fill_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<pyOpenFHE_CKKS::CKKSCiphertext> shifted_shards(num_expanded_shards);
|
||||
#pragma omp parallel for collapse(2)
|
||||
for (int s = 0 ; s < num_input_shards; ++s) {
|
||||
for(int i = 0; i < num_shifts_per_shard; ++i) {
|
||||
shifted_shards[i + s * num_shifts_per_shard] = shards[s] << (i * distance_to_next_subchannel);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<pyOpenFHE_CKKS::CKKSCiphertext> vertical_expansions(num_rows_per_shard_after_upsample * num_expanded_shards);
|
||||
#pragma omp parallel for collapse(2)
|
||||
for (int s = 0 ; s < num_expanded_shards; ++s) {
|
||||
for (int i = 0 ; i < num_rows_per_shard_after_upsample; ++i) {
|
||||
// if performed sequentially, we could have a loop with `shifted_shards[s] >>= 3 * num_cols`
|
||||
vertical_expansions[i + s * num_rows_per_shard_after_upsample] = (shifted_shards[s] >> (3 * num_cols * i)) * vertical_masks[i];
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<pyOpenFHE_CKKS::CKKSCiphertext> new_shards(num_expanded_shards);
|
||||
#pragma omp parallel for
|
||||
for (int s = 0; s < num_expanded_shards; ++s) {
|
||||
auto ctxt = vertical_expansions[s * num_rows_per_shard_after_upsample];
|
||||
for (int i = 1 ; i < num_rows_per_shard_after_upsample; ++i) {
|
||||
ctxt += vertical_expansions[i + s * num_rows_per_shard_after_upsample];
|
||||
}
|
||||
new_shards[s] = ctxt;
|
||||
}
|
||||
|
||||
return new_shards;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
Note: the channel dimensions in this function are a little strange,
|
||||
because this is called after the upsample_vertical_expand() function
|
||||
has already done the bulk of the work in reshaping the inputs.
|
||||
*/
|
||||
void upsample_horizontal_expand(std::vector<pyOpenFHE_CKKS::CKKSCiphertext>& shards, int num_rows, int num_cols, double fill_value) {
|
||||
int num_input_shards = shards.size();
|
||||
int shard_size = shards[0].getBatchSize();
|
||||
|
||||
const int channel_size = num_rows * num_cols;
|
||||
const int channel_size_after_upsample = channel_size * 4;
|
||||
int num_physical_channels_per_shard = shard_size / channel_size;
|
||||
if(num_physical_channels_per_shard == 0) {
|
||||
num_physical_channels_per_shard = 1;
|
||||
}
|
||||
|
||||
int num_rows_per_shard_after_upsample = num_rows;
|
||||
if(channel_size_after_upsample > shard_size) {
|
||||
num_rows_per_shard_after_upsample = num_rows / (channel_size_after_upsample / shard_size);
|
||||
}
|
||||
|
||||
std::vector<std::vector<double>> horizontal_masks(num_cols, std::vector<double>(shard_size));
|
||||
#pragma omp parallel for
|
||||
for (int i = 0; i < num_cols; ++i) { // column index
|
||||
for (int j = 0; j < num_rows_per_shard_after_upsample; ++j) { // row index
|
||||
for (int k = 0; k < num_physical_channels_per_shard; k += 4) { // channel index
|
||||
horizontal_masks[i][i * 2 + j * num_cols * 4 + k * channel_size] = fill_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<pyOpenFHE_CKKS::CKKSCiphertext> horizontal_expansions(num_cols * num_input_shards);
|
||||
#pragma omp parallel for collapse(2)
|
||||
for (int s = 0 ; s < num_input_shards; ++s) {
|
||||
for (int i = 0 ; i < num_cols; ++i) {
|
||||
// if performed sequentially, we could have a loop with `shards[s] >>= 1`
|
||||
horizontal_expansions[i + s * num_cols] = (shards[s] >> (i)) * horizontal_masks[i];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma omp parallel for
|
||||
for (int s = 0 ; s < num_input_shards; ++s) {
|
||||
auto ctxt = horizontal_expansions[s * num_cols];
|
||||
for (int i = 1 ; i < num_cols; ++i) {
|
||||
ctxt += horizontal_expansions[i + s * num_cols];
|
||||
}
|
||||
shards[s] = ctxt;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
go from a 2x2 bed of nails upsample to a 2x2 nearest neighbor upsample
|
||||
*/
|
||||
void nearest_neighbor_interpolate(std::vector<pyOpenFHE_CKKS::CKKSCiphertext>& shards, int num_cols) {
|
||||
int num_input_shards = shards.size();
|
||||
#pragma omp parallel for
|
||||
for (int s = 0; s < num_input_shards; ++s) {
|
||||
auto ctxt = shards[s];
|
||||
ctxt += ctxt >> 1;
|
||||
ctxt += ctxt >> num_cols;
|
||||
shards[s] = ctxt;
|
||||
}
|
||||
}
|
||||
|
||||
boost::python::list small_shards_upsample(const std::vector<pyOpenFHE_CKKS::CKKSCiphertext> & shards, const int mtx_size, const ndarray &permutation, int upsample_type) {
|
||||
|
||||
int shard_size = shards[0].getBatchSize();
|
||||
int channel_size = mtx_size * mtx_size; // assuming square matrices, may want to change this assumption later though
|
||||
|
||||
auto sigma = numpyListToCppLongIntVector(permutation);
|
||||
int num_logical_channels = sigma.size();
|
||||
int num_physical_channels_per_shard = shard_size / channel_size;
|
||||
|
||||
int duplication_ratio = 1;
|
||||
if(shard_size > channel_size) {
|
||||
duplication_ratio = num_physical_channels_per_shard / num_logical_channels;
|
||||
}
|
||||
|
||||
double fill_value = 1.0;
|
||||
|
||||
auto output_shards = upsample_vertical_expand(shards, mtx_size, mtx_size, num_physical_channels_per_shard, duplication_ratio, fill_value);
|
||||
upsample_horizontal_expand(output_shards, mtx_size, mtx_size, fill_value);
|
||||
|
||||
if(upsample_type == 0) {
|
||||
// bed of nails
|
||||
// nothing to do here.
|
||||
}
|
||||
else if(upsample_type == 1) {
|
||||
// nearest neighbor
|
||||
nearest_neighbor_interpolate(output_shards, mtx_size * 2);
|
||||
}
|
||||
else {
|
||||
std::string s = fmt::format("Upsample type #{} is not supported", upsample_type);
|
||||
throw std::runtime_error(s);
|
||||
}
|
||||
|
||||
int num_output_shards = output_shards.size();
|
||||
boost::python::list res = pyOpenFHE::make_list(num_output_shards);
|
||||
|
||||
for (int s = 0 ; s < num_output_shards; ++s) {
|
||||
res[s] = output_shards[s];
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
}
|
||||
|
||||
boost::python::list big_shards_upsample(const std::vector<pyOpenFHE_CKKS::CKKSCiphertext> & shards, const int mtx_size, const ndarray &permutation, int upsample_type) {
|
||||
|
||||
int shard_size = shards[0].getBatchSize();
|
||||
|
||||
int num_rows_per_shard = shard_size / mtx_size;
|
||||
|
||||
int num_physical_channels_per_shard = 1;
|
||||
int duplication_ratio = 1;
|
||||
double fill_value = 1.0;
|
||||
|
||||
auto output_shards = upsample_vertical_expand(shards, num_rows_per_shard, mtx_size, num_physical_channels_per_shard, duplication_ratio, fill_value);
|
||||
upsample_horizontal_expand(output_shards, num_rows_per_shard, mtx_size, fill_value);
|
||||
|
||||
if(upsample_type == 0) {
|
||||
// bed of nails
|
||||
// nothing to do here.
|
||||
}
|
||||
else if(upsample_type == 1) {
|
||||
// nearest neighbor
|
||||
nearest_neighbor_interpolate(output_shards, mtx_size * 2);
|
||||
}
|
||||
else {
|
||||
std::string s = fmt::format("Upsample type #{} is not supported", upsample_type);
|
||||
throw std::runtime_error(s);
|
||||
}
|
||||
|
||||
int num_output_shards = output_shards.size();
|
||||
boost::python::list res = pyOpenFHE::make_list(num_output_shards);
|
||||
|
||||
for (int s = 0 ; s < num_output_shards; ++s) {
|
||||
res[s] = output_shards[s];
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
The permutation is passed in because big shards are never permuted.
|
||||
If we have small shards before upsampling but big shards after,
|
||||
then we rearrange the shards to remove the permutation.
|
||||
If we output small shards, then we ignore the permutation.
|
||||
|
||||
upsample_type:
|
||||
= 0 for bed of nails (fill with zeroes)
|
||||
= 1 for nearest neighbor
|
||||
*/
|
||||
boost::python::list pyOpenFHE_CKKS::upsample(const boost::python::list &py_shards, const int mtx_size, const ndarray &permutation, int upsample_type){
|
||||
|
||||
int num_input_shards = len(py_shards);
|
||||
std::vector<pyOpenFHE_CKKS::CKKSCiphertext> shards(num_input_shards);
|
||||
|
||||
for (int i = 0 ; i < num_input_shards; ++i) {
|
||||
shards[i] = extract<pyOpenFHE_CKKS::CKKSCiphertext>(py_shards[i]);
|
||||
}
|
||||
|
||||
int shard_size = shards[0].getBatchSize();
|
||||
int channel_size = mtx_size * mtx_size; // assuming square matrices, may want to change this assumption later though
|
||||
|
||||
|
||||
if (shard_size >= channel_size) {
|
||||
return small_shards_upsample(shards, mtx_size, permutation, upsample_type);
|
||||
|
||||
} else {
|
||||
return big_shards_upsample(shards, mtx_size, permutation, upsample_type);
|
||||
}
|
||||
|
||||
return py_shards;
|
||||
|
||||
}
|
||||
183
src/ckks/utils.cpp
Normal file
183
src/ckks/utils.cpp
Normal file
@@ -0,0 +1,183 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
#include "ckks/CKKS_ciphertext_extension.hpp"
|
||||
#include "ckks/cnn/he_cnn.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
using namespace pyOpenFHE;
|
||||
using namespace boost::python;
|
||||
using namespace boost::python::numpy;
|
||||
|
||||
// this could be reworked, but not a priority since it works
|
||||
int kernel_index_to_shift(int i, int ker_size) {
|
||||
int shift;
|
||||
if ((ker_size % 2) == 0) {
|
||||
shift = i - (ker_size / 2) + 1;
|
||||
} else {
|
||||
shift = i - (ker_size / 2);
|
||||
}
|
||||
return shift;
|
||||
}
|
||||
|
||||
int shift_to_kernel_index(int shift, int ker_size) {
|
||||
for (int i = 0; i < ker_size; i++) {
|
||||
if (kernel_index_to_shift(i, ker_size) == shift) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
throw std::invalid_argument("test");
|
||||
}
|
||||
|
||||
// mask gen
|
||||
std::vector<int> generate_up_mask(int mtx_num_rows, int mtx_num_cols, int num_shift_up) {
|
||||
std::vector<int> mask(mtx_num_rows * mtx_num_cols);
|
||||
std::iota(std::begin(mask), std::end(mask), 0);
|
||||
std::transform(mask.begin(), mask.end(), mask.begin(), [=](int i){
|
||||
if (i / mtx_num_cols >= (mtx_num_rows - num_shift_up)) {
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
return mask;
|
||||
}
|
||||
|
||||
std::vector<int> generate_down_mask(int mtx_num_rows, int mtx_num_cols, int num_shift_down) {
|
||||
std::vector<int> mask(mtx_num_rows * mtx_num_cols);
|
||||
std::iota(std::begin(mask), std::end(mask), 0);
|
||||
std::transform(mask.begin(), mask.end(), mask.begin(), [=](int i){
|
||||
if (i / mtx_num_cols < num_shift_down) {
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
return mask;
|
||||
}
|
||||
|
||||
std::vector<int> generate_left_mask(int mtx_num_rows, int mtx_num_cols, int num_shift_left) {
|
||||
std::vector<int> mask(mtx_num_rows * mtx_num_cols);
|
||||
std::iota(std::begin(mask), std::end(mask), 0);
|
||||
std::transform(mask.begin(), mask.end(), mask.begin(), [=](int i){
|
||||
if (i % mtx_num_cols >= (mtx_num_cols - num_shift_left)) {
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
return mask;
|
||||
}
|
||||
|
||||
std::vector<int> generate_right_mask(int mtx_num_rows, int mtx_num_cols, int num_shift_right) {
|
||||
std::vector<int> mask(mtx_num_rows * mtx_num_cols);
|
||||
std::iota(std::begin(mask), std::end(mask), 0);
|
||||
std::transform(mask.begin(), mask.end(), mask.begin(), [=](int i){
|
||||
if (i % mtx_num_cols < num_shift_right) {
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
return mask;
|
||||
}
|
||||
|
||||
std::vector<int> generate_ud_mask(int mtx_num_rows, int mtx_num_cols, int num_shift_up) {
|
||||
if (num_shift_up < 0) {
|
||||
return generate_down_mask(mtx_num_rows, mtx_num_cols, -num_shift_up);
|
||||
} else {
|
||||
return generate_up_mask(mtx_num_rows, mtx_num_cols, num_shift_up);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<int> generate_lr_mask(int mtx_num_rows, int mtx_num_cols, int num_shift_left) {
|
||||
if (num_shift_left < 0) {
|
||||
return generate_right_mask(mtx_num_rows, mtx_num_cols, -num_shift_left);
|
||||
} else {
|
||||
return generate_left_mask(mtx_num_rows, mtx_num_cols, num_shift_left);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<int> make_shift_mask_image_sharded(int num_mtxs, int mtx_num_rows, int mtx_num_cols, int num_shift_up, int num_shift_left) {
|
||||
std::vector<int> ud_mask = generate_ud_mask(mtx_num_rows, mtx_num_cols, num_shift_up);
|
||||
std::vector<int> lr_mask = generate_lr_mask(mtx_num_rows, mtx_num_cols, num_shift_left);
|
||||
|
||||
int mtx_area = mtx_num_rows * mtx_num_cols;
|
||||
int batch_size = num_mtxs * mtx_area;
|
||||
|
||||
// AND of the two masks
|
||||
std::vector<int> mask(mtx_area);
|
||||
for (int i = 0; i < mtx_area; i++) {
|
||||
mask[i] = ud_mask[i] & lr_mask[i];
|
||||
}
|
||||
|
||||
// tile the mask using std::copy
|
||||
tileVector(mask, batch_size);
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
std::vector<int> make_shift_mask_channel_shard(int num_rows, int num_cols, int num_shift_up, int num_shift_left) {
|
||||
std::vector<int> ud_mask = generate_ud_mask(num_rows, num_cols, num_shift_up);
|
||||
std::vector<int> lr_mask = generate_lr_mask(num_rows, num_cols, num_shift_left);
|
||||
|
||||
int area = num_rows * num_cols;
|
||||
|
||||
// AND of the two masks
|
||||
std::vector<int> mask(area);
|
||||
for (int i = 0; i < area; i++) {
|
||||
mask[i] = ud_mask[i] & lr_mask[i];
|
||||
}
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
std::vector<int> make_shift_mask_bleed_channel_shard(int num_rows, int num_cols, int num_shift_up, int num_shift_left) {
|
||||
// only the up-down mask should be inverted for bleed shards
|
||||
std::vector<int> ud_mask = generate_ud_mask(num_rows, num_cols, num_shift_up);
|
||||
std::vector<int> lr_mask = generate_lr_mask(num_rows, num_cols, num_shift_left);
|
||||
|
||||
int area = num_rows * num_cols;
|
||||
|
||||
// AND of the two masks
|
||||
std::vector<int> mask(area);
|
||||
for (int i = 0; i < area; i++) {
|
||||
mask[i] = (1 - ud_mask[i]) & lr_mask[i];
|
||||
}
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
// template <typename T>
|
||||
void print_vector(std::vector<int> vec) {
|
||||
for (int i = 0; i < (int) vec.size(); i++) {
|
||||
std::cout << vec[i] << " ";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
void print_vector(std::vector<double> vec) {
|
||||
for (int i = 0; i < (int) vec.size(); i++) {
|
||||
std::cout << vec[i] << " ";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
void print_mask(std::vector<int> vec, int num_rows, int num_cols) {
|
||||
for (int i = 0; i < num_rows; i++) {
|
||||
for (int j = 0; j < num_cols; j++) {
|
||||
std::cout << vec[i * num_cols + j];
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
void print_mask(std::vector<double> vec, int num_rows, int num_cols) {
|
||||
for (int i = 0; i < num_rows; i++) {
|
||||
for (int j = 0; j < num_cols; j++) {
|
||||
std::cout << vec[i * num_cols + j];
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
106
src/pyOpenFHE.cpp
Normal file
106
src/pyOpenFHE.cpp
Normal file
@@ -0,0 +1,106 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
// python bindings for OpenFHE's BGV and CKKS functionality
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/numpy.hpp>
|
||||
// nested scopes
|
||||
#include <boost/python/scope.hpp>
|
||||
|
||||
#include "bgv/bindings.hpp"
|
||||
#include "ckks/bindings.hpp"
|
||||
#include "utils/enums_binding.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
|
||||
using namespace boost::python;
|
||||
using namespace boost::python::numpy;
|
||||
|
||||
/*
|
||||
boost doesn't natively support std::shared_ptr
|
||||
anyway, the fix is easy, this will teach it to extract values from
|
||||
shared pointers
|
||||
*/
|
||||
namespace boost {
|
||||
template <typename T> T *get_pointer(std::shared_ptr<T> p) { return p.get(); }
|
||||
} // namespace boost
|
||||
|
||||
// where the actual module is defined
|
||||
BOOST_PYTHON_MODULE(pyOpenFHE) {
|
||||
Py_Initialize();
|
||||
|
||||
// necessary for numpy to work
|
||||
boost::python::numpy::initialize();
|
||||
|
||||
// register exception handlers
|
||||
register_exception_translator<pyOpenFHE::not_implemented_exception>(
|
||||
&pyOpenFHE::translate_not_implemented);
|
||||
register_exception_translator<pyOpenFHE::type_exception>(
|
||||
&pyOpenFHE::translate_type_exception);
|
||||
// get outermost scope, not sure if this is necessary
|
||||
scope package = scope();
|
||||
|
||||
{
|
||||
using namespace pyOpenFHE;
|
||||
|
||||
object enums_module(handle<>(borrowed(PyImport_AddModule("pyOpenFHE.enums"))));
|
||||
package.attr("enums") = enums_module;
|
||||
|
||||
scope enums_scope = enums_module;
|
||||
|
||||
export_enums_boost();
|
||||
}
|
||||
|
||||
{
|
||||
using namespace pyOpenFHE_CKKS;
|
||||
|
||||
object CKKS_module(handle<>(borrowed(PyImport_AddModule("pyOpenFHE.CKKS"))));
|
||||
package.attr("CKKS") = CKKS_module;
|
||||
|
||||
scope CKKS_scope = CKKS_module;
|
||||
|
||||
export_CKKS_CryptoContext_boost();
|
||||
export_CKKS_Ciphertext_boost();
|
||||
|
||||
{
|
||||
object CKKS_serialization_module(handle<>(borrowed(PyImport_AddModule("pyOpenFHE.CKKS.serial"))));
|
||||
CKKS_scope.attr("serial") = CKKS_serialization_module;
|
||||
|
||||
scope CKKS_serialization_scope = CKKS_serialization_module;
|
||||
|
||||
export_CKKS_serialization_boost();
|
||||
}
|
||||
|
||||
{
|
||||
object CKKS_CNN_module(handle<>(borrowed(PyImport_AddModule("pyOpenFHE.CKKS.CNN"))));
|
||||
CKKS_scope.attr("CNN") = CKKS_CNN_module;
|
||||
|
||||
scope CKKS_CNN_scope = CKKS_CNN_module;
|
||||
|
||||
export_he_cnn_functions_boost();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
using namespace pyOpenFHE_BGV;
|
||||
|
||||
// this supposedly makes "from pyOpenFHE.BGV import <thing>" work
|
||||
object BGV_module(handle<>(borrowed(PyImport_AddModule("pyOpenFHE.BGV"))));
|
||||
|
||||
package.attr("BGV") = BGV_module;
|
||||
|
||||
// so we no longer need a dummy class
|
||||
scope BGV_scope = BGV_module;
|
||||
|
||||
export_BGV_CryptoContext_boost();
|
||||
export_BGV_Ciphertext_boost();
|
||||
{
|
||||
object BGV_serialization_module(handle<>(borrowed(PyImport_AddModule("pyOpenFHE.BGV.serial"))));
|
||||
BGV_scope.attr("serial") = BGV_serialization_module;
|
||||
|
||||
scope BGV_serialization_scope = BGV_serialization_module;
|
||||
|
||||
export_BGV_serialization_boost();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
137
src/utils/enums_bindings.cpp
Normal file
137
src/utils/enums_bindings.cpp
Normal file
@@ -0,0 +1,137 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
// python bindings for OpenFHE's pke functionality
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/numpy.hpp>
|
||||
|
||||
#include "bgv/BGV_key_operations.hpp"
|
||||
#include "ckks/CKKS_key_operations.hpp"
|
||||
#include "openfhe.h"
|
||||
|
||||
using namespace boost::python;
|
||||
using namespace boost::python::numpy;
|
||||
using namespace lbcrypto;
|
||||
|
||||
namespace pyOpenFHE {
|
||||
|
||||
void export_enums_boost() {
|
||||
|
||||
// individual key classes
|
||||
// every "large" type is usually just typedef'd to be a shared pointer to an
|
||||
// "Impl" class so to make this seamless for python bindings, we wrap the
|
||||
// "Impl" class, but let boost know that sometimes (see: always) we contain
|
||||
// these objects in std::shared_ptr
|
||||
class_<PublicKeyImpl<DCRTPoly>, std::shared_ptr<PublicKeyImpl<DCRTPoly>>>(
|
||||
"PublicKey")
|
||||
.def("getCryptoContext",
|
||||
+[](PublicKeyImpl<DCRTPoly> &self) -> PyObject * {
|
||||
CryptoContext<DCRTPoly> cc = self.GetCryptoContext();
|
||||
if (cc->getSchemeId() == SCHEME::BGVRNS_SCHEME) {
|
||||
auto bgv_cc = pyOpenFHE_BGV::BGVCryptoContext(cc);
|
||||
return to_python_value<decltype(bgv_cc)>()(bgv_cc);
|
||||
} else if (cc->getSchemeId() == SCHEME::CKKSRNS_SCHEME) {
|
||||
auto ckks_cc = pyOpenFHE_CKKS::CKKSCryptoContext(cc);
|
||||
return to_python_value<decltype(ckks_cc)>()(ckks_cc);
|
||||
}
|
||||
|
||||
throw std::runtime_error(
|
||||
fmt::format("Unsupported encryption scheme: {}",
|
||||
cc->GetScheme()->SerializedObjectName()));
|
||||
});
|
||||
|
||||
class_<PrivateKeyImpl<DCRTPoly>, std::shared_ptr<PrivateKeyImpl<DCRTPoly>>>(
|
||||
"PrivateKey")
|
||||
.def("getCryptoContext",
|
||||
+[](PrivateKeyImpl<DCRTPoly> &self) -> PyObject * {
|
||||
CryptoContext<DCRTPoly> cc = self.GetCryptoContext();
|
||||
if (cc->getSchemeId() == SCHEME::BGVRNS_SCHEME) {
|
||||
auto bgv_cc = pyOpenFHE_BGV::BGVCryptoContext(cc);
|
||||
return to_python_value<decltype(bgv_cc)>()(bgv_cc);
|
||||
} else if (cc->getSchemeId() == SCHEME::CKKSRNS_SCHEME) {
|
||||
auto ckks_cc = pyOpenFHE_CKKS::CKKSCryptoContext(cc);
|
||||
return to_python_value<decltype(ckks_cc)>()(ckks_cc);
|
||||
}
|
||||
|
||||
throw std::runtime_error(
|
||||
fmt::format("Unsupported encryption scheme: {}",
|
||||
cc->GetScheme()->SerializedObjectName()));
|
||||
});
|
||||
|
||||
// combined public/private key. weird that the class is "PrivateKey" but the
|
||||
// data member is "secretKey", huh? anyway, the individual keys really
|
||||
// shouldn't be changed, I don't think it would break anything but why would
|
||||
// you do that "good" method makes sure the keys are valid
|
||||
class_<KeyPair<DCRTPoly>>("KeyPair",
|
||||
init<PublicKey<DCRTPoly>, PrivateKey<DCRTPoly>>())
|
||||
.def_readonly("publicKey", &KeyPair<DCRTPoly>::publicKey)
|
||||
.def_readonly("secretKey", &KeyPair<DCRTPoly>::secretKey)
|
||||
.def("good", &KeyPair<DCRTPoly>::good);
|
||||
|
||||
// not sure if this is necessary
|
||||
class_<EvalKeyImpl<DCRTPoly>, std::shared_ptr<EvalKeyImpl<DCRTPoly>>>(
|
||||
"EvalKey");
|
||||
|
||||
// enums used with CryptoContext::Enable to enable certain features
|
||||
enum_<PKESchemeFeature>("PKESchemeFeature")
|
||||
.value("PKE", PKESchemeFeature::PKE)
|
||||
.value("KEYSWITCH", PKESchemeFeature::KEYSWITCH)
|
||||
.value("PRE", PKESchemeFeature::PRE)
|
||||
.value("LEVELEDSHE", PKESchemeFeature::LEVELEDSHE)
|
||||
.value("ADVANCEDSHE", PKESchemeFeature::ADVANCEDSHE)
|
||||
.value("MULTIPARTY", PKESchemeFeature::MULTIPARTY)
|
||||
.value("FHE", PKESchemeFeature::FHE);
|
||||
|
||||
// enums used for constructing crypto contexts
|
||||
enum_<SecretKeyDist>("SecretKeyDist")
|
||||
.value("GAUSSIAN", SecretKeyDist::GAUSSIAN)
|
||||
.value("UNIFORM_TERNARY", SecretKeyDist::UNIFORM_TERNARY)
|
||||
.value("SPARSE_TERNARY", SecretKeyDist::SPARSE_TERNARY);
|
||||
|
||||
enum_<ScalingTechnique>("ScalingTechnique")
|
||||
.value("FIXEDMANUAL", ScalingTechnique::FIXEDMANUAL)
|
||||
.value("FIXEDAUTO", ScalingTechnique::FIXEDAUTO)
|
||||
.value("FLEXIBLEAUTO", ScalingTechnique::FLEXIBLEAUTO)
|
||||
.value("FLEXIBLEAUTOEXT", ScalingTechnique::FLEXIBLEAUTOEXT)
|
||||
.value("NORESCALE", ScalingTechnique::NORESCALE)
|
||||
.value("INVALID_RS_TECHNIQUE", ScalingTechnique::INVALID_RS_TECHNIQUE);
|
||||
|
||||
enum_<SecurityLevel>("SecurityLevel")
|
||||
.value("HEStd_128_classic", SecurityLevel::HEStd_128_classic)
|
||||
.value("HEStd_192_classic", SecurityLevel::HEStd_192_classic)
|
||||
.value("HEStd_256_classic", SecurityLevel::HEStd_256_classic)
|
||||
.value("HEStd_NotSet", SecurityLevel::HEStd_NotSet);
|
||||
|
||||
enum_<EncryptionTechnique>("EncryptionTechnique")
|
||||
.value("STANDARD", EncryptionTechnique::STANDARD)
|
||||
.value("EXTENDED", EncryptionTechnique::EXTENDED);
|
||||
|
||||
enum_<KeySwitchTechnique>("KeySwitchTechnique")
|
||||
.value("INVALID_KS_TECH", KeySwitchTechnique::INVALID_KS_TECH)
|
||||
.value("BV", KeySwitchTechnique::BV)
|
||||
.value("HYBRID", KeySwitchTechnique::HYBRID);
|
||||
|
||||
enum_<MultiplicationTechnique>("MultiplicationTechnique")
|
||||
.value("BEHZ", MultiplicationTechnique::BEHZ)
|
||||
.value("HPS", MultiplicationTechnique::HPS)
|
||||
.value("HPSPOVERQ", MultiplicationTechnique::HPSPOVERQ)
|
||||
.value("HPSPOVERQLEVELED", MultiplicationTechnique::HPSPOVERQLEVELED);
|
||||
|
||||
enum_<LargeScalingFactorConstants>("LargeScalingFactorConstants")
|
||||
.value("MAX_BITS_IN_WORD", LargeScalingFactorConstants::MAX_BITS_IN_WORD)
|
||||
.value("MAX_LOG_STEP", LargeScalingFactorConstants::MAX_LOG_STEP);
|
||||
|
||||
// not sure if we want this (or need it), but getSchemeId is exported so I
|
||||
// figure this should be too
|
||||
enum_<SCHEME>("SCHEME")
|
||||
.value("INVALID_SCHEME", SCHEME::INVALID_SCHEME)
|
||||
.value("CKKSRNS_SCHEME", SCHEME::CKKSRNS_SCHEME)
|
||||
.value("BFVRNS_SCHEME", SCHEME::BFVRNS_SCHEME)
|
||||
.value("BGVRNS_SCHEME", SCHEME::BGVRNS_SCHEME);
|
||||
}
|
||||
|
||||
} // namespace pyOpenFHE
|
||||
21
src/utils/exceptions.cpp
Normal file
21
src/utils/exceptions.cpp
Normal file
@@ -0,0 +1,21 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <boost/python.hpp>
|
||||
|
||||
#include "utils/exceptions.hpp"
|
||||
|
||||
using namespace boost::python;
|
||||
|
||||
namespace pyOpenFHE {
|
||||
|
||||
void translate_not_implemented(not_implemented_exception const &e) {
|
||||
PyErr_SetString(PyExc_NotImplementedError, e.what());
|
||||
}
|
||||
|
||||
void translate_type_exception(type_exception const &e) {
|
||||
PyErr_SetString(PyExc_TypeError, e.what());
|
||||
}
|
||||
|
||||
} // namespace pyOpenFHE
|
||||
89
src/utils/rotate_utils.cpp
Normal file
89
src/utils/rotate_utils.cpp
Normal file
@@ -0,0 +1,89 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
#include <algorithm>
|
||||
#include <complex>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
// string formatting for exceptions
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/numpy.hpp>
|
||||
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
using namespace boost::python;
|
||||
using namespace boost::python::numpy;
|
||||
|
||||
/*
|
||||
positive power-of-2 decompose
|
||||
represent number as a sum of powers of 2, basically binary notation
|
||||
e.g. sumOfPo2s(15) = {8, 4, 2, 1}
|
||||
*/
|
||||
std::vector<int> sumOfPo2s(int num) {
|
||||
std::vector<int> po2s;
|
||||
while (num > 0) {
|
||||
int po2 = std::log2(num);
|
||||
po2s.push_back(1 << po2);
|
||||
num -= (1 << po2);
|
||||
}
|
||||
return po2s;
|
||||
}
|
||||
|
||||
bool is_power_of_two(int num) { return ((num & (num - 1)) == 0) and num != 0; }
|
||||
|
||||
int num_bits(int num) {
|
||||
int exponent = 1;
|
||||
while (num >>= 1)
|
||||
exponent++;
|
||||
return exponent;
|
||||
}
|
||||
|
||||
int last_power_of_two(int num) { return 1 << (num_bits(num) - 1); }
|
||||
|
||||
int next_power_of_two(int num) { return 1 << num_bits(num); }
|
||||
|
||||
/*
|
||||
positive and negative power-of-2 decompose
|
||||
e.g. po2Decompose(15) = {16, -1}
|
||||
*/
|
||||
std::vector<int> po2Decompose(int num) {
|
||||
std::vector<int> elts;
|
||||
|
||||
if (num == 0) {
|
||||
return elts;
|
||||
}
|
||||
|
||||
if (num < 0) {
|
||||
elts = po2Decompose(-num);
|
||||
std::transform(elts.begin(), elts.end(), elts.begin(),
|
||||
[](int elt) { return -elt; });
|
||||
return elts;
|
||||
}
|
||||
|
||||
if (is_power_of_two(num)) {
|
||||
elts.push_back(num);
|
||||
return elts;
|
||||
}
|
||||
|
||||
int lower = last_power_of_two(num);
|
||||
int upper = next_power_of_two(num);
|
||||
|
||||
int lower_diff = num - lower;
|
||||
int upper_diff = upper - num;
|
||||
|
||||
std::vector<int> lower_sum = po2Decompose(lower_diff);
|
||||
std::vector<int> upper_sum = po2Decompose(upper_diff);
|
||||
|
||||
if (lower_sum.size() <= upper_sum.size()) {
|
||||
lower_sum.push_back(lower);
|
||||
return lower_sum;
|
||||
} else {
|
||||
std::transform(upper_sum.begin(), upper_sum.end(), upper_sum.begin(),
|
||||
[](int elt) { return -elt; });
|
||||
upper_sum.push_back(upper);
|
||||
return upper_sum;
|
||||
}
|
||||
}
|
||||
322
src/utils/utils.cpp
Normal file
322
src/utils/utils.cpp
Normal file
@@ -0,0 +1,322 @@
|
||||
// (c) 2021-2024 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
|
||||
|
||||
#include <complex>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
// string formatting for exceptions
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/numpy.hpp>
|
||||
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
using namespace boost::python;
|
||||
using namespace boost::python::numpy;
|
||||
|
||||
/// @brief Construct list with `n` elements. each element is a copy
|
||||
/// of `item`.
|
||||
/// @param n Initial container size.
|
||||
/// @param item Item with which to fill the container.
|
||||
boost::python::list pyOpenFHE::make_list(const std::size_t n,
|
||||
boost::python::object item) {
|
||||
|
||||
// >>> [None] * n;
|
||||
boost::python::list result;
|
||||
result.append(item);
|
||||
result *= n;
|
||||
return result;
|
||||
}
|
||||
|
||||
list pyOpenFHE::cppVectorToPythonList(const std::vector<double> &vector) {
|
||||
boost::python::list pythonList;
|
||||
for (unsigned int i = 0; i < vector.size(); i++) {
|
||||
pythonList.append(vector[i]);
|
||||
}
|
||||
return pythonList;
|
||||
}
|
||||
|
||||
list pyOpenFHE::cppLongIntVectorToPythonList(
|
||||
const std::vector<int64_t> &vector) {
|
||||
boost::python::list pythonList;
|
||||
for (unsigned int i = 0; i < vector.size(); i++) {
|
||||
pythonList.append(vector[i]);
|
||||
}
|
||||
return pythonList;
|
||||
}
|
||||
|
||||
ndarray
|
||||
pyOpenFHE::cppDoubleVectorToNumpyList(const std::vector<double> &vector) {
|
||||
Py_intptr_t shape[1] = {(Py_intptr_t)vector.size()};
|
||||
|
||||
boost::python::numpy::ndarray npList =
|
||||
boost::python::numpy::zeros(1, shape, dtype::get_builtin<double>());
|
||||
for (unsigned int i = 0; i < vector.size(); i++) {
|
||||
npList[i] = vector[i];
|
||||
}
|
||||
return npList;
|
||||
}
|
||||
|
||||
ndarray
|
||||
pyOpenFHE::cppLongIntVectorToNumpyList(const std::vector<int64_t> &vector) {
|
||||
Py_intptr_t shape[1] = {(Py_intptr_t)vector.size()};
|
||||
|
||||
boost::python::numpy::ndarray npList =
|
||||
boost::python::numpy::zeros(1, shape, dtype::get_builtin<int64_t>());
|
||||
for (unsigned int i = 0; i < vector.size(); i++) {
|
||||
npList[i] = vector[i];
|
||||
}
|
||||
return npList;
|
||||
}
|
||||
|
||||
std::vector<int> pyOpenFHE::pythonListToCppIntVector(const list &pylist) {
|
||||
std::vector<int> cppVector;
|
||||
for (unsigned int i = 0; i < len(pylist); i++) {
|
||||
cppVector.push_back(extract<int>(pylist[i]));
|
||||
}
|
||||
return cppVector;
|
||||
}
|
||||
|
||||
std::vector<int> pyOpenFHE::numpyListToCppIntVector(const ndarray &nplist) {
|
||||
std::vector<int> cppVector;
|
||||
for (unsigned int i = 0; i < nplist.shape(0); i++) {
|
||||
cppVector.push_back(extract<int>(nplist[i]));
|
||||
}
|
||||
return cppVector;
|
||||
}
|
||||
|
||||
std::vector<int64_t>
|
||||
pyOpenFHE::pythonListToCppLongIntVector(const list &pylist) {
|
||||
std::vector<int64_t> cppVector;
|
||||
for (unsigned int i = 0; i < len(pylist); i++) {
|
||||
cppVector.push_back(extract<int64_t>(pylist[i]));
|
||||
}
|
||||
return cppVector;
|
||||
}
|
||||
|
||||
std::vector<int64_t>
|
||||
pyOpenFHE::numpyListToCppLongIntVector(const ndarray &nplist) {
|
||||
std::vector<int64_t> cppVector;
|
||||
for (unsigned int i = 0; i < nplist.shape(0); i++) {
|
||||
cppVector.push_back(extract<int64_t>(nplist[i]));
|
||||
}
|
||||
return cppVector;
|
||||
}
|
||||
|
||||
std::vector<double>
|
||||
pyOpenFHE::numpyListToCppDoubleVector(const ndarray &nplist) {
|
||||
std::string dtype(extract<char const *>(str(nplist.get_dtype())));
|
||||
|
||||
int dimensions = nplist.get_nd();
|
||||
if (dimensions != 1) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("Numpy array must be one-dimensional but had dimension: {}",
|
||||
dimensions));
|
||||
}
|
||||
|
||||
std::vector<double> cppVector;
|
||||
if (dtype == "float64") {
|
||||
for (unsigned int i = 0; i < nplist.shape(0); i++) {
|
||||
cppVector.push_back(extract<double>(nplist[i]));
|
||||
}
|
||||
return cppVector;
|
||||
}
|
||||
if (dtype == "float32") {
|
||||
for (unsigned int i = 0; i < nplist.shape(0); i++) {
|
||||
cppVector.push_back((double)extract<float>(nplist[i]));
|
||||
}
|
||||
return cppVector;
|
||||
}
|
||||
if (dtype == "int64") {
|
||||
for (unsigned int i = 0; i < nplist.shape(0); i++) {
|
||||
cppVector.push_back((double)extract<long int>(nplist[i]));
|
||||
}
|
||||
return cppVector;
|
||||
}
|
||||
if (dtype == "int32") {
|
||||
for (unsigned int i = 0; i < nplist.shape(0); i++) {
|
||||
cppVector.push_back((double)extract<int>(nplist[i]));
|
||||
}
|
||||
return cppVector;
|
||||
}
|
||||
|
||||
throw std::runtime_error(
|
||||
fmt::format("Unsupported dtype for converting to float64: {}", dtype));
|
||||
}
|
||||
|
||||
pyOpenFHE::boost_vector2d
|
||||
pyOpenFHE::numpyArrayToCppArray2D(const ndarray &nplist) {
|
||||
|
||||
std::string dtype(extract<char const *>(str(nplist.get_dtype())));
|
||||
|
||||
int dimensions = nplist.get_nd();
|
||||
if (dimensions != 2) {
|
||||
throw std::runtime_error(fmt::format(
|
||||
"Numpy array must be four-dimensional but had dimension: {}",
|
||||
dimensions));
|
||||
}
|
||||
|
||||
pyOpenFHE::boost_vector2d cppVector(
|
||||
boost::extents[nplist.shape(0)][nplist.shape(1)]);
|
||||
|
||||
if (dtype == "float64") {
|
||||
for (unsigned int i0 = 0; i0 < nplist.shape(0); i0++) {
|
||||
for (unsigned int i1 = 0; i1 < nplist.shape(1); i1++) {
|
||||
cppVector[i0][i1] = extract<double>(nplist[i0][i1]);
|
||||
}
|
||||
}
|
||||
return cppVector;
|
||||
}
|
||||
if (dtype == "float32") {
|
||||
for (unsigned int i0 = 0; i0 < nplist.shape(0); i0++) {
|
||||
for (unsigned int i1 = 0; i1 < nplist.shape(1); i1++) {
|
||||
cppVector[i0][i1] = extract<float>(nplist[i0][i1]);
|
||||
}
|
||||
}
|
||||
return cppVector;
|
||||
}
|
||||
if (dtype == "int64") {
|
||||
for (unsigned int i0 = 0; i0 < nplist.shape(0); i0++) {
|
||||
for (unsigned int i1 = 0; i1 < nplist.shape(1); i1++) {
|
||||
cppVector[i0][i1] = (double)extract<long int>(nplist[i0][i1]);
|
||||
}
|
||||
}
|
||||
return cppVector;
|
||||
}
|
||||
if (dtype == "int32") {
|
||||
for (unsigned int i0 = 0; i0 < nplist.shape(0); i0++) {
|
||||
for (unsigned int i1 = 0; i1 < nplist.shape(1); i1++) {
|
||||
cppVector[i0][i1] = (double)extract<int>(nplist[i0][i1]);
|
||||
}
|
||||
}
|
||||
return cppVector;
|
||||
}
|
||||
|
||||
throw std::runtime_error(
|
||||
fmt::format("Unsupported dtype for converting to float64: {}", dtype));
|
||||
}
|
||||
|
||||
pyOpenFHE::boost_vector4d
|
||||
pyOpenFHE::numpyArrayToCppArray4D(const ndarray &nplist) {
|
||||
|
||||
std::string dtype(extract<char const *>(str(nplist.get_dtype())));
|
||||
|
||||
int dimensions = nplist.get_nd();
|
||||
if (dimensions != 4) {
|
||||
throw std::runtime_error(fmt::format(
|
||||
"Numpy array must be four-dimensional but had dimension: {}",
|
||||
dimensions));
|
||||
}
|
||||
|
||||
pyOpenFHE::boost_vector4d cppVector(boost::extents[nplist.shape(
|
||||
0)][nplist.shape(1)][nplist.shape(2)][nplist.shape(3)]);
|
||||
|
||||
if (dtype == "float64") {
|
||||
for (unsigned int i0 = 0; i0 < nplist.shape(0); i0++) {
|
||||
for (unsigned int i1 = 0; i1 < nplist.shape(1); i1++) {
|
||||
for (unsigned int i2 = 0; i2 < nplist.shape(2); i2++) {
|
||||
for (unsigned int i3 = 0; i3 < nplist.shape(3); i3++) {
|
||||
cppVector[i0][i1][i2][i3] = extract<double>(nplist[i0][i1][i2][i3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return cppVector;
|
||||
}
|
||||
if (dtype == "float32") {
|
||||
for (unsigned int i0 = 0; i0 < nplist.shape(0); i0++) {
|
||||
for (unsigned int i1 = 0; i1 < nplist.shape(1); i1++) {
|
||||
for (unsigned int i2 = 0; i2 < nplist.shape(2); i2++) {
|
||||
for (unsigned int i3 = 0; i3 < nplist.shape(3); i3++) {
|
||||
cppVector[i0][i1][i2][i3] = extract<float>(nplist[i0][i1][i2][i3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return cppVector;
|
||||
}
|
||||
if (dtype == "int64") {
|
||||
for (unsigned int i0 = 0; i0 < nplist.shape(0); i0++) {
|
||||
for (unsigned int i1 = 0; i1 < nplist.shape(1); i1++) {
|
||||
for (unsigned int i2 = 0; i2 < nplist.shape(2); i2++) {
|
||||
for (unsigned int i3 = 0; i3 < nplist.shape(3); i3++) {
|
||||
cppVector[i0][i1][i2][i3] =
|
||||
(double)extract<long int>(nplist[i0][i1][i2][i3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return cppVector;
|
||||
}
|
||||
if (dtype == "int32") {
|
||||
for (unsigned int i0 = 0; i0 < nplist.shape(0); i0++) {
|
||||
for (unsigned int i1 = 0; i1 < nplist.shape(1); i1++) {
|
||||
for (unsigned int i2 = 0; i2 < nplist.shape(2); i2++) {
|
||||
for (unsigned int i3 = 0; i3 < nplist.shape(3); i3++) {
|
||||
cppVector[i0][i1][i2][i3] =
|
||||
(double)extract<int>(nplist[i0][i1][i2][i3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return cppVector;
|
||||
}
|
||||
|
||||
throw std::runtime_error(
|
||||
fmt::format("Unsupported dtype for converting to float64: {}", dtype));
|
||||
}
|
||||
|
||||
std::vector<double> pyOpenFHE::pythonListToCppDoubleVector(const list &pylist) {
|
||||
std::vector<double> cppVector;
|
||||
for (unsigned int i = 0; i < len(pylist); i++) {
|
||||
cppVector.push_back(extract<double>(pylist[i]));
|
||||
}
|
||||
return cppVector;
|
||||
}
|
||||
|
||||
// tile a vector to fill out a max size
|
||||
// i.e. tileVector([1, 2], 16) gives
|
||||
// [1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2]
|
||||
// i.e. vector with m elements becomes vector with final_size elements
|
||||
// useful for resizing vectors before encrypting
|
||||
void tileVector(std::vector<double> &vals, unsigned int final_size) {
|
||||
auto current_size = vals.size();
|
||||
// reserve new size, lets us use copy
|
||||
vals.resize(final_size);
|
||||
while (current_size < final_size) {
|
||||
// the end() reference is slippery, just use relative to begin()
|
||||
std::copy_n(vals.begin(), current_size, vals.begin() + current_size);
|
||||
current_size <<= 1; // double, but cooler
|
||||
}
|
||||
}
|
||||
|
||||
void tileVector(std::vector<int64_t> &vals, unsigned int final_size) {
|
||||
auto current_size = vals.size();
|
||||
// reserve new size, lets us use copy
|
||||
vals.resize(final_size);
|
||||
while (current_size < final_size) {
|
||||
// the end() reference is slippery, just use relative to begin()
|
||||
std::copy_n(vals.begin(), current_size, vals.begin() + current_size);
|
||||
current_size <<= 1; // double, but cooler
|
||||
}
|
||||
}
|
||||
|
||||
void tileVector(std::vector<int> &vals, unsigned int final_size) {
|
||||
auto current_size = vals.size();
|
||||
// reserve new size, lets us use copy
|
||||
vals.resize(final_size);
|
||||
while (current_size < final_size) {
|
||||
// the end() reference is slippery, just use relative to begin()
|
||||
std::copy_n(vals.begin(), current_size, vals.begin() + current_size);
|
||||
current_size <<= 1; // double, but cooler
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T> void print_vector(std::vector<T> vec) {
|
||||
for (int i = 0; i < (int)vec.size(); i++) {
|
||||
std::cout << vec[i] << " ";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
Reference in New Issue
Block a user