Browse Source

Merge pull request #172 from openfheorg/dev

Updates to v0.8.9
yspolyakov 1 year ago
parent
commit
2447d372d6

+ 19 - 15
CMakeLists.txt

@@ -4,7 +4,7 @@ project (OpenFHE-Python)
 
 set(OPENFHE_PYTHON_VERSION_MAJOR 0)
 set(OPENFHE_PYTHON_VERSION_MINOR 8)
-set(OPENFHE_PYTHON_VERSION_PATCH 8)
+set(OPENFHE_PYTHON_VERSION_PATCH 9)
 set(OPENFHE_PYTHON_VERSION ${OPENFHE_PYTHON_VERSION_MAJOR}.${OPENFHE_PYTHON_VERSION_MINOR}.${OPENFHE_PYTHON_VERSION_PATCH})
 
 set(CMAKE_CXX_STANDARD 17)
@@ -14,7 +14,7 @@ if(APPLE)
     set(CMAKE_CXX_VISIBILITY_PRESET default)
 endif()
 
-find_package(OpenFHE 1.2.0 REQUIRED)
+find_package(OpenFHE 1.2.1 REQUIRED)
 find_package(pybind11 REQUIRED)
 
 set( CMAKE_CXX_FLAGS ${OpenFHE_CXX_FLAGS} )
@@ -75,20 +75,24 @@ find_package(PythonInterp REQUIRED)
 
 # Check Python version
 if(${PYTHON_VERSION_MAJOR} EQUAL 3 AND ${PYTHON_VERSION_MINOR} GREATER_EQUAL 10)
-execute_process(
-    COMMAND "${Python_EXECUTABLE}" -c "from sys import exec_prefix; print(exec_prefix)"
-    OUTPUT_VARIABLE PYTHON_SITE_PACKAGES
-    OUTPUT_STRIP_TRAILING_WHITESPACE
- )       
+    execute_process(
+        COMMAND "${Python_EXECUTABLE}" -c "from sys import exec_prefix; print(exec_prefix)"
+        OUTPUT_VARIABLE PYTHON_SITE_PACKAGES
+        OUTPUT_STRIP_TRAILING_WHITESPACE
+    )
 else()
-execute_process(
-    COMMAND "${Python_EXECUTABLE}" -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())"
-    OUTPUT_VARIABLE PYTHON_SITE_PACKAGES
-    OUTPUT_STRIP_TRAILING_WHITESPACE
- )    
+    execute_process(
+        COMMAND "${Python_EXECUTABLE}" -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())"
+        OUTPUT_VARIABLE PYTHON_SITE_PACKAGES
+        OUTPUT_STRIP_TRAILING_WHITESPACE
+    )
 endif()
 
-
-
 message(STATUS "Python site packages directory: ${PYTHON_SITE_PACKAGES}")
-install(TARGETS openfhe LIBRARY DESTINATION ${PYTHON_SITE_PACKAGES})
+if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+    set(Python_Install_Location "${PYTHON_SITE_PACKAGES}")
+else()
+    set(Python_Install_Location "${CMAKE_INSTALL_PREFIX}")
+endif()
+message("***** INSTALL IS AT ${Python_Install_Location}; to change, run cmake with -DCMAKE_INSTALL_PREFIX=/your/path")
+install(TARGETS openfhe LIBRARY DESTINATION ${Python_Install_Location})

+ 5 - 3
README.md

@@ -38,7 +38,7 @@ To install OpenFHE-python directly to your system, ensure the dependencies are s
 pip install "pybind11[global]" 
 mkdir build
 cd build
-cmake ..  # Alternatively, cmake .. -DOpenFHE_DIR=/path/to/installed/openfhe if you installed OpenFHE elsewhere
+cmake ..  # Alternatively, cmake .. -DCMAKE_PREFIX_PATH=/path/to/installed/openfhe if you installed OpenFHE elsewhere
 make
 make install  # You may have to run sudo make install
 ```
@@ -50,11 +50,13 @@ If you see an error saying that one of OpenFHE .so files cannot be found when ru
 add the path where the .so files reside to the `PYTHONPATH` environment variable:
 
 ```
-export PYTHONPATH=(path_to_OpenFHE_so_files):$PYTHONPATH
+export PYTHONPATH=(/path/to/installed/openfhe):$PYTHONPATH
 ```
 
 In some environments (this happens rarely), it may also be necessary to add the OpenFHE libraries path to `LD_LIBRARY_PATH`.
 
+If OpenFHE is not installed in the default location, then both `PYTHONPATH and LD_LIBRARY_PATH` must be set before running any Python example.
+
 #### Conda
 
 Alternatively you can install the library and handle the linking via Conda. Clone the repository, open a terminal in the repo folder and run the following commands:
@@ -73,7 +75,7 @@ Now, you would clone the repository, and run the following commands to install :
 ```bash
 mkdir build
 cd build
-cmake .. # Add in -DOpenFHE_DIR=/path/to/installed/openfhe if you installed OpenFHE elsewhere
+cmake .. # Add in -DCMAKE_PREFIX_PATH=/path/to/installed/openfhe if you installed OpenFHE elsewhere
 make
 make install  # You may have to run sudo make install
 ```

+ 3 - 3
examples/pke/advanced-real-numbers-128.py

@@ -52,15 +52,15 @@ def automatic_rescale_demo(scal_tech):
     c_res3 = cc.EvalMult(cc.EvalAdd(c18,c9), 0.5)  # Final result 3
 
     result1 = cc.Decrypt(c_res1,keys.secretKey)
-    result.SetLength(batch_size)
+    result1.SetLength(batch_size)
     print("x^18 + x^9 + 1 = ", result1)
     
     result2 = cc.Decrypt(c_res2,keys.secretKey)
-    result.SetLength(batch_size)
+    result2.SetLength(batch_size)
     print("x^18 + x^9 - 1 = ", result2)
 
     result3 = cc.Decrypt(c_res3,keys.secretKey)
-    result.SetLength(batch_size)
+    result3.SetLength(batch_size)
     print("0.5 * (x^18 + x^9) = ", result3)
 
 

+ 8 - 4
examples/pke/advanced-real-numbers.py

@@ -124,7 +124,8 @@ def hybrid_key_switching_demo1():
     parameters.SetMultiplicativeDepth(5)
     parameters.SetScalingModSize(50)
     parameters.SetBatchSize(batch_size)
-    parameters.SetScalingTechnique(ScalingTechnique.FLEXIBLEAUTO)
+    if get_native_int()!=128:
+        parameters.SetScalingTechnique(ScalingTechnique.FLEXIBLEAUTO)
     parameters.SetNumLargeDigits(dnum)
 
     cc = GenCryptoContext(parameters)
@@ -167,7 +168,8 @@ def hybrid_key_switching_demo2():
     parameters.SetMultiplicativeDepth(5)
     parameters.SetScalingModSize(50)
     parameters.SetBatchSize(batch_size)
-    parameters.SetScalingTechnique(ScalingTechnique.FLEXIBLEAUTO)
+    if get_native_int()!=128:
+        parameters.SetScalingTechnique(ScalingTechnique.FLEXIBLEAUTO)
     parameters.SetNumLargeDigits(dnum)
 
     cc = GenCryptoContext(parameters)
@@ -287,7 +289,8 @@ def fast_rotation_demo2():
     parameters.SetMultiplicativeDepth(1)
     parameters.SetScalingModSize(50)
     parameters.SetBatchSize(batch_size)
-    parameters.SetScalingTechnique(ScalingTechnique.FLEXIBLEAUTO)
+    if get_native_int()!=128:
+        parameters.SetScalingTechnique(ScalingTechnique.FLEXIBLEAUTO)
     parameters.SetKeySwitchTechnique(KeySwitchTechnique.BV)
     parameters.SetFirstModSize(60)
     parameters.SetDigitSize(digit_size)
@@ -361,7 +364,8 @@ def fast_rotation_demo2():
 
 
 def main():
-    automatic_rescale_demo(ScalingTechnique.FLEXIBLEAUTO)
+    if get_native_int()!=128:
+        automatic_rescale_demo(ScalingTechnique.FLEXIBLEAUTO)
     automatic_rescale_demo(ScalingTechnique.FIXEDAUTO)
     manual_rescale_demo(ScalingTechnique.FIXEDMANUAL)
     hybrid_key_switching_demo1()

+ 10 - 8
examples/pke/scheme-switching.py

@@ -1002,16 +1002,17 @@ def ArgminViaSchemeSwitchingUnit():
     slots = 32  # sparsely-packed
     batchSize = slots
     numValues = 32
-    scTech = FLEXIBLEAUTOEXT
     multDepth = 9 + 3 + 1 + int(log2(numValues))  # 1 for CKKS to FHEW, 13 for FHEW to CKKS, log2(numValues) for argmin
-    if scTech == FLEXIBLEAUTOEXT:
-        multDepth += 1
 
     parameters = CCParamsCKKSRNS()
+    if get_native_int()!=128:
+        scTech = FLEXIBLEAUTOEXT
+        multDepth += 1
+        parameters.SetScalingTechnique(scTech)
+
     parameters.SetMultiplicativeDepth(multDepth)
     parameters.SetScalingModSize(scaleModSize)
     parameters.SetFirstModSize(firstModSize)
-    parameters.SetScalingTechnique(scTech)
     parameters.SetSecurityLevel(sl)
     parameters.SetRingDim(ringDim)
     parameters.SetBatchSize(batchSize)
@@ -1119,16 +1120,17 @@ def ArgminViaSchemeSwitchingAltUnit():
     slots = 32  # sparsely-packed
     batchSize = slots
     numValues = 32
-    scTech = FLEXIBLEAUTOEXT
     multDepth = 9 + 3 + 1 + int(log2(numValues))  # 1 for CKKS to FHEW, 13 for FHEW to CKKS, log2(numValues) for argmin
-    if scTech == FLEXIBLEAUTOEXT:
-        multDepth += 1
 
     parameters = CCParamsCKKSRNS()
+    if get_native_int()!=128:
+        scTech = FLEXIBLEAUTOEXT
+        multDepth += 1
+        parameters.SetScalingTechnique(scTech)
+
     parameters.SetMultiplicativeDepth(multDepth)
     parameters.SetScalingModSize(scaleModSize)
     parameters.SetFirstModSize(firstModSize)
-    parameters.SetScalingTechnique(scTech)
     parameters.SetSecurityLevel(sl)
     parameters.SetRingDim(ringDim)
     parameters.SetBatchSize(batchSize)

+ 1 - 1
examples/pke/simple-integers-serial-bgvrns.py

@@ -121,7 +121,7 @@ def main_action():
     # of the keys. When deserializing a context, OpenFHE checks for the tag and
     # if it finds it in the CryptoContext map, it will return the stored version.
     # Hence, we need to clear the context and clear the keys.
-    cryptoContext.ClearEvalMultKeys()
+    ClearEvalMultKeys()
     cryptoContext.ClearEvalAutomorphismKeys()
     ReleaseAllContexts()
 

+ 1 - 1
examples/pke/simple-integers-serial.py

@@ -121,7 +121,7 @@ def main_action():
     # of the keys. When deserializing a context, OpenFHE checks for the tag and
     # if it finds it in the CryptoContext map, it will return the stored version.
     # Hence, we need to clear the context and clear the keys.
-    cryptoContext.ClearEvalMultKeys()
+    ClearEvalMultKeys()
     cryptoContext.ClearEvalAutomorphismKeys()
     ReleaseAllContexts()
 

+ 1 - 1
examples/pke/simple-real-numbers-serial.py

@@ -142,9 +142,9 @@ def serverSetupAndWrite(multDepth, scaleModSize, batchSize):
 
 def clientProcess():
     # clientCC = CryptoContext()
-    # clientCC.ClearEvalMultKeys()
     # clientCC.ClearEvalAutomorphismKeys()
     ReleaseAllContexts()
+    ClearEvalMultKeys()
 
     clientCC, res = DeserializeCryptoContext(datafolder + ccLocation, BINARY)
     if not res:

+ 9 - 8
examples/pke/simple-real-numbers.py

@@ -51,37 +51,38 @@ def main():
     # Step 5: Decryption and output
     # Decrypt the result of additions
     ptAdd = cc.Decrypt(c_add, keys.secretKey)
+    print("\nResults of homomorphic additions: ")
+    print(ptAdd)
 
     # We set the precision to 8 decimal digits for a nicer output.
     # If you want to see the error/noise introduced by CKKS, bump it up
     # to 15 and it should become visible.
 
     precision = 8
-    print("Results of homomorphic computations:")
+    print("\nResults of homomorphic computations:")
     result = cc.Decrypt(c1, keys.secretKey)
     result.SetLength(batch_size)
-    print("x1 = " + str(result))
-    print("Estimated precision in bits: " + str(result.GetLogPrecision()))
+    print("x1 = " + result.GetFormattedValues(precision))
 
     # Decrypt the result of scalar multiplication
     result = cc.Decrypt(c_scalar, keys.secretKey)
     result.SetLength(batch_size)
-    print("4 * x1 = " + str(result))
+    print("4 * x1 = " + result.GetFormattedValues(precision))
 
     # Decrypt the result of multiplication
     result = cc.Decrypt(c_mult, keys.secretKey)
     result.SetLength(batch_size)
-    print("x1 * x2 = " + str(result))
+    print("x1 * x2 = " + result.GetFormattedValues(precision))
 
     # Decrypt the result of rotations
     result = cc.Decrypt(c_rot1, keys.secretKey)
     result.SetLength(batch_size)
-    print("In rotations, very small outputs (~10^-10 here) correspond to 0's:")
-    print("x1 rotated by 1 = " + str(result))
+    print("\nIn rotations, very small outputs (~10^-10 here) correspond to 0's:")
+    print("x1 rotated by 1 = " + result.GetFormattedValues(precision))
 
     result = cc.Decrypt(c_rot2, keys.secretKey)
     result.SetLength(batch_size)
-    print("x1 rotated by -2 = " + str(result))
+    print("x1 rotated by -2 = " + result.GetFormattedValues(precision))
 
 
 if __name__ == "__main__":

+ 3 - 2
examples/pke/tckks-interactive-mp-bootstrapping-Chebyschev.py

@@ -6,8 +6,9 @@ def main():
     # Same test with different rescaling techniques in CKKS
     TCKKSCollectiveBoot(FIXEDMANUAL)
     TCKKSCollectiveBoot(FIXEDAUTO)
-    TCKKSCollectiveBoot(FLEXIBLEAUTO)
-    TCKKSCollectiveBoot(FLEXIBLEAUTOEXT)
+    if get_native_int()!=128:
+        TCKKSCollectiveBoot(FLEXIBLEAUTO)
+        TCKKSCollectiveBoot(FLEXIBLEAUTOEXT)
 
     print("Interactive (3P) Bootstrapping Ciphertext [Chebyshev] (TCKKS) terminated gracefully!")
 

+ 4 - 3
examples/pke/tckks-interactive-mp-bootstrapping.py

@@ -21,8 +21,9 @@ def main():
     # Same test with different rescaling techniques in CKKS
     TCKKSCollectiveBoot(FIXEDMANUAL)
     TCKKSCollectiveBoot(FIXEDAUTO)
-    TCKKSCollectiveBoot(FLEXIBLEAUTO)
-    TCKKSCollectiveBoot(FLEXIBLEAUTOEXT)
+    if get_native_int()!=128:
+        TCKKSCollectiveBoot(FLEXIBLEAUTO)
+        TCKKSCollectiveBoot(FLEXIBLEAUTOEXT)
 
     print("Interactive Multi-Party Bootstrapping Ciphertext (TCKKS) terminated gracefully!\n")
 
@@ -170,4 +171,4 @@ def TCKKSCollectiveBoot(scaleTech):
     print("\n============================ INTERACTIVE DECRYPTION ENDED ============================\n")      
 
 if __name__ == "__main__":
-    main()
+    main()

+ 1 - 1
src/include/binfhe/binfhecontext_wrapper.h

@@ -63,7 +63,7 @@ std::vector<uint64_t> GenerateLUTviaFunctionWrapper(BinFHEContext &self, py::fun
 NativeInteger StaticFunction(NativeInteger m, NativeInteger p);
 
 // Define static variables to hold the state
-extern py::function static_f;
+// extern py::function static_f;
 
 LWECiphertext EvalFuncWrapper(BinFHEContext &self, ConstLWECiphertext &ct, const std::vector<uint64_t> &LUT);
 #endif // BINFHE_CRYPTOCONTEXT_BINDINGS_H

+ 19 - 14
src/include/pke/cryptocontext_wrapper.h

@@ -28,32 +28,34 @@
 #ifndef OPENFHE_CRYPTOCONTEXT_BINDINGS_H
 #define OPENFHE_CRYPTOCONTEXT_BINDINGS_H
 
+#include "bindings.h"
+#include "openfhe.h"
+
 #include <pybind11/pybind11.h>
 #include <pybind11/stl.h>
 #include <vector>
 #include <algorithm>
 #include <complex>
-#include "openfhe.h"
-#include "bindings.h"
+
 
 namespace py = pybind11;
 using namespace lbcrypto;
 using ParmType = typename DCRTPoly::Params;
 
-Ciphertext<DCRTPoly> EvalFastRotationPrecomputeWrapper(CryptoContext<DCRTPoly>& self,
-                                                        ConstCiphertext<DCRTPoly> ciphertext);
+Ciphertext<DCRTPoly> EvalFastRotationPrecomputeWrapper(CryptoContext<DCRTPoly> &self,
+                                                       ConstCiphertext<DCRTPoly> ciphertext);
 
-Ciphertext<DCRTPoly> EvalFastRotationWrapper(CryptoContext<DCRTPoly>& self,
-                                            ConstCiphertext<DCRTPoly> ciphertext,
-                                              const usint index,
-                                              const usint m,
-                                              ConstCiphertext<DCRTPoly> digits);
-Ciphertext<DCRTPoly> EvalFastRotationExtWrapper(CryptoContext<DCRTPoly>& self,ConstCiphertext<DCRTPoly> ciphertext, const usint index, ConstCiphertext<DCRTPoly> digits, bool addFirst);
+Ciphertext<DCRTPoly> EvalFastRotationWrapper(CryptoContext<DCRTPoly> &self,
+                                             ConstCiphertext<DCRTPoly> ciphertext,
+                                             const usint index,
+                                             const usint m,
+                                             ConstCiphertext<DCRTPoly> digits);
+Ciphertext<DCRTPoly> EvalFastRotationExtWrapper(CryptoContext<DCRTPoly> &self, ConstCiphertext<DCRTPoly> ciphertext, const usint index, ConstCiphertext<DCRTPoly> digits, bool addFirst);
 
-Plaintext DecryptWrapper(CryptoContext<DCRTPoly>& self,
-ConstCiphertext<DCRTPoly> ciphertext,const PrivateKey<DCRTPoly> privateKey);
-Plaintext DecryptWrapper(CryptoContext<DCRTPoly>& self,
-const PrivateKey<DCRTPoly> privateKey,ConstCiphertext<DCRTPoly> ciphertext);
+Plaintext DecryptWrapper(CryptoContext<DCRTPoly> &self,
+                         ConstCiphertext<DCRTPoly> ciphertext, const PrivateKey<DCRTPoly> privateKey);
+Plaintext DecryptWrapper(CryptoContext<DCRTPoly> &self,
+                         const PrivateKey<DCRTPoly> privateKey, ConstCiphertext<DCRTPoly> ciphertext);
 Plaintext MultipartyDecryptFusionWrapper(CryptoContext<DCRTPoly>& self,const std::vector<Ciphertext<DCRTPoly>>& partialCiphertextVec);
 
 const std::map<usint, EvalKey<DCRTPoly>> EvalAutomorphismKeyGenWrapper(CryptoContext<DCRTPoly>& self,const PrivateKey<DCRTPoly> privateKey,const std::vector<usint> &indexList);
@@ -65,4 +67,7 @@ const double GetScalingFactorRealWrapper(CryptoContext<DCRTPoly>& self, uint32_t
 const uint64_t GetModulusCKKSWrapper(CryptoContext<DCRTPoly>& self);
 const ScalingTechnique GetScalingTechniqueWrapper(CryptoContext<DCRTPoly>& self);
 const usint GetDigitSizeWrapper(CryptoContext<DCRTPoly>& self);
+
+void ClearEvalMultKeysWrapper();
+
 #endif // OPENFHE_CRYPTOCONTEXT_BINDINGS_H

+ 37 - 0
src/include/pke/serialization.h

@@ -33,6 +33,7 @@
 #include "bindings.h"
 
 using namespace lbcrypto;
+namespace py = pybind11;
 
 template <typename ST>
 bool SerializeEvalMultKeyWrapper(const std::string& filename, const ST& sertype, std::string id);
@@ -43,4 +44,40 @@ bool SerializeEvalAutomorphismKeyWrapper(const std::string& filename, const ST&
 template <typename ST>
 bool DeserializeEvalMultKeyWrapper(const std::string& filename, const ST& sertype);
 
+template <typename T, typename ST>
+std::string SerializeToStringWrapper(const T& obj, const ST& sertype);
+
+template <typename T, typename ST>
+py::bytes SerializeToBytesWrapper(const T& obj, const ST& sertype);
+
+template <typename T, typename ST>
+T DeserializeFromStringWrapper(const std::string& str, const ST& sertype);
+
+template <typename T, typename ST>
+T DeserializeFromBytesWrapper(const py::bytes& bytes, const ST& sertype);
+
+template <typename ST>
+std::string SerializeEvalMultKeyToStringWrapper(const ST& sertype, const std::string& id);
+
+template <typename ST>
+py::bytes SerializeEvalMultKeyToBytesWrapper(const ST& sertype, const std::string& id);
+
+template <typename ST>
+std::string SerializeEvalAutomorphismKeyToStringWrapper(const ST& sertype, const std::string& id);
+
+template <typename ST>
+py::bytes SerializeEvalAutomorphismKeyToBytesWrapper(const ST& sertype, const std::string& id);
+
+template <typename ST>
+void DeserializeEvalMultKeyFromStringWrapper(const std::string& data, const ST& sertype);
+
+template <typename ST>
+void DeserializeEvalMultKeyFromBytesWrapper(const std::string& data, const ST& sertype);
+
+template <typename ST>
+void DeserializeEvalAutomorphismKeyFromStringWrapper(const std::string& data, const ST& sertype);
+
+template <typename ST>
+void DeserializeEvalAutomorphismKeyFromBytesWrapper(const std::string& data, const ST& sertype);
+
 #endif // OPENFHE_SERIALIZATION_BINDINGS_H

+ 12 - 11
src/lib/bindings.cpp

@@ -715,10 +715,6 @@ void bind_crypto_context(py::module &m)
         .def("FindAutomorphismIndices", &CryptoContextImpl<DCRTPoly>::FindAutomorphismIndices,
             cc_FindAutomorphismIndices_docs,
             py::arg("idxList"))
-        .def_static(
-            "ClearEvalMultKeys", []()
-            { CryptoContextImpl<DCRTPoly>::ClearEvalMultKeys(); },
-            cc_ClearEvalMultKeys_docs)
         .def_static(
             "InsertEvalSumKey", &CryptoContextImpl<DCRTPoly>::InsertEvalSumKey,
             cc_InsertEvalSumKey_docs,
@@ -727,7 +723,8 @@ void bind_crypto_context(py::module &m)
         .def_static(
             "InsertEvalMultKey", &CryptoContextImpl<DCRTPoly>::InsertEvalMultKey,
             cc_InsertEvalMultKey_docs,
-            py::arg("evalKeyVec"))
+            py::arg("evalKeyVec"),
+            py::arg("keyTag") = "")
         .def_static(
             "ClearEvalAutomorphismKeys", []()
             { CryptoContextImpl<DCRTPoly>::ClearEvalAutomorphismKeys(); },
@@ -835,15 +832,20 @@ void bind_crypto_context(py::module &m)
         py::arg("params"));
     m.def("GenCryptoContext", &GenCryptoContext<CryptoContextCKKSRNS>,
         py::arg("params"));
-    m.def("ReleaseAllContexts", &CryptoContextFactory<DCRTPoly>::ReleaseAllContexts);
+
     m.def("GetAllContexts", &CryptoContextFactory<DCRTPoly>::GetAllContexts);
+
+    m.def("ReleaseAllContexts", &CryptoContextFactory<DCRTPoly>::ReleaseAllContexts);
+    m.def("ClearEvalMultKeys", &ClearEvalMultKeysWrapper);
 }
 
 int get_native_int(){
     #if NATIVEINT == 128 && !defined(__EMSCRIPTEN__)
         return 128;
+    #elif NATIVEINT == 32
+        return 32;
     #else
-        return 64;    
+        return 64;
     #endif
 }
 
@@ -1078,17 +1080,16 @@ void bind_encodings(py::module &m)
         .def("GetStringValue", &PlaintextImpl::GetStringValue)
         .def("SetStringValue", &PlaintextImpl::SetStringValue)
         .def("SetIntVectorValue", &PlaintextImpl::SetIntVectorValue)
+        .def("GetFormattedValues", &PlaintextImpl::GetFormattedValues)
         .def("__repr__", [](const PlaintextImpl &p)
              {
         std::stringstream ss;
-        ss << "<Plaintext Object: ";
-        p.PrintValue(ss);
-        ss << ">";
+        ss << "<Plaintext Object: " << p << ">";
         return ss.str(); })
         .def("__str__", [](const PlaintextImpl &p)
              {
         std::stringstream ss;
-        p.PrintValue(ss);
+        ss << p;
         return ss.str(); });
 }
 

+ 4 - 3
src/lib/binfhe/binfhecontext_wrapper.cpp

@@ -77,7 +77,7 @@ const uint64_t GetLWECiphertextModulusWrapper(LWECiphertext &self)
 }
 
 // Define static variables to hold the state
-py::function static_f;
+py::function* static_f = nullptr;
 
 // Define a static function that uses the static variables
 NativeInteger StaticFunction(NativeInteger m, NativeInteger p) {
@@ -85,7 +85,7 @@ NativeInteger StaticFunction(NativeInteger m, NativeInteger p) {
     uint64_t m_int = m.ConvertToInt<uint64_t>();
     uint64_t p_int = p.ConvertToInt<uint64_t>();
     // Call the Python function
-    py::object result_py = static_f(m_int, p_int);
+    py::object result_py = (*static_f)(m_int, p_int);
     // Convert the result to a NativeInteger
     return NativeInteger(py::cast<uint64_t>(result_py));
 }
@@ -93,8 +93,9 @@ NativeInteger StaticFunction(NativeInteger m, NativeInteger p) {
 std::vector<uint64_t> GenerateLUTviaFunctionWrapper(BinFHEContext &self, py::function f, uint64_t p)
 {
     NativeInteger p_native_int = NativeInteger(p);
-    static_f = f;
+    static_f = &f;
     std::vector<NativeInteger> result = self.GenerateLUTviaFunction(StaticFunction, p_native_int);
+    static_f = nullptr;
     std::vector<uint64_t> result_uint64_t;
     // int size_int = static_cast<int>(result.size());
     for (const auto& value : result)

+ 11 - 5
src/lib/pke/cryptocontext_wrapper.cpp

@@ -25,13 +25,15 @@
 // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
+#include "cryptocontext_wrapper.h"
+#include <openfhe.h>
+
 #include <pybind11/pybind11.h>
 #include <pybind11/stl.h>
-#include <openfhe.h>
 #include <vector>
 #include <algorithm>
-#include <complex> 
-#include "cryptocontext_wrapper.h"
+#include <complex>
+#include <iomanip>
 
 using namespace lbcrypto;
 namespace py = pybind11;
@@ -117,7 +119,7 @@ const double GetScalingFactorRealWrapper(CryptoContext<DCRTPoly>& self, uint32_t
         return scFactor;
     }
     else{
-        OPENFHE_THROW(not_available_error, "GetScalingFactorRealWrapper: Invalid scheme");
+        OPENFHE_THROW("Invalid scheme");
         return 0;
     }
 }
@@ -146,7 +148,11 @@ const ScalingTechnique GetScalingTechniqueWrapper(CryptoContext<DCRTPoly> & self
         return cryptoParams->GetScalingTechnique();
     }
     else{
-        OPENFHE_THROW(not_available_error, "GetScalingTechniqueWrapper: Invalid scheme");
+        OPENFHE_THROW("Invalid scheme");
     }
 
 }
+
+void ClearEvalMultKeysWrapper() {
+    CryptoContextImpl<DCRTPoly>::ClearEvalMultKeys();
+}

+ 193 - 3
src/lib/pke/serialization.cpp

@@ -88,6 +88,137 @@ std::tuple<CryptoContext<DCRTPoly>, bool> DeserializeCCWrapper(const std::string
     return std::make_tuple(newob, result);
 }
 
+template <typename T, typename ST>
+std::string SerializeToStringWrapper(const T& obj, const ST& sertype) {
+    std::ostringstream oss;
+    Serial::Serialize<T>(obj, oss, sertype);
+    return oss.str();
+}
+
+template <typename T, typename ST>
+py::bytes SerializeToBytesWrapper(const T& obj, const ST& sertype) {
+    std::ostringstream oss(std::ios::binary);
+    Serial::Serialize<T>(obj, oss, sertype);
+    std::string str = oss.str();
+    return py::bytes(str);
+}
+
+template <typename T, typename ST>
+T DeserializeFromStringWrapper(const std::string& str, const ST& sertype) {
+    T obj;
+    std::istringstream iss(str);
+    Serial::Deserialize<T>(obj, iss, sertype);
+    return obj;
+}
+
+template <typename ST>
+CryptoContext<DCRTPoly> DeserializeCCFromStringWrapper(const std::string& str, const ST& sertype) {
+    CryptoContext<DCRTPoly> obj;
+    std::istringstream iss(str);
+    Serial::Deserialize<DCRTPoly>(obj, iss, sertype);
+    return obj;
+}
+
+template <typename T, typename ST>
+T DeserializeFromBytesWrapper(const py::bytes& bytes, const ST& sertype) {
+    T obj;
+    std::string str(bytes);
+    std::istringstream iss(str, std::ios::binary);
+    Serial::Deserialize<T>(obj, iss, sertype);
+    return obj;
+}
+
+template <typename ST>
+CryptoContext<DCRTPoly> DeserializeCCFromBytesWrapper(const py::bytes& bytes, const ST& sertype) {
+    CryptoContext<DCRTPoly> obj;
+    std::string str(bytes);
+    std::istringstream iss(str, std::ios::binary);
+    Serial::Deserialize<DCRTPoly>(obj, iss, sertype);
+    return obj;
+}
+
+template <typename ST>
+std::string SerializeEvalMultKeyToStringWrapper(const ST& sertype, const std::string& id) {
+    std::ostringstream oss;
+    bool res = CryptoContextImpl<DCRTPoly>::SerializeEvalMultKey(oss, sertype, id);
+    if (!res) {
+        throw std::runtime_error("Failed to serialize EvalMultKey");
+    }
+    return oss.str();
+}
+
+template <typename ST>
+py::bytes SerializeEvalMultKeyToBytesWrapper(const ST& sertype, const std::string& id) {
+    std::ostringstream oss(std::ios::binary);
+    bool res = CryptoContextImpl<DCRTPoly>::SerializeEvalMultKey(oss, sertype, id);
+    if (!res) {
+        throw std::runtime_error("Failed to serialize EvalMultKey");
+    }
+    std::string str = oss.str();
+    return py::bytes(str);
+}
+
+
+template <typename ST>
+std::string SerializeEvalAutomorphismKeyToStringWrapper(const ST& sertype, const std::string& id) {
+    std::ostringstream oss;
+    bool res = CryptoContextImpl<DCRTPoly>::SerializeEvalAutomorphismKey(oss, sertype, id);
+    if (!res) {
+        throw std::runtime_error("Failed to serialize EvalAutomorphismKey");
+    }
+    return oss.str();
+}
+
+
+template <typename ST>
+py::bytes SerializeEvalAutomorphismKeyToBytesWrapper(const ST& sertype, const std::string& id) {
+    std::ostringstream oss(std::ios::binary);
+    bool res = CryptoContextImpl<DCRTPoly>::SerializeEvalAutomorphismKey(oss, sertype, id);
+    if (!res) {
+        throw std::runtime_error("Failed to serialize EvalAutomorphismKey");
+    }
+    return oss.str();
+}
+
+template <typename ST>
+void DeserializeEvalMultKeyFromStringWrapper(const std::string& data, const ST& sertype) {
+    std::istringstream iss(data);
+    bool res = CryptoContextImpl<DCRTPoly>::DeserializeEvalMultKey<ST>(iss, sertype);
+    if (!res) {
+        throw std::runtime_error("Failed to deserialize EvalMultKey");
+    }
+}
+
+template <typename ST>
+void DeserializeEvalMultKeyFromBytesWrapper(const std::string& data, const ST& sertype) {
+    std::string str(data);
+    std::istringstream iss(str, std::ios::binary);
+    bool res = CryptoContextImpl<DCRTPoly>::DeserializeEvalMultKey<ST>(iss, sertype);
+    if (!res) {
+        throw std::runtime_error("Failed to deserialize EvalMultKey");
+    }
+}
+
+template <typename ST>
+void DeserializeEvalAutomorphismKeyFromStringWrapper(const std::string& data, const ST& sertype) {
+    std::istringstream iss(data);
+    std::map<std::string, std::shared_ptr<std::map<usint, EvalKey<DCRTPoly>>>> keyMap;
+    bool res = CryptoContextImpl<DCRTPoly>::DeserializeEvalAutomorphismKey<ST>(iss, sertype);
+    if (!res) {
+        throw std::runtime_error("Failed to deserialize EvalAutomorphismKey");
+    }
+}
+
+template <typename ST>
+void DeserializeEvalAutomorphismKeyFromBytesWrapper(const std::string& data, const ST& sertype) {
+    std::string str(data);
+    std::istringstream iss(str, std::ios::binary);
+    bool res = CryptoContextImpl<DCRTPoly>::DeserializeEvalAutomorphismKey<ST>(iss, sertype);
+    if (!res) {
+        throw std::runtime_error("Failed to deserialize EvalAutomorphismKey");
+    }
+}
+
 void bind_serialization(pybind11::module &m) {
     // Json Serialization
     m.def("SerializeToFile", static_cast<bool (*)(const std::string &, const CryptoContext<DCRTPoly> &, const SerType::SERJSON &)>(&Serial::SerializeToFile<DCRTPoly>),
@@ -110,6 +241,37 @@ void bind_serialization(pybind11::module &m) {
           py::arg("filename"), py::arg("obj"), py::arg("sertype"));
     m.def("DeserializeEvalKey", static_cast<std::tuple<EvalKey<DCRTPoly>,bool> (*)(const std::string&, const SerType::SERJSON&)>(&DeserializeFromFileWrapper<EvalKey<DCRTPoly>, SerType::SERJSON>),
           py::arg("filename"), py::arg("sertype"));
+
+    // JSON Serialization to string
+    m.def("Serialize", &SerializeToStringWrapper<CryptoContext<DCRTPoly>, SerType::SERJSON>,
+          py::arg("obj"), py::arg("sertype"));
+    m.def("DeserializeCryptoContextString", &DeserializeCCFromStringWrapper<SerType::SERJSON>,
+          py::arg("str"), py::arg("sertype"));
+    m.def("Serialize", &SerializeToStringWrapper<PublicKey<DCRTPoly>, SerType::SERJSON>,
+          py::arg("obj"), py::arg("sertype"));
+    m.def("DeserializePublicKeyString", &DeserializeFromStringWrapper<PublicKey<DCRTPoly>, SerType::SERJSON>,
+          py::arg("str"), py::arg("sertype"));
+    m.def("Serialize", &SerializeToStringWrapper<PrivateKey<DCRTPoly>, SerType::SERJSON>,
+          py::arg("obj"), py::arg("sertype"));
+    m.def("DeserializePrivateKeyString", &DeserializeFromStringWrapper<PrivateKey<DCRTPoly>, SerType::SERJSON>,
+          py::arg("str"), py::arg("sertype"));
+    m.def("Serialize", &SerializeToStringWrapper<Ciphertext<DCRTPoly>, SerType::SERJSON>,
+          py::arg("obj"), py::arg("sertype"));
+    m.def("DeserializeCiphertextString", &DeserializeFromStringWrapper<Ciphertext<DCRTPoly>, SerType::SERJSON>,
+          py::arg("str"), py::arg("sertype"));
+    m.def("Serialize", &SerializeToStringWrapper<EvalKey<DCRTPoly>, SerType::SERJSON>,
+          py::arg("obj"), py::arg("sertype"));
+    m.def("DeserializeEvalKeyString", &DeserializeFromStringWrapper<EvalKey<DCRTPoly>, SerType::SERJSON>,
+          py::arg("str"), py::arg("sertype"));
+    m.def("SerializeEvalMultKeyString", &SerializeEvalMultKeyToStringWrapper<SerType::SERJSON>,
+          py::arg("sertype"), py::arg("id") = "");
+    m.def("DeserializeEvalMultKeyString", &DeserializeEvalMultKeyFromStringWrapper<SerType::SERJSON>,
+          py::arg("sertype"), py::arg("id") = "");
+    m.def("SerializeEvalAutomorphismKeyString", &SerializeEvalAutomorphismKeyToStringWrapper<SerType::SERJSON>,
+          py::arg("sertype"), py::arg("id") = "");
+    m.def("DeserializeEvalAutomorphismKeyString", &DeserializeEvalAutomorphismKeyFromStringWrapper<SerType::SERJSON>,
+          py::arg("sertype"), py::arg("id") = "");
+
     // Binary Serialization
     m.def("SerializeToFile", static_cast<bool (*)(const std::string&,const CryptoContext<DCRTPoly>&, const SerType::SERBINARY&)>(&Serial::SerializeToFile<DCRTPoly>),
           py::arg("filename"), py::arg("obj"), py::arg("sertype"));
@@ -130,7 +292,35 @@ void bind_serialization(pybind11::module &m) {
     m.def("SerializeToFile", static_cast<bool (*)(const std::string&, const EvalKey<DCRTPoly>&, const SerType::SERBINARY&)>(&Serial::SerializeToFile<EvalKey<DCRTPoly>>),
           py::arg("filename"), py::arg("obj"), py::arg("sertype"));
     m.def("DeserializeEvalKey", static_cast<std::tuple<EvalKey<DCRTPoly>,bool> (*)(const std::string&, const SerType::SERBINARY&)>(&DeserializeFromFileWrapper<EvalKey<DCRTPoly>, SerType::SERBINARY>),
-            py::arg("filename"), py::arg("sertype"));  
-    
-}
+          py::arg("filename"), py::arg("sertype"));
 
+    // Binary Serialization to bytes
+    m.def("Serialize", &SerializeToBytesWrapper<CryptoContext<DCRTPoly>, SerType::SERBINARY>,
+          py::arg("obj"), py::arg("sertype"));
+    m.def("DeserializeCryptoContextString", &DeserializeCCFromBytesWrapper<SerType::SERBINARY>,
+          py::arg("str"), py::arg("sertype"));
+    m.def("Serialize", &SerializeToBytesWrapper<PublicKey<DCRTPoly>, SerType::SERBINARY>,
+          py::arg("obj"), py::arg("sertype"));
+    m.def("DeserializePublicKeyString", &DeserializeFromBytesWrapper<PublicKey<DCRTPoly>, SerType::SERBINARY>,
+          py::arg("str"), py::arg("sertype"));
+    m.def("Serialize", &SerializeToBytesWrapper<PrivateKey<DCRTPoly>, SerType::SERBINARY>,
+          py::arg("obj"), py::arg("sertype"));
+    m.def("DeserializePrivateKeyString", &DeserializeFromBytesWrapper<PrivateKey<DCRTPoly>, SerType::SERBINARY>,
+          py::arg("str"), py::arg("sertype"));
+    m.def("Serialize", &SerializeToBytesWrapper<Ciphertext<DCRTPoly>, SerType::SERBINARY>,
+          py::arg("obj"), py::arg("sertype"));
+    m.def("DeserializeCiphertextString", &DeserializeFromBytesWrapper<Ciphertext<DCRTPoly>, SerType::SERBINARY>,
+          py::arg("str"), py::arg("sertype"));
+    m.def("Serialize", &SerializeToBytesWrapper<EvalKey<DCRTPoly>, SerType::SERBINARY>,
+          py::arg("obj"), py::arg("sertype"));
+    m.def("DeserializeEvalKeyString", &DeserializeFromBytesWrapper<EvalKey<DCRTPoly>, SerType::SERBINARY>,
+          py::arg("str"), py::arg("sertype"));
+    m.def("SerializeEvalMultKeyString", &SerializeEvalMultKeyToBytesWrapper<SerType::SERBINARY>,
+          py::arg("sertype"), py::arg("id") = "");
+    m.def("DeserializeEvalMultKeyString", &DeserializeEvalMultKeyFromBytesWrapper<SerType::SERBINARY>,
+          py::arg("sertype"), py::arg("id") = "");
+    m.def("SerializeEvalAutomorphismKeyString", &SerializeEvalAutomorphismKeyToBytesWrapper<SerType::SERBINARY>,
+          py::arg("sertype"), py::arg("id") = "");
+    m.def("DeserializeEvalAutomorphismKeyString", &DeserializeEvalAutomorphismKeyFromBytesWrapper<SerType::SERBINARY>,
+          py::arg("sertype"), py::arg("id") = "");
+}

+ 1 - 0
tests/test_bgv.py

@@ -4,6 +4,7 @@ import random
 import pytest
 import openfhe as fhe
 
+pytestmark = pytest.mark.skipif(fhe.get_native_int() == 32, reason="Doesn't work for NATIVE_INT=32")
 
 LOGGER = logging.getLogger("test_bgv")
 

+ 4 - 3
tests/test_ckks.py

@@ -3,6 +3,7 @@ import random
 import pytest
 import openfhe as fhe
 
+pytestmark = pytest.mark.skipif(fhe.get_native_int() == 32, reason="Doesn't work for NATIVE_INT=32")
 
 @pytest.fixture(scope="module")
 def ckks_context():
@@ -13,14 +14,14 @@ def ckks_context():
     batch_size = 8
     parameters = fhe.CCParamsCKKSRNS()
     parameters.SetMultiplicativeDepth(5)
-    if fhe.get_native_int() > 90:
+    if fhe.get_native_int() == 128:
         parameters.SetFirstModSize(89)
         parameters.SetScalingModSize(78)
         parameters.SetBatchSize(batch_size)
         parameters.SetScalingTechnique(fhe.ScalingTechnique.FIXEDAUTO)
         parameters.SetNumLargeDigits(2)
 
-    elif fhe.get_native_int() > 60:
+    elif fhe.get_native_int() == 64:
         parameters.SetFirstModSize(60)
         parameters.SetScalingModSize(56)
         parameters.SetBatchSize(batch_size)
@@ -28,7 +29,7 @@ def ckks_context():
         parameters.SetNumLargeDigits(2)
 
     else:
-        raise ValueError("Expected a native int size greater than 60.")
+        raise ValueError("Expected a native int size 64 or 128.")
 
     cc = fhe.GenCryptoContext(parameters)
     cc.Enable(fhe.PKESchemeFeature.PKE)

+ 1 - 2
tests/test_cryptocontext.py

@@ -1,9 +1,8 @@
 import pytest
 import openfhe as fhe
 
+pytestmark = pytest.mark.skipif(fhe.get_native_int() != 128, reason="Only for NATIVE_INT=128")
 
-@pytest.mark.long
-@pytest.mark.skipif(fhe.get_native_int() < 80, reason="Only for NATIVE_INT=128")
 @pytest.mark.parametrize("scaling", [fhe.FIXEDAUTO, fhe.FIXEDMANUAL])
 def test_ckks_context(scaling):
     batch_size = 8

+ 3 - 0
tests/test_examples.py

@@ -5,6 +5,9 @@ import importlib.util
 import pytest
 import tempfile
 import shutil
+import openfhe as fhe
+
+pytestmark = pytest.mark.skipif(fhe.get_native_int() == 32, reason="Doesn't work for NATIVE_INT=32")
 
 EXAMPLES_SCRIPTS_PATH = os.path.join(Path(__file__).parent.parent, "examples", "pke")
 

+ 180 - 1
tests/test_serial_cc.py

@@ -1,7 +1,10 @@
 import logging
+import pytest
 
 import openfhe as fhe
 
+pytestmark = pytest.mark.skipif(fhe.get_native_int() == 32, reason="Doesn't work for NATIVE_INT=32")
+
 LOGGER = logging.getLogger("test_serial_cc")
 
 
@@ -22,7 +25,7 @@ def test_serial_cryptocontext(tmp_path):
     LOGGER.debug("The cryptocontext has been serialized.")
     assert fhe.SerializeToFile(str(tmp_path / "ciphertext1.json"), ciphertext1, fhe.JSON)
 
-    cryptoContext.ClearEvalMultKeys()
+    fhe.ClearEvalMultKeys()
     cryptoContext.ClearEvalAutomorphismKeys()
     fhe.ReleaseAllContexts()
 
@@ -37,3 +40,179 @@ def test_serial_cryptocontext(tmp_path):
     assert isinstance(ct1, fhe.Ciphertext)
     LOGGER.debug("Cryptocontext deserializes to %s %s", success, ct1)
     assert fhe.SerializeToFile(str(tmp_path / "ciphertext12.json"), ct1, fhe.JSON)
+
+
+VECTOR1_ROTATION = 1
+VECTOR2_ROTATION = 2
+VECTOR3_ROTATION = -1
+VECTOR4_ROTATION = -2
+
+@pytest.mark.parametrize("mode", [fhe.JSON, fhe.BINARY])
+def test_serial_cryptocontext_str(mode):
+    parameters = fhe.CCParamsBFVRNS()
+    parameters.SetPlaintextModulus(65537)
+    parameters.SetMultiplicativeDepth(2)
+
+    cryptoContext = fhe.GenCryptoContext(parameters)
+    cryptoContext.Enable(fhe.PKE)
+    cryptoContext.Enable(fhe.KEYSWITCH)
+    cryptoContext.Enable(fhe.LEVELEDSHE)
+    cryptoContext.Enable(fhe.PKESchemeFeature.PRE)
+
+    keypair = cryptoContext.KeyGen()
+
+    # First plaintext vector is encoded
+    vectorOfInts1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
+    plaintext1 = cryptoContext.MakePackedPlaintext(vectorOfInts1)
+
+    # Second plaintext vector is encoded
+    vectorOfInts2 = [3, 2, 1, 4, 5, 6, 7, 8, 9, 10, 11, 12]
+    plaintext2 = cryptoContext.MakePackedPlaintext(vectorOfInts2)
+
+    # Third plaintext vector is encoded
+    vectorOfInts3 = [1, 2, 5, 2, 5, 6, 7, 8, 9, 10, 11, 12]
+    plaintext3 = cryptoContext.MakePackedPlaintext(vectorOfInts3)
+
+    # Create a final array adding the three vectors
+    initialPlaintextAddResult = [vectorOfInts1[i] + vectorOfInts2[i] + vectorOfInts3[i] for i in range(len(vectorOfInts1))]
+    initialPlaintextAddResult = cryptoContext.MakePackedPlaintext(initialPlaintextAddResult)
+
+    # Multiply the values
+    initialPlaintextMultResult = [vectorOfInts1[i] * vectorOfInts2[i] * vectorOfInts3[i] for i in range(len(vectorOfInts1))]
+    initialPlaintextMultResult = cryptoContext.MakePackedPlaintext(initialPlaintextMultResult)
+
+    # Rotate the values
+    initialPlaintextRot1 = rotate_vector(vectorOfInts1, VECTOR1_ROTATION)
+    initialPlaintextRot1 = cryptoContext.MakePackedPlaintext(initialPlaintextRot1)
+    initialPlaintextRot2 = rotate_vector(vectorOfInts2, VECTOR2_ROTATION)
+    initialPlaintextRot2 = cryptoContext.MakePackedPlaintext(initialPlaintextRot2)
+    initialPlaintextRot3 = rotate_vector(vectorOfInts3, VECTOR3_ROTATION)
+    initialPlaintextRot3 = cryptoContext.MakePackedPlaintext(initialPlaintextRot3)
+    initialPlaintextRot4 = rotate_vector(vectorOfInts3, VECTOR4_ROTATION)
+    initialPlaintextRot4 = cryptoContext.MakePackedPlaintext(initialPlaintextRot4)
+
+    # The encoded vectors are encrypted
+    ciphertext1 = cryptoContext.Encrypt(keypair.publicKey, plaintext1)
+    ciphertext2 = cryptoContext.Encrypt(keypair.publicKey, plaintext2)
+    ciphertext3 = cryptoContext.Encrypt(keypair.publicKey, plaintext3)
+
+    evalKey = cryptoContext.ReKeyGen(keypair.secretKey, keypair.publicKey)
+    cryptoContext.EvalMultKeyGen(keypair.secretKey)
+    cryptoContext.EvalRotateKeyGen(keypair.secretKey, [VECTOR1_ROTATION, VECTOR2_ROTATION, VECTOR3_ROTATION, VECTOR4_ROTATION])
+
+    cryptoContext_ser = fhe.Serialize(cryptoContext, mode)
+    LOGGER.debug("The cryptocontext has been serialized.")
+    publickey_ser = fhe.Serialize(keypair.publicKey, mode)
+    LOGGER.debug("The public key has been serialized.")
+    secretkey_ser = fhe.Serialize(keypair.secretKey, mode)
+    LOGGER.debug("The private key has been serialized.")
+    ciphertext1_ser = fhe.Serialize(ciphertext1, mode)
+    LOGGER.debug("The ciphertext 1 has been serialized.")
+    ciphertext2_ser = fhe.Serialize(ciphertext2, mode)
+    LOGGER.debug("The ciphertext 2 has been serialized.")
+    ciphertext3_ser = fhe.Serialize(ciphertext3, mode)
+    LOGGER.debug("The ciphertext 3 has been serialized.")
+    evalKey_ser = fhe.Serialize(evalKey, mode)
+    LOGGER.debug("The evaluation key has been serialized.")
+    multKey_ser = fhe.SerializeEvalMultKeyString(mode, "")
+    LOGGER.debug("The relinearization key has been serialized.")
+    automorphismKey_ser = fhe.SerializeEvalAutomorphismKeyString(mode, "")
+    LOGGER.debug("The rotation evaluation keys have been serialized.")
+
+    fhe.ClearEvalMultKeys()
+    cryptoContext.ClearEvalAutomorphismKeys()
+    fhe.ReleaseAllContexts()
+
+    cc = fhe.DeserializeCryptoContextString(cryptoContext_ser, mode)
+    assert isinstance(cc, fhe.CryptoContext)
+    LOGGER.debug("The cryptocontext has been deserialized.")
+
+    pk = fhe.DeserializePublicKeyString(publickey_ser, mode)
+    assert isinstance(pk, fhe.PublicKey)
+    LOGGER.debug("The public key has been deserialized.")
+
+    sk = fhe.DeserializePrivateKeyString(secretkey_ser, mode)
+    assert isinstance(sk, fhe.PrivateKey)
+    LOGGER.debug("The private key has been deserialized.")
+
+    ct1 = fhe.DeserializeCiphertextString(ciphertext1_ser, mode)
+    assert isinstance(ct1, fhe.Ciphertext)
+    LOGGER.debug("The ciphertext 1 has been reserialized.")
+
+    ct2 = fhe.DeserializeCiphertextString(ciphertext2_ser, mode)
+    assert isinstance(ct2, fhe.Ciphertext)
+    LOGGER.debug("The ciphertext 2 has been reserialized.")
+
+    ct3 = fhe.DeserializeCiphertextString(ciphertext3_ser, mode)
+    assert isinstance(ct3, fhe.Ciphertext)
+    LOGGER.debug("The ciphertext 3 has been reserialized.")
+
+    ek = fhe.DeserializeEvalKeyString(evalKey_ser, mode)
+    assert isinstance(ek, fhe.EvalKey)
+    LOGGER.debug("The evaluation key has been deserialized.")
+
+    fhe.DeserializeEvalMultKeyString(multKey_ser, mode)
+    LOGGER.debug("The relinearization key has been deserialized.")
+
+    fhe.DeserializeEvalAutomorphismKeyString(automorphismKey_ser, mode)
+    LOGGER.debug("The rotation evaluation keys have been deserialized.")
+
+    # Homomorphic addition
+
+    ciphertextAdd12 = cc.EvalAdd(ct1, ct2)
+    ciphertextAddResult = cc.EvalAdd(ciphertextAdd12, ct3)
+
+    # Homomorphic multiplication
+    ciphertextMult12 = cc.EvalMult(ct1, ct2)
+    ciphertextMultResult = cc.EvalMult(ciphertextMult12, ct3)
+
+    # Homomorphic rotation
+    ciphertextRot1 = cc.EvalRotate(ct1, VECTOR1_ROTATION)
+    ciphertextRot2 = cc.EvalRotate(ct2, VECTOR2_ROTATION)
+    ciphertextRot3 = cc.EvalRotate(ct3, VECTOR3_ROTATION)
+    ciphertextRot4 = cc.EvalRotate(ct3, VECTOR4_ROTATION)
+    
+    # Decrypt the result of additions
+    plaintextAddResult = cc.Decrypt(sk, ciphertextAddResult)
+
+    # Decrypt the result of multiplications
+    plaintextMultResult = cc.Decrypt(sk, ciphertextMultResult)
+
+    # Decrypt the result of rotations
+    plaintextRot1 = cc.Decrypt(sk, ciphertextRot1)
+    plaintextRot2 = cc.Decrypt(sk, ciphertextRot2)
+    plaintextRot3 = cc.Decrypt(sk, ciphertextRot3)
+    plaintextRot4 = cc.Decrypt(sk, ciphertextRot4)
+
+    # Shows only the same number of elements as in the original plaintext vector
+    # By default it will show all coefficients in the BFV-encoded polynomial
+    plaintextRot1.SetLength(len(vectorOfInts1))
+    plaintextRot2.SetLength(len(vectorOfInts1))
+    plaintextRot3.SetLength(len(vectorOfInts1))
+    plaintextRot4.SetLength(len(vectorOfInts1))
+
+    assert str(plaintextAddResult) == str(initialPlaintextAddResult)
+    assert str(plaintextMultResult) == str(initialPlaintextMultResult)
+    assert str(plaintextRot1) == str(initialPlaintextRot1)
+    assert str(plaintextRot2) == str(initialPlaintextRot2)
+    assert str(plaintextRot3) == str(initialPlaintextRot3)
+    assert str(plaintextRot4) == str(initialPlaintextRot4)
+
+def rotate_vector(vector, rotation):
+    """
+    Rotate a vector by a specified number of positions.
+    Positive values rotate left, negative values rotate right.
+
+    :param vector: List[int], the vector to rotate.
+    :param rotation: int, the number of positions to rotate.
+    :return: List[int], the rotated vector.
+    """
+    n = len(vector)
+    if rotation > 0:
+        rotated = vector[rotation:] + [0] * rotation
+    elif rotation < 0:
+        rotation = abs(rotation)
+        rotated = [0] * rotation + vector[:n - rotation]
+    else:
+        rotated = vector
+    return rotated

+ 33 - 0
utils/print-used-modules-and-libraries-linux.py

@@ -0,0 +1,33 @@
+import sys
+
+def print_python_imported_modules():
+    # print imported Python modules with their paths
+    print("          ===== imported Python modules =====")
+    for module_name, module in sorted(sys.modules.items()):
+        try:
+            module_file = module.__file__
+            if module_file:
+                print(f"{module_name}: {module_file}")
+        except AttributeError:
+            pass
+
+def print_loaded_shared_libraries():
+    # print loaded shared libraries from /proc/self/maps
+    print("          ===== loaded shared C/C++ libraries =====")
+    with open("/proc/self/maps", "r") as maps_file:
+        lines = maps_file.readlines()
+        for line in lines:
+            if ".so" in line:
+                parts = line.split()
+                if len(parts) > 5:
+                    print(parts[5])
+
+if __name__ == "__main__":
+    # import numpy
+    # import pandas
+
+    print("")
+    print_python_imported_modules()
+    print("")
+    print_loaded_shared_libraries()
+    print("")