diff --git a/.gitignore b/.gitignore
index 3e759b75b..f8bc871b2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,50 @@
+# Other stuff
+examples/CMakeCache.txt
+examples/CMakeFiles
+examples/Makefile
+examples/cmake_install.cmake
+src/CMakeCache.txt
+src/CMakeFiles
+src/Makefile
+src/.config
+src/autom4te.cache/*
+src/cmake/SEALConfig.cmake
+src/cmake/SEALConfigVersion.cmake
+src/cmake/SEALTargets.cmake
+src/cmake_install.cmake
+src/install_manifest.txt
+src/seal/CMakeCache.txt
+src/seal/CMakeFiles
+src/seal/Makefile
+src/seal/cmake_install.cmake
+src/seal/util/CMakeCache.txt
+src/seal/util/CMakeFiles
+src/seal/util/Makefile
+src/seal/util/cmake_install.cmake
+src/seal/util/config.h
+tests/CMakeCache.txt
+tests/CMakeFiles
+tests/Makefile
+tests/cmake_install.cmake
+tests/install_manifest.txt
+tests/seal/CMakeCache.txt
+tests/seal/CMakeFiles
+tests/seal/Makefile
+tests/seal/cmake_install.cmake
+tests/seal/util/CMakeCache.txt
+tests/seal/util/CMakeFiles
+tests/seal/util/Makefile
+tests/seal/util/cmake_install.cmake
+.ycm_extra_conf.py
+.vimrc
+.lvimrc
+.local_vimrc
+*/*.code-workspace
+*/.vscode
+*/build
+*/*.build
+*/compile_commands.json
+
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
@@ -23,15 +70,13 @@ bld/
[Bb]in/
[Oo]bj/
[Ll]og/
+[Ll]ib/
-# Visual Studio 2015/2017 cache/options directory
+# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
-# Visual Studio 2017 auto generated files
-Generated\ Files/
-
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
@@ -54,20 +99,14 @@ project.fragment.lock.json
artifacts/
**/Properties/launchSettings.json
-# StyleCop
-StyleCopReport.xml
-
-# Files built by Visual Studio
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
-*.iobj
*.pch
*.pdb
-*.ipdb
*.pgc
*.pgd
*.rsp
@@ -221,10 +260,6 @@ ClientBin/
*.publishsettings
orleans.codegen.cs
-# Including strong name files can present a security risk
-# (https://github.com/github/gitignore/pull/2483#issue-259490424)
-#*.snk
-
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
@@ -239,8 +274,6 @@ _UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
-ServiceFabricBackup/
-*.rptproj.bak
# SQL Server files
*.mdf
@@ -251,7 +284,6 @@ ServiceFabricBackup/
*.rdl.data
*.bim.layout
*.bim_*.settings
-*.rptproj.rsuser
# Microsoft Fakes
FakesAssemblies/
@@ -263,6 +295,9 @@ FakesAssemblies/
.ntvs_analysis.dat
node_modules/
+# Typescript v1 declaration files
+typings/
+
# Visual Studio 6 build log
*.plg
@@ -316,15 +351,3 @@ __pycache__/
# OpenCover UI analysis results
OpenCover/
-
-# Azure Stream Analytics local run output
-ASALocalRun/
-
-# MSBuild Binary and Structured Log
-*.binlog
-
-# NVidia Nsight GPU debugger configuration file
-*.nvuser
-
-# MFractors (Xamarin productivity tool) working folder
-.mfractor/
diff --git a/Contrib.md b/Contrib.md
new file mode 100644
index 000000000..0e8e69417
--- /dev/null
+++ b/Contrib.md
@@ -0,0 +1,18 @@
+# Contributing
+
+This project welcomes contributions and suggestions. Most contributions require you
+to agree to a Contributor License Agreement (CLA) declaring that you have the right to,
+and actually do, grant us the rights to use your contribution. For details, visit
+https://cla.microsoft.com.
+
+When you submit a pull request, a CLA-bot will automatically determine whether you need
+to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow
+the instructions provided by the bot. You will only need to do this once across all
+repos using our CLA.
+
+This project has adopted the
+[Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
+For more information see the
+[Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
+or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional
+questions or comments.
diff --git a/Issues.md b/Issues.md
new file mode 100644
index 000000000..6d2149e8f
--- /dev/null
+++ b/Issues.md
@@ -0,0 +1,27 @@
+# Issues
+
+## Technical questions
+
+The best way to get help with technical questions is on
+[StackOverflow](https://stackoverflow.com/questions/tagged/seal) using the
+[seal] tag. To contact the Microsoft SEAL team directly, please email
+[sealcrypto@microsoft.com](mailto:sealcrypto@microsoft.com).
+
+## Bug reports
+
+We appreciate community efforts to find and fix bugs and issues in SEAL. If
+you believe you have found a bug or want to report some other issue, please
+do so on [GitHub](https://github.com/Microsoft/SEAL/issues). To help others
+determine what the problem may be, we provide a helpful script that collects
+relevant system information that you can submit with the bug report (see below).
+
+### System information
+
+To collect system information for an improved bug report, please run
+ make -C tools system_info
+This will result in a file system\_info.tar.gz to be generated, which you can
+optionally attach with your bug report.
+
+## Critical security issues
+
+For reporting critical security issues, see Security.md.
diff --git a/README.md b/README.md
index 441291b75..282c9a8dc 100644
--- a/README.md
+++ b/README.md
@@ -1,46 +1,141 @@
# Introduction
-SEAL (Simple Encrypted Arithmetic Library) is an easy-to-use homomorphic encryption
-library, developed by researchers in the Cryptography Research group at Microsoft
-Research. SEAL is written in standard C++17 and can be compiled also as C++14.
-# System requirements
-Since SEAL has no external dependencies and is written in standard C++ it is easy
-to build on any 64-bit system. For building in Windows, SEAL contains a Visual
-Studio 2017 solution file. For building in Linux and Mac OS X, SEAL requires either
-g++-6 or newer, or clang++-5 or newer. Please see INSTALL.txt for installation
-instructions using CMake.
+Microsoft Simple Encrypted Arithmetic Library (Microsoft SEAL) is an easy-to-use
+homomorphic encryption library developed by researchers in the Cryptography
+Research group at Microsoft Research. SEAL is written in modern standard C++ and
+has no external dependencies, making it easy to compile and run in many different
+environments.
-# Documentation
-The code-base contains (see SEALExamples/main.cpp) extensive and thoroughly
-commented examples that should serve as a self-contained introduction to using SEAL.
-In addition, the header files contain detailed comments for the public API.
+For more information about the Microsoft SEAL project, see [http://sealcrypto.org](http://sealcrypto.org).
# License
-SEAL is licensed under the MIT license; see LICENSE.txt.
-
-# Contributing
-
-This project welcomes contributions and suggestions. Most contributions require you
-to agree to a Contributor License Agreement (CLA) declaring that you have the right to,
-and actually do, grant us the rights to use your contribution. For details, visit
-https://cla.microsoft.com.
-
-When you submit a pull request, a CLA-bot will automatically determine whether you need
-to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow
-the instructions provided by the bot. You will only need to do this once across all
-repos using our CLA.
-
-This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
-For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
-or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional
-questions or comments.
-
-# Acknowledgements
-We would like to thank John Wernsing, Michael Naehrig, Nathan Dowlin, Rachel Player,
-Gizem Cetin, Susan Xia, Peter Rindal, Kyoohyung Han, Zhicong Huang, Amir Jalali, Wei Dai,
-Ilia Iliashenko, and Sadegh Riazi for their contributions to the SEAL project. We would also
-like to thank everyone who has sent us helpful comments, suggestions, and bug reports.
-
-# Contact Us
-The best way to ask technical questions is on StackOverflow using the [seal] tag. To contact
-us directly, please email [sealcrypto@microsoft.com](mailto:sealcrypto@microsoft.com).
+
+SEAL is licensed under the MIT license; see LICENSE.
+
+# Building and using SEAL
+
+## Windows
+
+SEAL comes with a Microsoft Visual Studio 2017 solution file SEAL.sln that can be
+used to conveniently build the library, examples, and unit tests.
+
+#### Debug and release builds
+
+You can easily switch from Visual Studio configuration menu whether SEAL should be
+built in Debug mode (no optimizations) or in Release mode. Please note that Debug
+mode should not be used except for debugging SEAL itself, as the performance will be
+orders of magnitude worse than in Release mode.
+
+#### Library
+
+Build the SEAL project (src/SEAL.vcxproj) from SEAL.sln. Building SEAL results
+in the static library seal.lib to be created in lib/x64/$(Configuration). When
+linking with applications, you need to add src/ (full path) as an include directory
+for SEAL header files.
+
+#### Examples
+
+Build the SEALExamples project (examples/SEALExamples.vcxproj) from SEAL.sln.
+This results in an executable sealexamples.exe to be created in bin/x64/$(Configuration).
+
+#### Unit tests
+
+The unit tests require the Google Test framework to be installed. The appropriate
+NuGet package is already listed in tests/packages.config, so once you attempt to build
+the SEALTest project (tests/SEALTest.vcxproj) from SEAL.sln Visual Studio will
+automatically download and install it for you.
+
+## Linux and OS X
+
+SEAL is very easy to configure and build in Linux and OS X using CMake (>= 3.10).
+A modern version of GNU G++ (>= 6.0) or Clang++ (>= 5.0) is needed. In OS X the
+Xcode toolchain (>= 9.3) will work.
+
+In OS X you will need CMake with command line tools. For this, you can either
+1. install the cmake package with [Homebrew](https://brew.sh), or
+2. download CMake directly from [https://cmake.org/download](https://cmake.org/download) and [enable command line tools](https://stackoverflow.com/questions/30668601/installing-cmake-command-line-tools-on-a-mac).
+
+Below we give instructions for how to configure, build, and install SEAL either
+system-wide (global install), or for a single user (local install). A system-wide
+install requires elevated (root) privileges.
+
+#### Debug and release builds
+
+You can easily switch from CMake configuration options whether SEAL should be built in
+Debug mode (no optimizations) or in Release mode. Please note that Debug mode should not
+be used except for debugging SEAL itself, as the performance will be orders of magnitude
+worse than in Release mode.
+
+### Global install
+
+#### Library
+
+If you have root access to the system you can install SEAL system-wide as follows:
+ cd src
+ cmake .
+ make
+ sudo make install
+ cd ..
+
+#### Examples
+
+To build the examples do:
+ cd examples
+ cmake .
+ make
+ cd ..
+
+After completing the above steps the sealexamples executable can be found in bin/.
+See examples/CMakeLists.txt for how to link SEAL with your own project using cmake.
+
+#### Unit tests
+
+To build the unit tests, make sure you have the Google Test library (libgtest-dev)
+installed. Then do:
+ cd tests
+ cmake .
+ make
+ cd ..
+
+After completing these steps the sealtest executable can be found in bin/. All unit
+tests should pass successfully.
+
+### Local install
+
+#### Library
+
+To install SEAL locally, e.g., to ~/mylibs, do the following:
+ cd src
+ cmake -DCMAKE_INSTALL_PREFIX=~/mylibs .
+ make
+ make install
+ cd ..
+
+#### Examples
+
+To build the examples do:
+ cd examples
+ cmake -DCMAKE_PREFIX_PATH=~/mylibs .
+ make
+ cd ..
+
+After completing the above steps the sealexamples executable can be found in bin/.
+See examples/CMakeLists.txt for how to link SEAL with your own project using cmake.
+
+#### Unit tests
+
+To build the unit tests, make sure you have the Google Test library (libgtest-dev)
+installed. Then do:
+ cd tests
+ cmake -DCMAKE_PREFIX_PATH=~/mylibs .
+ make
+ cd ..
+
+After completing these steps the sealtest executable can be found in bin/. All unit
+tests should pass successfully.
+
+# Documentation
+
+The code-base contains extensive and thoroughly commented examples that should
+serve as a self-contained introduction to using SEAL (see examples/examples.cpp). In
+addition, the header files contain detailed comments for the public API.
diff --git a/SEAL.sln b/SEAL.sln
new file mode 100644
index 000000000..de2ef65ad
--- /dev/null
+++ b/SEAL.sln
@@ -0,0 +1,48 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.26430.16
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SEAL", "src\SEAL.vcxproj", "{7EA96C25-FC0D-485A-BB71-32B6DA55652A}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SEALTest", "tests\SEALTest.vcxproj", "{0345DC4D-EFE3-460E-AB7E-AA6E05BB8DFF}"
+ ProjectSection(ProjectDependencies) = postProject
+ {7EA96C25-FC0D-485A-BB71-32B6DA55652A} = {7EA96C25-FC0D-485A-BB71-32B6DA55652A}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SEALExamples", "examples\SEALExamples.vcxproj", "{2B57D847-26DC-45FF-B9AF-EE33910B5093}"
+ ProjectSection(ProjectDependencies) = postProject
+ {7EA96C25-FC0D-485A-BB71-32B6DA55652A} = {7EA96C25-FC0D-485A-BB71-32B6DA55652A}
+ EndProjectSection
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x64 = Debug|x64
+ Release|x64 = Release|x64
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {7EA96C25-FC0D-485A-BB71-32B6DA55652A}.Debug|x64.ActiveCfg = Debug|x64
+ {7EA96C25-FC0D-485A-BB71-32B6DA55652A}.Debug|x64.Build.0 = Debug|x64
+ {7EA96C25-FC0D-485A-BB71-32B6DA55652A}.Release|x64.ActiveCfg = Release|x64
+ {7EA96C25-FC0D-485A-BB71-32B6DA55652A}.Release|x64.Build.0 = Release|x64
+ {0345DC4D-EFE3-460E-AB7E-AA6E05BB8DFF}.Debug|x64.ActiveCfg = Debug|x64
+ {0345DC4D-EFE3-460E-AB7E-AA6E05BB8DFF}.Debug|x64.Build.0 = Debug|x64
+ {0345DC4D-EFE3-460E-AB7E-AA6E05BB8DFF}.Release|x64.ActiveCfg = Release|x64
+ {0345DC4D-EFE3-460E-AB7E-AA6E05BB8DFF}.Release|x64.Build.0 = Release|x64
+ {2B57D847-26DC-45FF-B9AF-EE33910B5093}.Debug|x64.ActiveCfg = Debug|x64
+ {2B57D847-26DC-45FF-B9AF-EE33910B5093}.Debug|x64.Build.0 = Debug|x64
+ {2B57D847-26DC-45FF-B9AF-EE33910B5093}.Release|x64.ActiveCfg = Release|x64
+ {2B57D847-26DC-45FF-B9AF-EE33910B5093}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {15A17F22-F747-4B82-BF5F-E0224AF4B3ED}
+ EndGlobalSection
+ GlobalSection(Performance) = preSolution
+ HasPerformanceSessions = true
+ EndGlobalSection
+ GlobalSection(Performance) = preSolution
+ HasPerformanceSessions = true
+ EndGlobalSection
+EndGlobal
diff --git a/Security.md b/Security.md
new file mode 100644
index 000000000..777860636
--- /dev/null
+++ b/Security.md
@@ -0,0 +1,8 @@
+# Reporting Security Issues
+
+Security issues and bugs should be reported privately, via email, to the Microsoft Security
+Response Center (MSRC) at [secure@microsoft.com](mailto:secure@microsoft.com). You should
+receive a response within 24 hours. If for some reason you do not, please follow up via
+email to ensure we received your original message. Further information, including the
+[MSRC PGP](https://technet.microsoft.com/en-us/security/dn606155) key, can be found in
+the [Security TechCenter](https://technet.microsoft.com/en-us/security/default).
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
new file mode 100644
index 000000000..03ca7661d
--- /dev/null
+++ b/examples/CMakeLists.txt
@@ -0,0 +1,17 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT license.
+
+cmake_minimum_required(VERSION 3.10)
+
+project(SEALExamples VERSION 3.1.0 LANGUAGES CXX)
+
+# Executable will be in ../bin
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/../bin)
+
+add_executable(sealexamples examples.cpp)
+
+# Import SEAL
+find_package(SEAL 3.1.0 EXACT REQUIRED)
+
+# Link SEAL
+target_link_libraries(sealexamples SEAL::seal)
diff --git a/examples/SEALExamples.vcxproj b/examples/SEALExamples.vcxproj
new file mode 100644
index 000000000..4364cbc56
--- /dev/null
+++ b/examples/SEALExamples.vcxproj
@@ -0,0 +1,109 @@
+
+
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ {2B57D847-26DC-45FF-B9AF-EE33910B5093}
+ Win32Proj
+ SEALExamples
+ 10.0.16299.0
+
+
+
+ Application
+ true
+ v141
+ Unicode
+
+
+ Application
+ false
+ v141
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ $(SolutionDir)bin\$(Platform)\$(Configuration)\
+ $(ProjectDir)obj\$(Platform)\$(Configuration)\
+ sealexamples
+
+
+ false
+ $(SolutionDir)bin\$(Platform)\$(Configuration)\
+ $(ProjectDir)obj\$(Platform)\$(Configuration)\
+ sealexamples
+
+
+
+ Level3
+ NotUsing
+ Disabled
+
+
+ true
+ $(SolutionDir)src
+ stdcpp17
+ /Zc:__cplusplus %(AdditionalOptions)
+
+
+ Console
+ true
+ $(SolutionDir)lib\$(Platform)\$(Configuration);%(AdditionalLibraryDirectories)
+ seal.lib;%(AdditionalDependencies)
+
+
+
+
+ Level3
+ NotUsing
+ MaxSpeed
+ true
+ true
+
+
+ true
+ $(SolutionDir)src
+ stdcpp17
+ /Zc:__cplusplus %(AdditionalOptions)
+
+
+ Console
+ true
+ true
+ true
+ $(SolutionDir)lib\$(Platform)\$(Configuration);%(AdditionalLibraryDirectories)
+ seal.lib;%(AdditionalDependencies)
+ true
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/SEALExamples.vcxproj.filters b/examples/SEALExamples.vcxproj.filters
new file mode 100644
index 000000000..272ea16f4
--- /dev/null
+++ b/examples/SEALExamples.vcxproj.filters
@@ -0,0 +1,30 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;hm;inl;inc;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+ {abd2e216-316f-4dad-a2a4-a72ffccfd92b}
+
+
+
+
+ Source Files
+
+
+
+
+ Linux
+
+
+
diff --git a/examples/examples.cpp b/examples/examples.cpp
new file mode 100644
index 000000000..21950a821
--- /dev/null
+++ b/examples/examples.cpp
@@ -0,0 +1,2666 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "seal/seal.h"
+
+using namespace std;
+using namespace seal;
+
+/*
+Helper function: Prints the name of the example in a fancy banner.
+*/
+void print_example_banner(string title)
+{
+ if (!title.empty())
+ {
+ size_t title_length = title.length();
+ size_t banner_length = title_length + 2 + 2 * 10;
+ string banner_top(banner_length, '*');
+ string banner_middle = string(10, '*') + " " + title + " " + string(10, '*');
+
+ cout << endl
+ << banner_top << endl
+ << banner_middle << endl
+ << banner_top << endl
+ << endl;
+ }
+}
+
+/*
+Helper function: Prints the parameters in a SEALContext.
+*/
+void print_parameters(shared_ptr context)
+{
+ // Verify parameters
+ if (!context)
+ {
+ throw invalid_argument("context is not set");
+ }
+ auto &context_data = *context->context_data();
+
+ /*
+ Which scheme are we using?
+ */
+ string scheme_name;
+ switch (context_data.parms().scheme())
+ {
+ case scheme_type::BFV:
+ scheme_name = "BFV";
+ break;
+ case scheme_type::CKKS:
+ scheme_name = "CKKS";
+ break;
+ default:
+ throw invalid_argument("unsupported scheme");
+ }
+
+ cout << "/ Encryption parameters:" << endl;
+ cout << "| scheme: " << scheme_name << endl;
+ cout << "| poly_modulus_degree: " <<
+ context_data.parms().poly_modulus_degree() << endl;
+
+ /*
+ Print the size of the true (product) coefficient modulus.
+ */
+ cout << "| coeff_modulus size: " << context_data.
+ total_coeff_modulus_bit_count() << " bits" << endl;
+
+ /*
+ For the BFV scheme print the plain_modulus parameter.
+ */
+ if (context_data.parms().scheme() == scheme_type::BFV)
+ {
+ cout << "| plain_modulus: " << context_data.
+ parms().plain_modulus().value() << endl;
+ }
+
+ cout << "\\ noise_standard_deviation: " << context_data.
+ parms().noise_standard_deviation() << endl;
+ cout << endl;
+}
+
+/*
+Helper function: Prints the `parms_id' to std::ostream.
+*/
+ostream &operator <<(ostream &stream, parms_id_type parms_id)
+{
+ stream << hex << parms_id[0] << " " << parms_id[1] << " "
+ << parms_id[2] << " " << parms_id[3] << dec;
+ return stream;
+}
+
+/*
+Helper function: Prints a vector of floating-point values.
+*/
+template
+void print_vector(vector vec, size_t print_size = 4, int prec = 3)
+{
+ /*
+ Save the formatting information for std::cout.
+ */
+ ios old_fmt(nullptr);
+ old_fmt.copyfmt(cout);
+
+ size_t slot_count = vec.size();
+
+ cout << fixed << setprecision(prec) << endl;
+ if(slot_count <= 2 * print_size)
+ {
+ cout << " [";
+ for (size_t i = 0; i < slot_count; i++)
+ {
+ cout << " " << vec[i] << ((i != slot_count - 1) ? "," : " ]\n");
+ }
+ }
+ else
+ {
+ vec.resize(max(vec.size(), 2 * print_size));
+ cout << " [";
+ for (size_t i = 0; i < print_size; i++)
+ {
+ cout << " " << vec[i] << ",";
+ }
+ if(vec.size() > 2 * print_size)
+ {
+ cout << " ...,";
+ }
+ for (size_t i = slot_count - print_size; i < slot_count; i++)
+ {
+ cout << " " << vec[i] << ((i != slot_count - 1) ? "," : " ]\n");
+ }
+ }
+ cout << endl;
+
+ /*
+ Restore the old std::cout formatting.
+ */
+ cout.copyfmt(old_fmt);
+}
+
+void example_bfv_basics_i();
+
+void example_bfv_basics_ii();
+
+void example_bfv_basics_iii();
+
+void example_bfv_basics_iv();
+
+void example_ckks_basics_i();
+
+void example_ckks_basics_ii();
+
+void example_ckks_basics_iii();
+
+void example_bfv_performance();
+
+void example_ckks_performance();
+
+int main()
+{
+#ifdef SEAL_VERSION
+ cout << "SEAL version: " << SEAL_VERSION << endl;
+#endif
+ while (true)
+ {
+ cout << "\nSEAL Examples:" << endl << endl;
+ cout << " 1. BFV Basics I" << endl;
+ cout << " 2. BFV Basics II" << endl;
+ cout << " 3. BFV Basics III" << endl;
+ cout << " 4. BFV Basics IV" << endl;
+ cout << " 5. BFV Performance Test" << endl;
+ cout << " 6. CKKS Basics I" << endl;
+ cout << " 7. CKKS Basics II" << endl;
+ cout << " 8. CKKS Basics III" << endl;
+ cout << " 9. CKKS Performance Test" << endl;
+ cout << " 0. Exit" << endl;
+
+ /*
+ Print how much memory we have allocated from the current memory pool.
+ By default the memory pool will be a static global pool and the
+ MemoryManager class can be used to change it. Most users should have
+ little or no reason to touch the memory allocation system.
+ */
+ cout << "\nTotal memory allocated from the current memory pool: "
+ << (MemoryManager::GetPool().alloc_byte_count() >> 20) << " MB" << endl;
+
+ int selection = 0;
+ cout << endl << "Run example: ";
+ if (!(cin >> selection))
+ {
+ cout << "Invalid option." << endl;
+ cin.clear();
+ cin.ignore(numeric_limits::max(), '\n');
+ continue;
+ }
+
+ switch (selection)
+ {
+ case 1:
+ example_bfv_basics_i();
+ break;
+
+ case 2:
+ example_bfv_basics_ii();
+ break;
+
+ case 3:
+ example_bfv_basics_iii();
+ break;
+
+ case 4:
+ example_bfv_basics_iv();
+ break;
+
+ case 5:
+ example_bfv_performance();
+ break;
+
+ case 6:
+ example_ckks_basics_i();
+ break;
+
+ case 7:
+ example_ckks_basics_ii();
+ break;
+
+ case 8:
+ example_ckks_basics_iii();
+ break;
+
+ case 9: {
+ example_ckks_performance();
+ break;
+ }
+
+ case 0:
+ return 0;
+
+ default:
+ cout << "Invalid option." << endl;
+ }
+ }
+
+ return 0;
+}
+
+void example_bfv_basics_i()
+{
+ print_example_banner("Example: BFV Basics I");
+
+ /*
+ In this example we demonstrate setting up encryption parameters and other
+ relevant objects for performing simple computations on encrypted integers.
+
+ SEAL implements two encryption schemes: the Brakerski/Fan-Vercauteren (BFV)
+ scheme and the Cheon-Kim-Kim-Song (CKKS) scheme. In the first examples we
+ use the BFV scheme as it is far easier to understand and use than CKKS. For
+ more details on the basics of the BFV scheme, we refer the reader to the
+ original paper https://eprint.iacr.org/2012/144. In truth, to achieve good
+ performance SEAL implements the "FullRNS" optimization as described in
+ https://eprint.iacr.org/2016/510, but this optiomization is invisible to
+ the user and has no security implications. We will discuss the CKKS scheme
+ in later examples.
+
+ The first task is to set up an instance of the EncryptionParameters class.
+ It is critical to understand how these different parameters behave, how they
+ affect the encryption scheme, performance, and the security level. There are
+ three encryption parameters that are necessary to set:
+
+ - poly_modulus_degree (degree of polynomial modulus);
+ - coeff_modulus ([ciphertext] coefficient modulus);
+ - plain_modulus (plaintext modulus).
+
+ A fourth parameter -- noise_standard_deviation -- has a default value 3.20
+ and should not be necessary to modify unless the user has a specific reason
+ to do so and has an in-depth understanding of the security implications.
+
+ A fifth parameter -- random_generator -- can be set to use customized random
+ number generators. By default, SEAL uses hardware-based AES in counter mode
+ for pseudo-randomness with key generated using std::random_device. If the
+ AES-NI instruction set is not available, all randomness is generated from
+ std::random_device. Most academic users in particular should have little
+ reason to change this.
+
+ The BFV scheme cannot perform arbitrary computations on encrypted data.
+ Instead, each ciphertext has a specific quantity called the `invariant noise
+ budget' -- or `noise budget' for short -- measured in bits. The noise budget
+ in a freshly encrypted ciphertext (initial noise budget) is determined by
+ the encryption parameters. Homomorphic operations consume the noise budget
+ at a rate also determined by the encryption parameters. In BFV the two basic
+ operations allowed on encrypted data are additions and multiplications, of
+ which additions can generally be thought of as being nearly free in terms of
+ noise budget consumption compared to multiplications. Since noise budget
+ consumption compounds in sequential multiplications, the most significant
+ factor in choosing appropriate encryption parameters is the multiplicative
+ depth of the arithmetic circuit that the user wants to evaluate on encrypted
+ data. Once the noise budget of a ciphertext reaches zero it becomes too
+ corrupted to be decrypted. Thus, it is essential to choose the parameters to
+ be large enough to support the desired computation; otherwise the result is
+ impossible to make sense of even with the secret key.
+ */
+ EncryptionParameters parms(scheme_type::BFV);
+
+ /*
+ The first parameter we set is the degree of the polynomial modulus. This must
+ be a positive power of 2, representing the degree of a power-of-2 cyclotomic
+ polynomial; it is not necessary to understand what this means. The polynomial
+ modulus degree should be thought of mainly affecting the security level of the
+ scheme: larger degree makes the scheme more secure. Larger degree also makes
+ ciphertext sizes larger, and consequently all operations slower. Recommended
+ degrees are 1024, 2048, 4096, 8192, 16384, 32768, but it is also possible to
+ go beyond this. In this example we use a relatively small polynomial modulus.
+ */
+ parms.set_poly_modulus_degree(2048);
+
+ /*
+ Next we set the [ciphertext] coefficient modulus (coeff_modulus). The size
+ of the coefficient modulus should be thought of as the most significant
+ factor in determining the noise budget in a freshly encrypted ciphertext:
+ bigger means more noise budget, which is desirable. On the other hand,
+ a larger coefficient modulus lowers the security level of the scheme. Thus,
+ if a large noise budget is required for complicated computations, a large
+ coefficient modulus needs to be used, and the reduction in the security
+ level must be countered by simultaneously increasing the polynomial modulus.
+ Overall, this will result in worse performance.
+
+ To make parameter selection easier for the user, we have constructed sets
+ of largest safe coefficient moduli for 128-bit and 192-bit security levels
+ for different choices of the polynomial modulus. These default parameters
+ follow the recommendations in the Security Standard Draft available at
+ http://HomomorphicEncryption.org. The security estimates are a complicated
+ topic and we highly recommend consulting with experts in the field when
+ selecting parameters.
+
+ Our recommended values for the coefficient modulus can be easily accessed
+ through the functions
+
+ coeff_modulus_128bit(int)
+ coeff_modulus_192bit(int)
+ coeff_modulus_256bit(int)
+
+ for 128-bit, 192-bit, and 256-bit security levels. The integer parameter is
+ the degree of the polynomial modulus used.
+
+ In SEAL the coefficient modulus is a positive composite number -- a product
+ of distinct primes of size up to 60 bits. When we talk about the size of the
+ coefficient modulus we mean the bit length of the product of the primes. The
+ small primes are represented by instances of the SmallModulus class so for
+ example coeff_modulus_128bit(int) returns a vector of SmallModulus instances.
+
+ It is possible for the user to select their own small primes. Since SEAL uses
+ the Number Theoretic Transform (NTT) for polynomial multiplications modulo the
+ factors of the coefficient modulus, the factors need to be prime numbers
+ congruent to 1 modulo 2*poly_modulus_degree. We have generated a list of such
+ prime numbers of various sizes that the user can easily access through the
+ functions
+
+ small_mods_60bit(int)
+ small_mods_50bit(int)
+ small_mods_40bit(int)
+ small_mods_30bit(int)
+
+ each of which gives access to an array of primes of the denoted size. These
+ primes are located in the source file util/globals.cpp. Again, please keep
+ in mind that the choice of coeff_modulus has a dramatic effect on security
+ and should almost always be obtained through coeff_modulus_xxx(int).
+
+ Performance is mainly affected by the size of the polynomial modulus, and
+ the number of prime factors in the coefficient modulus; hence in some cases
+ it can be important to use as few prime factors in the coefficient modulus
+ as possible.
+
+ In this example we use the default coefficient modulus for a 128-bit security
+ level. Concretely, this coefficient modulus consists of only one 54-bit prime
+ factor: 0x3fffffff000001.
+ */
+ parms.set_coeff_modulus(coeff_modulus_128(2048));
+
+ /*
+ The plaintext modulus can be any positive integer, even though here we take
+ it to be a power of two. In fact, in many cases one might instead want it
+ to be a prime number; we will see this in later examples. The plaintext
+ modulus determines the size of the plaintext data type but it also affects
+ the noise budget in a freshly encrypted ciphertext and the consumption of
+ noise budget in homomorphic (encrypted) multiplications. Thus, it is
+ essential to try to keep the plaintext data type as small as possible for
+ best performance. The noise budget in a freshly encrypted ciphertext is
+
+ ~ log2(coeff_modulus/plain_modulus) (bits)
+
+ and the noise budget consumption in a homomorphic multiplication is of the
+ form log2(plain_modulus) + (other terms).
+ */
+ parms.set_plain_modulus(1 << 8);
+
+ /*
+ Now that all parameters are set, we are ready to construct a SEALContext
+ object. This is a heavy class that checks the validity and properties of the
+ parameters we just set and performs several important pre-computations.
+ */
+ auto context = SEALContext::Create(parms);
+
+ /*
+ Print the parameters that we have chosen.
+ */
+ print_parameters(context);
+
+ /*
+ Plaintexts in the BFV scheme are polynomials with coefficients integers
+ modulo plain_modulus. This is not a very practical object to encrypt: much
+ more useful would be encrypting integers or floating point numbers. For this
+ we need an `encoding scheme' to convert data from integer representation to
+ an appropriate plaintext polynomial representation than can subsequently be
+ encrypted. SEAL comes with a few basic encoders for the BFV scheme:
+
+ [IntegerEncoder]
+ Given an integer base b, encodes integers as plaintext polynomials as follows.
+ First, a base-b expansion of the integer is computed. This expansion uses
+ a `balanced' set of representatives of integers modulo b as the coefficients.
+ Namely, when b is odd the coefficients are integers between -(b-1)/2 and
+ (b-1)/2. When b is even, the integers are between -b/2 and (b-1)/2, except
+ when b is two and the usual binary expansion is used (coefficients 0 and 1).
+ Decoding amounts to evaluating the polynomial at x=b. For example, if b=2,
+ the integer
+
+ 26 = 2^4 + 2^3 + 2^1
+
+ is encoded as the polynomial 1x^4 + 1x^3 + 1x^1. When b=3,
+
+ 26 = 3^3 - 3^0
+
+ is encoded as the polynomial 1x^3 - 1. In memory polynomial coefficients are
+ always stored as unsigned integers by storing their smallest non-negative
+ representatives modulo plain_modulus. To create a base-b integer encoder,
+ use the constructor IntegerEncoder(plain_modulus, b). If no b is given, b=2
+ is used.
+
+ [FractionalEncoder]
+ The FractionalEncoder encodes fixed-precision rational numbers as follows.
+ It expands the number in a given base b, possibly truncating an infinite
+ fractional part to finite precision, e.g.
+
+ 26.75 = 2^4 + 2^3 + 2^1 + 2^(-1) + 2^(-2)
+
+ when b=2. For the sake of the example, suppose poly_modulus is 1x^1024 + 1.
+ It then represents the integer part of the number in the same way as in
+ IntegerEncoder (with b=2 here), and moves the fractional part instead to the
+ highest degree part of the polynomial, but with signs of the coefficients
+ changed. In this example we would represent 26.75 as the polynomial
+
+ -1x^1023 - 1x^1022 + 1x^4 + 1x^3 + 1x^1.
+
+ In memory the negative coefficients of the polynomial will be represented as
+ their negatives modulo plain_modulus. While easy to use, the fractional
+ encoder suffers from drawbacks that can be avoided using the CKKS scheme
+ instead of BFV; hence, we do not demonstrate the FractionalEncoder in these
+ examples.
+
+ [BatchEncoder]
+ If plain_modulus is a prime congruent to 1 modulo 2*poly_modulus_degree, the
+ plaintext elements can be viewed as 2-by-(poly_modulus_degree / 2) matrices
+ with elements integers modulo plain_modulus. When a desired computation can
+ be vectorized, using BatchEncoder can result in a massive performance boost
+ over naively encrypting and operating on each input number separately. Thus,
+ in more complicated computations this is likely to be by far the most
+ important and useful encoder. In example_bfv_basics_iii() we show how to
+ operate on encrypted matrix plaintexts.
+
+ Here we choose to create an IntegerEncoder with base b=2. For most use-cases
+ of the IntegerEncoder this is a good choice.
+ */
+ IntegerEncoder encoder(parms.plain_modulus());
+
+ /*
+ We are now ready to generate the secret and public keys. For this purpose
+ we need an instance of the KeyGenerator class. Constructing a KeyGenerator
+ automatically generates the public and secret key, which can then be read to
+ local variables.
+ */
+ KeyGenerator keygen(context);
+ PublicKey public_key = keygen.public_key();
+ SecretKey secret_key = keygen.secret_key();
+
+ /*
+ To be able to encrypt we need to construct an instance of Encryptor. Note
+ that the Encryptor only requires the public key, as expected.
+ */
+ Encryptor encryptor(context, public_key);
+
+ /*
+ Computations on the ciphertexts are performed with the Evaluator class. In
+ a real use-case the Evaluator would not be constructed by the same party
+ that holds the secret key.
+ */
+ Evaluator evaluator(context);
+
+ /*
+ We will of course want to decrypt our results to verify that everything worked,
+ so we need to also construct an instance of Decryptor. Note that the Decryptor
+ requires the secret key.
+ */
+ Decryptor decryptor(context, secret_key);
+
+ /*
+ We start by encoding two integers as plaintext polynomials.
+ */
+ int value1 = 5;
+ Plaintext plain1 = encoder.encode(value1);
+ cout << "Encoded " << value1 << " as polynomial " << plain1.to_string()
+ << " (plain1)" << endl;
+
+ int value2 = -7;
+ Plaintext plain2 = encoder.encode(value2);
+ cout << "Encoded " << value2 << " as polynomial " << plain2.to_string()
+ << " (plain2)" << endl;
+
+ /*
+ Encrypting the encoded values is easy.
+ */
+ Ciphertext encrypted1, encrypted2;
+ cout << "Encrypting plain1: ";
+ encryptor.encrypt(plain1, encrypted1);
+ cout << "Done (encrypted1)" << endl;
+
+ cout << "Encrypting plain2: ";
+ encryptor.encrypt(plain2, encrypted2);
+ cout << "Done (encrypted2)" << endl;
+
+ /*
+ To illustrate the concept of noise budget, we print the budgets in the fresh
+ encryptions.
+ */
+ cout << "Noise budget in encrypted1: "
+ << decryptor.invariant_noise_budget(encrypted1) << " bits" << endl;
+ cout << "Noise budget in encrypted2: "
+ << decryptor.invariant_noise_budget(encrypted2) << " bits" << endl;
+
+ /*
+ As a simple example, we compute (-encrypted1 + encrypted2) * encrypted2. Most
+ basic arithmetic operations come as in-place two-argument versions that
+ overwrite the first argument with the result, and as three-argument versions
+ taking as separate destination parameter. In most cases the in-place variants
+ are slightly faster.
+ */
+
+ /*
+ Negation is a unary operation and does not consume any noise budget.
+ */
+ evaluator.negate_inplace(encrypted1);
+ cout << "Noise budget in -encrypted1: "
+ << decryptor.invariant_noise_budget(encrypted1) << " bits" << endl;
+
+ /*
+ Compute the sum of encrypted1 and encrypted2; the sum overwrites encrypted1.
+ */
+ evaluator.add_inplace(encrypted1, encrypted2);
+
+ /*
+ Addition sets the noise budget to the minimum of the input noise budgets.
+ In this case both inputs had roughly the same budget going in, so the output
+ (in encrypted1) has just a slightly lower budget. Depending on probabilistic
+ effects the noise growth consumption may or may not be visible when measured
+ in whole bits.
+ */
+ cout << "Noise budget in -encrypted1 + encrypted2: "
+ << decryptor.invariant_noise_budget(encrypted1) << " bits" << endl;
+
+ /*
+ Finally multiply with encrypted2. Again, we use the in-place version of the
+ function, overwriting encrypted1 with the product.
+ */
+ evaluator.multiply_inplace(encrypted1, encrypted2);
+
+ /*
+ Multiplication consumes a lot of noise budget. This is clearly seen in the
+ print-out. The user can change the plain_modulus to see its effect on the
+ rate of noise budget consumption.
+ */
+ cout << "Noise budget in (-encrypted1 + encrypted2) * encrypted2: "
+ << decryptor.invariant_noise_budget(encrypted1) << " bits" << endl;
+
+ /*
+ Now we decrypt and decode our result.
+ */
+ Plaintext plain_result;
+ cout << "Decrypting result: ";
+ decryptor.decrypt(encrypted1, plain_result);
+ cout << "Done" << endl;
+
+ /*
+ Print the result plaintext polynomial.
+ */
+ cout << "Plaintext polynomial: " << plain_result.to_string() << endl;
+
+ /*
+ Decode to obtain an integer result.
+ */
+ cout << "Decoded integer: " << encoder.decode_int32(plain_result) << endl;
+}
+
+void example_bfv_basics_ii()
+{
+ print_example_banner("Example: BFV Basics II");
+
+ /*
+ In this example we explain what relinearization is, how to use it, and how
+ it affects noise budget consumption. Relinearization is used both in the BFV
+ and the CKKS schemes but in this example (for the sake of simplicity) we
+ again focus on BFV.
+
+ First we set the parameters, create a SEALContext, and generate the public
+ and secret keys. We use slightly larger parameters than before to be able to
+ do more homomorphic multiplications.
+ */
+ EncryptionParameters parms(scheme_type::BFV);
+ parms.set_poly_modulus_degree(8192);
+
+ /*
+ The default coefficient modulus consists of the following primes:
+
+ 0x7fffffff380001, 0x7ffffffef00001,
+ 0x3fffffff000001, 0x3ffffffef40001
+
+ The total size is 218 bits.
+ */
+ parms.set_coeff_modulus(coeff_modulus_128(8192));
+ parms.set_plain_modulus(1 << 10);
+
+ auto context = SEALContext::Create(parms);
+ print_parameters(context);
+
+ /*
+ We generate the public and secret keys as before.
+
+ There are actually two more types of keys in SEAL: `relinearization keys'
+ and `Galois keys'. In this example we will discuss relinearization keys, and
+ Galois keys will be discussed later in example_bfv_basics_iii().
+ */
+ KeyGenerator keygen(context);
+ auto public_key = keygen.public_key();
+ auto secret_key = keygen.secret_key();
+
+ /*
+ We also set up an Encryptor, Evaluator, and Decryptor here. We will
+ encrypt polynomials directly in this example, so there is no need for
+ an encoder.
+ */
+ Encryptor encryptor(context, public_key);
+ Evaluator evaluator(context);
+ Decryptor decryptor(context, secret_key);
+
+ /*
+ We can easily construct a plaintext polynomial from a string. Again, note
+ how there is no need for encoding since the BFV scheme natively encrypts
+ polynomials.
+ */
+ Plaintext plain1("1x^2 + 2x^1 + 3");
+ Ciphertext encrypted;
+ cout << "Encrypting " << plain1.to_string() << ": ";
+ encryptor.encrypt(plain1, encrypted);
+ cout << "Done" << endl;
+
+ /*
+ In SEAL, a valid ciphertext consists of two or more polynomials whose
+ coefficients are integers modulo the product of the primes in coeff_modulus.
+ The current size of a ciphertext can be found using Ciphertext::size().
+ A freshly encrypted ciphertext always has size 2.
+ */
+ cout << "Size of a fresh encryption: " << encrypted.size() << endl;
+ cout << "Noise budget in fresh encryption: "
+ << decryptor.invariant_noise_budget(encrypted) << " bits" << endl;
+
+ /*
+ Homomorphic multiplication results in the output ciphertext growing in size.
+ More precisely, if the input ciphertexts have size M and N, then the output
+ ciphertext after homomorphic multiplication will have size M+N-1. In this
+ case we square encrypted twice to observe this growth (also observe noise
+ budget consumption).
+ */
+ evaluator.square_inplace(encrypted);
+ cout << "Size after squaring: " << encrypted.size() << endl;
+ cout << "Noise budget after squaring: "
+ << decryptor.invariant_noise_budget(encrypted) << " bits" << endl;
+
+ evaluator.square_inplace(encrypted);
+ cout << "Size after second squaring: " << encrypted.size() << endl;
+ cout << "Noise budget after second squaring: "
+ << decryptor.invariant_noise_budget(encrypted) << " bits" << endl;
+
+ /*
+ It does not matter that the size has grown -- decryption works as usual.
+ Observe from the print-out that the coefficients in the plaintext have grown
+ quite large. One more squaring would cause some of them to wrap around the
+ plain_modulus (0x400) and as a result we would no longer obtain the expected
+ result as an integer-coefficient polynomial. We can fix this problem to some
+ extent by increasing plain_modulus. This makes sense since we still have
+ plenty of noise budget left.
+ */
+ Plaintext plain2;
+ decryptor.decrypt(encrypted, plain2);
+ cout << "Fourth power: " << plain2.to_string() << endl;
+ cout << endl;
+
+ /*
+ The problem here is that homomorphic operations on large ciphertexts are
+ computationally much more costly than on small ciphertexts. Specifically,
+ homomorphic multiplication on input ciphertexts of size M and N will require
+ O(M*N) polynomial multiplications to be performed, and an addition will
+ require O(M+N) additions. Relinearization reduces the size of ciphertexts
+ after multiplication back to the initial size (2). Thus, relinearizing one
+ or both inputs before the next multiplication or e.g. before serializing the
+ ciphertexts, can have a huge positive impact on performance.
+
+ Another problem is that the noise budget consumption in multiplication is
+ bigger when the input ciphertexts sizes are bigger. In a complicated
+ computation the contribution of the sizes to the noise budget consumption
+ can actually become the dominant term. We will point this out again below
+ once we get to our example.
+
+ Relinearization itself has both a computational cost and a noise budget cost.
+ These both depend on a parameter called `decomposition bit count', which can
+ be any integer at least 1 [dbc_min()] and at most 60 [dbc_max()]. A large
+ decomposition bit count makes relinearization fast, but consumes more noise
+ budget. A small decomposition bit count can make relinearization slower, but
+ might not change the noise budget by any observable amount.
+
+ Relinearization requires a special type of key called `relinearization keys'.
+ These can be created by the KeyGenerator for any decomposition bit count.
+ To relinearize a ciphertext of size M >= 2 back to size 2, we actually need
+ M-2 relinearization keys. Attempting to relinearize a too large ciphertext
+ with too few relinearization keys will result in an exception being thrown.
+
+ We repeat our computation, but this time relinearize after both squarings.
+ Since our ciphertext never grows past size 3 (we relinearize after every
+ multiplication), it suffices to generate only one relinearization key. This
+ (relinearizing after every multiplication) should be the preferred approach
+ in almost all cases.
+
+ First, we need to create relinearization keys. We use a decomposition bit
+ count of 16 here, which should be thought of as very small.
+
+ This function generates one single relinearization key. Another overload
+ of KeyGenerator::relin_keys takes the number of keys to be generated as an
+ argument, but one is all we need in this example (see above).
+ */
+ auto relin_keys16 = keygen.relin_keys(16);
+
+ cout << "Encrypting " << plain1.to_string() << ": ";
+ encryptor.encrypt(plain1, encrypted);
+ cout << "Done" << endl;
+ cout << "Size of a fresh encryption: " << encrypted.size() << endl;
+ cout << "Noise budget in fresh encryption: "
+ << decryptor.invariant_noise_budget(encrypted) << " bits" << endl;
+
+ evaluator.square_inplace(encrypted);
+ cout << "Size after squaring: " << encrypted.size() << endl;
+ cout << "Noise budget after squaring: "
+ << decryptor.invariant_noise_budget(encrypted) << " bits" << endl;
+
+ evaluator.relinearize_inplace(encrypted, relin_keys16);
+ cout << "Size after relinearization: " << encrypted.size() << endl;
+ cout << "Noise budget after relinearizing (dbc = "
+ << relin_keys16.decomposition_bit_count() << "): "
+ << decryptor.invariant_noise_budget(encrypted) << " bits" << endl;
+
+ evaluator.square_inplace(encrypted);
+ cout << "Size after second squaring: " << encrypted.size() << endl;
+ cout << "Noise budget after second squaring: "
+ << decryptor.invariant_noise_budget(encrypted) << " bits" << endl;
+
+ evaluator.relinearize_inplace(encrypted, relin_keys16);
+ cout << "Size after relinearization: " << encrypted.size() << endl;
+ cout << "Noise budget after relinearizing (dbc = "
+ << relin_keys16.decomposition_bit_count() << "): "
+ << decryptor.invariant_noise_budget(encrypted) << " bits" << endl;
+
+ decryptor.decrypt(encrypted, plain2);
+ cout << "Fourth power: " << plain2.to_string() << endl;
+ cout << endl;
+
+ /*
+ Of course the result is still the same, but this time we actually used less
+ of our noise budget. This is not surprising for two reasons:
+
+ - We used a very small decomposition bit count, which is why
+ relinearization itself did not consume the noise budget by any
+ observable amount;
+ - Since our ciphertext sizes remain small throughout the two
+ squarings, the noise budget consumption rate in multiplication
+ remains as small as possible. Recall from above that operations
+ on larger ciphertexts actually cause more noise growth.
+
+ To make things more clear, we repeat the computation a third time, now using
+ the largest possible decomposition bit count (60). We are not measuring
+ running time here, but relinearization with relin_keys60 (below) is much
+ faster than with relin_keys16.
+ */
+ auto relin_keys60 = keygen.relin_keys(dbc_max());
+
+ cout << "Encrypting " << plain1.to_string() << ": ";
+ encryptor.encrypt(plain1, encrypted);
+ cout << "Done" << endl;
+ cout << "Size of a fresh encryption: " << encrypted.size() << endl;
+ cout << "Noise budget in fresh encryption: "
+ << decryptor.invariant_noise_budget(encrypted) << " bits" << endl;
+
+ evaluator.square_inplace(encrypted);
+ cout << "Size after squaring: " << encrypted.size() << endl;
+ cout << "Noise budget after squaring: "
+ << decryptor.invariant_noise_budget(encrypted) << " bits" << endl;
+ evaluator.relinearize_inplace(encrypted, relin_keys60);
+ cout << "Size after relinearization: " << encrypted.size() << endl;
+ cout << "Noise budget after relinearizing (dbc = "
+ << relin_keys60.decomposition_bit_count() << "): "
+ << decryptor.invariant_noise_budget(encrypted) << " bits" << endl;
+
+ evaluator.square_inplace(encrypted);
+ cout << "Size after second squaring: " << encrypted.size() << endl;
+ cout << "Noise budget after second squaring: "
+ << decryptor.invariant_noise_budget(encrypted) << " bits" << endl;
+ evaluator.relinearize_inplace(encrypted, relin_keys60);
+ cout << "Size after relinearization: " << encrypted.size() << endl;
+ cout << "Noise budget after relinearizing (dbc = "
+ << relin_keys60.decomposition_bit_count() << "): "
+ << decryptor.invariant_noise_budget(encrypted) << " bits" << endl;
+
+ decryptor.decrypt(encrypted, plain2);
+ cout << "Fourth power: " << plain2.to_string() << endl;
+ cout << endl;
+
+ /*
+ Observe from the print-out that we have now used significantly more of our
+ noise budget than in the two previous runs. This is again not surprising,
+ since the first relinearization chops off a huge part of the noise budget.
+
+ However, note that the second relinearization does not change the noise
+ budget by any observable amount. This is very important to understand when
+ optimal performance is desired: relinearization always drops the noise
+ budget from the maximum (freshly encrypted ciphertext) down to a fixed
+ amount depending on the encryption parameters and the decomposition bit
+ count. On the other hand, homomorphic multiplication always consumes the
+ noise budget from its current level. This is why the second relinearization
+ does not change the noise budget anymore: it is already consumed past the
+ fixed amount determinted by the decomposition bit count and the encryption
+ parameters.
+
+ We now perform a third squaring and observe an even further compounded
+ decrease in the noise budget. Again, relinearization does not consume the
+ noise budget at this point by any observable amount, even with the largest
+ possible decomposition bit count.
+ */
+ evaluator.square_inplace(encrypted);
+ cout << "Size after third squaring: " << encrypted.size() << endl;
+ cout << "Noise budget after third squaring: "
+ << decryptor.invariant_noise_budget(encrypted) << " bits" << endl;
+
+ evaluator.relinearize_inplace(encrypted, relin_keys60);
+ cout << "Size after relinearization: " << encrypted.size() << endl;
+ cout << "Noise budget after relinearizing (dbc = "
+ << relin_keys60.decomposition_bit_count() << "): "
+ << decryptor.invariant_noise_budget(encrypted) << " bits" << endl;
+
+ decryptor.decrypt(encrypted, plain2);
+ cout << "Eighth power: " << plain2.to_string() << endl;
+
+ /*
+ Observe from the print-out that the polynomial coefficients are no longer
+ correct as integers: they have been reduced modulo plain_modulus, and there
+ was no warning sign about this. It might be necessary to carefully analyze
+ the computation to make sure such overflow does not occur unexpectedly.
+
+ These experiments suggest that an optimal strategy might be to relinearize
+ first with relinearization keys with a small decomposition bit count, and
+ later with relinearization keys with a larger decomposition bit count (for
+ performance) when noise budget has already been consumed past the bound
+ determined by the larger decomposition bit count. For example, the best
+ strategy might have been to use relin_keys16 in the first relinearization
+ and relin_keys60 in the next two relinearizations for optimal noise budget
+ consumption/performance trade-off. Luckily, in most use-cases it is not so
+ critical to squeeze out every last bit of performance, especially when
+ larger parameters are used.
+ */
+}
+
+void example_bfv_basics_iii()
+{
+ print_example_banner("Example: BFV Basics III");
+
+ /*
+ In this fundamental example we discuss and demonstrate a powerful technique
+ called `batching'. If N denotes the degree of the polynomial modulus, and T
+ the plaintext modulus, then batching is automatically enabled for the BFV
+ scheme when T is a prime number congruent to 1 modulo 2*N. In batching the
+ plaintexts are viewed as matrices of size 2-by-(N/2) with each element an
+ integer modulo T. Homomorphic operations act element-wise between encrypted
+ matrices, allowing the user to obtain speeds-ups of several orders of
+ magnitude in naively vectorizable computations. We demonstrate two more
+ homomorphic operations which act on encrypted matrices by rotating the rows
+ cyclically, or rotate the columns (i.e. swap the rows). These operations
+ require the construction of so-called `Galois keys', which are very similar
+ to relinearization keys.
+
+ The batching functionality is totally optional in the BFV scheme and is
+ exposed through the BatchEncoder class.
+ */
+ EncryptionParameters parms(scheme_type::BFV);
+
+ parms.set_poly_modulus_degree(4096);
+ parms.set_coeff_modulus(coeff_modulus_128(4096));
+
+ /*
+ Note that 40961 is a prime number and 2*4096 divides 40960, so batching will
+ automatically be enabled for these parameters.
+ */
+ parms.set_plain_modulus(40961);
+
+ auto context = SEALContext::Create(parms);
+ print_parameters(context);
+
+ /*
+ We can verify that batching is indeed enabled by looking at the encryption
+ parameter qualifiers created by SEALContext.
+ */
+ auto qualifiers = context->context_data()->qualifiers();
+ cout << "Batching enabled: " << boolalpha << qualifiers.using_batching << endl;
+
+ KeyGenerator keygen(context);
+ auto public_key = keygen.public_key();
+ auto secret_key = keygen.secret_key();
+
+ /*
+ We need to create so-called `Galois keys' for performing matrix row and
+ column rotations on encrypted matrices. Like relinearization keys, the
+ behavior of Galois keys depends on a decomposition bit count. The noise
+ budget consumption behavior of matrix row and column rotations is exactly
+ like that of relinearization (recall example_bfv_basics_ii()).
+
+ Here we use a moderate size decomposition bit count.
+ */
+ auto gal_keys = keygen.galois_keys(30);
+
+ /*
+ Since we are going to do some multiplications we will also relinearize.
+ */
+ auto relin_keys = keygen.relin_keys(30);
+
+ /*
+ We also set up an Encryptor, Evaluator, and Decryptor here.
+ */
+ Encryptor encryptor(context, public_key);
+ Evaluator evaluator(context);
+ Decryptor decryptor(context, secret_key);
+
+ /*
+ Batching is done through an instance of the BatchEncoder class so need to
+ construct one.
+ */
+ BatchEncoder batch_encoder(context);
+
+ /*
+ The total number of batching `slots' is poly_modulus_degree. The matrices
+ we encrypt are of size 2-by-(slot_count / 2).
+ */
+ size_t slot_count = batch_encoder.slot_count();
+ size_t row_size = slot_count / 2;
+ cout << "Plaintext matrix row size: " << row_size << endl;
+
+ /*
+ Printing the matrix is a bit of a pain.
+ */
+ auto print_matrix = [row_size](auto &matrix)
+ {
+ cout << endl;
+
+ /*
+ We're not going to print every column of the matrix (there are 2048). Instead
+ print this many slots from beginning and end of the matrix.
+ */
+ size_t print_size = 5;
+
+ cout << " [";
+ for (size_t i = 0; i < print_size; i++)
+ {
+ cout << setw(3) << matrix[i] << ",";
+ }
+ cout << setw(3) << " ...,";
+ for (size_t i = row_size - print_size; i < row_size; i++)
+ {
+ cout << setw(3) << matrix[i] << ((i != row_size - 1) ? "," : " ]\n");
+ }
+ cout << " [";
+ for (size_t i = row_size; i < row_size + print_size; i++)
+ {
+ cout << setw(3) << matrix[i] << ",";
+ }
+ cout << setw(3) << " ...,";
+ for (size_t i = 2 * row_size - print_size; i < 2 * row_size; i++)
+ {
+ cout << setw(3) << matrix[i] << ((i != 2 * row_size - 1) ? "," : " ]\n");
+ }
+ cout << endl;
+ };
+
+ /*
+ The matrix plaintext is simply given to BatchEncoder as a flattened vector
+ of numbers of size slot_count. The first row_size numbers form the first row,
+ and the rest form the second row. Here we create the following matrix:
+
+ [ 0, 1, 2, 3, 0, 0, ..., 0 ]
+ [ 4, 5, 6, 7, 0, 0, ..., 0 ]
+ */
+ vector pod_matrix(slot_count, 0ULL);
+ pod_matrix[0] = 0ULL;
+ pod_matrix[1] = 1ULL;
+ pod_matrix[2] = 2ULL;
+ pod_matrix[3] = 3ULL;
+ pod_matrix[row_size] = 4ULL;
+ pod_matrix[row_size + 1] = 5ULL;
+ pod_matrix[row_size + 2] = 6ULL;
+ pod_matrix[row_size + 3] = 7ULL;
+
+ cout << "Input plaintext matrix:" << endl;
+ print_matrix(pod_matrix);
+
+ /*
+ First we use BatchEncoder to compose the matrix into a plaintext.
+ */
+ Plaintext plain_matrix;
+ batch_encoder.encode(pod_matrix, plain_matrix);
+
+ /*
+ Next we encrypt the plaintext as usual.
+ */
+ Ciphertext encrypted_matrix;
+ cout << "Encrypting: ";
+ encryptor.encrypt(plain_matrix, encrypted_matrix);
+ cout << "Done" << endl;
+ cout << "Noise budget in fresh encryption: "
+ << decryptor.invariant_noise_budget(encrypted_matrix) << " bits" << endl;
+
+ /*
+ Operating on the ciphertext results in homomorphic operations being performed
+ simultaneously in all 4096 slots (matrix elements). To illustrate this, we
+ form another plaintext matrix
+
+ [ 1, 2, 1, 2, 1, 2, ..., 2 ]
+ [ 1, 2, 1, 2, 1, 2, ..., 2 ]
+
+ and compose it into a plaintext.
+ */
+ vector pod_matrix2;
+ for (size_t i = 0; i < slot_count; i++)
+ {
+ pod_matrix2.push_back((i % 2) + 1);
+ }
+ Plaintext plain_matrix2;
+ batch_encoder.encode(pod_matrix2, plain_matrix2);
+ cout << "Second input plaintext matrix:" << endl;
+ print_matrix(pod_matrix2);
+
+ /*
+ We now add the second (plaintext) matrix to the encrypted one using another
+ new operation -- plain addition -- and square the sum.
+ */
+ cout << "Adding and squaring: ";
+ evaluator.add_plain_inplace(encrypted_matrix, plain_matrix2);
+ evaluator.square_inplace(encrypted_matrix);
+ evaluator.relinearize_inplace(encrypted_matrix, relin_keys);
+ cout << "Done" << endl;
+
+ /*
+ How much noise budget do we have left?
+ */
+ cout << "Noise budget in result: "
+ << decryptor.invariant_noise_budget(encrypted_matrix) << " bits" << endl;
+
+ /*
+ We decrypt and decompose the plaintext to recover the result as a matrix.
+ */
+ Plaintext plain_result;
+ cout << "Decrypting result: ";
+ decryptor.decrypt(encrypted_matrix, plain_result);
+ cout << "Done" << endl;
+
+ vector pod_result;
+ batch_encoder.decode(plain_result, pod_result);
+
+ cout << "Result plaintext matrix:" << endl;
+ print_matrix(pod_result);
+
+ /*
+ Note how the operation was performed in one go for each of the elements of
+ the matrix. It is possible to achieve incredible performance improvements by
+ using this method when the computation is easily vectorizable.
+
+ Our discussion so far could have applied just as well for a simple vector
+ data type (not matrix). Now we show how the matrix view of the plaintext can
+ be used for more functionality. Namely, it is possible to rotate the matrix
+ rows cyclically, and same for the columns (i.e. swap the two rows). For this
+ we need the Galois keys that we generated earlier.
+
+ We return to the original matrix that we started with.
+ */
+ encryptor.encrypt(plain_matrix, encrypted_matrix);
+ cout << "Unrotated matrix: " << endl;
+ print_matrix(pod_matrix);
+ cout << "Noise budget in fresh encryption: "
+ << decryptor.invariant_noise_budget(encrypted_matrix) << " bits" << endl;
+
+ /*
+ Now rotate the rows to the left 3 steps, decrypt, decompose, and print.
+ */
+ evaluator.rotate_rows_inplace(encrypted_matrix, 3, gal_keys);
+ cout << "Rotated rows 3 steps left: " << endl;
+ decryptor.decrypt(encrypted_matrix, plain_result);
+ batch_encoder.decode(plain_result, pod_result);
+ print_matrix(pod_result);
+ cout << "Noise budget after rotation: "
+ << decryptor.invariant_noise_budget(encrypted_matrix) << " bits" << endl;
+
+ /*
+ Rotate columns (swap rows), decrypt, decompose, and print.
+ */
+ evaluator.rotate_columns_inplace(encrypted_matrix, gal_keys);
+ cout << "Rotated columns: " << endl;
+ decryptor.decrypt(encrypted_matrix, plain_result);
+ batch_encoder.decode(plain_result, pod_result);
+ print_matrix(pod_result);
+ cout << "Noise budget after rotation: "
+ << decryptor.invariant_noise_budget(encrypted_matrix) << " bits" << endl;
+
+ /*
+ Rotate rows to the right 4 steps, decrypt, decompose, and print.
+ */
+ evaluator.rotate_rows_inplace(encrypted_matrix, -4, gal_keys);
+ cout << "Rotated rows 4 steps right: " << endl;
+ decryptor.decrypt(encrypted_matrix, plain_result);
+ batch_encoder.decode(plain_result, pod_result);
+ print_matrix(pod_result);
+ cout << "Noise budget after rotation: "
+ << decryptor.invariant_noise_budget(encrypted_matrix) << " bits" << endl;
+
+ /*
+ The output is as expected. Note how the noise budget gets a big hit in the
+ first rotation, but remains almost unchanged in the next rotations. This is
+ again the same phenomenon that occurs with relinearization, where the noise
+ budget is consumed down to some bound determined by the decomposition bit
+ count and the encryption parameters. For example, after some multiplications
+ have been performed rotations come basically for free (noise budget-wise),
+ whereas they can be relatively expensive when the noise budget is nearly
+ full unless a small decomposition bit count is used, which on the other hand
+ is computationally costly.
+ */
+}
+
+void example_bfv_basics_iv()
+{
+ print_example_banner("Example: BFV Basics IV");
+
+ /*
+ In this example we describe the concept of `parms_id' in the context of the
+ BFV scheme and show how modulus switching can be used for improving both
+ computation and communication cost.
+
+ We start by setting up medium size parameters for BFV as usual.
+ */
+ EncryptionParameters parms(scheme_type::BFV);
+
+ parms.set_poly_modulus_degree(8192);
+ parms.set_coeff_modulus(coeff_modulus_128(8192));
+ parms.set_plain_modulus(1 << 20);
+
+ /*
+ In SEAL a particular set of encryption parameters (excluding the random
+ number generator) is identified uniquely by a SHA-3 hash of the parameters.
+ This hash is called the `parms_id' and can be easily accessed and printed
+ at any time. The hash will change as soon as any of the relevant parameters
+ is changed.
+ */
+ cout << "Current parms_id: " << parms.parms_id() << endl;
+ cout << "Changing plain_modulus ..." << endl;
+ parms.set_plain_modulus((1 << 20) + 1);
+ cout << "Current parms_id: " << parms.parms_id() << endl << endl;
+
+ /*
+ Create the context.
+ */
+ auto context = SEALContext::Create(parms);
+ print_parameters(context);
+
+ /*
+ All keys and ciphertext, and in the CKKS also plaintexts, carry the parms_id
+ for the encryption parameters they are created with, allowing SEAL to very
+ quickly determine whether the objects are valid for use and compatible for
+ homomorphic computations. SEAL takes care of managing, and verifying the
+ parms_id for all objects so the user should have no reason to change it by
+ hand.
+ */
+ KeyGenerator keygen(context);
+ auto public_key = keygen.public_key();
+ auto secret_key = keygen.secret_key();
+ cout << "parms_id of public_key: " << public_key.parms_id() << endl;
+ cout << "parms_id of secret_key: " << secret_key.parms_id() << endl;
+
+ Encryptor encryptor(context, public_key);
+ Evaluator evaluator(context);
+ Decryptor decryptor(context, secret_key);
+
+ /*
+ Note how in the BFV scheme plaintexts do not carry the parms_id, but
+ ciphertexts do.
+ */
+ Plaintext plain("1x^3 + 2x^2 + 3x^1 + 4");
+ Ciphertext encrypted;
+ encryptor.encrypt(plain, encrypted);
+ cout << "parms_id of plain: " << plain.parms_id() << " (not set)" << endl;
+ cout << "parms_id of encrypted: " << encrypted.parms_id() << endl << endl;
+
+ /*
+ When SEALContext is created from a given EncryptionParameters instance,
+ SEAL automatically creates a so-called "modulus switching chain", which is
+ a chain of other encryption parameters derived from the original set.
+ The parameters in the modulus switching chain are the same as the original
+ parameters with the exception that size of the coefficient modulus is
+ decreasing going down the chain. More precisely, each parameter set in the
+ chain attempts to remove one of the coefficient modulus primes from the
+ previous set; this continues until the parameter set is no longer valid
+ (e.g. plain_modulus is larger than the remaining coeff_modulus). It is easy
+ to walk through the chain and access all the parameter sets. Additionally,
+ each parameter set in the chain has a `chain_index' that indicates its
+ position in the chain so that the last set has index 0. We say that a set
+ of encryption parameters, or an object carrying those encryption parameters,
+ is at a higher level in the chain than another set of parameters if its the
+ chain index is bigger, i.e. it is earlier in the chain.
+ */
+ for(auto context_data = context->context_data(); context_data;
+ context_data = context_data->next_context_data())
+ {
+ cout << "Chain index: " << context_data->chain_index() << endl;
+ cout << "parms_id: " << context_data->parms().parms_id() << endl;
+ cout << "coeff_modulus primes: ";
+ cout << hex;
+ for(const auto &prime : context_data->parms().coeff_modulus())
+ {
+ cout << prime.value() << " ";
+ }
+ cout << dec << endl;
+ cout << "\\" << endl;
+ cout << " \\-->" << endl;
+ }
+ cout << "End of chain reached" << endl << endl;
+
+ /*
+ Modulus switching changes the ciphertext parameters to any set down the
+ chain from the current one. The function mod_switch_to_next(...) always
+ switches to the next set down the chain, whereas mod_switch_to(...) switches
+ to a parameter set down the chain corresponding to a given parms_id.
+ */
+ auto context_data = context->context_data();
+ while(context_data->next_context_data())
+ {
+ cout << "Chain index: " << context_data->chain_index() << endl;
+ cout << "parms_id of encrypted: " << encrypted.parms_id() << endl;
+ cout << "Noise budget at this level: "
+ << decryptor.invariant_noise_budget(encrypted) << " bits" << endl;
+ cout << "\\" << endl;
+ cout << " \\-->" << endl;
+ evaluator.mod_switch_to_next_inplace(encrypted);
+ context_data = context_data->next_context_data();
+ }
+ cout << "Chain index: " << context_data->chain_index() << endl;
+ cout << "parms_id of encrypted: " << encrypted.parms_id() << endl;
+ cout << "Noise budget at this level: "
+ << decryptor.invariant_noise_budget(encrypted) << " bits" << endl;
+ cout << "\\" << endl;
+ cout << " \\-->" << endl;
+ cout << "End of chain reached" << endl << endl;
+
+ /*
+ At this point it is hard to see any benefit in doing this: we lost a huge
+ amount of noise budget (i.e. computational power) at each switch and seemed
+ to get nothing in return. The ciphertext still decrypts to the exact same
+ value.
+ */
+ decryptor.decrypt(encrypted, plain);
+ cout << "Decryption: " << plain.to_string() << endl << endl;
+
+ /*
+ However, there is a hidden benefit: the size of the ciphertext depends
+ linearly on the number of primes in the coefficient modulus. Thus, if there
+ is no need or intention to perform any more computations on a given
+ ciphertext, we might as well switch it down to the smallest (last) set of
+ parameters in the chain before sending it back to the secret key holder for
+ decryption.
+
+ Also the lost noise budget is actually not as issue at all, if we do things
+ right, as we will see below. First we recreate the original ciphertext (with
+ largest parameters) and perform some simple computations on it.
+ */
+ encryptor.encrypt(plain, encrypted);
+ auto relin_keys = keygen.relin_keys(60);
+ cout << "Noise budget before squaring: "
+ << decryptor.invariant_noise_budget(encrypted) << " bits" << endl;
+ evaluator.square_inplace(encrypted);
+ evaluator.relinearize_inplace(encrypted, relin_keys);
+ cout << "Noise budget after squaring: "
+ << decryptor.invariant_noise_budget(encrypted) << " bits" << endl;
+
+ /*
+ From the print-out we see that the noise budget after these computations is
+ just slightly below the level we would have in a fresh ciphertext after one
+ modulus switch (135 bits). Surprisingly, in this case modulus switching has
+ no effect at all on the modulus.
+ */
+ evaluator.mod_switch_to_next_inplace(encrypted);
+ cout << "Noise budget after modulus switching: "
+ << decryptor.invariant_noise_budget(encrypted) << " bits" << endl;
+
+ /*
+ This means that there is no harm at all in dropping some of the coefficient
+ modulus after doing enough computations. In some cases one might want to
+ switch to a lower level slightly earlier, actually sacrificing some of the
+ noise budget in the process, to gain computational performance from having
+ a smaller coefficient modulus. We see from the print-out that that the next
+ modulus switch should be done ideally when the noise budget reaches 81 bits.
+ */
+ evaluator.square_inplace(encrypted);
+ evaluator.relinearize_inplace(encrypted, relin_keys);
+ cout << "Noise budget after squaring: "
+ << decryptor.invariant_noise_budget(encrypted) << " bits" << endl;
+ evaluator.mod_switch_to_next_inplace(encrypted);
+ cout << "Noise budget after modulus switching: "
+ << decryptor.invariant_noise_budget(encrypted) << " bits" << endl;
+ evaluator.square_inplace(encrypted);
+ evaluator.relinearize_inplace(encrypted, relin_keys);
+ cout << "Noise budget after squaring: "
+ << decryptor.invariant_noise_budget(encrypted) << " bits" << endl;
+ evaluator.mod_switch_to_next_inplace(encrypted);
+ cout << "Noise budget after modulus switching: "
+ << decryptor.invariant_noise_budget(encrypted) << " bits" << endl << endl;
+
+ /*
+ At this point the ciphertext still decrypts correctly, has very small size,
+ and the computation was as efficient as possible. Note that the decryptor
+ can be used to decrypt a ciphertext at any level in the modulus switching
+ chain as long as the secret key is at a higher level in the same chain.
+ */
+ decryptor.decrypt(encrypted, plain);
+ cout << "Decryption of eighth power: " << plain.to_string() << endl << endl;
+
+ /*
+ In BFV modulus switching is not necessary and in some cases the user might
+ not want to create the modulus switching chain. This can be done by passing
+ a bool `false' to the SEALContext::Create(...) function as follows.
+ */
+ context = SEALContext::Create(parms, false);
+
+ /*
+ We can check that indeed the modulus switching chain has not been created.
+ The following loop should execute only once.
+ */
+ for (context_data = context->context_data(); context_data;
+ context_data = context_data->next_context_data())
+ {
+ cout << "Chain index: " << context_data->chain_index() << endl;
+ cout << "parms_id: " << context_data->parms().parms_id() << endl;
+ cout << "coeff_modulus primes: ";
+ cout << hex;
+ for (const auto &prime : context_data->parms().coeff_modulus())
+ {
+ cout << prime.value() << " ";
+ }
+ cout << dec << endl;
+ cout << "\\" << endl;
+ cout << " \\-->" << endl;
+ }
+ cout << "End of chain reached" << endl << endl;
+
+ /*
+ It is very important to understand how this example works since in the CKKS
+ scheme modulus switching has a much more fundamental purpose and the next
+ examples will be difficult to understand unless these basic properties are
+ totally clear.
+ */
+}
+
+void example_ckks_basics_i()
+{
+ print_example_banner("Example: CKKS Basics I");
+
+ /*
+ In this example we demonstrate using the Cheon-Kim-Kim-Song (CKKS) scheme
+ for encrypting and computing on floating point numbers. For full details on
+ the CKKS scheme, we refer the reader to https://eprint.iacr.org/2016/421.
+ For better performance, SEAL implements the "FullRNS" optimization for CKKS
+ described in https://eprint.iacr.org/2018/931.
+ */
+
+ /*
+ We start by creating encryption parameters for the CKKS scheme. One major
+ difference to the BFV scheme is that the CKKS scheme does not use the
+ plain_modulus parameter.
+ */
+ EncryptionParameters parms(scheme_type::CKKS);
+ parms.set_poly_modulus_degree(8192);
+ parms.set_coeff_modulus(coeff_modulus_128(8192));
+
+ /*
+ We create the SEALContext as usual and print the parameters.
+ */
+ auto context = SEALContext::Create(parms);
+ print_parameters(context);
+
+ /*
+ Keys are created the same way as for the BFV scheme.
+ */
+ KeyGenerator keygen(context);
+ auto public_key = keygen.public_key();
+ auto secret_key = keygen.secret_key();
+ auto relin_keys = keygen.relin_keys(60);
+
+ /*
+ We also set up an Encryptor, Evaluator, and Decryptor as usual.
+ */
+ Encryptor encryptor(context, public_key);
+ Evaluator evaluator(context);
+ Decryptor decryptor(context, secret_key);
+
+ /*
+ To create CKKS plaintexts we need a special encoder: we cannot create them
+ directly from polynomials. Note that the IntegerEncoder, FractionalEncoder,
+ and BatchEncoder cannot be used with the CKKS scheme. The CKKS scheme allows
+ encryption and approximate computation on vectors of real or complex numbers
+ which the CKKSEncoder converts into Plaintext objects. At a high level this
+ looks a lot like BatchEncoder for the BFV scheme, but the theory behind it
+ is different.
+ */
+ CKKSEncoder encoder(context);
+
+ /*
+ In CKKS the number of slots is poly_modulus_degree / 2 and each slot encodes
+ one complex (or real) number. This should be contrasted with BatchEncoder in
+ the BFV scheme, where the number of slots is equal to poly_modulus_degree
+ and they are arranged into a 2-by-(poly_modulus_degree / 2) matrix.
+ */
+ size_t slot_count = encoder.slot_count();
+ cout << "Number of slots: " << slot_count << endl;
+
+ /*
+ We create a small vector to encode; the CKKSEncoder will implicitly pad it
+ with zeros to full size (poly_modulus_degree / 2) when encoding.
+ */
+ vector input{ 0.0, 1.1, 2.2, 3.3 };
+ cout << "Input vector: " << endl;
+ print_vector(input);
+
+ /*
+ Now we encode it with CKKSEncoder. The floating-point coefficients of input
+ will be scaled up by the parameter `scale'; this is necessary since even in
+ the CKKS scheme the plaintexts are polynomials with integer coefficients.
+ It is instructive to think of the scale as determining the bit-precision of
+ the encoding; naturally it will also affect the precision of the result.
+
+ In CKKS the message is stored modulo coeff_modulus (in BFV it is stored
+ modulo plain_modulus), so the scale must not get too close to the total size
+ of coeff_modulus. In this case our coeff_modulus is quite large (218 bits)
+ so we have little to worry about in this regard. For this example a 60-bit
+ scale is more than enough.
+ */
+ Plaintext plain;
+ double scale = pow(2.0, 60);
+ encoder.encode(input, scale, plain);
+
+ /*
+ The vector is encrypted the same was as in BFV.
+ */
+ Ciphertext encrypted;
+ encryptor.encrypt(plain, encrypted);
+
+ /*
+ Another difference to the BFV scheme is that in CKKS also plaintexts are
+ linked to specific parameter sets: they carry the corresponding parms_id.
+ An overload of CKKSEncoder::encode(...) allows the caller to specify which
+ parameter set in the modulus switching chain (identified by parms_id) should
+ be used to encode the plaintext. This is important as we will see later.
+ */
+ cout << "parms_id of plain: " << plain.parms_id() << endl;
+ cout << "parms_id of encrypted: " << encrypted.parms_id() << endl << endl;
+
+ /*
+ The ciphertexts will keep track of the scales in the underlying plaintexts.
+ The current scale in every plaintext and ciphertext is easy to access.
+ */
+ cout << "Scale in plain: " << plain.scale() << endl;
+ cout << "Scale in encrypted: " << encrypted.scale() << endl << endl;
+
+ /*
+ Basic operations on the ciphertexts are still easy to do. Here we square
+ the ciphertext, decrypt, decode, and print the result. We note also that
+ decoding returns a vector of full size (poly_modulus_degree / 2); this is
+ because of the implicit zero-padding mentioned above.
+ */
+ evaluator.square_inplace(encrypted);
+ evaluator.relinearize_inplace(encrypted, relin_keys);
+ decryptor.decrypt(encrypted, plain);
+ encoder.decode(plain, input);
+ cout << "Squared input: " << endl;
+ print_vector(input);
+
+ /*
+ We notice that the results are correct. We can also print the scale in the
+ result and observe that it has increased. In fact, it is now the square of
+ the original scale (2^60).
+ */
+ cout << "Scale in the square: " << encrypted.scale()
+ << " (" << log2(encrypted.scale()) << " bits)" << endl;
+
+ /*
+ CKKS supports modulus switching just like the BFV scheme. We can switch
+ away parts of the coefficient modulus.
+ */
+ cout << "Current coeff_modulus size: "
+ << context->context_data(encrypted.parms_id())->
+ total_coeff_modulus_bit_count() << " bits" << endl;
+
+ cout << "Modulus switching ..." << endl;
+ evaluator.mod_switch_to_next_inplace(encrypted);
+
+ cout << "Current coeff_modulus size: "
+ << context->context_data(encrypted.parms_id())->
+ total_coeff_modulus_bit_count() << " bits" << endl;
+ cout << endl;
+
+ /*
+ At this point if we tried switching further SEAL would throw an exception.
+ This is because the scale is 120 bits and after modulus switching we would
+ be down to a total coeff_modulus smaller than that, which is not enough to
+ contain the plaintext. We decrypt and decode, and observe that the result
+ is the same as before.
+ */
+ decryptor.decrypt(encrypted, plain);
+ encoder.decode(plain, input);
+ cout << "Squared input: " << endl;
+ print_vector(input);
+
+ /*
+ In some cases it can be convenient to change the scale of a ciphertext by
+ hand. For example, multiplying the scale by a number effectively divides the
+ underlying plaintext by that number, and vice versa. The caveat is that the
+ resulting scale can be incompatible with the scales of other ciphertexts.
+ Here we divide the ciphertext by 3.
+ */
+ encrypted.scale() *= 3;
+ decryptor.decrypt(encrypted, plain);
+ encoder.decode(plain, input);
+ cout << "Divided by 3: " << endl;
+ print_vector(input);
+
+ /*
+ Homomorphic addition and subtraction naturally require that the scales of
+ the inputs are the same, but also that the encryption parameters (parms_id)
+ are the same. Here we add a plaintext to encrypted. Note that a scale or
+ parms_id mismatch would make Evaluator::add_plain(..) throw an exception;
+ there is no problem here since we encode the plaintext just-in-time with
+ exactly the right scale.
+ */
+ vector vec_summand{ 20.2, 30.3, 40.4, 50.5 };
+ cout << "Plaintext summand: " << endl;
+ print_vector(vec_summand);
+
+ /*
+ Get the parms_id and scale from encrypted and do the addition.
+ */
+ Plaintext plain_summand;
+ encoder.encode(vec_summand, encrypted.parms_id(), encrypted.scale(),
+ plain_summand);
+ evaluator.add_plain_inplace(encrypted, plain_summand);
+
+ /*
+ Decryption and decoding should give the correct result.
+ */
+ decryptor.decrypt(encrypted, plain);
+ encoder.decode(plain, input);
+ cout << "Sum: " << endl;
+ print_vector(input);
+
+ /*
+ Note that we have not mentioned noise budget at all. In fact, CKKS does not
+ have a similar concept of a noise budget as BFV; instead, the homomorphic
+ encryption noise will overlap the low-order bits of the message. This is why
+ scaling is needed: the message must be moved to higher-order bits to protect
+ it from the noise. Still, it is difficult to completely decouple the noise
+ from the message itself; hence the noise/error budget cannot be exactly
+ measured from a ciphertext alone.
+ */
+}
+
+void example_ckks_basics_ii()
+{
+ print_example_banner("Example: CKKS Basics II");
+
+ /*
+ The previous example did not really make it clear why CKKS is useful at all.
+ Certainly one can scale floating-point numbers to integers, encrypt them,
+ keep track of the scale, and operate on them by just using BFV. The problem
+ with this approach is that the scale quickly grows larger than the size of
+ the coefficient modulus, preventing further computations. The true power of
+ CKKS is that it allows the scale to be switched down (`rescaling') without
+ changing the encrypted values.
+
+ To demonstrate this, we start by setting up the same environment we had in
+ the previous example.
+ */
+ EncryptionParameters parms(scheme_type::CKKS);
+ parms.set_poly_modulus_degree(8192);
+ parms.set_coeff_modulus(coeff_modulus_128(8192));
+
+ auto context = SEALContext::Create(parms);
+ print_parameters(context);
+
+ KeyGenerator keygen(context);
+ auto public_key = keygen.public_key();
+ auto secret_key = keygen.secret_key();
+ auto relin_keys = keygen.relin_keys(60);
+
+ Encryptor encryptor(context, public_key);
+ Evaluator evaluator(context);
+ Decryptor decryptor(context, secret_key);
+
+ CKKSEncoder encoder(context);
+
+ size_t slot_count = encoder.slot_count();
+ cout << "Number of slots: " << slot_count << endl;
+
+ vector input{ 0.0, 1.1, 2.2, 3.3 };
+ cout << "Input vector: " << endl;
+ print_vector(input);
+
+ /*
+ We use a slightly smaller scale in this example.
+ */
+ Plaintext plain;
+ double scale = pow(2.0, 60);
+ encoder.encode(input, scale, plain);
+
+ Ciphertext encrypted;
+ encryptor.encrypt(plain, encrypted);
+
+ /*
+ Print the scale and the parms_id for encrypted.
+ */
+ cout << "Chain index of (encryption parameters of) encrypted: "
+ << context->context_data(encrypted.parms_id())->chain_index() << endl;
+ cout << "Scale in encrypted before squaring: " << encrypted.scale() << endl;
+
+ /*
+ We did this already in the previous example: square encrypted and observe
+ the scale growth.
+ */
+ evaluator.square_inplace(encrypted);
+ evaluator.relinearize_inplace(encrypted, relin_keys);
+ cout << "Scale in encrypted after squaring: " << encrypted.scale()
+ << " (" << log2(encrypted.scale()) << " bits)" << endl;
+ cout << "Current coeff_modulus size: "
+ << context->context_data(encrypted.parms_id())->
+ total_coeff_modulus_bit_count() << " bits" << endl;
+ cout << endl;
+
+ /*
+ Now, to prevent the scale from growing too large in subsequent operations,
+ we apply rescaling.
+ */
+ cout << "Rescaling ..." << endl << endl;
+ evaluator.rescale_to_next_inplace(encrypted);
+
+ /*
+ Rescaling changes the coefficient modulus as modulus switching does. These
+ operations are in fact very closely related. Moreover, the scale indeed has
+ been significantly reduced: rescaling divides the scale by the coefficient
+ modulus prime that was switched away. Since our coefficient modulus in this
+ case consisted of the primes (see seal/utils/global.cpp)
+
+ 0x7fffffff380001, 0x7ffffffef00001,
+ 0x3fffffff000001, 0x3ffffffef40001,
+
+ the last of which is 54 bits, the bit-size of the scale was reduced by
+ precisely 54 bits. Finer granularity rescaling would require smaller primes
+ to be used, but this might lead to performance problems as the computational
+ cost of homomorphic operations and the size of ciphertexts depends linearly
+ on the number of primes in coeff_modulus.
+ */
+ cout << "Chain index of (encryption parameters of) encrypted: "
+ << context->context_data(encrypted.parms_id())->chain_index() << endl;
+ cout << "Scale in encrypted: " << encrypted.scale()
+ << " (" << log2(encrypted.scale()) << " bits)" << endl;
+ cout << "Current coeff_modulus size: "
+ << context->context_data(encrypted.parms_id())->
+ total_coeff_modulus_bit_count() << " bits" << endl;
+ cout << endl;
+
+ /*
+ We can even compute the fourth power of the input. Note that it is very
+ important to first relinearize and then rescale. Trying to do these two
+ operations in the opposite order will make SEAL throw and exception.
+ */
+ cout << "Squaring and rescaling ..." << endl << endl;
+ evaluator.square_inplace(encrypted);
+ evaluator.relinearize_inplace(encrypted, relin_keys);
+ evaluator.rescale_to_next_inplace(encrypted);
+
+ cout << "Chain index of (encryption parameters of) encrypted: "
+ << context->context_data(encrypted.parms_id())->chain_index() << endl;
+ cout << "Scale in encrypted: " << encrypted.scale()
+ << " (" << log2(encrypted.scale()) << " bits)" << endl;
+ cout << "Current coeff_modulus size: "
+ << context->context_data(encrypted.parms_id())->
+ total_coeff_modulus_bit_count() << " bits" << endl;
+ cout << endl;
+
+ /*
+ At this point our scale is 78 bits and the coefficient modulus is 110 bits.
+ This means that we cannot square the result anymore, but if we rescale once
+ more and then square, things should work out better. We cannot relinearize
+ with relin_keys at this point due to the large decomposition bit count we
+ used: the noise from relinearization would completely destroy our result
+ due to the small scale we are at.
+ */
+ cout << "Rescaling and squaring (no relinearization) ..." << endl << endl;
+ evaluator.rescale_to_next_inplace(encrypted);
+ evaluator.square_inplace(encrypted);
+
+ cout << "Chain index of (encryption parameters of) encrypted: "
+ << context->context_data(encrypted.parms_id())->chain_index() << endl;
+ cout << "Scale in encrypted: " << encrypted.scale()
+ << " (" << log2(encrypted.scale()) << " bits)" << endl;
+ cout << "Current coeff_modulus size: "
+ << context->context_data(encrypted.parms_id())->
+ total_coeff_modulus_bit_count() << " bits" << endl;
+ cout << endl;
+
+ /*
+ We decrypt, decode, and print the results.
+ */
+ decryptor.decrypt(encrypted, plain);
+ vector result;
+ encoder.decode(plain, result);
+ cout << "Eighth powers: " << endl;
+ print_vector(result);
+
+ /*
+ We have gone pretty low in the scale at this point and can no longer expect
+ to get entirely accurate results. Still, our results are quite accurate.
+ */
+ vector precise_result{};
+ transform(input.begin(), input.end(), back_inserter(precise_result),
+ [](auto in) { return pow(in, 8); });
+ cout << "Precise result: " << endl;
+ print_vector(precise_result);
+}
+
+void example_ckks_basics_iii()
+{
+ print_example_banner("Example: CKKS Basics III");
+
+ /*
+ In this example we demonstrate evaluating a polynomial function on
+ floating-point input data. The challenges we encounter will be related to
+ matching scales and encryption parameters when adding together terms of
+ different degrees in the polynomial evaluation. We start by setting up an
+ environment similar to what we had in the above examples.
+ */
+ EncryptionParameters parms(scheme_type::CKKS);
+ parms.set_poly_modulus_degree(8192);
+
+ /*
+ In this example we decide to use four 40-bit moduli for more flexible
+ rescaling. Note that 4*40 bits = 160 bits, which is well below the size of
+ the default coefficient modulus (see seal/util/globals.cpp). It is always
+ more secure to use a smaller coefficient modulus while keeping the degree of
+ the polynomial modulus fixed. Since the coeff_mod_128(8192) default 218-bit
+ coefficient modulus achieves already a 128-bit security level, this 160-bit
+ modulus must be much more secure.
+
+ We use the small_mods_40bit(int) function to get primes from a hard-coded
+ list of 40-bit prime numbers; it is important that all primes used for the
+ coefficient modulus are distinct.
+ */
+ parms.set_coeff_modulus({
+ small_mods_40bit(0), small_mods_40bit(1),
+ small_mods_40bit(2), small_mods_40bit(3) });
+
+ auto context = SEALContext::Create(parms);
+ print_parameters(context);
+
+ KeyGenerator keygen(context);
+ auto public_key = keygen.public_key();
+ auto secret_key = keygen.secret_key();
+ auto relin_keys = keygen.relin_keys(60);
+
+ Encryptor encryptor(context, public_key);
+ Evaluator evaluator(context);
+ Decryptor decryptor(context, secret_key);
+
+ CKKSEncoder encoder(context);
+ size_t slot_count = encoder.slot_count();
+ cout << "Number of slots: " << slot_count << endl;
+
+ /*
+ In this example our goal is to evaluate the polynomial PI*x^3 + 0.4x + 1 on
+ an encrypted input x for 4096 equidistant points x in the interval [0, 1].
+ */
+ vector input;
+ input.reserve(slot_count);
+ double curr_point = 0, step_size = 1.0 / (static_cast(slot_count) - 1);
+ for (size_t i = 0; i < slot_count; i++, curr_point += step_size)
+ {
+ input.push_back(curr_point);
+ }
+ cout << "Input vector: " << endl;
+ print_vector(input, 3, 7);
+ cout << "Evaluating polynomial PI*x^3 + 0.4x + 1 ..." << endl << endl;
+
+ /*
+ Now encode and encrypt the input using the last of the coeff_modulus primes
+ as the scale for a reason that will become clear soon.
+ */
+ auto scale = static_cast(parms.coeff_modulus().back().value());
+ Plaintext plain_x;
+ encoder.encode(input, scale, plain_x);
+ Ciphertext encrypted_x1;
+ encryptor.encrypt(plain_x, encrypted_x1);
+
+ /*
+ We create plaintext elements for PI, 0.4, and 1, using an overload of
+ CKKSEncoder::encode(...) that encodes the given floating-point value to
+ every slot in the vector.
+ */
+ Plaintext plain_coeff3, plain_coeff1, plain_coeff0;
+ encoder.encode(3.14159265, scale, plain_coeff3);
+ encoder.encode(0.4, scale, plain_coeff1);
+ encoder.encode(1.0, scale, plain_coeff0);
+
+ /*
+ To compute x^3 we first compute x^2, relinearize, and rescale.
+ */
+ Ciphertext encrypted_x3;
+ evaluator.square(encrypted_x1, encrypted_x3);
+ evaluator.relinearize_inplace(encrypted_x3, relin_keys);
+ evaluator.rescale_to_next_inplace(encrypted_x3);
+
+ /*
+ Now encrypted_x3 is at different encryption parameters than encrypted_x1,
+ preventing us from multiplying them together to compute x^3. We could simply
+ switch encrypted_x1 down to the next parameters in the modulus switching
+ chain. Since we still need to multiply the x^3 term with PI (plain_coeff3),
+ we instead compute PI*x first and multiply that with x^2 to obtain PI*x^3.
+ This product poses no problems since both inputs are at the same scale and
+ use the same encryption parameters. We rescale afterwards to change the
+ scale back to 40 bits, which will also drop the coefficient modulus down to
+ 120 bits.
+ */
+ Ciphertext encrypted_x1_coeff3;
+ evaluator.multiply_plain(encrypted_x1, plain_coeff3, encrypted_x1_coeff3);
+ evaluator.rescale_to_next_inplace(encrypted_x1_coeff3);
+
+ /*
+ Since both encrypted_x3 and encrypted_x1_coeff3 now have the same scale and
+ use same encryption parameters, we can multiply them together. We write the
+ result to encrypted_x3.
+ */
+ evaluator.multiply_inplace(encrypted_x3, encrypted_x1_coeff3);
+ evaluator.relinearize_inplace(encrypted_x3, relin_keys);
+ evaluator.rescale_to_next_inplace(encrypted_x3);
+
+ /*
+ Next we compute the degree one term. All this requires is one multiply_plain
+ with plain_coeff1. We overwrite encrypted_x1 with the result.
+ */
+ evaluator.multiply_plain_inplace(encrypted_x1, plain_coeff1);
+ evaluator.rescale_to_next_inplace(encrypted_x1);
+
+ /*
+ Now we would hope to compute the sum of all three terms. However, there is
+ a serious problem: the encryption parameters used by all three terms are
+ different due to modulus switching from rescaling.
+ */
+ cout << "Parameters used by all three terms are different:" << endl;
+ cout << "Modulus chain index for encrypted_x3: "
+ << context->context_data(encrypted_x3.parms_id())->chain_index() << endl;
+ cout << "Modulus chain index for encrypted_x1: "
+ << context->context_data(encrypted_x1.parms_id())->chain_index() << endl;
+ cout << "Modulus chain index for plain_coeff0: "
+ << context->context_data(plain_coeff0.parms_id())->chain_index() << endl;
+ cout << endl;
+
+ /*
+ Let us carefully consider what the scales are at this point. If we denote
+ the primes in coeff_modulus as q1, q2, q3, q4 (order matters here), then all
+ fresh encodings start with a scale equal to q4 (this was a choice we made
+ above). After the computations above the scale in encrypted_x3 is q4^2/q3:
+
+ * The product x^2 has scale q4^2;
+ * The produt PI*x has scale q4^2;
+ * Rescaling both of these by q4 (last prime) results in scale q4;
+ * Multiplication to obtain PI*x^3 raises the scale to q4^2;
+ * Rescaling by q3 (last prime) yields a scale of q4^2/q3.
+
+ The scale in both encrypted_x1 and plain_coeff0 is just q4.
+ */
+ ios old_fmt(nullptr);
+ old_fmt.copyfmt(cout);
+ cout << fixed << setprecision(10);
+ cout << "Scale in encrypted_x3: " << encrypted_x3.scale() << endl;
+ cout << "Scale in encrypted_x1: " << encrypted_x1.scale() << endl;
+ cout << "Scale in plain_coeff0: " << plain_coeff0.scale() << endl;
+ cout << endl;
+ cout.copyfmt(old_fmt);
+
+ /*
+ There are a couple of ways to fix this this problem. Since q4 and q3 are
+ really close to each other, we could simply "lie" to SEAL and set the scales
+ to be the same. For example, changing the scale of encrypted_x3 to be q4
+ simply means that we scale the value of encrypted_x3 by q4/q3 which is very
+ close to 1; this should not result in any noticeable error.
+
+ Another option would be to encode 1 with scale q4, perform a multiply_plain
+ with encrypted_x1, and finally rescale. In this case we would additionally
+ make sure to encode 1 with the appropriate encryption parameters (parms_id).
+
+ A third option would be to initially encode plain_coeff1 with scale q4^2/q3.
+ Then, after multiplication with encrypted_x1 and rescaling, the result would
+ have scale q4^2/q3. Since encoding can be computationally costly, this may
+ not be a realistic option in some cases.
+
+ In this example we will use the first (simplest) approach and simply change
+ the scale of encrypted_x3.
+ */
+ encrypted_x3.scale() = encrypted_x1.scale();
+
+ /*
+ We still have a problem with mismatching encryption parameters. This is easy
+ to fix by using traditional modulus switching (no rescaling). Note that we
+ use here the Evaluator::mod_switch_to_inplace(...) function to switch to
+ encryption parameters down the chain with a specific parms_id.
+ */
+ evaluator.mod_switch_to_inplace(encrypted_x1, encrypted_x3.parms_id());
+ evaluator.mod_switch_to_inplace(plain_coeff0, encrypted_x3.parms_id());
+
+ /*
+ All three ciphertexts are now compatible and can be added.
+ */
+ Ciphertext encrypted_result;
+ evaluator.add(encrypted_x3, encrypted_x1, encrypted_result);
+ evaluator.add_plain_inplace(encrypted_result, plain_coeff0);
+
+ /*
+ Print the chain index and scale for encrypted_result.
+ */
+ cout << "Modulus chain index for encrypted_result: "
+ << context->context_data(encrypted_result.parms_id())
+ ->chain_index() << endl;
+ old_fmt.copyfmt(cout);
+ cout << fixed << setprecision(10);
+ cout << "Scale in encrypted_result: " << encrypted_result.scale();
+ cout.copyfmt(old_fmt);
+ cout << " (" << log2(encrypted_result.scale()) << " bits)" << endl;
+
+ /*
+ We decrypt, decode, and print the result.
+ */
+ Plaintext plain_result;
+ decryptor.decrypt(encrypted_result, plain_result);
+ vector result;
+ encoder.decode(plain_result, result);
+ cout << "Result of PI*x^3 + 0.4x + 1:" << endl;
+ print_vector(result, 3, 7);
+
+ /*
+ At this point if we wanted to multiply encrypted_result one more time, the
+ other multiplicand would have to have scale less than 40 bits, otherwise
+ the scale would become larger than the coeff_modulus itself.
+ */
+ cout << "Current coeff_modulus size for encrypted_result: "
+ << context->context_data(encrypted_result.parms_id())->
+ total_coeff_modulus_bit_count() << " bits" << endl << endl;
+
+ /*
+ A very extreme case for multiplication is where we multiply a ciphertext
+ with a vector of values that are all the same integer. For example, let us
+ multiply encrypted_result by 7. In this case we do not need any scaling in
+ the multiplicand due to a different (much simpler) encoding process.
+ */
+ Plaintext plain_integer_scalar;
+ encoder.encode(7, encrypted_result.parms_id(), plain_integer_scalar);
+ evaluator.multiply_plain_inplace(encrypted_result, plain_integer_scalar);
+
+ old_fmt.copyfmt(cout);
+ cout << fixed << setprecision(10);
+ cout << "Scale in plain_integer_scalar scale: "
+ << plain_integer_scalar.scale() << endl;
+ cout << "Scale in encrypted_result: " << encrypted_result.scale() << endl;
+ cout.copyfmt(old_fmt);
+
+ /*
+ We decrypt, decode, and print the result.
+ */
+ decryptor.decrypt(encrypted_result, plain_result);
+ encoder.decode(plain_result, result);
+ cout << "Result of 7 * (PI*x^3 + 0.4x + 1):" << endl;
+ print_vector(result, 3, 7);
+
+ /*
+ Finally, we show how to apply vector rotations on the encrypted data. This
+ is very similar to how matrix rotations work in the BFV scheme. We try this
+ with three sizes of Galois keys. In some cases it is desirable for memory
+ reasons to create Galois keys that support only specific rotations. This can
+ be done by passing to KeyGenerator::galois_keys(...) a vector of signed
+ integers specifying the desired rotation step counts. Here we create Galois
+ keys that only allow cyclic rotation by a single step (at a time) to the left.
+ */
+ auto gal_keys30 = keygen.galois_keys(30, vector{ 1 });
+ auto gal_keys15 = keygen.galois_keys(15, vector{ 1 });
+
+ Ciphertext rotated_result;
+ evaluator.rotate_vector(encrypted_result, 1, gal_keys15, rotated_result);
+ decryptor.decrypt(rotated_result, plain_result);
+ encoder.decode(plain_result, result);
+ cout << "Result rotated with dbc 15:" << endl;
+ print_vector(result, 3, 7);
+
+ evaluator.rotate_vector(encrypted_result, 1, gal_keys30, rotated_result);
+ decryptor.decrypt(rotated_result, plain_result);
+ encoder.decode(plain_result, result);
+ cout << "Result rotated with dbc 30:" << endl;
+ print_vector(result, 3, 5);
+
+ /*
+ We notice that the using the smallest decomposition bit count introduces
+ the least amount of error in the result. The problem is that our scale at
+ this point is very small -- only 40 bits -- so a rotation with decomposition
+ bit count 30 or bigger already destroys most or all of the message bits.
+ Ideally rotations would be performed right after multiplications before any
+ rescaling takes place. This way the scale is as large as possible and the
+ additive noise coming from the rotation (or relinearization) will be totally
+ shadowed by the large scale, and subsequently scaled down by the following
+ rescaling. Of course this may not always be possible to arrange.
+
+ We did not show any computations on complex numbers in these examples, but
+ the CKKSEncoder would allow us to have done that just as easily. Additions
+ and multiplications behave just as one would expect. It is also possible
+ to complex conjugate the values in a ciphertext by using the functions
+ Evaluator::complex_conjugate[_inplace](...).
+ */
+}
+
+void example_bfv_performance()
+{
+ print_example_banner("Example: BFV Performance Test");
+
+ /*
+ In this example we time all the basic operations. We use the following
+ lambda function to run the test.
+ */
+ auto performance_test = [](auto context)
+ {
+ chrono::high_resolution_clock::time_point time_start, time_end;
+
+ print_parameters(context);
+ auto &parms = context->context_data()->parms();
+ auto &plain_modulus = parms.plain_modulus();
+ size_t poly_modulus_degree = parms.poly_modulus_degree();
+
+ /*
+ Set up keys. For both relinearization and rotations we use a large
+ decomposition bit count for best possible computational performance.
+ */
+ cout << "Generating secret/public keys: ";
+ KeyGenerator keygen(context);
+ cout << "Done" << endl;
+
+ auto secret_key = keygen.secret_key();
+ auto public_key = keygen.public_key();
+
+ /*
+ Generate relinearization keys.
+ */
+ int dbc = dbc_max();
+ cout << "Generating relinearization keys (dbc = " << dbc << "): ";
+ time_start = chrono::high_resolution_clock::now();
+ auto relin_keys = keygen.relin_keys(dbc);
+ time_end = chrono::high_resolution_clock::now();
+ auto time_diff = chrono::duration_cast(time_end - time_start);
+ cout << "Done [" << time_diff.count() << " microseconds]" << endl;
+
+ /*
+ Generate Galois keys. In larger examples the Galois keys can use
+ a significant amount of memory, which can be a problem in constrained
+ systems. The user should try enabling some of the larger runs of the
+ test (see below) and to observe their effect on the memory pool
+ allocation size. The key generation can also take a significant amount
+ of time, as can be observed from the print-out.
+ */
+ if (!context->context_data()->qualifiers().using_batching)
+ {
+ cout << "Given encryption parameters do not support batching." << endl;
+ return;
+ }
+ cout << "Generating Galois keys (dbc = " << dbc << "): ";
+ time_start = chrono::high_resolution_clock::now();
+ auto gal_keys = keygen.galois_keys(dbc);
+ time_end = chrono::high_resolution_clock::now();
+ time_diff = chrono::duration_cast(time_end - time_start);
+ cout << "Done [" << time_diff.count() << " microseconds]" << endl;
+
+ Encryptor encryptor(context, public_key);
+ Decryptor decryptor(context, secret_key);
+ Evaluator evaluator(context);
+ BatchEncoder batch_encoder(context);
+ IntegerEncoder encoder(plain_modulus);
+
+ /*
+ These will hold the total times used by each operation.
+ */
+ chrono::microseconds time_batch_sum(0);
+ chrono::microseconds time_unbatch_sum(0);
+ chrono::microseconds time_encrypt_sum(0);
+ chrono::microseconds time_decrypt_sum(0);
+ chrono::microseconds time_add_sum(0);
+ chrono::microseconds time_multiply_sum(0);
+ chrono::microseconds time_multiply_plain_sum(0);
+ chrono::microseconds time_square_sum(0);
+ chrono::microseconds time_relinearize_sum(0);
+ chrono::microseconds time_rotate_rows_one_step_sum(0);
+ chrono::microseconds time_rotate_rows_random_sum(0);
+ chrono::microseconds time_rotate_columns_sum(0);
+
+ /*
+ How many times to run the test?
+ */
+ int count = 10;
+
+ /*
+ Populate a vector of values to batch.
+ */
+ vector pod_vector;
+ random_device rd;
+ for (size_t i = 0; i < batch_encoder.slot_count(); i++)
+ {
+ pod_vector.push_back(rd() % plain_modulus.value());
+ }
+
+ cout << "Running tests ";
+ for (int i = 0; i < count; i++)
+ {
+ /*
+ [Batching]
+ There is nothing unusual here. We batch our random plaintext matrix
+ into the polynomial. The user can try changing the decomposition bit
+ count to something smaller to see the effect. Note how the plaintext
+ we create is of the exactly right size so unnecessary reallocations
+ are avoided.
+ */
+ Plaintext plain(parms.poly_modulus_degree(), 0);
+ time_start = chrono::high_resolution_clock::now();
+ batch_encoder.encode(pod_vector, plain);
+ time_end = chrono::high_resolution_clock::now();
+ time_batch_sum += chrono::duration_cast<
+ chrono::microseconds>(time_end - time_start);
+
+ /*
+ [Unbatching]
+ We unbatch what we just batched.
+ */
+ vector pod_vector2(batch_encoder.slot_count());
+ time_start = chrono::high_resolution_clock::now();
+ batch_encoder.decode(plain, pod_vector2);
+ time_end = chrono::high_resolution_clock::now();
+ time_unbatch_sum += chrono::duration_cast<
+ chrono::microseconds>(time_end - time_start);
+ if (pod_vector2 != pod_vector)
+ {
+ throw runtime_error("Batch/unbatch failed. Something is wrong.");
+ }
+
+ /*
+ [Encryption]
+ We make sure our ciphertext is already allocated and large enough to
+ hold the encryption with these encryption parameters. We encrypt our
+ random batched matrix here.
+ */
+ Ciphertext encrypted(context);
+ time_start = chrono::high_resolution_clock::now();
+ encryptor.encrypt(plain, encrypted);
+ time_end = chrono::high_resolution_clock::now();
+ time_encrypt_sum += chrono::duration_cast<
+ chrono::microseconds>(time_end - time_start);
+
+ /*
+ [Decryption]
+ We decrypt what we just encrypted.
+ */
+ Plaintext plain2(poly_modulus_degree, 0);
+ time_start = chrono::high_resolution_clock::now();
+ decryptor.decrypt(encrypted, plain2);
+ time_end = chrono::high_resolution_clock::now();
+ time_decrypt_sum += chrono::duration_cast<
+ chrono::microseconds>(time_end - time_start);
+ if (plain2 != plain)
+ {
+ throw runtime_error("Encrypt/decrypt failed. Something is wrong.");
+ }
+
+ /*
+ [Add]
+ We create two ciphertexts that are both of size 2, and perform a few
+ additions with them.
+ */
+ Ciphertext encrypted1(context);
+ encryptor.encrypt(encoder.encode(i), encrypted1);
+ Ciphertext encrypted2(context);
+ encryptor.encrypt(encoder.encode(i + 1), encrypted2);
+ time_start = chrono::high_resolution_clock::now();
+ evaluator.add_inplace(encrypted1, encrypted1);
+ evaluator.add_inplace(encrypted2, encrypted2);
+ evaluator.add_inplace(encrypted1, encrypted2);
+ time_end = chrono::high_resolution_clock::now();
+ time_add_sum += chrono::duration_cast<
+ chrono::microseconds>(time_end - time_start) / 3;
+
+ /*
+ [Multiply]
+ We multiply two ciphertexts of size 2. Since the size of the result
+ will be 3, and will overwrite the first argument, we reserve first
+ enough memory to avoid reallocating during multiplication.
+ */
+ encrypted1.reserve(3);
+ time_start = chrono::high_resolution_clock::now();
+ evaluator.multiply_inplace(encrypted1, encrypted2);
+ time_end = chrono::high_resolution_clock::now();
+ time_multiply_sum += chrono::duration_cast<
+ chrono::microseconds>(time_end - time_start);
+
+ /*
+ [Multiply Plain]
+ We multiply a ciphertext of size 2 with a random plaintext. Recall
+ that multiply_plain does not change the size of the ciphertext so we
+ use encrypted2 here, which still has size 2.
+ */
+ time_start = chrono::high_resolution_clock::now();
+ evaluator.multiply_plain_inplace(encrypted2, plain);
+ time_end = chrono::high_resolution_clock::now();
+ time_multiply_plain_sum += chrono::duration_cast<
+ chrono::microseconds>(time_end - time_start);
+
+ /*
+ [Square]
+ We continue to use the size 2 ciphertext encrypted2. Now we square
+ it; this should be faster than generic homomorphic multiplication.
+ */
+ time_start = chrono::high_resolution_clock::now();
+ evaluator.square_inplace(encrypted2);
+ time_end = chrono::high_resolution_clock::now();
+ time_square_sum += chrono::duration_cast<
+ chrono::microseconds>(time_end - time_start);
+
+ /*
+ [Relinearize]
+ Time to get back to encrypted1; at this point it still has size 3.
+ We now relinearize it back to size 2. Since the allocation is
+ currently big enough to contain a ciphertext of size 3, no costly
+ reallocations are needed in the process.
+ */
+ time_start = chrono::high_resolution_clock::now();
+ evaluator.relinearize_inplace(encrypted1, relin_keys);
+ time_end = chrono::high_resolution_clock::now();
+ time_relinearize_sum += chrono::duration_cast<
+ chrono::microseconds>(time_end - time_start);
+
+ /*
+ [Rotate Rows One Step]
+ We rotate matrix rows by one step left and measure the time.
+ */
+ time_start = chrono::high_resolution_clock::now();
+ evaluator.rotate_rows_inplace(encrypted, 1, gal_keys);
+ evaluator.rotate_rows_inplace(encrypted, -1, gal_keys);
+ time_end = chrono::high_resolution_clock::now();
+ time_rotate_rows_one_step_sum += chrono::duration_cast<
+ chrono::microseconds>(time_end - time_start) / 2;
+
+ /*
+ [Rotate Rows Random]
+ We rotate matrix rows by a random number of steps. This is more
+ expensive than rotating by just one step.
+ */
+ size_t row_size = batch_encoder.slot_count() / 2;
+ int random_rotation = static_cast(rd() % row_size);
+ time_start = chrono::high_resolution_clock::now();
+ evaluator.rotate_rows_inplace(encrypted, random_rotation, gal_keys);
+ time_end = chrono::high_resolution_clock::now();
+ time_rotate_rows_random_sum += chrono::duration_cast<
+ chrono::microseconds>(time_end - time_start);
+
+ /*
+ [Rotate Columns]
+ Nothing surprising here.
+ */
+ time_start = chrono::high_resolution_clock::now();
+ evaluator.rotate_columns_inplace(encrypted, gal_keys);
+ time_end = chrono::high_resolution_clock::now();
+ time_rotate_columns_sum += chrono::duration_cast<
+ chrono::microseconds>(time_end - time_start);
+
+ /*
+ Print a dot to indicate progress.
+ */
+ cout << ".";
+ cout.flush();
+ }
+
+ cout << " Done" << endl << endl;
+ cout.flush();
+
+ auto avg_batch = time_batch_sum.count() / count;
+ auto avg_unbatch = time_unbatch_sum.count() / count;
+ auto avg_encrypt = time_encrypt_sum.count() / count;
+ auto avg_decrypt = time_decrypt_sum.count() / count;
+ auto avg_add = time_add_sum.count() / count;
+ auto avg_multiply = time_multiply_sum.count() / count;
+ auto avg_multiply_plain = time_multiply_plain_sum.count() / count;
+ auto avg_square = time_square_sum.count() / count;
+ auto avg_relinearize = time_relinearize_sum.count() / count;
+ auto avg_rotate_rows_one_step = time_rotate_rows_one_step_sum.count() / count;
+ auto avg_rotate_rows_random = time_rotate_rows_random_sum.count() / count;
+ auto avg_rotate_columns = time_rotate_columns_sum.count() / count;
+
+ cout << "Average batch: " << avg_batch << " microseconds" << endl;
+ cout << "Average unbatch: " << avg_unbatch << " microseconds" << endl;
+ cout << "Average encrypt: " << avg_encrypt << " microseconds" << endl;
+ cout << "Average decrypt: " << avg_decrypt << " microseconds" << endl;
+ cout << "Average add: " << avg_add << " microseconds" << endl;
+ cout << "Average multiply: " << avg_multiply << " microseconds" << endl;
+ cout << "Average multiply plain: " << avg_multiply_plain << " microseconds" << endl;
+ cout << "Average square: " << avg_square << " microseconds" << endl;
+ cout << "Average relinearize: " << avg_relinearize << " microseconds" << endl;
+ cout << "Average rotate rows one step: " << avg_rotate_rows_one_step << " microseconds" << endl;
+ cout << "Average rotate rows random: " << avg_rotate_rows_random << " microseconds" << endl;
+ cout << "Average rotate columns: " << avg_rotate_columns << " microseconds" << endl;
+ cout.flush();
+ };
+
+ EncryptionParameters parms(scheme_type::BFV);
+ parms.set_poly_modulus_degree(4096);
+ parms.set_coeff_modulus(coeff_modulus_128(4096));
+ parms.set_plain_modulus(786433);
+ performance_test(SEALContext::Create(parms));
+
+ cout << endl;
+ parms.set_poly_modulus_degree(8192);
+ parms.set_coeff_modulus(coeff_modulus_128(8192));
+ parms.set_plain_modulus(786433);
+ performance_test(SEALContext::Create(parms));
+
+ cout << endl;
+ parms.set_poly_modulus_degree(16384);
+ parms.set_coeff_modulus(coeff_modulus_128(16384));
+ parms.set_plain_modulus(786433);
+ performance_test(SEALContext::Create(parms));
+
+ /*
+ Comment out the following to run the biggest example.
+ */
+ // cout << endl;
+ // parms.set_poly_modulus_degree(32768);
+ // parms.set_coeff_modulus(coeff_modulus_128(32768));
+ // parms.set_plain_modulus(786433);
+ // performance_test(SEALContext::Create(parms));
+}
+
+void example_ckks_performance()
+{
+ print_example_banner("Example: CKKS Performance Test");
+
+ /*
+ In this example we time all the basic operations. We use the following
+ lambda function to run the test. This is largely similar to the function
+ in the previous example.
+ */
+ auto performance_test = [](auto context)
+ {
+ chrono::high_resolution_clock::time_point time_start, time_end;
+
+ print_parameters(context);
+ auto &parms = context->context_data()->parms();
+ size_t poly_modulus_degree = parms.poly_modulus_degree();
+
+ cout << "Generating secret/public keys: ";
+ KeyGenerator keygen(context);
+ cout << "Done" << endl;
+
+ auto secret_key = keygen.secret_key();
+ auto public_key = keygen.public_key();
+
+ int dbc = dbc_max();
+ cout << "Generating relinearization keys (dbc = " << dbc << "): ";
+ time_start = chrono::high_resolution_clock::now();
+ auto relin_keys = keygen.relin_keys(dbc);
+ time_end = chrono::high_resolution_clock::now();
+ auto time_diff = chrono::duration_cast(time_end - time_start);
+ cout << "Done [" << time_diff.count() << " microseconds]" << endl;
+
+ if (!context->context_data()->qualifiers().using_batching)
+ {
+ cout << "Given encryption parameters do not support batching." << endl;
+ return;
+ }
+ cout << "Generating Galois keys (dbc = " << dbc << "): ";
+ time_start = chrono::high_resolution_clock::now();
+ auto gal_keys = keygen.galois_keys(dbc);
+ time_end = chrono::high_resolution_clock::now();
+ time_diff = chrono::duration_cast(time_end - time_start);
+ cout << "Done [" << time_diff.count() << " microseconds]" << endl;
+
+ Encryptor encryptor(context, public_key);
+ Decryptor decryptor(context, secret_key);
+ Evaluator evaluator(context);
+ CKKSEncoder ckks_encoder(context);
+
+ chrono::microseconds time_encode_sum(0);
+ chrono::microseconds time_decode_sum(0);
+ chrono::microseconds time_encrypt_sum(0);
+ chrono::microseconds time_decrypt_sum(0);
+ chrono::microseconds time_add_sum(0);
+ chrono::microseconds time_multiply_sum(0);
+ chrono::microseconds time_multiply_plain_sum(0);
+ chrono::microseconds time_square_sum(0);
+ chrono::microseconds time_relinearize_sum(0);
+ chrono::microseconds time_rescale_sum(0);
+ chrono::microseconds time_rotate_one_step_sum(0);
+ chrono::microseconds time_rotate_random_sum(0);
+ chrono::microseconds time_conjugate_sum(0);
+
+ /*
+ How many times to run the test?
+ */
+ int count = 10;
+
+ /*
+ Populate a vector of floating-point values to batch.
+ */
+ vector pod_vector;
+ random_device rd;
+ for (size_t i = 0; i < ckks_encoder.slot_count(); i++)
+ {
+ pod_vector.push_back(1.001 * static_cast(i));
+ }
+
+ cout << "Running tests ";
+ for (int i = 0; i < count; i++)
+ {
+ /*
+ [Encoding]
+ */
+ Plaintext plain(parms.poly_modulus_degree() *
+ parms.coeff_modulus().size(), 0);
+ time_start = chrono::high_resolution_clock::now();
+ ckks_encoder.encode(pod_vector,
+ static_cast(parms.coeff_modulus().back().value()), plain);
+ time_end = chrono::high_resolution_clock::now();
+ time_encode_sum += chrono::duration_cast<
+ chrono::microseconds>(time_end - time_start);
+
+ /*
+ [Decoding]
+ */
+ vector pod_vector2(ckks_encoder.slot_count());
+ time_start = chrono::high_resolution_clock::now();
+ ckks_encoder.decode(plain, pod_vector2);
+ time_end = chrono::high_resolution_clock::now();
+ time_decode_sum += chrono::duration_cast<
+ chrono::microseconds>(time_end - time_start);
+
+ /*
+ [Encryption]
+ */
+ Ciphertext encrypted(context);
+ time_start = chrono::high_resolution_clock::now();
+ encryptor.encrypt(plain, encrypted);
+ time_end = chrono::high_resolution_clock::now();
+ time_encrypt_sum += chrono::duration_cast<
+ chrono::microseconds>(time_end - time_start);
+
+ /*
+ [Decryption]
+ */
+ Plaintext plain2(poly_modulus_degree, 0);
+ time_start = chrono::high_resolution_clock::now();
+ decryptor.decrypt(encrypted, plain2);
+ time_end = chrono::high_resolution_clock::now();
+ time_decrypt_sum += chrono::duration_cast<
+ chrono::microseconds>(time_end - time_start);
+
+ /*
+ [Add]
+ */
+ Ciphertext encrypted1(context);
+ ckks_encoder.encode(i + 1, plain);
+ encryptor.encrypt(plain, encrypted1);
+ Ciphertext encrypted2(context);
+ ckks_encoder.encode(i + 1, plain2);
+ encryptor.encrypt(plain2, encrypted2);
+ time_start = chrono::high_resolution_clock::now();
+ evaluator.add_inplace(encrypted1, encrypted1);
+ evaluator.add_inplace(encrypted2, encrypted2);
+ evaluator.add_inplace(encrypted1, encrypted2);
+ time_end = chrono::high_resolution_clock::now();
+ time_add_sum += chrono::duration_cast<
+ chrono::microseconds>(time_end - time_start) / 3;
+
+ /*
+ [Multiply]
+ */
+ encrypted1.reserve(3);
+ time_start = chrono::high_resolution_clock::now();
+ evaluator.multiply_inplace(encrypted1, encrypted2);
+ time_end = chrono::high_resolution_clock::now();
+ time_multiply_sum += chrono::duration_cast<
+ chrono::microseconds>(time_end - time_start);
+
+ /*
+ [Multiply Plain]
+ */
+ time_start = chrono::high_resolution_clock::now();
+ evaluator.multiply_plain_inplace(encrypted2, plain);
+ time_end = chrono::high_resolution_clock::now();
+ time_multiply_plain_sum += chrono::duration_cast<
+ chrono::microseconds>(time_end - time_start);
+
+ /*
+ [Square]
+ */
+ time_start = chrono::high_resolution_clock::now();
+ evaluator.square_inplace(encrypted2);
+ time_end = chrono::high_resolution_clock::now();
+ time_square_sum += chrono::duration_cast<
+ chrono::microseconds>(time_end - time_start);
+
+ /*
+ [Relinearize]
+ */
+ time_start = chrono::high_resolution_clock::now();
+ evaluator.relinearize_inplace(encrypted1, relin_keys);
+ time_end = chrono::high_resolution_clock::now();
+ time_relinearize_sum += chrono::duration_cast<
+ chrono::microseconds>(time_end - time_start);
+
+ /*
+ [Rescale]
+ */
+ time_start = chrono::high_resolution_clock::now();
+ evaluator.rescale_to_next_inplace(encrypted1);
+ time_end = chrono::high_resolution_clock::now();
+ time_rescale_sum += chrono::duration_cast<
+ chrono::microseconds>(time_end - time_start);
+
+ /*
+ [Rotate Vector]
+ */
+ time_start = chrono::high_resolution_clock::now();
+ evaluator.rotate_vector_inplace(encrypted, 1, gal_keys);
+ evaluator.rotate_vector_inplace(encrypted, -1, gal_keys);
+ time_end = chrono::high_resolution_clock::now();
+ time_rotate_one_step_sum += chrono::duration_cast<
+ chrono::microseconds>(time_end - time_start) / 2;
+
+ /*
+ [Rotate Vector Random]
+ */
+ int random_rotation = static_cast(rd() % ckks_encoder.slot_count());
+ time_start = chrono::high_resolution_clock::now();
+ evaluator.rotate_vector_inplace(encrypted, random_rotation, gal_keys);
+ time_end = chrono::high_resolution_clock::now();
+ time_rotate_random_sum += chrono::duration_cast<
+ chrono::microseconds>(time_end - time_start);
+
+ /*
+ [Complex Conjugate]
+ */
+ time_start = chrono::high_resolution_clock::now();
+ evaluator.complex_conjugate_inplace(encrypted, gal_keys);
+ time_end = chrono::high_resolution_clock::now();
+ time_conjugate_sum += chrono::duration_cast<
+ chrono::microseconds>(time_end - time_start);
+
+ /*
+ Print a dot to indicate progress.
+ */
+ cout << ".";
+ cout.flush();
+ }
+
+ cout << " Done" << endl << endl;
+ cout.flush();
+
+ auto avg_encode = time_encode_sum.count() / count;
+ auto avg_decode = time_decode_sum.count() / count;
+ auto avg_encrypt = time_encrypt_sum.count() / count;
+ auto avg_decrypt = time_decrypt_sum.count() / count;
+ auto avg_add = time_add_sum.count() / count;
+ auto avg_multiply = time_multiply_sum.count() / count;
+ auto avg_multiply_plain = time_multiply_plain_sum.count() / count;
+ auto avg_square = time_square_sum.count() / count;
+ auto avg_relinearize = time_relinearize_sum.count() / count;
+ auto avg_rescale = time_rescale_sum.count() / count;
+ auto avg_rotate_one_step = time_rotate_one_step_sum.count() / count;
+ auto avg_rotate_random = time_rotate_random_sum.count() / count;
+ auto avg_conjugate = time_conjugate_sum.count() / count;
+
+ cout << "Average encode: " << avg_encode << " microseconds" << endl;
+ cout << "Average decode: " << avg_decode << " microseconds" << endl;
+ cout << "Average encrypt: " << avg_encrypt << " microseconds" << endl;
+ cout << "Average decrypt: " << avg_decrypt << " microseconds" << endl;
+ cout << "Average add: " << avg_add << " microseconds" << endl;
+ cout << "Average multiply: " << avg_multiply << " microseconds" << endl;
+ cout << "Average multiply plain: " << avg_multiply_plain << " microseconds" << endl;
+ cout << "Average square: " << avg_square << " microseconds" << endl;
+ cout << "Average relinearize: " << avg_relinearize << " microseconds" << endl;
+ cout << "Average rescale: " << avg_rescale << " microseconds" << endl;
+ cout << "Average rotate vector one step: " << avg_rotate_one_step << " microseconds" << endl;
+ cout << "Average rotate vector random: " << avg_rotate_random << " microseconds" << endl;
+ cout << "Average complex conjugate: " << avg_conjugate << " microseconds" << endl;
+ cout.flush();
+ };
+
+ EncryptionParameters parms(scheme_type::CKKS);
+ parms.set_poly_modulus_degree(4096);
+ parms.set_coeff_modulus(coeff_modulus_128(4096));
+ performance_test(SEALContext::Create(parms));
+
+ cout << endl;
+ parms.set_poly_modulus_degree(8192);
+ parms.set_coeff_modulus(coeff_modulus_128(8192));
+ performance_test(SEALContext::Create(parms));
+
+ cout << endl;
+ parms.set_poly_modulus_degree(16384);
+ parms.set_coeff_modulus(coeff_modulus_128(16384));
+ performance_test(SEALContext::Create(parms));
+
+ /*
+ Comment out the following to run the biggest example.
+ */
+ // cout << endl;
+ // parms.set_poly_modulus_degree(32768);
+ // parms.set_coeff_modulus(coeff_modulus_128(32768));
+ // performance_test(SEALContext::Create(parms));
+}
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
index 000000000..274a92824
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,369 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT license.
+
+cmake_minimum_required(VERSION 3.10)
+
+project(SEAL VERSION 3.1.0 LANGUAGES CXX C)
+
+if(DEFINED MSVC AND NOT DEFINED ALLOW_COMMAND_LINE_BUILD)
+ message(FATAL_ERROR "Please build SEAL using the attached Visual Studio solution/project files")
+endif()
+
+if(${ALLOW_COMMAND_LINE_BUILD})
+ message(STATUS "Configuring for Visual Studio")
+endif()
+
+# Build in Release mode by default; otherwise use selected option
+set(SEAL_DEFAULT_BUILD_TYPE "Release")
+if(NOT CMAKE_BUILD_TYPE)
+ set(CMAKE_BUILD_TYPE ${SEAL_DEFAULT_BUILD_TYPE} CACHE
+ STRING "Build type" FORCE)
+ set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
+ "Release" "Debug" "MinSizeRel" "RelWithDebInfo")
+endif()
+message(STATUS "Build type (CMAKE_BUILD_TYPE): ${CMAKE_BUILD_TYPE}")
+
+# In Debug mode enable also SEAL_DEBUG by default
+if(${CMAKE_BUILD_TYPE} STREQUAL "Debug")
+ set(SEAL_DEBUG_DEFAULT ON)
+else()
+ set(SEAL_DEBUG_DEFAULT OFF)
+endif()
+
+# Required files and directories
+set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${SEAL_SOURCE_DIR}/../lib)
+set(SEAL_INCLUDES_INSTALL_DIR include)
+set(SEAL_CONFIG_IN_FILENAME ${SEAL_SOURCE_DIR}/cmake/SEALConfig.cmake.in)
+set(SEAL_CONFIG_FILENAME ${SEAL_SOURCE_DIR}/cmake/SEALConfig.cmake)
+set(SEAL_TARGETS_FILENAME ${SEAL_SOURCE_DIR}/cmake/SEALTargets.cmake)
+set(SEAL_CONFIG_VERSION_FILENAME ${SEAL_SOURCE_DIR}/cmake/SEALConfigVersion.cmake)
+set(SEAL_CONFIG_INSTALL_DIR lib/cmake/SEAL)
+
+# For extra modules we might have
+list(APPEND CMAKE_MODULE_PATH ${SEAL_SOURCE_DIR}/cmake)
+
+include(CMakePushCheckState)
+include(CMakeDependentOption)
+include(CheckIncludeFiles)
+include(CheckCXXSourceRuns)
+include(CheckTypeSize)
+
+# Are we using SEAL_DEBUG?
+set(SEAL_DEBUG ${SEAL_DEBUG_DEFAULT})
+message(STATUS "SEAL debug mode: ${SEAL_DEBUG}")
+
+# Should we use C++14 or C++17?
+set(SEAL_USE_CXX17_OPTION_STR "Use C++17")
+option(SEAL_USE_CXX17 ${SEAL_USE_CXX17_OPTION_STR} ON)
+message(STATUS "${SEAL_USE_CXX17_OPTION_STR} (SEAL_USE_CXX17): ${SEAL_USE_CXX17}")
+
+# Conditionally enable features from C++17
+set(SEAL_USE_STD_BYTE OFF)
+set(SEAL_USE_SHARED_MUTEX OFF)
+set(SEAL_USE_IF_CONSTEXPR OFF)
+set(SEAL_USE_MAYBE_UNUSED OFF)
+set(SEAL_LANG_FLAG "-std=c++14")
+if(SEAL_USE_CXX17)
+ set(SEAL_USE_STD_BYTE ON)
+ set(SEAL_USE_SHARED_MUTEX ON)
+ set(SEAL_USE_IF_CONSTEXPR ON)
+ set(SEAL_USE_MAYBE_UNUSED ON)
+ set(SEAL_LANG_FLAG "-std=c++17")
+endif()
+
+# Enforce at least 128-bit security level based on HomomorphicEncryption.org estimates
+set(SEAL_ENFORCE_HE_STD_SECURITY_STR "Enforce at least 128-bit security level from HomomorphicEncryption.org security standard")
+option(SEAL_ENFORCE_HE_STD_SECURITY ${SEAL_ENFORCE_HE_STD_SECURITY_STR} OFF)
+
+# Use intrinsics if available
+set(SEAL_USE_INTRIN_OPTION_STR "Use intrinsics")
+option(SEAL_USE_INTRIN ${SEAL_USE_INTRIN_OPTION_STR} ON)
+
+# Use Microsoft GSL if available
+set(SEAL_USE_MSGSL_OPTION_STR "Use Microsoft GSL")
+option(SEAL_USE_MSGSL ${SEAL_USE_MSGSL_OPTION_STR} ON)
+
+# Check for intrin.h or x64intrin.h
+if(SEAL_USE_INTRIN)
+ if(DEFINED MSVC)
+ set(SEAL_INTRIN_HEADER "intrin.h")
+ else()
+ set(SEAL_INTRIN_HEADER "x86intrin.h")
+ endif()
+
+ check_include_file_cxx(${SEAL_INTRIN_HEADER} HAVE_INTRIN_HEADER)
+
+ if(NOT HAVE_INTRIN_HEADER)
+ set(SEAL_USE_INTRIN OFF CACHE BOOL ${SEAL_USE_INTRIN_OPTION_STR} FORCE)
+ endif()
+endif()
+message(STATUS "${SEAL_USE_INTRIN_OPTION_STR} (SEAL_USE_INTRIN): ${SEAL_USE_INTRIN}")
+
+# Specific intrinsics depending on SEAL_USE_INTRIN
+if(DEFINED MSVC)
+ set(SEAL_USE__UMUL128_OPTION_STR "Use _umul128")
+ cmake_dependent_option(SEAL_USE__UMUL128 SEAL_USE__UMUL128_OPTION_STR ON "SEAL_USE_INTRIN" OFF)
+
+ set(SEAL_USE__BITSCANREVERSE64_OPTION_STR "Use _BitScanReverse64")
+ cmake_dependent_option(SEAL_USE__BITSCANREVERSE64 SEAL_USE__BITSCANREVERSE64_OPTION_STR ON "SEAL_USE_INTRIN" OFF)
+else()
+ set(SEAL_USE___INT128_OPTION_STR "Use __int128")
+ cmake_dependent_option(SEAL_USE___INT128 SEAL_USE___INT128_OPTION_STR ON "SEAL_USE_INTRIN" OFF)
+
+ set(SEAL_USE___BUILTIN_CLZLL_OPTION_STR "Use __builtin_clzll")
+ cmake_dependent_option(SEAL_USE___BUILTIN_CLZLL SEAL_USE___BUILTIN_CLZLL_OPTION_STR ON "SEAL_USE_INTRIN" OFF)
+endif()
+
+set(SEAL_USE__ADDCARRY_U64_OPTION_STR "Use _addcarry_u64")
+cmake_dependent_option(SEAL_USE__ADDCARRY_U64 SEAL_USE__ADDCARRY_U64_OPTION_STR ON "SEAL_USE_INTRIN" OFF)
+
+set(SEAL_USE__SUBBORROW_U64_OPTION_STR "Use _subborrow_u64")
+cmake_dependent_option(SEAL_USE__SUBBORROW_U64 SEAL_USE__SUBBORROW_U64_OPTION_STR ON "SEAL_USE_INTRIN" OFF)
+
+set(SEAL_USE_AES_NI_PRNG_OPTION_STR "Use fast AES-NI PRNG")
+cmake_dependent_option(SEAL_USE_AES_NI_PRNG SEAL_USE_AES_NI_PRNG_OPTION_STR ON "SEAL_USE_INTRIN" OFF)
+
+if(SEAL_USE_INTRIN)
+ cmake_push_check_state(RESET)
+ set(CMAKE_REQUIRED_QUIET TRUE)
+ if(NOT DEFINED MSVC)
+ set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -O0 ${SEAL_LANG_FLAG}")
+ endif()
+
+ if(DEFINED MSVC)
+ # Check for presence of _umul128
+ if(SEAL_USE__UMUL128)
+ check_cxx_source_runs("
+ #include <${SEAL_INTRIN_HEADER}>
+ int main() {
+ unsigned long long a = 0, b = 0;
+ unsigned long long c;
+ volatile unsigned long long d;
+ d = _umul128(a, b, &c);
+ return 0;
+ }"
+ USE_UMUL128
+ )
+ if(NOT USE_UMUL128 EQUAL 1)
+ set(SEAL_USE__UMUL128 OFF CACHE BOOL ${SEAL_USE__UMUL128_OPTION_STR} FORCE)
+ endif()
+ endif()
+
+ # Check for _BitScanReverse64
+ if(SEAL_USE__BITSCANREVERSE64)
+ check_cxx_source_runs("
+ #include <${SEAL_INTRIN_HEADER}>
+ int main() {
+ unsigned long a = 0, b = 0;
+ volatile unsigned char res = _BitScanReverse64(&a, b);
+ return 0;
+ }"
+ USE_BITSCANREVERSE64
+ )
+ if(NOT USE_BITSCANREVERSE64 EQUAL 1)
+ set(SEAL_USE__BITSCANREVERSE64 OFF CACHE BOOL ${SEAL_USE__BITSCANREVERSE64_OPTION_STR} FORCE)
+ endif()
+ endif()
+ else()
+ # Check for presence of ___int128
+ if(SEAL_USE___INT128)
+ check_type_size("__int128" INT128 LANGUAGE CXX)
+ if(NOT INT128 EQUAL 16)
+ set(SEAL_USE___INT128 OFF CACHE BOOL ${SEAL_USE___INT128_OPTION_STR} FORCE)
+ endif()
+ endif()
+
+ # Check for __builtin_clzll
+ if(SEAL_USE___BUILTIN_CLZLL)
+ check_cxx_source_runs("
+ int main() {
+ volatile auto = __builtin_clzll(0);
+ return 0;
+ }"
+ USE_BUILTIN_CLZLL
+ )
+ if(NOT USE_BUILTIN_CLZLL EQUAL 1)
+ set(SEAL_USE___BUILTIN_CLZLL OFF CACHE BOOL ${SEAL_USE___BUILTIN_CLZLL_OPTION_STR} FORCE)
+ endif()
+ endif()
+ endif()
+
+ # Check for _addcarry_u64
+ if(SEAL_USE__ADDCARRY_U64)
+ check_cxx_source_runs("
+ #include <${SEAL_INTRIN_HEADER}>
+ int main() {
+ unsigned long long a;
+ volatile auto res = _addcarry_u64(0,0,0,&a);
+ return 0;
+ }"
+ USE_ADDCARRY_U64
+ )
+ if(NOT USE_ADDCARRY_U64 EQUAL 1)
+ set(SEAL_USE__ADDCARRY_U64 OFF CACHE BOOL ${SEAL_USE__ADDCARRY_U64_OPTION_STR} FORCE)
+ endif()
+ endif()
+
+ # Check for _subborrow_u64
+ if(SEAL_USE__SUBBORROW_U64)
+ check_cxx_source_runs("
+ #include <${SEAL_INTRIN_HEADER}>
+ int main() {
+ unsigned long long a;
+ volatile auto res = _subborrow_u64(0,0,0,&a);
+ return 0;
+ }"
+ USE_SUBBORROW_U64
+ )
+ if(NOT USE_SUBBORROW_U64 EQUAL 1)
+ set(SEAL_USE__SUBBORROW_U64 OFF CACHE BOOL ${SEAL_USE__SUBBORROW_U64_OPTION_STR} FORCE)
+ endif()
+ endif()
+
+ check_include_file_cxx("wmmintrin.h" HAVE_WMMINTRIN_HEADER)
+ if(NOT HAVE_WMMINTRIN_HEADER)
+ set(SEAL_USE_AES_NI_PRNG OFF CACHE BOOL ${SEAL_USE_AES_NI_PRNG_OPTION_STR} FORCE)
+ endif()
+
+ # Check that AES-NI runs
+ if(SEAL_USE_AES_NI_PRNG)
+ if(NOT DEFINED MSVC)
+ set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -maes")
+ endif()
+ check_cxx_source_runs("
+ #include
+ int main() {
+ __m128i a{ 0 };
+ volatile auto b = _mm_aeskeygenassist_si128(a, 0);
+ return 0;
+ }"
+ USE_AES_KEYGEN_ASSIST
+ )
+ if(NOT USE_AES_KEYGEN_ASSIST EQUAL 1)
+ set(SEAL_USE_AES_NI_PRNG OFF CACHE BOOL ${SEAL_USE_AES_NI_PRNG_OPTION_STR} FORCE)
+ endif()
+ endif()
+ message(STATUS "${SEAL_USE_AES_NI_PRNG_OPTION_STR}: ${SEAL_USE_AES_NI_PRNG}")
+
+ cmake_pop_check_state()
+endif()
+
+# Try to find MSGSL if requested
+if(SEAL_USE_MSGSL)
+ find_package(msgsl MODULE)
+ if(NOT msgsl_FOUND)
+ set(SEAL_USE_MSGSL OFF CACHE BOOL ${SEAL_USE_MSGSL_OPTION_STR} FORCE)
+ endif()
+endif()
+message(STATUS "${SEAL_USE_MSGSL_OPTION_STR} (SEAL_USE_MSGSL): ${SEAL_USE_MSGSL}")
+
+# Specific options depending on SEAL_USE_MSGSL
+set(SEAL_USE_MSGSL_SPAN_OPTION_STR "Use gsl::span")
+cmake_dependent_option(SEAL_USE_MSGSL_SPAN ${SEAL_USE_MSGSL_SPAN_OPTION_STR} ON "SEAL_USE_MSGSL" OFF)
+
+set(SEAL_USE_MSGSL_MULTISPAN_OPTION_STR "Use gsl::multi_span")
+cmake_dependent_option(SEAL_USE_MSGSL_MULTISPAN ${SEAL_USE_MSGSL_MULTISPAN_OPTION_STR} ON "SEAL_USE_MSGSL" OFF)
+
+if(SEAL_USE_MSGSL)
+ # Now check for individual classes
+ cmake_push_check_state(RESET)
+ set(CMAKE_REQUIRED_INCLUDES ${MSGSL_INCLUDE_DIR})
+ set(CMAKE_EXTRA_INCLUDE_FILES gsl/gsl)
+ set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -O0 ${SEAL_LANG_FLAG}")
+ set(CMAKE_REQUIRED_QUIET TRUE)
+
+ # Detect gsl::span
+ if(SEAL_USE_MSGSL_SPAN)
+ check_type_size("gsl::span" MSGSL_SPAN LANGUAGE CXX)
+ if(NOT MSGSL_SPAN GREATER 0)
+ set(SEAL_USE_MSGSL_SPAN OFF CACHE BOOL ${SEAL_USE_MSGSL_SPAN_OPTION_STR} FORCE)
+ endif()
+ endif()
+
+ # Detect gsl::multi_span
+ if(SEAL_USE_MSGSL_MULTISPAN)
+ check_type_size("gsl::multi_span" MSGSL_MULTISPAN LANGUAGE CXX)
+ if(NOT MSGSL_MULTISPAN GREATER 0)
+ set(SEAL_USE_MSGSL_MULTISPAN OFF CACHE BOOL ${SEAL_USE_MSGSL_MULTISPAN_OPTION_STR} FORCE)
+ endif()
+ endif()
+
+ cmake_pop_check_state()
+endif()
+
+# Create library but add no source files yet
+add_library(seal STATIC "")
+
+# Add source files to library and header files to install
+add_subdirectory(seal)
+
+# Add local include directories for build
+target_include_directories(seal PUBLIC
+ $)
+
+# We require at least C++14
+if(SEAL_USE_CXX17)
+ target_compile_features(seal PUBLIC cxx_std_17)
+else()
+ target_compile_features(seal PUBLIC cxx_std_14)
+endif()
+
+# Add -maes flag if needed
+if(SEAL_USE_AES_NI_PRNG)
+ target_compile_options(seal PUBLIC "-maes")
+endif()
+
+# Require thread library
+set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
+set(THREADS_PREFER_PTHREAD_FLAG TRUE)
+find_package(Threads REQUIRED)
+
+# Link Threads with seal
+target_link_libraries(seal PUBLIC Threads::Threads)
+
+# Create msgsl interface target
+if(SEAL_USE_MSGSL)
+ # Create interface target
+ add_library(msgsl INTERFACE)
+ set_target_properties(msgsl PROPERTIES
+ INTERFACE_INCLUDE_DIRECTORIES ${MSGSL_INCLUDE_DIR})
+
+ # Associate msgsl with export seal_export
+ install(TARGETS msgsl EXPORT seal_export)
+
+ # Link with seal
+ target_link_libraries(seal PUBLIC msgsl)
+endif()
+
+# Associate seal to export seal_export
+install(TARGETS seal EXPORT seal_export
+ ARCHIVE DESTINATION lib
+ INCLUDES DESTINATION ${SEAL_INCLUDES_INSTALL_DIR})
+
+# Export the targets
+export(EXPORT seal_export
+ FILE ${SEAL_TARGETS_FILENAME}
+ NAMESPACE SEAL::)
+
+# Create the CMake config file
+configure_file(${SEAL_CONFIG_IN_FILENAME} ${SEAL_CONFIG_FILENAME} @ONLY)
+
+# Install the export
+install(
+ EXPORT seal_export
+ FILE SEALTargets.cmake
+ NAMESPACE SEAL::
+ DESTINATION ${SEAL_CONFIG_INSTALL_DIR})
+
+# Version file; we require exact version match for downstream
+include(CMakePackageConfigHelpers)
+write_basic_package_version_file(${SEAL_CONFIG_VERSION_FILENAME}
+ VERSION ${SEAL_VERSION}
+ COMPATIBILITY ExactVersion)
+
+# Install other files
+install(
+ FILES
+ ${SEAL_CONFIG_FILENAME}
+ ${SEAL_CONFIG_VERSION_FILENAME}
+ DESTINATION ${SEAL_CONFIG_INSTALL_DIR})
diff --git a/src/SEAL.vcxproj b/src/SEAL.vcxproj
new file mode 100644
index 000000000..ce9896235
--- /dev/null
+++ b/src/SEAL.vcxproj
@@ -0,0 +1,201 @@
+
+
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {7EA96C25-FC0D-485A-BB71-32B6DA55652A}
+ SEAL
+ 10.0.16299.0
+
+
+
+ StaticLibrary
+ true
+ v141
+ Unicode
+ false
+
+
+ StaticLibrary
+ false
+ v141
+ true
+ Unicode
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(SolutionDir)lib\$(Platform)\$(Configuration)\
+ $(ProjectDir)obj\$(Platform)\$(Configuration)\
+ .lib
+ seal
+
+
+ $(SolutionDir)lib\$(Platform)\$(Configuration)\
+ $(ProjectDir)obj\$(Platform)\$(Configuration)\
+ .lib
+ seal
+
+
+
+ Level3
+ Disabled
+ true
+ $(ProjectDir)
+ true
+ Neither
+ stdcpp17
+ %(PreprocessorDefinitions); _ENABLE_EXTENDED_ALIGNED_STORAGE
+ /Zc:__cplusplus %(AdditionalOptions)
+
+
+ true
+
+
+ "$(SolutionDir)tools\scripts\cmake_config.cmd" $(Configuration) "$(DevEnvDir)" "$(IncludePath)"
+
+
+ Configure SEAL through CMake
+
+
+
+
+ Level3
+ MaxSpeed
+ true
+ true
+ true
+ $(ProjectDir)
+ Speed
+ Default
+ stdcpp17
+ %(PreprocessorDefinitions); _ENABLE_EXTENDED_ALIGNED_STORAGE
+ /Zc:__cplusplus %(AdditionalOptions)
+
+
+ true
+ true
+ true
+
+
+ "$(SolutionDir)tools\scripts\cmake_config.cmd" $(Configuration) "$(DevEnvDir)" "$(IncludePath)"
+
+
+ Configure SEAL through CMake
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/SEAL.vcxproj.filters b/src/SEAL.vcxproj.filters
new file mode 100644
index 000000000..11de12252
--- /dev/null
+++ b/src/SEAL.vcxproj.filters
@@ -0,0 +1,295 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;hm;inl;inc;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+ {a119ce23-aae9-4b06-be2c-1c8aada4ab20}
+
+
+ {8740bd83-253c-49f3-8f9a-3b9c526f67c2}
+
+
+ {8585bc5e-eaa9-481a-a6ee-c38be1319a32}
+
+
+ {aaf838b1-cab2-4ccc-a016-a81c7edf506e}
+
+
+ {31fb1149-1a6f-438b-a86a-744384986d21}
+
+
+ {497d5f96-98a3-44e9-8b38-a2ea4bbea366}
+
+
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files\util
+
+
+ Header Files\util
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files\util
+
+
+ Header Files\util
+
+
+ Header Files\util
+
+
+ Header Files\util
+
+
+ Header Files\util
+
+
+ Header Files\util
+
+
+ Header Files\util
+
+
+ Header Files\util
+
+
+ Header Files\util
+
+
+ Header Files\util
+
+
+ Header Files\util
+
+
+ Header Files\util
+
+
+ Header Files\util
+
+
+ Header Files\util
+
+
+ Header Files\util
+
+
+ Header Files\util
+
+
+ Header Files\util
+
+
+ Header Files\util
+
+
+ Header Files
+
+
+ Header Files\util
+
+
+ Header Files\util
+
+
+ Header Files\util
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files\util
+
+
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files\util
+
+
+ Source Files\util
+
+
+ Source Files
+
+
+ Source Files\util
+
+
+ Source Files\util
+
+
+ Source Files\util
+
+
+ Source Files\util
+
+
+ Source Files\util
+
+
+ Source Files\util
+
+
+ Source Files\util
+
+
+ Source Files\util
+
+
+ Source Files\util
+
+
+ Source Files\util
+
+
+ Source Files\util
+
+
+ Source Files\util
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files\util
+
+
+
+
+ Other
+
+
+ Other\seal
+
+
+ Other\seal\util
+
+
+
+
+ Other\cmake
+
+
+ Other\seal\util
+
+
+ Other\cmake
+
+
+
\ No newline at end of file
diff --git a/src/cmake/Findmsgsl.cmake b/src/cmake/Findmsgsl.cmake
new file mode 100644
index 000000000..65f41536d
--- /dev/null
+++ b/src/cmake/Findmsgsl.cmake
@@ -0,0 +1,14 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT license.
+
+# Simple attempt to locate Microsoft GSL
+set(CURRENT_MSGSL_INCLUDE_DIR ${MSGSL_INCLUDE_DIR})
+unset(MSGSL_INCLUDE_DIR CACHE)
+find_path(MSGSL_INCLUDE_DIR
+ NAMES gsl/gsl gsl/span gsl/multi_span
+ HINTS ${CMAKE_INCLUDE_PATH} ${CURRENT_MSGSL_INCLUDE_DIR})
+
+# Determine whether found based on MSGSL_INCLUDE_DIR
+find_package(PackageHandleStandardArgs)
+find_package_handle_standard_args(msgsl
+ REQUIRED_VARS MSGSL_INCLUDE_DIR)
diff --git a/src/cmake/SEALConfig.cmake.in b/src/cmake/SEALConfig.cmake.in
new file mode 100644
index 000000000..666759149
--- /dev/null
+++ b/src/cmake/SEALConfig.cmake.in
@@ -0,0 +1,34 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT license.
+
+# Exports target SEAL::seal
+#
+# Creates variables:
+# SEAL_BUILD_TYPE : The build configuration used
+# SEAL_DEBUG : Set to non-zero value if SEAL is compiled with extra debugging code (very slow!)
+# SEAL_ENFORCE_HE_STD_SECURITY : Set to non-zero value if SEAL is compiled to enforce at least
+# a 128-bit security level based on HomomorphicEncryption.org security estimates
+# SEAL_USE_MSGSL : Set to non-zero value if SEAL is compiled with Microsoft GSL support
+# MSGSL_INCLUDE_DIR : Holds the path to Microsoft GSL if SEAL is compiled with Microsoft GSL support
+
+include(CMakeFindDependencyMacro)
+
+set(SEAL_BUILD_TYPE @CMAKE_BUILD_TYPE@)
+set(SEAL_DEBUG @SEAL_DEBUG@)
+set(SEAL_USE_CXX17 @SEAL_USE_CXX17@)
+set(SEAL_ENFORCE_HE_STD_SECURITY @SEAL_ENFORCE_HE_STD_SECURITY@)
+set(SEAL_USE_MSGSL @SEAL_USE_MSGSL@)
+if(SEAL_USE_MSGSL)
+ set(MSGSL_INCLUDE_DIR @MSGSL_INCLUDE_DIR@)
+endif()
+
+set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
+set(THREADS_PREFER_PTHREAD_FLAG TRUE)
+find_dependency(Threads REQUIRED)
+
+include(${CMAKE_CURRENT_LIST_DIR}/SEALTargets.cmake)
+
+message(STATUS "SEAL detected (version ${SEAL_VERSION})")
+if(SEAL_DEBUG)
+ message(STATUS "Performance warning: SEAL compiled in debug mode")
+endif()
diff --git a/src/seal/CMakeLists.txt b/src/seal/CMakeLists.txt
new file mode 100644
index 000000000..eb6b0f8bf
--- /dev/null
+++ b/src/seal/CMakeLists.txt
@@ -0,0 +1,53 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT license.
+
+target_sources(seal
+ PRIVATE
+ ${CMAKE_CURRENT_LIST_DIR}/batchencoder.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/biguint.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/ciphertext.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/ckks.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/context.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/decryptor.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/encoder.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/encryptionparams.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/encryptor.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/evaluator.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/galoiskeys.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/keygenerator.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/memorymanager.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/plaintext.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/randomgen.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/relinkeys.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/smallmodulus.cpp
+)
+
+install(
+ FILES
+ ${CMAKE_CURRENT_LIST_DIR}/batchencoder.h
+ ${CMAKE_CURRENT_LIST_DIR}/biguint.h
+ ${CMAKE_CURRENT_LIST_DIR}/ciphertext.h
+ ${CMAKE_CURRENT_LIST_DIR}/ckks.h
+ ${CMAKE_CURRENT_LIST_DIR}/context.h
+ ${CMAKE_CURRENT_LIST_DIR}/decryptor.h
+ ${CMAKE_CURRENT_LIST_DIR}/defaultparams.h
+ ${CMAKE_CURRENT_LIST_DIR}/encoder.h
+ ${CMAKE_CURRENT_LIST_DIR}/encryptionparams.h
+ ${CMAKE_CURRENT_LIST_DIR}/encryptor.h
+ ${CMAKE_CURRENT_LIST_DIR}/evaluator.h
+ ${CMAKE_CURRENT_LIST_DIR}/galoiskeys.h
+ ${CMAKE_CURRENT_LIST_DIR}/intarray.h
+ ${CMAKE_CURRENT_LIST_DIR}/keygenerator.h
+ ${CMAKE_CURRENT_LIST_DIR}/memorymanager.h
+ ${CMAKE_CURRENT_LIST_DIR}/plaintext.h
+ ${CMAKE_CURRENT_LIST_DIR}/publickey.h
+ ${CMAKE_CURRENT_LIST_DIR}/randomgen.h
+ ${CMAKE_CURRENT_LIST_DIR}/relinkeys.h
+ ${CMAKE_CURRENT_LIST_DIR}/seal.h
+ ${CMAKE_CURRENT_LIST_DIR}/secretkey.h
+ ${CMAKE_CURRENT_LIST_DIR}/smallmodulus.h
+ DESTINATION
+ ${SEAL_INCLUDES_INSTALL_DIR}/seal
+)
+
+add_subdirectory(util)
diff --git a/src/seal/batchencoder.cpp b/src/seal/batchencoder.cpp
new file mode 100644
index 000000000..e2e8ef46f
--- /dev/null
+++ b/src/seal/batchencoder.cpp
@@ -0,0 +1,581 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+#include
+#include
+#include
+#include
+#include "seal/batchencoder.h"
+#include "seal/util/polycore.h"
+
+using namespace std;
+using namespace seal::util;
+
+namespace seal
+{
+ BatchEncoder::BatchEncoder(std::shared_ptr context) :
+ context_(std::move(context))
+ {
+ // Verify parameters
+ if (!context_)
+ {
+ throw invalid_argument("invalid context");
+ }
+ if (!context_->parameters_set())
+ {
+ throw invalid_argument("encryption parameters are not set correctly");
+ }
+
+ auto &context_data = *context_->context_data();
+ if (context_data.parms().scheme() != scheme_type::BFV)
+ {
+ throw invalid_argument("unsupported scheme");
+ }
+ if (!context_data.qualifiers().using_batching)
+ {
+ throw invalid_argument("encryption parameters are not valid for batching");
+ }
+
+ // Set the slot count
+ slots_ = context_data.parms().poly_modulus_degree();
+
+ // Reserve space for all of the primitive roots
+ roots_of_unity_ = allocate_uint(slots_, pool_);
+
+ // Fill the vector of roots of unity with all distinct odd powers of generator.
+ // These are all the primitive (2*slots_)-th roots of unity in integers modulo
+ // parms.plain_modulus().
+ populate_roots_of_unity_vector(context_data);
+
+ // Populate matrix representation index map
+ populate_matrix_reps_index_map();
+ }
+
+ void BatchEncoder::populate_roots_of_unity_vector(
+ const SEALContext::ContextData &context_data)
+ {
+ uint64_t root = context_data.plain_ntt_tables()->get_root();
+ auto &modulus = context_data.parms().plain_modulus();
+
+ uint64_t generator_sq = multiply_uint_uint_mod(root, root, modulus);
+ roots_of_unity_[0] = root;
+
+ for (size_t i = 1; i < slots_; i++)
+ {
+ roots_of_unity_[i] = multiply_uint_uint_mod(roots_of_unity_[i - 1],
+ generator_sq, modulus);
+ }
+ }
+
+ void BatchEncoder::populate_matrix_reps_index_map()
+ {
+ int logn = get_power_of_two(slots_);
+ matrix_reps_index_map_ = allocate_uint(slots_, pool_);
+
+ // Copy from the matrix to the value vectors
+ size_t row_size = slots_ >> 1;
+ size_t m = slots_ << 1;
+ uint64_t gen = 3;
+ uint64_t pos = 1;
+ for (size_t i = 0; i < row_size; i++)
+ {
+ // Position in normal bit order
+ uint64_t index1 = (pos - 1) >> 1;
+ uint64_t index2 = (m - pos - 1) >> 1;
+
+ // Set the bit-reversed locations
+ matrix_reps_index_map_[i] = util::reverse_bits(index1, logn);
+ matrix_reps_index_map_[row_size | i] = util::reverse_bits(index2, logn);
+
+ // Next primitive root
+ pos *= gen;
+ pos &= (m - 1);
+ }
+ }
+
+ void BatchEncoder::encode(const vector &values_matrix,
+ Plaintext &destination)
+ {
+ auto &context_data = *context_->context_data();
+
+ // Validate input parameters
+ size_t values_matrix_size = values_matrix.size();
+ if (values_matrix_size > slots_)
+ {
+ throw logic_error("values_matrix size is too large");
+ }
+#ifdef SEAL_DEBUG
+ uint64_t modulus = context_data.parms().plain_modulus().value();
+ for (auto v : values_matrix)
+ {
+ // Validate the i-th input
+ if (v >= modulus)
+ {
+ throw invalid_argument("input value is larger than plain_modulus");
+ }
+ }
+#endif
+ // Set destination to full size
+ destination.resize(slots_);
+ destination.parms_id() = parms_id_zero;
+
+ // First write the values to destination coefficients.
+ // Read in top row, then bottom row.
+ for (size_t i = 0; i < values_matrix_size; i++)
+ {
+ *(destination.data() + matrix_reps_index_map_[i]) = values_matrix[i];
+ }
+ for (size_t i = values_matrix_size; i < slots_; i++)
+ {
+ *(destination.data() + matrix_reps_index_map_[i]) = 0;
+ }
+
+ // Transform destination using inverse of negacyclic NTT
+ // Note: We already performed bit-reversal when reading in the matrix
+ inverse_ntt_negacyclic_harvey(destination.data(), *context_data.plain_ntt_tables());
+ }
+
+ void BatchEncoder::encode(const vector &values_matrix,
+ Plaintext &destination)
+ {
+ auto &context_data = *context_->context_data();
+ uint64_t modulus = context_data.parms().plain_modulus().value();
+
+ // Validate input parameters
+ size_t values_matrix_size = values_matrix.size();
+ if (values_matrix_size > slots_)
+ {
+ throw logic_error("values_matrix size is too large");
+ }
+#ifdef SEAL_DEBUG
+ uint64_t plain_modulus_div_two = modulus >> 1;
+ for (auto v : values_matrix)
+ {
+ // Validate the i-th input
+ if (unsigned_gt(llabs(v), plain_modulus_div_two))
+ {
+ throw invalid_argument("input value is larger than plain_modulus");
+ }
+ }
+#endif
+ // Set destination to full size
+ destination.resize(slots_);
+ destination.parms_id() = parms_id_zero;
+
+ // First write the values to destination coefficients.
+ // Read in top row, then bottom row.
+ for (size_t i = 0; i < values_matrix_size; i++)
+ {
+ *(destination.data() + matrix_reps_index_map_[i]) =
+ (values_matrix[i] < 0) ? (modulus + static_cast(values_matrix[i])) :
+ static_cast(values_matrix[i]);
+ }
+ for (size_t i = values_matrix_size; i < slots_; i++)
+ {
+ *(destination.data() + matrix_reps_index_map_[i]) = 0;
+ }
+
+ // Transform destination using inverse of negacyclic NTT
+ // Note: We already performed bit-reversal when reading in the matrix
+ inverse_ntt_negacyclic_harvey(destination.data(), *context_data.plain_ntt_tables());
+ }
+#ifdef SEAL_USE_MSGSL_SPAN
+ void BatchEncoder::encode(gsl::span values_matrix,
+ Plaintext &destination)
+ {
+ auto &context_data = *context_->context_data();
+
+ // Validate input parameters
+ size_t values_matrix_size = static_cast(values_matrix.size());
+ if (values_matrix_size > slots_)
+ {
+ throw logic_error("values_matrix size is too large");
+ }
+#ifdef SEAL_DEBUG
+ uint64_t modulus = context_data.parms().plain_modulus().value();
+ for (auto v : values_matrix)
+ {
+ // Validate the i-th input
+ if (v >= modulus)
+ {
+ throw invalid_argument("input value is larger than plain_modulus");
+ }
+ }
+#endif
+ // Set destination to full size
+ destination.resize(slots_);
+ destination.parms_id() = parms_id_zero;
+
+ // First write the values to destination coefficients. Read
+ // in top row, then bottom row.
+ using index_type = decltype(values_matrix)::index_type;
+ for (size_t i = 0; i < values_matrix_size; i++)
+ {
+ *(destination.data() + matrix_reps_index_map_[i]) =
+ values_matrix[static_cast(i)];
+ }
+ for (size_t i = values_matrix_size; i < slots_; i++)
+ {
+ *(destination.data() + matrix_reps_index_map_[i]) = 0;
+ }
+
+ // Transform destination using inverse of negacyclic NTT
+ // Note: We already performed bit-reversal when reading in the matrix
+ inverse_ntt_negacyclic_harvey(destination.data(), *context_data.plain_ntt_tables());
+ }
+
+ void BatchEncoder::encode(gsl::span values_matrix,
+ Plaintext &destination)
+ {
+ auto &context_data = *context_->context_data();
+ uint64_t modulus = context_data.parms().plain_modulus().value();
+
+ // Validate input parameters
+ size_t values_matrix_size = static_cast(values_matrix.size());
+ if (values_matrix_size > slots_)
+ {
+ throw logic_error("values_matrix size is too large");
+ }
+#ifdef SEAL_DEBUG
+ uint64_t plain_modulus_div_two = modulus >> 1;
+ for (auto v : values_matrix)
+ {
+ // Validate the i-th input
+ if (unsigned_gt(llabs(v), plain_modulus_div_two))
+ {
+ throw invalid_argument("input value is larger than plain_modulus");
+ }
+ }
+#endif
+ // Set destination to full size
+ destination.resize(slots_);
+ destination.parms_id() = parms_id_zero;
+
+ // First write the values to destination coefficients. Read
+ // in top row, then bottom row.
+ using index_type = decltype(values_matrix)::index_type;
+ for (size_t i = 0; i < values_matrix_size; i++)
+ {
+ *(destination.data() + matrix_reps_index_map_[i]) =
+ (values_matrix[static_cast(i)] < 0) ?
+ (modulus + static_cast(values_matrix[static_cast(i)])) :
+ static_cast(values_matrix[static_cast(i)]);
+ }
+ for (size_t i = values_matrix_size; i < slots_; i++)
+ {
+ *(destination.data() + matrix_reps_index_map_[i]) = 0;
+ }
+
+ // Transform destination using inverse of negacyclic NTT
+ // Note: We already performed bit-reversal when reading in the matrix
+ inverse_ntt_negacyclic_harvey(destination.data(), *context_data.plain_ntt_tables());
+ }
+#endif
+ void BatchEncoder::encode(Plaintext &plain, MemoryPoolHandle pool)
+ {
+ if (plain.is_ntt_form())
+ {
+ throw invalid_argument("plain cannot be in NTT form");
+ }
+ if (!pool)
+ {
+ throw invalid_argument("pool is uninitialized");
+ }
+
+ auto &context_data = *context_->context_data();
+
+ // Validate input parameters
+ if (plain.coeff_count() > context_data.parms().poly_modulus_degree())
+ {
+ throw invalid_argument("plain is not valid for encryption parameters");
+ }
+#ifdef SEAL_DEBUG
+ if (!are_poly_coefficients_less_than(plain.data(),
+ plain.coeff_count(), context_data.parms().plain_modulus().value()))
+ {
+ throw invalid_argument("plain is not valid for encryption parameters");
+ }
+#endif
+ // We need to permute the coefficients of plain. To do this, we allocate
+ // temporary space.
+ size_t input_plain_coeff_count = min(plain.coeff_count(), slots_);
+ auto temp(allocate_uint(input_plain_coeff_count, pool));
+ set_uint_uint(plain.data(), input_plain_coeff_count, temp.get());
+
+ // Set plain to full slot count size.
+ plain.resize(slots_);
+ plain.parms_id() = parms_id_zero;
+
+ // First write the values to destination coefficients. Read
+ // in top row, then bottom row.
+ for (size_t i = 0; i < input_plain_coeff_count; i++)
+ {
+ *(plain.data() + matrix_reps_index_map_[i]) = temp[i];
+ }
+ for (size_t i = input_plain_coeff_count; i < slots_; i++)
+ {
+ *(plain.data() + matrix_reps_index_map_[i]) = 0;
+ }
+
+ // Transform destination using inverse of negacyclic NTT
+ // Note: We already performed bit-reversal when reading in the matrix
+ inverse_ntt_negacyclic_harvey(plain.data(), *context_data.plain_ntt_tables());
+ }
+
+ void BatchEncoder::decode(const Plaintext &plain, vector &destination,
+ MemoryPoolHandle pool)
+ {
+ if (plain.is_ntt_form())
+ {
+ throw invalid_argument("plain cannot be in NTT form");
+ }
+ if (!pool)
+ {
+ throw invalid_argument("pool is uninitialized");
+ }
+
+ auto &context_data = *context_->context_data();
+
+ // Validate input parameters
+ if (plain.coeff_count() > context_data.parms().poly_modulus_degree())
+ {
+ throw invalid_argument("plain is not valid for encryption parameters");
+ }
+#ifdef SEAL_DEBUG
+ if (!are_poly_coefficients_less_than(plain.data(),
+ plain.coeff_count(), context_data.parms().plain_modulus().value()))
+ {
+ throw invalid_argument("plain is not valid for encryption parameters");
+ }
+#endif
+ // Set destination size
+ destination.resize(slots_);
+
+ // Never include the leading zero coefficient (if present)
+ size_t plain_coeff_count = min(plain.coeff_count(), slots_);
+
+ auto temp_dest(allocate_uint(slots_, pool));
+
+ // Make a copy of poly
+ set_uint_uint(plain.data(), plain_coeff_count, temp_dest.get());
+ set_zero_uint(slots_ - plain_coeff_count, temp_dest.get() + plain_coeff_count);
+
+ // Transform destination using negacyclic NTT.
+ ntt_negacyclic_harvey(temp_dest.get(), *context_data.plain_ntt_tables());
+
+ // Read top row
+ for (size_t i = 0; i < slots_; i++)
+ {
+ destination[i] = temp_dest[matrix_reps_index_map_[i]];
+ }
+ }
+
+ void BatchEncoder::decode(const Plaintext &plain, vector &destination,
+ MemoryPoolHandle pool)
+ {
+ if (plain.is_ntt_form())
+ {
+ throw invalid_argument("plain cannot be in NTT form");
+ }
+ if (!pool)
+ {
+ throw invalid_argument("pool is uninitialized");
+ }
+
+ auto &context_data = *context_->context_data();
+ uint64_t modulus = context_data.parms().plain_modulus().value();
+
+ // Validate input parameters
+ if (plain.coeff_count() > context_data.parms().poly_modulus_degree())
+ {
+ throw invalid_argument("plain is not valid for encryption parameters");
+ }
+#ifdef SEAL_DEBUG
+ if (!are_poly_coefficients_less_than(plain.data(), plain.coeff_count(), modulus))
+ {
+ throw invalid_argument("plain is not valid for encryption parameters");
+ }
+#endif
+ // Set destination size
+ destination.resize(slots_);
+
+ // Never include the leading zero coefficient (if present)
+ size_t plain_coeff_count = min(plain.coeff_count(), slots_);
+
+ auto temp_dest(allocate_uint(slots_, pool));
+
+ // Make a copy of poly
+ set_uint_uint(plain.data(), plain_coeff_count, temp_dest.get());
+ set_zero_uint(slots_ - plain_coeff_count, temp_dest.get() + plain_coeff_count);
+
+ // Transform destination using negacyclic NTT.
+ ntt_negacyclic_harvey(temp_dest.get(), *context_data.plain_ntt_tables());
+
+ // Read top row, then bottom row
+ uint64_t plain_modulus_div_two = modulus >> 1;
+ for (size_t i = 0; i < slots_; i++)
+ {
+ uint64_t curr_value = temp_dest[matrix_reps_index_map_[i]];
+ destination[i] = (curr_value > plain_modulus_div_two) ?
+ (static_cast(curr_value) - static_cast(modulus)) :
+ static_cast(curr_value);
+ }
+ }
+#ifdef SEAL_USE_MSGSL_SPAN
+ void BatchEncoder::decode(const Plaintext &plain, gsl::span destination,
+ MemoryPoolHandle pool)
+ {
+ if (plain.is_ntt_form())
+ {
+ throw invalid_argument("plain cannot be in NTT form");
+ }
+ if (!pool)
+ {
+ throw invalid_argument("pool is uninitialized");
+ }
+
+ auto &context_data = *context_->context_data();
+
+ // Validate input parameters
+ if (plain.coeff_count() > context_data.parms().poly_modulus_degree())
+ {
+ throw invalid_argument("plain is not valid for encryption parameters");
+ }
+#ifdef SEAL_DEBUG
+ if (!are_poly_coefficients_less_than(plain.data(),
+ plain.coeff_count(), context_data.parms().plain_modulus().value()))
+ {
+ throw invalid_argument("plain is not valid for encryption parameters");
+ }
+#endif
+ using index_type = decltype(destination)::index_type;
+ if(unsigned_gt(destination.size(), numeric_limits::max()) ||
+ unsigned_neq(destination.size(), slots_))
+ {
+ throw invalid_argument("destination has incorrect size");
+ }
+
+ // Never include the leading zero coefficient (if present)
+ size_t plain_coeff_count = min(plain.coeff_count(), slots_);
+
+ auto temp_dest(allocate_uint(slots_, pool));
+
+ // Make a copy of poly
+ set_uint_uint(plain.data(), plain_coeff_count, temp_dest.get());
+ set_zero_uint(slots_ - plain_coeff_count, temp_dest.get() + plain_coeff_count);
+
+ // Transform destination using negacyclic NTT.
+ ntt_negacyclic_harvey(temp_dest.get(), *context_data.plain_ntt_tables());
+
+ // Read top row
+ for (size_t i = 0; i < slots_; i++)
+ {
+ destination[static_cast(i)] = temp_dest[matrix_reps_index_map_[i]];
+ }
+ }
+
+ void BatchEncoder::decode(const Plaintext &plain, gsl::span destination,
+ MemoryPoolHandle pool)
+ {
+ if (plain.is_ntt_form())
+ {
+ throw invalid_argument("plain cannot be in NTT form");
+ }
+ if (!pool)
+ {
+ throw invalid_argument("pool is uninitialized");
+ }
+
+ auto &context_data = *context_->context_data();
+ uint64_t modulus = context_data.parms().plain_modulus().value();
+
+ // Validate input parameters
+ if (plain.coeff_count() > context_data.parms().poly_modulus_degree())
+ {
+ throw invalid_argument("plain is not valid for encryption parameters");
+ }
+#ifdef SEAL_DEBUG
+ if (!are_poly_coefficients_less_than(plain.data(), plain.coeff_count(), modulus))
+ {
+ throw invalid_argument("plain is not valid for encryption parameters");
+ }
+#endif
+ using index_type = decltype(destination)::index_type;
+ if(unsigned_gt(destination.size(), numeric_limits::max()) ||
+ unsigned_neq(destination.size(), slots_))
+ {
+ throw invalid_argument("destination has incorrect size");
+ }
+
+ // Never include the leading zero coefficient (if present)
+ size_t plain_coeff_count = min(plain.coeff_count(), slots_);
+
+ auto temp_dest(allocate_uint(slots_, pool));
+
+ // Make a copy of poly
+ set_uint_uint(plain.data(), plain_coeff_count, temp_dest.get());
+ set_zero_uint(slots_ - plain_coeff_count, temp_dest.get() + plain_coeff_count);
+
+ // Transform destination using negacyclic NTT.
+ ntt_negacyclic_harvey(temp_dest.get(), *context_data.plain_ntt_tables());
+
+ // Read top row, then bottom row
+ uint64_t plain_modulus_div_two = modulus >> 1;
+ for (size_t i = 0; i < slots_; i++)
+ {
+ uint64_t curr_value = temp_dest[matrix_reps_index_map_[i]];
+ destination[static_cast(i)] = (curr_value > plain_modulus_div_two) ?
+ (static_cast(curr_value) - static_cast(modulus)) :
+ static_cast(curr_value);
+ }
+ }
+#endif
+ void BatchEncoder::decode(Plaintext &plain, MemoryPoolHandle pool)
+ {
+ if (plain.is_ntt_form())
+ {
+ throw invalid_argument("plain cannot be in NTT form");
+ }
+ if (!pool)
+ {
+ throw invalid_argument("pool is uninitialized");
+ }
+
+ auto &context_data = *context_->context_data();
+
+ // Validate input parameters
+ if (plain.coeff_count() > context_data.parms().poly_modulus_degree())
+ {
+ throw invalid_argument("plain is not valid for encryption parameters");
+ }
+#ifdef SEAL_DEBUG
+ if (!are_poly_coefficients_less_than(plain.data(),
+ plain.coeff_count(), context_data.parms().plain_modulus().value()))
+ {
+ throw invalid_argument("plain is not valid for encryption parameters");
+ }
+#endif
+ // Never include the leading zero coefficient (if present)
+ size_t plain_coeff_count = min(plain.coeff_count(), slots_);
+
+ // Allocate temporary space to store a wide copy of plain
+ auto temp(allocate_uint(slots_, pool));
+
+ // Make a copy of poly
+ set_uint_uint(plain.data(), plain_coeff_count, temp.get());
+ set_zero_uint(slots_ - plain_coeff_count, temp.get() + plain_coeff_count);
+
+ // Transform destination using negacyclic NTT.
+ ntt_negacyclic_harvey(temp.get(), *context_data.plain_ntt_tables());
+
+ // Set plain to full slot count size (note that all new coefficients are
+ // set to zero).
+ plain.resize(slots_);
+
+ // Read top row, then bottom row
+ for (size_t i = 0; i < slots_; i++)
+ {
+ *(plain.data() + i) = temp[matrix_reps_index_map_[i]];
+ }
+ }
+}
diff --git a/src/seal/batchencoder.h b/src/seal/batchencoder.h
new file mode 100644
index 000000000..e76c037e2
--- /dev/null
+++ b/src/seal/batchencoder.h
@@ -0,0 +1,405 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+#pragma once
+
+#include
+#include
+#include "seal/util/defines.h"
+#include "seal/util/common.h"
+#include "seal/util/uintcore.h"
+#include "seal/util/uintarithsmallmod.h"
+#include "seal/plaintext.h"
+#include "seal/context.h"
+
+namespace seal
+{
+ /**
+ Provides functionality for CRT batching. If the polynomial modulus degree is N, and
+ the plaintext modulus is a prime number T such that T is congruent to 1 modulo 2N,
+ then BatchEncoder allows the SEAL plaintext elements to be viewed as 2-by-(N/2)
+ matrices of integers modulo T. Homomorphic operations performed on such encrypted
+ matrices are applied coefficient (slot) wise, enabling powerful SIMD functionality
+ for computations that are vectorizable. This functionality is often called "batching"
+ in the homomorphic encryption literature.
+
+ @par Mathematical Background
+ Mathematically speaking, if the polynomial modulus is X^N+1, N is a power of two, and
+ plain_modulus is a prime number T such that 2N divides T-1, then integers modulo T
+ contain a primitive 2N-th root of unity and the polynomial X^N+1 splits into n distinct
+ linear factors as X^N+1 = (X-a_1)*...*(X-a_N) mod T, where the constants a_1, ..., a_n
+ are all the distinct primitive 2N-th roots of unity in integers modulo T. The Chinese
+ Remainder Theorem (CRT) states that the plaintext space Z_T[X]/(X^N+1) in this case is
+ isomorphic (as an algebra) to the N-fold direct product of fields Z_T. The isomorphism
+ is easy to compute explicitly in both directions, which is what this class does.
+ Furthermore, the Galois group of the extension is (Z/2NZ)* ~= Z/2Z x Z/(N/2) whose
+ action on the primitive roots of unity is easy to describe. Since the batching slots
+ correspond 1-to-1 to the primitive roots of unity, applying Galois automorphisms on the
+ plaintext act by permuting the slots. By applying generators of the two cyclic
+ subgroups of the Galois group, we can effectively view the plaintext as a 2-by-(N/2)
+ matrix, and enable cyclic row rotations, and column rotations (row swaps).
+
+ @par Valid Parameters
+ Whether batching can be used depends on whether the plaintext modulus has been chosen
+ appropriately. Thus, to construct a BatchEncoder the user must provide an instance
+ of SEALContext such that its associated EncryptionParameterQualifiers object has the
+ flags parameters_set and enable_batching set to true.
+
+ @see EncryptionParameters for more information about encryption parameters.
+ @see EncryptionParameterQualifiers for more information about parameter qualifiers.
+ @see Evaluator for rotating rows and columns of encrypted matrices.
+ */
+ class BatchEncoder
+ {
+ public:
+ /**
+ Creates a BatchEncoder. It is necessary that the encryption parameters
+ given through the SEALContext object support batching.
+
+ @param[in] context The SEALContext
+ @throws std::invalid_argument if the context is not set or encryption
+ parameters are not valid for batching
+ @throws std::invalid_argument if scheme is not scheme_type::BFV
+ */
+ BatchEncoder(std::shared_ptr context);
+
+ /**
+ Creates a SEAL plaintext from a given matrix. This function "batches" a given matrix
+ of integers modulo the plaintext modulus into a SEAL plaintext element, and stores
+ the result in the destination parameter. The input vector must have size at most equal
+ to the degree of the polynomial modulus. The first half of the elements represent the
+ first row of the matrix, and the second half represent the second row. The numbers
+ in the matrix can be at most equal to the plaintext modulus for it to represent
+ a valid SEAL plaintext.
+
+ If the destination plaintext overlaps the input values in memory, the behavior of
+ this function is undefined.
+
+ @param[in] values The matrix of integers modulo plaintext modulus to batch
+ @param[out] destination The plaintext polynomial to overwrite with the result
+ @throws std::invalid_argument if values is too large
+ */
+ void encode(const std::vector &values, Plaintext &destination);
+
+ /**
+ Creates a SEAL plaintext from a given matrix. This function "batches" a given matrix
+ of integers modulo the plaintext modulus into a SEAL plaintext element, and stores
+ the result in the destination parameter. The input vector must have size at most equal
+ to the degree of the polynomial modulus. The first half of the elements represent the
+ first row of the matrix, and the second half represent the second row. The numbers
+ in the matrix can be at most equal to the plaintext modulus for it to represent
+ a valid SEAL plaintext.
+
+ If the destination plaintext overlaps the input values in memory, the behavior of
+ this function is undefined.
+
+ @param[in] values The matrix of integers modulo plaintext modulus to batch
+ @param[out] destination The plaintext polynomial to overwrite with the result
+ @throws std::invalid_argument if values is too large
+ */
+ void encode(const std::vector &values, Plaintext &destination);
+#ifdef SEAL_USE_MSGSL_SPAN
+ /**
+ Creates a SEAL plaintext from a given matrix. This function "batches" a given matrix
+ of integers modulo the plaintext modulus into a SEAL plaintext element, and stores
+ the result in the destination parameter. The input vector must have size at most equal
+ to the degree of the polynomial modulus. The first half of the elements represent the
+ first row of the matrix, and the second half represent the second row. The numbers
+ in the matrix can be at most equal to the plaintext modulus for it to represent
+ a valid SEAL plaintext.
+
+ If the destination plaintext overlaps the input values in memory, the behavior of
+ this function is undefined.
+
+ @param[in] values The matrix of integers modulo plaintext modulus to batch
+ @param[out] destination The plaintext polynomial to overwrite with the result
+ @throws std::invalid_argument if values is too large
+ */
+ void encode(gsl::span values, Plaintext &destination);
+
+ /**
+ Creates a SEAL plaintext from a given matrix. This function "batches" a given matrix
+ of integers modulo the plaintext modulus into a SEAL plaintext element, and stores
+ the result in the destination parameter. The input vector must have size at most equal
+ to the degree of the polynomial modulus. The first half of the elements represent the
+ first row of the matrix, and the second half represent the second row. The numbers
+ in the matrix can be at most equal to the plaintext modulus for it to represent
+ a valid SEAL plaintext.
+
+ If the destination plaintext overlaps the input values in memory, the behavior of
+ this function is undefined.
+
+ @param[in] values The matrix of integers modulo plaintext modulus to batch
+ @param[out] destination The plaintext polynomial to overwrite with the result
+ @throws std::invalid_argument if values is too large
+ */
+ void encode(gsl::span values, Plaintext &destination);
+#ifdef SEAL_USE_MSGSL_MULTISPAN
+ /**
+ Creates a SEAL plaintext from a given matrix. This function "batches" a given matrix
+ of integers modulo the plaintext modulus into a SEAL plaintext element, and stores
+ the result in the destination parameter. The input must have dimensions [2, N/2],
+ where N denotes the degree of the polynomial modulus, representing a 2 x (N/2)
+ matrix. The numbers in the matrix can be at most equal to the plaintext modulus for
+ it to represent a valid SEAL plaintext.
+
+ If the destination plaintext overlaps the input values in memory, the behavior of
+ this function is undefined.
+
+ @param[in] values The matrix of integers modulo plaintext modulus to batch
+ @param[out] destination The plaintext polynomial to overwrite with the result
+ @throws std::invalid_argument if values is too large or has incorrect size
+ */
+ inline void encode(gsl::multi_span<
+ const std::uint64_t,
+ static_cast(2),
+ gsl::dynamic_range> values, Plaintext &destination)
+ {
+ encode(gsl::span(values.data(), values.size()),
+ destination);
+ }
+
+ /**
+ Creates a SEAL plaintext from a given matrix. This function "batches" a given matrix
+ of integers modulo the plaintext modulus into a SEAL plaintext element, and stores
+ the result in the destination parameter. The input must have dimensions [2, N/2],
+ where N denotes the degree of the polynomial modulus, representing a 2 x (N/2)
+ matrix. The numbers in the matrix can be at most equal to the plaintext modulus for
+ it to represent a valid SEAL plaintext.
+
+ If the destination plaintext overlaps the input values in memory, the behavior of
+ this function is undefined.
+
+ @param[in] values The matrix of integers modulo plaintext modulus to batch
+ @param[out] destination The plaintext polynomial to overwrite with the result
+ @throws std::invalid_argument if values is too large or has incorrect size
+ */
+ inline void encode(gsl::multi_span<
+ const std::int64_t,
+ static_cast(2),
+ gsl::dynamic_range> values, Plaintext &destination)
+ {
+ encode(gsl::span(values.data(), values.size()),
+ destination);
+ }
+#endif
+#endif
+ /**
+ Creates a SEAL plaintext from a given matrix. This function "batches" a given matrix
+ of integers modulo the plaintext modulus in-place into a SEAL plaintext ready to be
+ encrypted. The matrix is given as a plaintext element whose first N/2 coefficients
+ represent the first row of the matrix, and the second N/2 coefficients represent the
+ second row, where N denotes the degree of the polynomial modulus. The input plaintext
+ must have degress less than the polynomial modulus, and coefficients less than the
+ plaintext modulus, i.e. it must be a valid plaintext for the encryption parameters.
+ Dynamic memory allocations in the process are allocated from the memory pool pointed
+ to by the given MemoryPoolHandle.
+
+ @param[in] plain The matrix of integers modulo plaintext modulus to batch
+ @param[in] pool The MemoryPoolHandle pointing to a valid memory pool
+ @throws std::invalid_argument if plain is not valid for the encryption parameters
+ @throws std::invalid_argument if plain is in NTT form
+ @throws std::invalid_argument if pool is uninitialized
+ */
+ void encode(Plaintext &plain, MemoryPoolHandle pool = MemoryManager::GetPool());
+
+ /**
+ Inverse of encode. This function "unbatches" a given SEAL plaintext into a matrix
+ of integers modulo the plaintext modulus, and stores the result in the destination
+ parameter. The input plaintext must have degress less than the polynomial modulus,
+ and coefficients less than the plaintext modulus, i.e. it must be a valid plaintext
+ for the encryption parameters. Dynamic memory allocations in the process are
+ allocated from the memory pool pointed to by the given MemoryPoolHandle.
+
+ @param[in] plain The plaintext polynomial to unbatch
+ @param[out] destination The matrix to be overwritten with the values in the slots
+ @param[in] pool The MemoryPoolHandle pointing to a valid memory pool
+ @throws std::invalid_argument if plain is not valid for the encryption parameters
+ @throws std::invalid_argument if plain is in NTT form
+ @throws std::invalid_argument if pool is uninitialized
+ */
+ void decode(const Plaintext &plain, std::vector &destination,
+ MemoryPoolHandle pool = MemoryManager::GetPool());
+
+ /**
+ Inverse of encode. This function "unbatches" a given SEAL plaintext into a matrix
+ of integers modulo the plaintext modulus, and stores the result in the destination
+ parameter. The input plaintext must have degress less than the polynomial modulus,
+ and coefficients less than the plaintext modulus, i.e. it must be a valid plaintext
+ for the encryption parameters. Dynamic memory allocations in the process are
+ allocated from the memory pool pointed to by the given MemoryPoolHandle.
+
+ @param[in] plain The plaintext polynomial to unbatch
+ @param[out] destination The matrix to be overwritten with the values in the slots
+ @param[in] pool The MemoryPoolHandle pointing to a valid memory pool
+ @throws std::invalid_argument if plain is not valid for the encryption parameters
+ @throws std::invalid_argument if plain is in NTT form
+ @throws std::invalid_argument if pool is uninitialized
+ */
+ void decode(const Plaintext &plain, std::vector &destination,
+ MemoryPoolHandle pool = MemoryManager::GetPool());
+#ifdef SEAL_USE_MSGSL_SPAN
+ /**
+ Inverse of encode. This function "unbatches" a given SEAL plaintext into a matrix
+ of integers modulo the plaintext modulus, and stores the result in the destination
+ parameter. The input plaintext must have degress less than the polynomial modulus,
+ and coefficients less than the plaintext modulus, i.e. it must be a valid plaintext
+ for the encryption parameters. Dynamic memory allocations in the process are
+ allocated from the memory pool pointed to by the given MemoryPoolHandle.
+
+ @param[in] plain The plaintext polynomial to unbatch
+ @param[out] destination The matrix to be overwritten with the values in the slots
+ @param[in] pool The MemoryPoolHandle pointing to a valid memory pool
+ @throws std::invalid_argument if plain is not valid for the encryption parameters
+ @throws std::invalid_argument if plain is in NTT form
+ @throws std::invalid_argument if destination has incorrect size
+ @throws std::invalid_argument if pool is uninitialized
+ */
+ void decode(const Plaintext &plain, gsl::span destination,
+ MemoryPoolHandle pool = MemoryManager::GetPool());
+
+ /**
+ Inverse of encode. This function "unbatches" a given SEAL plaintext into a matrix
+ of integers modulo the plaintext modulus, and stores the result in the destination
+ parameter. The input plaintext must have degress less than the polynomial modulus,
+ and coefficients less than the plaintext modulus, i.e. it must be a valid plaintext
+ for the encryption parameters. Dynamic memory allocations in the process are
+ allocated from the memory pool pointed to by the given MemoryPoolHandle.
+
+ @param[in] plain The plaintext polynomial to unbatch
+ @param[out] destination The matrix to be overwritten with the values in the slots
+ @param[in] pool The MemoryPoolHandle pointing to a valid memory pool
+ @throws std::invalid_argument if plain is not valid for the encryption parameters
+ @throws std::invalid_argument if plain is in NTT form
+ @throws std::invalid_argument if destination has incorrect size
+ @throws std::invalid_argument if pool is uninitialized
+ */
+ void decode(const Plaintext &plain, gsl::span destination,
+ MemoryPoolHandle pool = MemoryManager::GetPool());
+#ifdef SEAL_USE_MSGSL_MULTISPAN
+ /**
+ Inverse of encode. This function "unbatches" a given SEAL plaintext into a matrix
+ of integers modulo the plaintext modulus, and stores the result in the destination
+ parameter. The destination must have dimensions [2, N/2], where N denotes the degree
+ of the polynomial modulus, representing a 2 x (N/2) matrix. The input plaintext must
+ have degress less than the polynomial modulus, and coefficients less than the
+ plaintext modulus, i.e. it must be a valid plaintext for the encryption parameters.
+ Dynamic memory allocations in the process are allocated from the memory pool pointed
+ to by the given MemoryPoolHandle.
+
+ @param[in] plain The plaintext polynomial to unbatch
+ @param[out] destination The matrix to be overwritten with the values in the slots
+ @param[in] pool The MemoryPoolHandle pointing to a valid memory pool
+ @throws std::invalid_argument if plain is not valid for the encryption parameters
+ @throws std::invalid_argument if plain is in NTT form
+ @throws std::invalid_argument if destination has incorrect size
+ @throws std::invalid_argument if pool is uninitialized
+ */
+ inline void decode(const Plaintext &plain,
+ gsl::multi_span(2),
+ gsl::dynamic_range> destination,
+ MemoryPoolHandle pool = MemoryManager::GetPool())
+ {
+ decode(plain, gsl::span(destination.data(),
+ destination.size()), std::move(pool));
+ }
+
+ /**
+ Inverse of encode. This function "unbatches" a given SEAL plaintext into a matrix
+ of integers modulo the plaintext modulus, and stores the result in the destination
+ parameter. The destination must have dimensions [2, N/2], where N denotes the degree
+ of the polynomial modulus, representing a 2 x (N/2) matrix. The input plaintext must
+ have degress less than the polynomial modulus, and coefficients less than the
+ plaintext modulus, i.e. it must be a valid plaintext for the encryption parameters.
+ Dynamic memory allocations in the process are allocated from the memory pool pointed
+ to by the given MemoryPoolHandle.
+
+ @param[in] plain The plaintext polynomial to unbatch
+ @param[out] destination The matrix to be overwritten with the values in the slots
+ @param[in] pool The MemoryPoolHandle pointing to a valid memory pool
+ @throws std::invalid_argument if plain is not valid for the encryption parameters
+ @throws std::invalid_argument if plain is in NTT form
+ @throws std::invalid_argument if destination has incorrect size
+ @throws std::invalid_argument if pool is uninitialized
+ */
+ inline void decode(const Plaintext &plain,
+ gsl::multi_span(2),
+ gsl::dynamic_range> destination,
+ MemoryPoolHandle pool = MemoryManager::GetPool())
+ {
+ decode(plain, gsl::span(destination.data(),
+ destination.size()), std::move(pool));
+ }
+#endif
+#endif
+ /**
+ Inverse of encode. This function "unbatches" a given SEAL plaintext in-place into
+ a matrix of integers modulo the plaintext modulus. The input plaintext must have
+ degress less than the polynomial modulus, and coefficients less than the plaintext
+ modulus, i.e. it must be a valid plaintext for the encryption parameters. Dynamic
+ memory allocations in the process are allocated from the memory pool pointed to by
+ the given MemoryPoolHandle.
+
+ @param[in] plain The plaintext polynomial to unbatch
+ @param[in] pool The MemoryPoolHandle pointing to a valid memory pool
+ @throws std::invalid_argument if plain is not valid for the encryption parameters
+ @throws std::invalid_argument if plain is in NTT form
+ @throws std::invalid_argument if pool is uninitialized
+ */
+ void decode(Plaintext &plain, MemoryPoolHandle pool = MemoryManager::GetPool());
+
+ /**
+ Returns the number of slots.
+ */
+ inline auto slot_count() const noexcept
+ {
+ return slots_;
+ }
+
+ private:
+ BatchEncoder(const BatchEncoder ©) = delete;
+
+ BatchEncoder(BatchEncoder &&source) = delete;
+
+ BatchEncoder &operator =(const BatchEncoder &assign) = delete;
+
+ BatchEncoder &operator =(BatchEncoder &&assign) = delete;
+
+ void populate_roots_of_unity_vector(
+ const SEALContext::ContextData &context_data);
+
+ void populate_matrix_reps_index_map();
+
+ inline void reverse_bits(std::uint64_t *input)
+ {
+#ifdef SEAL_DEBUG
+ if (input == nullptr)
+ {
+ throw std::invalid_argument("input cannot be null");
+ }
+#endif
+ std::size_t coeff_count = context_->context_data()->parms().poly_modulus_degree();
+ int logn = util::get_power_of_two(coeff_count);
+ for (std::size_t i = 0; i < coeff_count; i++)
+ {
+ std::uint64_t reversed_i = util::reverse_bits(i, logn);
+ if (i < reversed_i)
+ {
+ std::swap(input[i], input[reversed_i]);
+ }
+ }
+ }
+
+ MemoryPoolHandle pool_ = MemoryManager::GetPool();
+
+ std::shared_ptr context_{ nullptr };
+
+ std::size_t slots_;
+
+ util::Pointer roots_of_unity_;
+
+ util::Pointer matrix_reps_index_map_;
+ };
+}
diff --git a/src/seal/biguint.cpp b/src/seal/biguint.cpp
new file mode 100644
index 000000000..d6cd6d903
--- /dev/null
+++ b/src/seal/biguint.cpp
@@ -0,0 +1,312 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+#include "seal/biguint.h"
+#include "seal/util/common.h"
+#include "seal/util/uintcore.h"
+#include "seal/util/uintarith.h"
+#include "seal/util/uintarithmod.h"
+#include
+
+using namespace std;
+using namespace seal::util;
+
+namespace seal
+{
+ BigUInt::BigUInt(int bit_count)
+ {
+ resize(bit_count);
+ }
+
+ BigUInt::BigUInt(const string &hex_value)
+ {
+ operator =(hex_value);
+ }
+
+ BigUInt::BigUInt(int bit_count, const string &hex_value)
+ {
+ resize(bit_count);
+ operator =(hex_value);
+ if (bit_count != bit_count_)
+ {
+ resize(bit_count);
+ }
+ }
+
+ BigUInt::BigUInt(int bit_count, uint64_t *value) :
+ value_(decltype(value_)::Aliasing(value)), bit_count_(bit_count)
+ {
+ if (bit_count < 0)
+ {
+ throw invalid_argument("bit_count must be non-negative");
+ }
+ if (value == nullptr && bit_count > 0)
+ {
+ throw invalid_argument("value must be non-null for non-zero bit count");
+ }
+ }
+#ifdef SEAL_USE_MSGSL_SPAN
+ BigUInt::BigUInt(gsl::span value)
+ {
+ if(unsigned_gt(value.size(), numeric_limits::max() / bits_per_uint64))
+ {
+ throw std::invalid_argument("value has too large size");
+ }
+ value_ = decltype(value_)::Aliasing(value.data());
+ bit_count_ = static_cast(value.size()) * bits_per_uint64;
+ }
+#endif
+ BigUInt::BigUInt(int bit_count, uint64_t value)
+ {
+ resize(bit_count);
+ operator =(value);
+ if (bit_count != bit_count_)
+ {
+ resize(bit_count);
+ }
+ }
+
+ BigUInt::BigUInt(const BigUInt ©)
+ {
+ resize(copy.bit_count());
+ operator =(copy);
+ }
+
+ BigUInt::BigUInt(BigUInt &&source) noexcept :
+ pool_(move(source.pool_)),
+ value_(move(source.value_)),
+ bit_count_(source.bit_count_)
+ {
+ // Pointer in source has been taken over so set it to nullptr
+ source.bit_count_ = 0;
+ }
+
+ BigUInt::~BigUInt() noexcept
+ {
+ reset();
+ }
+
+ string BigUInt::to_string() const
+ {
+ return uint_to_hex_string(value_.get(), uint64_count());
+ }
+
+ string BigUInt::to_dec_string() const
+ {
+ return uint_to_dec_string(value_.get(), uint64_count(), pool_);
+ }
+
+ void BigUInt::resize(int bit_count)
+ {
+ if (bit_count < 0)
+ {
+ throw invalid_argument("bit_count must be non-negative");
+ }
+ if (value_.is_alias())
+ {
+ throw logic_error("Cannot resize an aliased BigUInt");
+ }
+ if (bit_count == bit_count_)
+ {
+ return;
+ }
+
+ // Lazy initialization of MemoryPoolHandle
+ if (!pool_)
+ {
+ pool_ = MemoryManager::GetPool();
+ }
+
+ // Fast path if allocation size doesn't change.
+ size_t old_uint64_count = uint64_count();
+ size_t new_uint64_count = safe_cast(
+ divide_round_up(bit_count, bits_per_uint64));
+ if (old_uint64_count == new_uint64_count)
+ {
+ bit_count_ = bit_count;
+ return;
+ }
+
+ // Allocate new space.
+ decltype(value_) new_value;
+ if (new_uint64_count > 0)
+ {
+ new_value.swap_with(allocate_uint(new_uint64_count, pool_));
+ }
+
+ // Copy over old value.
+ if (new_uint64_count > 0)
+ {
+ set_uint_uint(value_.get(), old_uint64_count, new_uint64_count, new_value.get());
+ filter_highbits_uint(new_value.get(), new_uint64_count, bit_count);
+ }
+
+ // Deallocate any owned pointers.
+ reset();
+
+ // Update class.
+ value_.swap_with(new_value);
+ bit_count_ = bit_count;
+ }
+
+ BigUInt &BigUInt::operator =(const BigUInt& assign)
+ {
+ // Do nothing if same thing.
+ if (&assign == this)
+ {
+ return *this;
+ }
+
+ // Verify assigned value will fit within bit count.
+ int assign_sig_bit_count = assign.significant_bit_count();
+ if (assign_sig_bit_count > bit_count_)
+ {
+ // Size is too large to currently fit, so resize.
+ resize(assign_sig_bit_count);
+ }
+
+ // Copy over value.
+ size_t assign_uint64_count = safe_cast(
+ divide_round_up(assign_sig_bit_count, bits_per_uint64));
+ if (uint64_count() > 0)
+ {
+ set_uint_uint(assign.value_.get(), assign_uint64_count,
+ uint64_count(), value_.get());
+ }
+ return *this;
+ }
+
+ BigUInt &BigUInt::operator =(const string &hex_value)
+ {
+ int hex_value_length = safe_cast(hex_value.size());
+
+ int assign_bit_count = get_hex_string_bit_count(hex_value.data(), hex_value_length);
+ if (assign_bit_count > bit_count_)
+ {
+ // Size is too large to currently fit, so resize.
+ resize(assign_bit_count);
+ }
+ if (bit_count_ > 0)
+ {
+ // Copy over value.
+ hex_string_to_uint(hex_value.data(), hex_value_length, uint64_count(), value_.get());
+ }
+ return *this;
+ }
+
+ BigUInt BigUInt::operator /(const BigUInt& operand2) const
+ {
+ int result_bits = significant_bit_count();
+ int operand2_bits = operand2.significant_bit_count();
+ if (operand2_bits == 0)
+ {
+ throw invalid_argument("operand2 must be positive");
+ }
+ if (operand2_bits > result_bits)
+ {
+ BigUInt zero(result_bits);
+ return zero;
+ }
+ BigUInt result(result_bits);
+ BigUInt remainder(result_bits);
+ size_t result_uint64_count = result.uint64_count();
+ if (result_uint64_count > operand2.uint64_count())
+ {
+ BigUInt operand2resized(result_bits);
+ operand2resized = operand2;
+ divide_uint_uint(value_.get(), operand2resized.data(), result_uint64_count,
+ result.data(), remainder.data(), pool_);
+ }
+ else
+ {
+ divide_uint_uint(value_.get(), operand2.data(), result_uint64_count,
+ result.data(), remainder.data(), pool_);
+ }
+ return result;
+ }
+
+ BigUInt BigUInt::divrem(const BigUInt& operand2, BigUInt &remainder) const
+ {
+ int result_bits = significant_bit_count();
+ remainder = *this;
+ int operand2_bits = operand2.significant_bit_count();
+ if (operand2_bits > result_bits)
+ {
+ BigUInt zero;
+ return zero;
+ }
+ BigUInt quotient(result_bits);
+ size_t uint64_count = remainder.uint64_count();
+ if (uint64_count > operand2.uint64_count())
+ {
+ BigUInt operand2resized(result_bits);
+ operand2resized = operand2;
+ divide_uint_uint_inplace(remainder.data(), operand2resized.data(),
+ uint64_count, quotient.data(), pool_);
+ }
+ else
+ {
+ divide_uint_uint_inplace(remainder.data(), operand2.data(),
+ uint64_count, quotient.data(), pool_);
+ }
+ return quotient;
+ }
+
+ void BigUInt::save(ostream &stream) const
+ {
+ auto old_except_mask = stream.exceptions();
+ try
+ {
+ // Throw exceptions on std::ios_base::badbit and std::ios_base::failbit
+ stream.exceptions(ios_base::badbit | ios_base::failbit);
+
+ int32_t bit_count32 = safe_cast(bit_count_);
+ streamsize data_size = safe_cast(mul_safe(uint64_count(), sizeof(uint64_t)));
+ stream.write(reinterpret_cast(&bit_count32), sizeof(int32_t));
+ stream.write(reinterpret_cast(value_.get()), data_size);
+ }
+ catch (const exception &)
+ {
+ stream.exceptions(old_except_mask);
+ throw;
+ }
+
+ stream.exceptions(old_except_mask);
+ }
+
+ void BigUInt::load(istream &stream)
+ {
+ auto old_except_mask = stream.exceptions();
+ try
+ {
+ // Throw exceptions on std::ios_base::badbit and std::ios_base::failbit
+ stream.exceptions(ios_base::badbit | ios_base::failbit);
+
+ int32_t read_bit_count = 0;
+ stream.read(reinterpret_cast(&read_bit_count), sizeof(int32_t));
+ if (read_bit_count > bit_count_)
+ {
+ // Size is too large to currently fit, so resize.
+ resize(read_bit_count);
+ }
+ size_t read_uint64_count = safe_cast(
+ divide_round_up(read_bit_count, bits_per_uint64));
+ streamsize data_size = safe_cast(mul_safe(read_uint64_count, sizeof(uint64_t)));
+ stream.read(reinterpret_cast(value_.get()), data_size);
+
+ // Zero any extra space.
+ if (uint64_count() > read_uint64_count)
+ {
+ set_zero_uint(uint64_count() - read_uint64_count,
+ value_.get() + read_uint64_count);
+ }
+ }
+ catch (const exception &)
+ {
+ stream.exceptions(old_except_mask);
+ throw;
+ }
+
+ stream.exceptions(old_except_mask);
+ }
+}
diff --git a/src/seal/biguint.h b/src/seal/biguint.h
new file mode 100644
index 000000000..6d0b7189d
--- /dev/null
+++ b/src/seal/biguint.h
@@ -0,0 +1,1648 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+#pragma once
+
+#include
+#include
+#include
+#include "seal/memorymanager.h"
+#include "seal/util/pointer.h"
+#include "seal/util/common.h"
+#include "seal/util/uintcore.h"
+#include "seal/util/uintarith.h"
+#include "seal/util/uintarithmod.h"
+
+namespace seal
+{
+ /**
+ Represents an unsigned integer with a specified bit width. Non-const
+ BigUInts are mutable and able to be resized. The bit count for a BigUInt
+ (which can be read with bit_count()) is set initially by the constructor
+ and can be resized either explicitly with the resize() function or
+ implicitly with an assignment operation (e.g., operator=(), operator+=(),
+ etc.). A rich set of unsigned integer operations are provided by the
+ BigUInt class, including comparison, traditional arithmetic (addition,
+ subtraction, multiplication, division), and modular arithmetic functions.
+
+ @par Backing Array
+ The backing array for a BigUInt stores its unsigned integer value as
+ a contiguous std::uint64_t array. Each std::uint64_t in the array
+ sequentially represents 64-bits of the integer value, with the least
+ significant quad-word storing the lower 64-bits and the order of the bits
+ for each quad word dependent on the architecture's std::uint64_t
+ representation. The size of the array equals the bit count of the BigUInt
+ (which can be read with bit_count()) rounded up to the next std::uint64_t
+ boundary (i.e., rounded up to the next 64-bit boundary). The uint64_count()
+ function returns the number of std::uint64_t in the backing array. The
+ data() function returns a pointer to the first std::uint64_t in the array.
+ Additionally, the operator [] function allows accessing the individual
+ bytes of the integer value in a platform-independent way - for example,
+ reading the third byte will always return bits 16-24 of the BigUInt value
+ regardless of the platform being little-endian or big-endian.
+
+ @par Implicit Resizing
+ Both the copy constructor and operator=() allocate more memory for the
+ backing array when needed, i.e. when the source BigUInt has a larger
+ backing array than the destination. Conversely, when the destination
+ backing array is already large enough, the data is only copied and the
+ unnecessary higher order bits are set to zero. When new memory has to be
+ allocated, only the significant bits of the source BigUInt are taken
+ into account. This is is important, because it avoids unnecessary zero
+ bits to be included in the destination, which in some cases could
+ accumulate and result in very large unnecessary allocations. However,
+ sometimes it is necessary to preserve the original size, even if some
+ of the leading bits are zero. For this purpose BigUInt contains functions
+ duplicate_from and duplicate_to, which create an exact copy of the source
+ BigUInt.
+
+ @par Alias BigUInts
+ An aliased BigUInt (which can be determined with is_alias()) is a special
+ type of BigUInt that does not manage its underlying std::uint64_t pointer
+ used to store the value. An aliased BigUInt supports most of the same
+ operations as a non-aliased BigUInt, including reading and writing the
+ value, however an aliased BigUInt does not internally allocate or
+ deallocate its backing array and, therefore, does not support resizing.
+ Any attempt, either explicitly or implicitly, to resize the BigUInt will
+ result in an exception being thrown. An aliased BigUInt can be created
+ with the BigUInt(int, std::uint64_t*) constructor or the alias() function.
+ Note that the pointer specified to be aliased must be deallocated
+ externally after the BigUInt is no longer in use. Aliasing is useful in
+ cases where it is desirable to not have each BigUInt manage its own memory
+ allocation and/or to prevent unnecessary copying.
+
+ @par Thread Safety
+ In general, reading a BigUInt is thread-safe while mutating is not.
+ Specifically, the backing array may be freed whenever a resize occurs,
+ the BigUInt is destroyed, or alias() is called, which would invalidate
+ the address returned by data() and the byte references returned by
+ operator []. When it is known that a resize will not occur, concurrent
+ reading and mutating will not inherently fail but it is possible for
+ a read to see a partially updated value from a concurrent write.
+ A non-aliased BigUInt allocates its backing array from the global
+ (thread-safe) memory pool. Consequently, creating or resizing a large
+ number of BigUInt can result in a performance loss due to thread
+ contention.
+ */
+ class BigUInt
+ {
+ public:
+ /**
+ Creates an empty BigUInt with zero bit width. No memory is allocated
+ by this constructor.
+ */
+ BigUInt() = default;
+
+ /**
+ Creates a zero-initialized BigUInt of the specified bit width.
+
+ @param[in] bit_count The bit width
+ @throws std::invalid_argument if bit_count is negative
+ */
+ BigUInt(int bit_count);
+
+ /**
+ Creates a BigUInt initialized and minimally sized to fit the unsigned
+ hexadecimal integer specified by the string. The string matches the format
+ returned by to_string() and must consist of only the characters 0-9, A-F,
+ or a-f, most-significant nibble first.
+
+ @param[in] hex_value The hexadecimal integer string specifying the initial
+ value
+ @throws std::invalid_argument if hex_value does not adhere to the expected
+ format
+ */
+ BigUInt(const std::string &hex_value);
+
+ /**
+ Creates a BigUInt of the specified bit width and initializes it with the
+ unsigned hexadecimal integer specified by the string. The string must match
+ the format returned by to_string() and must consist of only the characters
+ 0-9, A-F, or a-f, most-significant nibble first.
+
+ @param[in] bit_count The bit width
+ @param[in] hex_value The hexadecimal integer string specifying the initial
+ value
+ @throws std::invalid_argument if bit_count is negative
+ @throws std::invalid_argument if hex_value does not adhere to the expected
+ format
+ */
+ BigUInt(int bit_count, const std::string &hex_value);
+
+ /**
+ Creates an aliased BigUInt with the specified bit width and backing array.
+ An aliased BigUInt does not internally allocate or deallocate the backing
+ array, and instead uses the specified backing array for all read/write
+ operations. Note that resizing is not supported by an aliased BigUInt and
+ any required deallocation of the specified backing array must occur
+ externally after the aliased BigUInt is no longer in use.
+
+ @param[in] bit_count The bit width
+ @param[in] value The backing array to use
+ @throws std::invalid_argument if bit_count is negative or value is null
+ and bit_count is positive
+ */
+ BigUInt(int bit_count, std::uint64_t *value);
+#ifdef SEAL_USE_MSGSL_SPAN
+ /**
+ Creates an aliased BigUInt with given backing array and bit width set to
+ the size of the backing array. An aliased BigUInt does not internally
+ allocate or deallocate the backing array, and instead uses the specified
+ backing array for all read/write operations. Note that resizing is not
+ supported by an aliased BigUInt and any required deallocation of the
+ specified backing array must occur externally after the aliased BigUInt
+ is no longer in use.
+
+ @param[in] value The backing array to use
+ @throws std::invalid_argument if value has too large size
+ */
+ BigUInt(gsl::span value);
+#endif
+ /**
+ Creates a BigUInt of the specified bit width and initializes it to the
+ specified unsigned integer value.
+
+ @param[in] bit_count The bit width
+ @param[in] value The initial value to set the BigUInt
+ @throws std::invalid_argument if bit_count is negative
+ */
+ BigUInt(int bit_count, std::uint64_t value);
+
+ /**
+ Creates a deep copy of a BigUInt. The created BigUInt will have the same
+ bit count and value as the original.
+
+ @param[in] copy The BigUInt to copy from
+ */
+ BigUInt(const BigUInt ©);
+
+ /**
+ Creates a new BigUInt by moving an old one.
+
+ @param[in] source The BigUInt to move from
+ */
+ BigUInt(BigUInt &&source) noexcept;
+
+ /**
+ Destroys the BigUInt and deallocates the backing array if it is not
+ an aliased BigUInt.
+ */
+ ~BigUInt() noexcept;
+
+ /**
+ Returns whether or not the BigUInt is an alias.
+
+ @see BigUInt for a detailed description of aliased BigUInt.
+ */
+ inline bool is_alias() const noexcept
+ {
+ return value_.is_alias();
+ }
+
+ /**
+ Returns the bit count for the BigUInt.
+
+ @see significant_bit_count() to instead ignore leading zero bits.
+ */
+ inline int bit_count() const noexcept
+ {
+ return bit_count_;
+ }
+
+ /**
+ Returns a pointer to the backing array storing the BigUInt value.
+ The pointer points to the beginning of the backing array at the
+ least-significant quad word.
+
+ @warning The pointer is valid only until the backing array is freed,
+ which occurs when the BigUInt is resized, destroyed, or the alias()
+ function is called.
+ @see uint64_count() to determine the number of std::uint64_t values
+ in the backing array.
+ */
+ inline std::uint64_t *data()
+ {
+ return value_.get();
+ }
+
+ /**
+ Returns a const pointer to the backing array storing the BigUInt value.
+ The pointer points to the beginning of the backing array at the
+ least-significant quad word.
+
+ @warning The pointer is valid only until the backing array is freed, which
+ occurs when the BigUInt is resized, destroyed, or the alias() function is
+ called.
+ @see uint64_count() to determine the number of std::uint64_t values in the
+ backing array.
+ */
+ inline const std::uint64_t *data() const noexcept
+ {
+ return value_.get();
+ }
+#ifdef SEAL_USE_MSGSL_SPAN
+ /**
+ Returns the backing array storing the BigUInt value.
+
+ @warning The span is valid only until the backing array is freed, which
+ occurs when the BigUInt is resized, destroyed, or the alias() function is
+ called.
+ */
+ inline gsl::span data_span()
+ {
+ return gsl::span(value_.get(),
+ static_cast(uint64_count()));
+ }
+
+ /**
+ Returns the backing array storing the BigUInt value.
+
+ @warning The span is valid only until the backing array is freed, which
+ occurs when the BigUInt is resized, destroyed, or the alias() function is
+ called.
+ */
+ inline gsl::span data_span() const
+ {
+ return gsl::span(value_.get(),
+ static_cast(uint64_count()));
+ }
+#endif
+ /**
+ Returns the number of bytes in the backing array used to store the BigUInt
+ value.
+
+ @see BigUInt for a detailed description of the format of the backing array.
+ */
+ inline std::size_t byte_count() const
+ {
+ return static_cast(
+ util::divide_round_up(bit_count_, util::bits_per_byte));
+ }
+
+ /**
+ Returns the number of std::uint64_t in the backing array used to store
+ the BigUInt value.
+
+ @see BigUInt for a detailed description of the format of the backing array.
+ */
+ inline std::size_t uint64_count() const
+ {
+ return static_cast(
+ util::divide_round_up(bit_count_, util::bits_per_uint64));
+ }
+
+ /**
+ Returns the number of significant bits for the BigUInt.
+
+ @see bit_count() to instead return the bit count regardless of leading zero
+ bits.
+ */
+ inline int significant_bit_count() const
+ {
+ if (bit_count_ == 0)
+ {
+ return 0;
+ }
+ return util::get_significant_bit_count_uint(value_.get(), uint64_count());
+ }
+
+ /**
+ Returns the BigUInt value as a double. Note that precision may be lost during
+ the conversion.
+ */
+ double to_double() const noexcept
+ {
+ const double TwoToThe64 = 18446744073709551616.0;
+ double result = 0;
+ for (std::size_t i = uint64_count(); i--; )
+ {
+ result *= TwoToThe64;
+ result += static_cast(value_[i]);
+ }
+ return result;
+ }
+
+ /**
+ Returns the BigUInt value as a hexadecimal string.
+ */
+ std::string to_string() const;
+
+ /**
+ Returns the BigUInt value as a decimal string.
+ */
+ std::string to_dec_string() const;
+
+ /**
+ Returns whether or not the BigUInt has the value zero.
+ */
+ inline bool is_zero() const
+ {
+ if (bit_count_ == 0)
+ {
+ return true;
+ }
+ return util::is_zero_uint(value_.get(), uint64_count());
+ }
+
+ /**
+ Returns the byte at the corresponding byte index of the BigUInt's integer
+ value. The bytes of the BigUInt are indexed least-significant byte first.
+
+ @param[in] index The index of the byte to read
+ @throws std::out_of_range if index is not within [0, byte_count())
+ @see BigUInt for a detailed description of the format of the backing array.
+ */
+ inline const SEAL_BYTE &operator [](std::size_t index) const
+ {
+ if (index >= byte_count())
+ {
+ throw std::out_of_range("index must be within [0, byte count)");
+ }
+ return *util::get_uint64_byte(value_.get(), index);
+ }
+
+ /**
+ Returns an byte reference that can read/write the byte at the corresponding
+ byte index of the BigUInt's integer value. The bytes of the BigUInt are
+ indexed least-significant byte first.
+
+ @warning The returned byte is an reference backed by the BigUInt's backing
+ array. As such, it is only valid until the BigUInt is resized, destroyed,
+ or alias() is called.
+
+ @param[in] index The index of the byte to read
+ @throws std::out_of_range if index is not within [0, byte_count())
+ @see BigUInt for a detailed description of the format of the backing array.
+ */
+ inline SEAL_BYTE &operator [](std::size_t index)
+ {
+ if (index >= byte_count())
+ {
+ throw std::out_of_range("index must be within [0, byte count)");
+ }
+ return *util::get_uint64_byte(value_.get(), index);
+ }
+
+ /**
+ Sets the BigUInt value to zero. This does not resize the BigUInt.
+ */
+ inline void set_zero()
+ {
+ if (bit_count_)
+ {
+ return util::set_zero_uint(uint64_count(), value_.get());
+ }
+ }
+
+ /**
+ Resizes the BigUInt to the specified bit width, copying over the old value
+ as much as will fit.
+
+ @param[in] bit_count The bit width
+ @throws std::invalid_argument if bit_count is negative
+ @throws std::logic_error if the BigUInt is an alias
+ */
+ void resize(int bit_count);
+
+ /**
+ Makes the BigUInt an aliased BigUInt with the specified bit width and
+ backing array. An aliased BigUInt does not internally allocate or
+ deallocate the backing array, and instead uses the specified backing array
+ for all read/write operations. Note that resizing is not supported by
+ an aliased BigUInt and any required deallocation of the specified backing
+ array must occur externally after the aliased BigUInt is no longer in use.
+
+ @param[in] bit_count The bit width
+ @param[in] value The backing array to use
+ @throws std::invalid_argument if bit_count is negative or value is null
+ */
+ inline void alias(int bit_count, std::uint64_t *value)
+ {
+ if (bit_count < 0)
+ {
+ throw std::invalid_argument("bit_count must be non-negative");
+ }
+ if (value == nullptr && bit_count > 0)
+ {
+ throw std::invalid_argument("value must be non-null for non-zero bit count");
+ }
+
+ // Deallocate any owned pointers.
+ reset();
+
+ // Update class.
+ value_ = util::Pointer::Aliasing(value);
+ bit_count_ = bit_count;
+ }
+#ifdef SEAL_USE_MSGSL_SPAN
+ /**
+ Makes the BigUInt an aliased BigUInt with the given backing array
+ and bit width set equal to the size of the backing array. An aliased
+ BigUInt does not internally allocate or deallocate the backing array,
+ and instead uses the specified backing array for all read/write
+ operations. Note that resizing is not supported by an aliased BigUInt
+ and any required deallocation of the specified backing array must
+ occur externally after the aliased BigUInt is no longer in use.
+
+ @param[in] value The backing array to use
+ @throws std::invalid_argument if value has too large size
+ */
+ inline void alias(gsl::span value)
+ {
+ if(util::unsigned_gt(value.size(), std::numeric_limits::max()))
+ {
+ throw std::invalid_argument("value has too large size");
+ }
+
+ // Deallocate any owned pointers.
+ reset();
+
+ // Update class.
+ value_ = util::Pointer::Aliasing(value.data());
+ bit_count_ = static_cast(value.size());;
+ }
+#endif
+ /**
+ Resets an aliased BigUInt into an empty non-alias BigUInt with bit count
+ of zero.
+
+ @throws std::logic_error if BigUInt is not an alias
+ */
+ inline void unalias()
+ {
+ if (!value_.is_alias())
+ {
+ throw std::logic_error("BigUInt is not an alias");
+ }
+
+ // Reset class.
+ reset();
+ }
+
+ /**
+ Overwrites the BigUInt with the value of the specified BigUInt, enlarging
+ if needed to fit the assigned value. Only significant bits are used to
+ size the BigUInt.
+
+ @param[in] assign The BigUInt whose value should be assigned to the
+ current BigUInt
+ @throws std::logic_error if BigUInt is an alias and the assigned BigUInt is
+ too large to fit the current bit width
+ */
+ BigUInt &operator =(const BigUInt &assign);
+
+ /**
+ Overwrites the BigUInt with the unsigned hexadecimal value specified by
+ the string, enlarging if needed to fit the assigned value. The string must
+ match the format returned by to_string() and must consist of only the
+ characters 0-9, A-F, or a-f, most-significant nibble first.
+
+ @param[in] hex_value The hexadecimal integer string specifying the value
+ to assign
+ @throws std::invalid_argument if hex_value does not adhere to the
+ expected format
+ @throws std::logic_error if BigUInt is an alias and the assigned value
+ is too large to fit the current bit width
+ */
+ BigUInt &operator =(const std::string &hex_value);
+
+ /**
+ Overwrites the BigUInt with the specified integer value, enlarging if
+ needed to fit the value.
+
+ @param[in] value The value to assign
+ @throws std::logic_error if BigUInt is an alias and the significant bit
+ count of value is too large to fit the
+ current bit width
+ */
+ inline BigUInt &operator =(std::uint64_t value)
+ {
+ int assign_bit_count = util::get_significant_bit_count(value);
+ if (assign_bit_count > bit_count_)
+ {
+ // Size is too large to currently fit, so resize.
+ resize(assign_bit_count);
+ }
+ if (bit_count_ > 0)
+ {
+ util::set_uint(value, uint64_count(), value_.get());
+ }
+ return *this;
+ }
+
+ /**
+ Returns a copy of the BigUInt value resized to the significant bit count.
+ */
+ inline BigUInt operator +() const
+ {
+ BigUInt result;
+ result = *this;
+ return result;
+ }
+
+ /**
+ Returns a negated copy of the BigUInt value. The bit count does not change.
+ */
+ inline BigUInt operator -() const
+ {
+ BigUInt result(bit_count_);
+ util::negate_uint(value_.get(), result.uint64_count(), result.data());
+ util::filter_highbits_uint(result.data(), result.uint64_count(), result.bit_count());
+ return result;
+ }
+
+ /**
+ Returns an inverted copy of the BigUInt value. The bit count does not change.
+ */
+ inline BigUInt operator ~() const
+ {
+ BigUInt result(bit_count_);
+ util::not_uint(value_.get(), result.uint64_count(), result.data());
+ util::filter_highbits_uint(result.data(), result.uint64_count(), result.bit_count());
+ return result;
+ }
+
+ /**
+ Increments the BigUInt and returns the incremented value. The BigUInt will
+ increment the bit count if needed to fit the carry.
+
+ @throws std::logic_error if BigUInt is an alias and a carry occurs requiring
+ the BigUInt to be resized
+ */
+ inline BigUInt &operator ++()
+ {
+ if (util::increment_uint(value_.get(), uint64_count(), value_.get()))
+ {
+ resize(util::add_safe(bit_count_, 1));
+ util::set_bit_uint(value_.get(), uint64_count(), bit_count_);
+ }
+ bit_count_ = std::max(bit_count_, significant_bit_count());
+ return *this;
+ }
+
+ /**
+ Decrements the BigUInt and returns the decremented value. The bit count
+ does not change.
+ */
+ inline BigUInt &operator --()
+ {
+ util::decrement_uint(value_.get(), uint64_count(), value_.get());
+ util::filter_highbits_uint(value_.get(), uint64_count(), bit_count_);
+ return *this;
+ }
+
+ /**
+ Increments the BigUInt but returns its old value. The BigUInt will increment
+ the bit count if needed to fit the carry.
+ */
+ inline BigUInt operator ++(int postfix SEAL_MAYBE_UNUSED)
+ {
+ BigUInt result;
+ result = *this;
+ if (util::increment_uint(value_.get(), uint64_count(), value_.get()))
+ {
+ resize(util::add_safe(bit_count_, 1));
+ util::set_bit_uint(value_.get(), uint64_count(), bit_count_);
+ }
+ bit_count_ = std::max(bit_count_, significant_bit_count());
+ return result;
+ }
+
+ /**
+ Decrements the BigUInt but returns its old value. The bit count does not change.
+ */
+ inline BigUInt operator --(int postfix SEAL_MAYBE_UNUSED)
+ {
+ BigUInt result;
+ result = *this;
+ util::decrement_uint(value_.get(), uint64_count(), value_.get());
+ util::filter_highbits_uint(value_.get(), uint64_count(), bit_count_);
+ return result;
+ }
+
+ /**
+ Adds two BigUInts and returns the sum. The input operands are not modified.
+ The bit count of the sum is set to be one greater than the significant bit
+ count of the larger of the two input operands.
+
+ @param[in] operand2 The second operand to add
+ */
+ inline BigUInt operator +(const BigUInt &operand2) const
+ {
+ int result_bits = util::add_safe(std::max(significant_bit_count(),
+ operand2.significant_bit_count()), 1);
+ BigUInt result(result_bits);
+ util::add_uint_uint(value_.get(), uint64_count(), operand2.data(),
+ operand2.uint64_count(), false, result.uint64_count(), result.data());
+ return result;
+ }
+
+ /**
+ Adds a BigUInt and an unsigned integer and returns the sum. The input
+ operands are not modified. The bit count of the sum is set to be one greater
+ than the significant bit count of the larger of the two operands.
+
+ @param[in] operand2 The second operand to add
+ */
+ inline BigUInt operator +(std::uint64_t operand2) const
+ {
+ BigUInt operand2uint;
+ operand2uint = operand2;
+ return *this + operand2uint;
+ }
+
+ /**
+ Subtracts two BigUInts and returns the difference. The input operands are
+ not modified. The bit count of the difference is set to be the significant
+ bit count of the larger of the two input operands.
+
+ @param[in] operand2 The second operand to subtract
+ */
+ inline BigUInt operator -(const BigUInt &operand2) const
+ {
+ int result_bits = std::max(bit_count_, operand2.bit_count());
+ BigUInt result(result_bits);
+ util::sub_uint_uint(value_.get(), uint64_count(), operand2.data(),
+ operand2.uint64_count(), false, result.uint64_count(), result.data());
+ util::filter_highbits_uint(result.data(), result.uint64_count(), result_bits);
+ return result;
+ }
+
+ /**
+ Subtracts a BigUInt and an unsigned integer and returns the difference.
+ The input operands are not modified. The bit count of the difference is set
+ to be the significant bit count of the larger of the two operands.
+
+ @param[in] operand2 The second operand to subtract
+ */
+ inline BigUInt operator -(std::uint64_t operand2) const
+ {
+ BigUInt operand2uint;
+ operand2uint = operand2;
+ return *this - operand2uint;
+ }
+
+ /**
+ Multiplies two BigUInts and returns the product. The input operands are
+ not modified. The bit count of the product is set to be the sum of the
+ significant bit counts of the two input operands.
+
+ @param[in] operand2 The second operand to multiply
+ */
+ inline BigUInt operator *(const BigUInt &operand2) const
+ {
+ int result_bits = util::add_safe(significant_bit_count(),
+ operand2.significant_bit_count());
+ BigUInt result(result_bits);
+ util::multiply_uint_uint(value_.get(), uint64_count(), operand2.data(),
+ operand2.uint64_count(), result.uint64_count(), result.data());
+ return result;
+ }
+
+ /**
+ Multiplies a BigUInt and an unsigned integer and returns the product.
+ The input operands are not modified. The bit count of the product is set
+ to be the sum of the significant bit counts of the two input operands.
+
+ @param[in] operand2 The second operand to multiply
+ */
+ inline BigUInt operator *(std::uint64_t operand2) const
+ {
+ BigUInt operand2uint;
+ operand2uint = operand2;
+ return *this * operand2uint;
+ }
+
+ /**
+ Divides two BigUInts and returns the quotient. The input operands are
+ not modified. The bit count of the quotient is set to be the significant
+ bit count of the first input operand.
+
+ @param[in] operand2 The second operand to divide
+ @throws std::invalid_argument if operand2 is zero
+ */
+ BigUInt operator /(const BigUInt &operand2) const;
+
+ /**
+ Divides a BigUInt and an unsigned integer and returns the quotient. The
+ input operands are not modified. The bit count of the quotient is set
+ to be the significant bit count of the first input operand.
+
+ @param[in] operand2 The second operand to divide
+ @throws std::invalid_argument if operand2 is zero
+ */
+ inline BigUInt operator /(std::uint64_t operand2) const
+ {
+ BigUInt operand2uint;
+ operand2uint = operand2;
+ return *this / operand2uint;
+ }
+
+ /**
+ Performs a bit-wise XOR operation between two BigUInts and returns the
+ result. The input operands are not modified. The bit count of the result
+ is set to the maximum of the two input operand bit counts.
+
+ @param[in] operand2 The second operand to XOR
+ */
+ inline BigUInt operator ^(const BigUInt &operand2) const
+ {
+ int result_bits = std::max(bit_count_, operand2.bit_count());
+ BigUInt result(result_bits);
+ std::size_t uint64_count = result.uint64_count();
+ if (uint64_count != this->uint64_count())
+ {
+ result = *this;
+ util::xor_uint_uint(result.data(), operand2.data(), uint64_count, result.data());
+ }
+ else if (uint64_count != operand2.uint64_count())
+ {
+ result = operand2;
+ util::xor_uint_uint(result.data(), value_.get(), uint64_count, result.data());
+ }
+ else
+ {
+ util::xor_uint_uint(value_.get(), operand2.data(), uint64_count, result.data());
+ }
+ return result;
+ }
+
+ /**
+ Performs a bit-wise XOR operation between a BigUInt and an unsigned
+ integer and returns the result. The input operands are not modified.
+ The bit count of the result is set to the maximum of the two input
+ operand bit counts.
+
+ @param[in] operand2 The second operand to XOR
+ */
+ inline BigUInt operator ^(std::uint64_t operand2) const
+ {
+ BigUInt operand2uint;
+ operand2uint = operand2;
+ return *this ^ operand2uint;
+ }
+
+ /**
+ Performs a bit-wise AND operation between two BigUInts and returns the
+ result. The input operands are not modified. The bit count of the result
+ is set to the maximum of the two input operand bit counts.
+
+ @param[in] operand2 The second operand to AND
+ */
+ inline BigUInt operator &(const BigUInt &operand2) const
+ {
+ int result_bits = std::max(bit_count_, operand2.bit_count());
+ BigUInt result(result_bits);
+ std::size_t uint64_count = result.uint64_count();
+ if (uint64_count != this->uint64_count())
+ {
+ result = *this;
+ util::and_uint_uint(result.data(), operand2.data(), uint64_count, result.data());
+ }
+ else if (uint64_count != operand2.uint64_count())
+ {
+ result = operand2;
+ util::and_uint_uint(result.data(), value_.get(), uint64_count, result.data());
+ }
+ else
+ {
+ util::and_uint_uint(value_.get(), operand2.data(), uint64_count, result.data());
+ }
+ return result;
+ }
+
+ /**
+ Performs a bit-wise AND operation between a BigUInt and an unsigned
+ integer and returns the result. The input operands are not modified.
+ The bit count of the result is set to the maximum of the two input
+ operand bit counts.
+
+ @param[in] operand2 The second operand to AND
+ */
+ inline BigUInt operator &(std::uint64_t operand2) const
+ {
+ BigUInt operand2uint;
+ operand2uint = operand2;
+ return *this & operand2uint;
+ }
+
+ /**
+ Performs a bit-wise OR operation between two BigUInts and returns the
+ result. The input operands are not modified. The bit count of the result
+ is set to the maximum of the two input operand bit counts.
+
+ @param[in] operand2 The second operand to OR
+ */
+ inline BigUInt operator |(const BigUInt &operand2) const
+ {
+ int result_bits = std::max(bit_count_, operand2.bit_count());
+ BigUInt result(result_bits);
+ std::size_t uint64_count = result.uint64_count();
+ if (uint64_count != this->uint64_count())
+ {
+ result = *this;
+ util::or_uint_uint(result.data(), operand2.data(), uint64_count, result.data());
+ }
+ else if (uint64_count != operand2.uint64_count())
+ {
+ result = operand2;
+ util::or_uint_uint(result.data(), value_.get(), uint64_count, result.data());
+ }
+ else
+ {
+ util::or_uint_uint(value_.get(), operand2.data(), uint64_count, result.data());
+ }
+ return result;
+ }
+
+ /**
+ Performs a bit-wise OR operation between a BigUInt and an unsigned
+ integer and returns the result. The input operands are not modified.
+ The bit count of the result is set to the maximum of the two input
+ operand bit counts.
+
+ @param[in] operand2 The second operand to OR
+ */
+ inline BigUInt operator |(std::uint64_t operand2) const
+ {
+ BigUInt operand2uint;
+ operand2uint = operand2;
+ return *this | operand2uint;
+ }
+
+ /**
+ Compares two BigUInts and returns -1, 0, or 1 if the BigUInt is
+ less-than, equal-to, or greater-than the second operand respectively.
+ The input operands are not modified.
+
+ @param[in] compare The value to compare against
+ */
+ inline int compareto(const BigUInt &compare) const
+ {
+ return util::compare_uint_uint(value_.get(), uint64_count(),
+ compare.value_.get(), compare.uint64_count());
+ }
+
+ /**
+ Compares a BigUInt and an unsigned integer and returns -1, 0, or 1 if
+ the BigUInt is less-than, equal-to, or greater-than the second operand
+ respectively. The input operands are not modified.
+
+ @param[in] compare The value to compare against
+ */
+ inline int compareto(std::uint64_t compare) const
+ {
+ BigUInt compareuint;
+ compareuint = compare;
+ return compareto(compareuint);
+ }
+
+ /**
+ Returns whether or not a BigUInt is less-than a second BigUInt. The
+ input operands are not modified.
+
+ @param[in] operand2 The value to compare against
+ */
+ inline bool operator <(const BigUInt &compare) const
+ {
+ return util::compare_uint_uint(value_.get(), uint64_count(),
+ compare.value_.get(), compare.uint64_count()) < 0;
+ }
+
+ /**
+ Returns whether or not a BigUInt is less-than an unsigned integer.
+ The input operands are not modified.
+
+ @param[in] operand2 The value to compare against
+ */
+ inline bool operator <(std::uint64_t compare) const
+ {
+ BigUInt compareuint;
+ compareuint = compare;
+ return *this < compareuint;
+ }
+
+ /**
+ Returns whether or not a BigUInt is greater-than a second BigUInt.
+ The input operands are not modified.
+
+ @param[in] operand2 The value to compare against
+ */
+ inline bool operator >(const BigUInt &compare) const
+ {
+ return util::compare_uint_uint(value_.get(), uint64_count(),
+ compare.value_.get(), compare.uint64_count()) > 0;
+ }
+
+ /**
+ Returns whether or not a BigUInt is greater-than an unsigned integer.
+ The input operands are not modified.
+
+ @param[in] operand2 The value to compare against
+ */
+ inline bool operator >(std::uint64_t compare) const
+ {
+ BigUInt compareuint;
+ compareuint = compare;
+ return *this > compareuint;
+ }
+
+ /**
+ Returns whether or not a BigUInt is less-than or equal to a second
+ BigUInt. The input operands are not modified.
+
+ @param[in] operand2 The value to compare against
+ */
+ inline bool operator <=(const BigUInt &compare) const
+ {
+ return util::compare_uint_uint(value_.get(), uint64_count(),
+ compare.value_.get(), compare.uint64_count()) <= 0;
+ }
+
+ /**
+ Returns whether or not a BigUInt is less-than or equal to an unsigned
+ integer. The input operands are not modified.
+
+ @param[in] operand2 The value to compare against
+ */
+ inline bool operator <=(std::uint64_t compare) const
+ {
+ BigUInt compareuint;
+ compareuint = compare;
+ return *this <= compareuint;
+ }
+
+ /**
+ Returns whether or not a BigUInt is greater-than or equal to a second
+ BigUInt. The input operands are not modified.
+
+ @param[in] operand2 The value to compare against
+ */
+ inline bool operator >=(const BigUInt &compare) const
+ {
+ return util::compare_uint_uint(value_.get(), uint64_count(),
+ compare.value_.get(), compare.uint64_count()) >= 0;
+ }
+
+ /**
+ Returns whether or not a BigUInt is greater-than or equal to an unsigned
+ integer. The input operands are not modified.
+
+ @param[in] operand2 The value to compare against
+ */
+ inline bool operator >=(std::uint64_t compare) const
+ {
+ BigUInt compareuint;
+ compareuint = compare;
+ return *this >= compareuint;
+ }
+
+ /**
+ Returns whether or not a BigUInt is equal to a second BigUInt.
+ The input operands are not modified.
+
+ @param[in] compare The value to compare against
+ */
+ inline bool operator ==(const BigUInt &compare) const
+ {
+ return util::compare_uint_uint(value_.get(), uint64_count(),
+ compare.value_.get(), compare.uint64_count()) == 0;
+ }
+
+ /**
+ Returns whether or not a BigUInt is equal to an unsigned integer.
+ The input operands are not modified.
+
+ @param[in] compare The value to compare against
+ */
+ inline bool operator ==(std::uint64_t compare) const
+ {
+ BigUInt compareuint;
+ compareuint = compare;
+ return *this == compareuint;
+ }
+
+ /**
+ Returns whether or not a BigUInt is not equal to a second BigUInt.
+ The input operands are not modified.
+
+ @param[in] compare The value to compare against
+ */
+ inline bool operator !=(const BigUInt &compare) const
+ {
+ return !(operator ==(compare));
+ }
+
+ /**
+ Returns whether or not a BigUInt is not equal to an unsigned integer.
+ The input operands are not modified.
+
+ @param[in] operand2 The value to compare against
+ */
+ inline bool operator !=(std::uint64_t compare) const
+ {
+ BigUInt compareuint;
+ compareuint = compare;
+ return *this != compareuint;
+ }
+
+ /**
+ Returns a left-shifted copy of the BigUInt. The bit count of the
+ returned value is the sum of the original significant bit count and
+ the shift amount.
+
+ @param[in] shift The number of bits to shift by
+ @throws std::invalid_argument if shift is negative
+ */
+ inline BigUInt operator <<(int shift) const
+ {
+ if (shift < 0)
+ {
+ throw std::invalid_argument("shift must be non-negative");
+ }
+ int result_bits = util::add_safe(significant_bit_count(), shift);
+ BigUInt result(result_bits);
+ result = *this;
+ util::left_shift_uint(
+ result.data(), shift, result.uint64_count(), result.data());
+ return result;
+ }
+
+ /**
+ Returns a right-shifted copy of the BigUInt. The bit count of the
+ returned value is the original significant bit count subtracted by
+ the shift amount (clipped to zero if negative).
+
+ @param[in] shift The number of bits to shift by
+ @throws std::invalid_argument if shift is negative
+ */
+ inline BigUInt operator >>(int shift) const
+ {
+ if (shift < 0)
+ {
+ throw std::invalid_argument("shift must be non-negative");
+ }
+ int result_bits = util::sub_safe(significant_bit_count(), shift);
+ if (result_bits <= 0)
+ {
+ BigUInt zero;
+ return zero;
+ }
+ BigUInt result(result_bits);
+ result = *this;
+ util::right_shift_uint(
+ result.data(), shift, result.uint64_count(), result.data());
+ return result;
+ }
+
+ /**
+ Adds two BigUInts saving the sum to the first operand, returning
+ a reference of the first operand. The second input operand is not
+ modified. The first operand is resized if and only if its bit count
+ is smaller than one greater than the significant bit count of the
+ larger of the two input operands.
+
+ @param[in] operand2 The second operand to add
+ @throws std::logic_error if the BigUInt is an alias and the operator
+ attempts to enlarge the BigUInt to fit the result
+ */
+ inline BigUInt &operator +=(const BigUInt &operand2)
+ {
+ int result_bits = util::add_safe(std::max(
+ significant_bit_count(), operand2.significant_bit_count()), 1);
+ if (bit_count_ < result_bits)
+ {
+ resize(result_bits);
+ }
+ util::add_uint_uint(value_.get(), uint64_count(), operand2.data(),
+ operand2.uint64_count(), false, uint64_count(), value_.get());
+ return *this;
+ }
+
+ /**
+ Adds a BigUInt and an unsigned integer saving the sum to the first operand,
+ returning a reference of the first operand. The second input operand is not
+ modified. The first operand is resized if and only if its bit count is
+ smaller than one greater than the significant bit count of the larger of
+ the two input operands.
+
+ @param[in] operand2 The second operand to add
+ @throws std::logic_error if the BigUInt is an alias and the operator
+ attempts to enlarge the BigUInt to fit the result
+ */
+ inline BigUInt &operator +=(std::uint64_t operand2)
+ {
+ BigUInt operand2uint;
+ operand2uint = operand2;
+ return operator +=(operand2uint);
+ }
+
+ /**
+ Subtracts two BigUInts saving the difference to the first operand,
+ returning a reference of the first operand. The second input operand is
+ not modified. The first operand is resized if and only if its bit count
+ is smaller than the significant bit count of the second operand.
+
+ @param[in] operand2 The second operand to subtract
+ @throws std::logic_error if the BigUInt is an alias and the operator
+ attempts to enlarge the BigUInt to fit the result
+ */
+ inline BigUInt &operator -=(const BigUInt &operand2)
+ {
+ int result_bits = std::max(bit_count_, operand2.bit_count());
+ if (bit_count_ < result_bits)
+ {
+ resize(result_bits);
+ }
+ util::sub_uint_uint(value_.get(), uint64_count(), operand2.data(),
+ operand2.uint64_count(), false, uint64_count(), value_.get());
+ util::filter_highbits_uint(value_.get(), uint64_count(), result_bits);
+ return *this;
+ }
+
+ /**
+ Subtracts a BigUInt and an unsigned integer saving the difference to
+ the first operand, returning a reference of the first operand. The second
+ input operand is not modified. The first operand is resized if and only
+ if its bit count is smaller than the significant bit count of the second
+ operand.
+
+ @param[in] operand2 The second operand to subtract
+ @throws std::logic_error if the BigUInt is an alias and the operator
+ attempts to enlarge the BigUInt to fit the result
+ */
+ inline BigUInt &operator -=(std::uint64_t operand2)
+ {
+ BigUInt operand2uint;
+ operand2uint = operand2;
+ return operator -=(operand2uint);
+ }
+
+ /**
+ Multiplies two BigUInts saving the product to the first operand,
+ returning a reference of the first operand. The second input operand
+ is not modified. The first operand is resized if and only if its bit
+ count is smaller than the sum of the significant bit counts of the two
+ input operands.
+
+ @param[in] operand2 The second operand to multiply
+ @throws std::logic_error if the BigUInt is an alias and the operator
+ attempts to enlarge the BigUInt to fit the result
+ */
+ inline BigUInt &operator *=(const BigUInt &operand2)
+ {
+ *this = *this * operand2;
+ return *this;
+ }
+
+ /**
+ Multiplies a BigUInt and an unsigned integer saving the product to
+ the first operand, returning a reference of the first operand. The
+ second input operand is not modified. The first operand is resized if
+ and only if its bit count is smaller than the sum of the significant
+ bit counts of the two input operands.
+
+ @param[in] operand2 The second operand to multiply
+ @throws std::logic_error if the BigUInt is an alias and the operator
+ attempts to enlarge the BigUInt to fit the result
+ */
+ inline BigUInt &operator *=(std::uint64_t operand2)
+ {
+ BigUInt operand2uint;
+ operand2uint = operand2;
+ return operator *=(operand2uint);
+ }
+
+ /**
+ Divides two BigUInts saving the quotient to the first operand,
+ returning a reference of the first operand. The second input operand
+ is not modified. The first operand is never resized.
+
+ @param[in] operand2 The second operand to divide
+ @throws std::invalid_argument if operand2 is zero
+ */
+ inline BigUInt &operator /=(const BigUInt &operand2)
+ {
+ *this = *this / operand2;
+ return *this;
+ }
+
+ /**
+ Divides a BigUInt and an unsigned integer saving the quotient to
+ the first operand, returning a reference of the first operand. The
+ second input operand is not modified. The first operand is never resized.
+
+ @param[in] operand2 The second operand to divide
+ @throws std::invalid_argument if operand2 is zero
+ */
+ inline BigUInt &operator /=(std::uint64_t operand2)
+ {
+ BigUInt operand2uint;
+ operand2uint = operand2;
+ return operator /=(operand2uint);
+ }
+
+ /**
+ Performs a bit-wise XOR operation between two BigUInts saving the result
+ to the first operand, returning a reference of the first operand. The
+ second input operand is not modified. The first operand is resized if
+ and only if its bit count is smaller than the significant bit count of
+ the second operand.
+
+ @param[in] operand2 The second operand to XOR
+ @throws std::logic_error if the BigUInt is an alias and the operator
+ attempts to enlarge the BigUInt to fit the result
+ */
+ inline BigUInt &operator ^=(const BigUInt &operand2)
+ {
+ int result_bits = std::max(bit_count_, operand2.bit_count());
+ if (bit_count_ != result_bits)
+ {
+ resize(result_bits);
+ }
+ util::xor_uint_uint(
+ value_.get(), operand2.data(), operand2.uint64_count(), value_.get());
+ return *this;
+ }
+
+ /**
+ Performs a bit-wise XOR operation between a BigUInt and an unsigned integer
+ saving the result to the first operand, returning a reference of the first
+ operand. The second input operand is not modified. The first operand is
+ resized if and only if its bit count is smaller than the significant bit
+ count of the second operand.
+
+ @param[in] operand2 The second operand to XOR
+ @throws std::logic_error if the BigUInt is an alias and the operator
+ attempts to enlarge the BigUInt to fit the result
+ */
+ inline BigUInt &operator ^=(std::uint64_t operand2)
+ {
+ BigUInt operand2uint;
+ operand2uint = operand2;
+ return operator ^=(operand2uint);
+ }
+
+ /**
+ Performs a bit-wise AND operation between two BigUInts saving the result
+ to the first operand, returning a reference of the first operand. The
+ second input operand is not modified. The first operand is resized if
+ and only if its bit count is smaller than the significant bit count of
+ the second operand.
+
+ @param[in] operand2 The second operand to AND
+ @throws std::logic_error if the BigUInt is an alias and the operator
+ attempts to enlarge the BigUInt to fit the result
+ */
+ inline BigUInt &operator &=(const BigUInt &operand2)
+ {
+ int result_bits = std::max(bit_count_, operand2.bit_count());
+ if (bit_count_ != result_bits)
+ {
+ resize(result_bits);
+ }
+ util::and_uint_uint(
+ value_.get(), operand2.data(), operand2.uint64_count(), value_.get());
+ return *this;
+ }
+
+ /**
+ Performs a bit-wise AND operation between a BigUInt and an unsigned integer
+ saving the result to the first operand, returning a reference of the first
+ operand. The second input operand is not modified. The first operand is
+ resized if and only if its bit count is smaller than the significant bit
+ count of the second operand.
+
+ @param[in] operand2 The second operand to AND
+ @throws std::logic_error if the BigUInt is an alias and the operator
+ attempts to enlarge the BigUInt to fit the result
+ */
+ inline BigUInt &operator &=(std::uint64_t operand2)
+ {
+ BigUInt operand2uint;
+ operand2uint = operand2;
+ return operator &=(operand2uint);
+ }
+
+ /**
+ Performs a bit-wise OR operation between two BigUInts saving the result to
+ the first operand, returning a reference of the first operand. The second
+ input operand is not modified. The first operand is resized if and only if
+ its bit count is smaller than the significant bit count of the second
+ operand.
+
+ @param[in] operand2 The second operand to OR
+ @throws std::logic_error if the BigUInt is an alias and the operator
+ attempts to enlarge the BigUInt to fit the result
+ */
+ inline BigUInt &operator |=(const BigUInt &operand2)
+ {
+ int result_bits = std::max(bit_count_, operand2.bit_count());
+ if (bit_count_ != result_bits)
+ {
+ resize(result_bits);
+ }
+ util::or_uint_uint(value_.get(), operand2.data(),
+ operand2.uint64_count(), value_.get());
+ return *this;
+ }
+
+ /**
+ Performs a bit-wise OR operation between a BigUInt and an unsigned integer
+ saving the result to the first operand, returning a reference of the first
+ operand. The second input operand is not modified. The first operand is
+ resized if and only if its bit count is smaller than the significant bit
+ count of the second operand.
+
+ @param[in] operand2 The second operand to OR
+ @throws std::logic_error if the BigUInt is an alias and the operator
+ attempts to enlarge the BigUInt to fit the result
+ */
+ inline BigUInt &operator |=(std::uint64_t operand2)
+ {
+ BigUInt operand2uint;
+ operand2uint = operand2;
+ return operator |=(operand2uint);
+ }
+
+ /**
+ Left-shifts a BigUInt by the specified amount. The BigUInt is resized if
+ and only if its bit count is smaller than the sum of its significant bit
+ count and the shift amount.
+
+ @param[in] shift The number of bits to shift by
+ @throws std::Invalid_argument if shift is negative
+ @throws std::logic_error if the BigUInt is an alias and the operator
+ attempts to enlarge the BigUInt to fit the result
+ */
+ inline BigUInt &operator <<=(int shift)
+ {
+ if (shift < 0)
+ {
+ throw std::invalid_argument("shift must be non-negative");
+ }
+ int result_bits = util::add_safe(significant_bit_count(), shift);
+ if (bit_count_ < result_bits)
+ {
+ resize(result_bits);
+ }
+ util::left_shift_uint(value_.get(), shift, uint64_count(), value_.get());
+ return *this;
+ }
+
+ /**
+ Right-shifts a BigUInt by the specified amount. The BigUInt is never
+ resized.
+
+ @param[in] shift The number of bits to shift by
+ @throws std::Invalid_argument if shift is negative
+ */
+ inline BigUInt &operator >>=(int shift)
+ {
+ if (shift < 0)
+ {
+ throw std::invalid_argument("shift must be non-negative");
+ }
+ if (shift > bit_count_)
+ {
+ set_zero();
+ return *this;
+ }
+ util::right_shift_uint(value_.get(), shift, uint64_count(), value_.get());
+ return *this;
+ }
+
+ /**
+ Divides two BigUInts and returns the quotient and sets the remainder
+ parameter to the remainder. The bit count of the quotient is set to be
+ the significant bit count of the BigUInt. The remainder is resized if
+ and only if it is smaller than the bit count of the BigUInt.
+
+ @param[in] operand2 The second operand to divide
+ @param[out] remainder The BigUInt to store the remainder
+ @throws std::Invalid_argument if operand2 is zero
+ @throws std::logic_error if the remainder is an alias and the operator
+ attempts to enlarge the BigUInt to fit the result
+ */
+ BigUInt divrem(const BigUInt &operand2, BigUInt &remainder) const;
+
+ /**
+ Divides a BigUInt and an unsigned integer and returns the quotient and
+ sets the remainder parameter to the remainder. The bit count of the
+ quotient is set to be the significant bit count of the BigUInt. The
+ remainder is resized if and only if it is smaller than the bit count
+ of the BigUInt.
+
+ @param[in] operand2 The second operand to divide
+ @param[out] remainder The BigUInt to store the remainder
+ @throws std::Invalid_argument if operand2 is zero
+ @throws std::logic_error if the remainder is an alias which the
+ function attempts to enlarge to fit the result
+ */
+ inline BigUInt divrem(std::uint64_t operand2, BigUInt &remainder) const
+ {
+ BigUInt operand2uint;
+ operand2uint = operand2;
+ return divrem(operand2uint, remainder);
+ }
+
+ /**
+ Returns the inverse of a BigUInt with respect to the specified modulus.
+ The original BigUInt is not modified. The bit count of the inverse is
+ set to be the significant bit count of the modulus.
+
+ @param[in] modulus The modulus to calculate the inverse with respect to
+ @throws std::Invalid_argument if modulus is zero
+ @throws std::Invalid_argument if modulus is not greater than the BigUInt value
+ @throws std::Invalid_argument if the BigUInt value and modulus are not co-prime
+ */
+ inline BigUInt modinv(const BigUInt &modulus) const
+ {
+ if (modulus.is_zero())
+ {
+ throw std::invalid_argument("modulus must be positive");
+ }
+ int result_bits = modulus.significant_bit_count();
+ if (*this >= modulus)
+ {
+ throw std::invalid_argument("modulus must be greater than BigUInt");
+ }
+ BigUInt result(result_bits);
+ result = *this;
+ if (!util::try_invert_uint_mod(result.data(), modulus.data(),
+ result.uint64_count(), result.data(), pool_))
+ {
+ throw std::invalid_argument("BigUInt and modulus are not co-prime");
+ }
+ return result;
+ }
+
+ /**
+ Returns the inverse of a BigUInt with respect to the specified modulus.
+ The original BigUInt is not modified. The bit count of the inverse is set
+ to be the significant bit count of the modulus.
+
+ @param[in] modulus The modulus to calculate the inverse with respect to
+ @throws std::Invalid_argument if modulus is zero
+ @throws std::Invalid_argument if modulus is not greater than the BigUInt value
+ @throws std::Invalid_argument if the BigUInt value and modulus are not co-prime
+ */
+ inline BigUInt modinv(std::uint64_t modulus) const
+ {
+ BigUInt modulusuint;
+ modulusuint = modulus;
+ return modinv(modulusuint);
+ }
+
+ /**
+ Attempts to calculate the inverse of a BigUInt with respect to the
+ specified modulus, returning whether or not the inverse was successful
+ and setting the inverse parameter to the inverse. The original BigUInt
+ is not modified. The inverse parameter is resized if and only if its bit
+ count is smaller than the significant bit count of the modulus.
+
+ @param[in] modulus The modulus to calculate the inverse with respect to
+ @param[out] inverse Stores the inverse if the inverse operation was
+ successful
+ @throws std::Invalid_argument if modulus is zero
+ @throws std::Invalid_argument if modulus is not greater than the BigUInt
+ value
+ @throws std::logic_error if the inverse is an alias which the function
+ attempts to enlarge to fit the result
+ */
+ inline bool trymodinv(const BigUInt &modulus, BigUInt &inverse) const
+ {
+ if (modulus.is_zero())
+ {
+ throw std::invalid_argument("modulus must be positive");
+ }
+ int result_bits = modulus.significant_bit_count();
+ if (*this >= modulus)
+ {
+ throw std::invalid_argument("modulus must be greater than BigUInt");
+ }
+ if (inverse.bit_count() < result_bits)
+ {
+ inverse.resize(result_bits);
+ }
+ inverse = *this;
+ return util::try_invert_uint_mod(inverse.data(), modulus.data(),
+ inverse.uint64_count(), inverse.data(), pool_);
+ }
+
+ /**
+ Attempts to calculate the inverse of a BigUInt with respect to the
+ specified modulus, returning whether or not the inverse was successful
+ and setting the inverse parameter to the inverse. The original BigUInt
+ is not modified. The inverse parameter is resized if and only if its
+ bit count is smaller than the significant bit count of the modulus.
+
+ @param[in] modulus The modulus to calculate the inverse with respect to
+ @param[out] inverse Stores the inverse if the inverse operation was
+ successful
+ @throws std::Invalid_argument if modulus is zero
+ @throws std::Invalid_argument if modulus is not greater than the BigUInt
+ value
+ @throws std::logic_error if the inverse is an alias which the function
+ attempts to enlarge to fit the result
+ */
+ inline bool trymodinv(std::uint64_t modulus, BigUInt &inverse) const
+ {
+ BigUInt modulusuint;
+ modulusuint = modulus;
+ return trymodinv(modulusuint, inverse);
+ }
+
+ /**
+ Saves the BigUInt to an output stream. The full state of the BigUInt is
+ serialized, including insignificant bits. The output is in binary format
+ and not human-readable. The output stream must have the "binary" flag set.
+
+ @param[in] stream The stream to save the BigUInt to
+ @throws std::exception if the BigUInt could not be written to stream
+ */
+ void save(std::ostream &stream) const;
+
+ /**
+ Loads a BigUInt from an input stream overwriting the current BigUInt
+ and enlarging if needed to fit the loaded BigUInt.
+
+ @param[in] stream The stream to load the BigUInt from
+ @throws std::logic_error if BigUInt is an alias and the loaded BigUInt
+ is too large to fit with the current bit
+ @throws std::exception if a valid BigUInt could not be read from stream
+ */
+ void load(std::istream &stream);
+
+ /**
+ Creates a minimally sized BigUInt initialized to the specified unsigned
+ integer value.
+
+ @param[in] value The value to initialized the BigUInt to
+ */
+ inline static BigUInt of(std::uint64_t value)
+ {
+ BigUInt result;
+ result = value;
+ return result;
+ }
+
+ /**
+ Duplicates the current BigUInt. The bit count and the value of the
+ given BigUInt are set to be exactly the same as in the current one.
+
+ @param[out] destination The BigUInt to overwrite with the duplicate
+ @throws std::logic_error if the destination BigUInt is an alias
+ */
+ inline void duplicate_to(BigUInt &destination) const
+ {
+ destination.resize(this->bit_count_);
+ destination = *this;
+ }
+
+ /**
+ Duplicates a given BigUInt. The bit count and the value of the current
+ BigUInt are set to be exactly the same as in the given one.
+
+ @param[in] value The BigUInt to duplicate
+ @throws std::logic_error if the current BigUInt is an alias
+ */
+ inline void duplicate_from(const BigUInt &value)
+ {
+ this->resize(value.bit_count_);
+ *this = value;
+ }
+
+ private:
+ MemoryPoolHandle pool_;
+
+ /**
+ Resets the entire state of the BigUInt to an empty, zero-sized state,
+ freeing any memory it internally allocated. If the BigUInt was an alias,
+ the backing array is not freed but the alias is no longer referenced.
+ */
+ inline void reset() noexcept
+ {
+ value_.release();
+ bit_count_ = 0;
+ }
+
+ /**
+ Points to the backing array for the BigUInt. This pointer will be set
+ to nullptr if and only if the bit count is zero. This pointer is
+ automatically allocated and freed by the BigUInt if and only if
+ the BigUInt is not an alias. If the BigUInt is an alias, then the
+ pointer was passed-in to a constructor or alias() call, and will not be
+ deallocated by the BigUInt.
+
+ @see BigUInt for more information about aliased BigUInts or the format
+ of the backing array.
+ */
+ util::Pointer value_;
+
+ /**
+ The bit count for the BigUInt.
+ */
+ int bit_count_ = 0;
+ };
+}
diff --git a/src/seal/ciphertext.cpp b/src/seal/ciphertext.cpp
new file mode 100644
index 000000000..b2c84a78c
--- /dev/null
+++ b/src/seal/ciphertext.cpp
@@ -0,0 +1,254 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+#include "seal/ciphertext.h"
+#include "seal/util/polycore.h"
+
+using namespace std;
+using namespace seal::util;
+
+namespace seal
+{
+ Ciphertext &Ciphertext::operator =(const Ciphertext &assign)
+ {
+ // Check for self-assignment
+ if (this == &assign)
+ {
+ return *this;
+ }
+
+ // Copy over fields
+ parms_id_ = assign.parms_id_;
+ is_ntt_form_ = assign.is_ntt_form_;
+ scale_ = assign.scale_;
+
+ // Then resize
+ resize_internal(assign.size_, assign.poly_modulus_degree_,
+ assign.coeff_mod_count_);
+
+ // Size is guaranteed to be OK now so copy over
+ copy(assign.data_.cbegin(), assign.data_.cend(), data_.begin());
+
+ return *this;
+ }
+
+ void Ciphertext::reserve(shared_ptr context,
+ parms_id_type parms_id, size_type size_capacity)
+ {
+ // Verify parameters
+ if (!context)
+ {
+ throw invalid_argument("invalid context");
+ }
+ if (!context->parameters_set())
+ {
+ throw invalid_argument("encryption parameters are not set correctly");
+ }
+
+ auto context_data_ptr = context->context_data(parms_id);
+ if (!context_data_ptr)
+ {
+ throw invalid_argument("parms_id is not valid for encryption parameters");
+ }
+
+ // Need to set parms_id first
+ auto &parms = context_data_ptr->parms();
+ parms_id_ = parms.parms_id();
+
+ reserve_internal(size_capacity, parms.poly_modulus_degree(),
+ safe_cast(parms.coeff_modulus().size()));
+ }
+
+ void Ciphertext::reserve_internal(size_type size_capacity,
+ size_type poly_modulus_degree, size_type coeff_mod_count)
+ {
+ if (size_capacity < SEAL_CIPHERTEXT_SIZE_MIN ||
+ size_capacity > SEAL_CIPHERTEXT_SIZE_MAX)
+ {
+ throw invalid_argument("invalid size_capacity");
+ }
+
+ size_type new_data_capacity =
+ mul_safe(size_capacity, poly_modulus_degree, coeff_mod_count);
+ size_type new_data_size = min(new_data_capacity, data_.size());
+
+ // First reserve, then resize
+ data_.reserve(new_data_capacity);
+ data_.resize(new_data_size);
+
+ // Set the size and size_capacity
+ size_capacity_ = size_capacity;
+ size_ = min(size_capacity, size_);
+ poly_modulus_degree_ = poly_modulus_degree;
+ coeff_mod_count_ = coeff_mod_count;
+ }
+
+ void Ciphertext::resize(shared_ptr context,
+ parms_id_type parms_id, size_type size)
+ {
+ // Verify parameters
+ if (!context)
+ {
+ throw invalid_argument("invalid context");
+ }
+ if (!context->parameters_set())
+ {
+ throw invalid_argument("encryption parameters are not set correctly");
+ }
+
+ auto context_data_ptr = context->context_data(parms_id);
+ if (!context_data_ptr)
+ {
+ throw invalid_argument("parms_id is not valid for encryption parameters");
+ }
+
+ // Need to set parms_id first
+ auto &parms = context_data_ptr->parms();
+ parms_id_ = parms.parms_id();
+
+ resize_internal(size, parms.poly_modulus_degree(),
+ safe_cast(parms.coeff_modulus().size()));
+ }
+
+ void Ciphertext::resize_internal(size_type size,
+ size_type poly_modulus_degree, size_type coeff_mod_count)
+ {
+ if ((size < SEAL_CIPHERTEXT_SIZE_MIN && size != 0) ||
+ size > SEAL_CIPHERTEXT_SIZE_MAX)
+ {
+ throw invalid_argument("invalid size");
+ }
+
+ // Resize the data
+ size_type new_data_size =
+ mul_safe(size, poly_modulus_degree, coeff_mod_count);
+ data_.resize(new_data_size);
+
+ // Set the size parameters
+ size_ = size;
+ poly_modulus_degree_ = poly_modulus_degree;
+ coeff_mod_count_ = coeff_mod_count;
+ }
+
+ bool Ciphertext::is_valid_for(shared_ptr context) const noexcept
+ {
+ // Verify parameters
+ if (!context || !context->parameters_set())
+ {
+ return false;
+ }
+
+ auto context_data_ptr = context->context_data(parms_id_);
+ if (!context_data_ptr)
+ {
+ return false;
+ }
+
+ auto &coeff_modulus = context_data_ptr->parms().coeff_modulus();
+ size_t poly_modulus_degree = context_data_ptr->parms().poly_modulus_degree();
+ if ((coeff_modulus.size() != coeff_mod_count_) ||
+ (poly_modulus_degree != poly_modulus_degree_))
+ {
+ return false;
+ }
+
+ const ct_coeff_type *ptr = data();
+ for (size_t i = 0; i < size_; i++)
+ {
+ for (size_t j = 0; j < coeff_mod_count_; j++)
+ {
+ uint64_t modulus = coeff_modulus[j].value();
+ for (size_t k = 0; k < poly_modulus_degree_; k++, ptr++)
+ {
+ if (*ptr >= modulus)
+ {
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ void Ciphertext::save(ostream &stream) const
+ {
+ auto old_except_mask = stream.exceptions();
+ try
+ {
+ // Throw exceptions on std::ios_base::badbit and std::ios_base::failbit
+ stream.exceptions(ios_base::badbit | ios_base::failbit);
+
+ stream.write(reinterpret_cast(&parms_id_), sizeof(parms_id_type));
+ SEAL_BYTE is_ntt_form_byte = static_cast(is_ntt_form_);
+ stream.write(reinterpret_cast(&is_ntt_form_byte), sizeof(SEAL_BYTE));
+ uint64_t size64 = safe_cast(size_);
+ stream.write(reinterpret_cast(&size64), sizeof(uint64_t));
+ uint64_t poly_modulus_degree64 = safe_cast(poly_modulus_degree_);
+ stream.write(reinterpret_cast(&poly_modulus_degree64), sizeof(uint64_t));
+ uint64_t coeff_mod_count64 = safe_cast(coeff_mod_count_);
+ stream.write(reinterpret_cast(&coeff_mod_count64), sizeof(uint64_t));
+ stream.write(reinterpret_cast(&scale_), sizeof(double));
+
+ // Save the data
+ data_.save(stream);
+ }
+ catch (const exception &)
+ {
+ stream.exceptions(old_except_mask);
+ throw;
+ }
+
+ stream.exceptions(old_except_mask);
+ }
+
+ void Ciphertext::unsafe_load(istream &stream)
+ {
+ auto old_except_mask = stream.exceptions();
+ try
+ {
+ // Throw exceptions on std::ios_base::badbit and std::ios_base::failbit
+ stream.exceptions(ios_base::badbit | ios_base::failbit);
+
+ parms_id_type parms_id{};
+ stream.read(reinterpret_cast(&parms_id), sizeof(parms_id_type));
+ SEAL_BYTE is_ntt_form_byte;
+ stream.read(reinterpret_cast(&is_ntt_form_byte), sizeof(SEAL_BYTE));
+ uint64_t size64 = 0;
+ stream.read(reinterpret_cast(&size64), sizeof(uint64_t));
+ uint64_t poly_modulus_degree64 = 0;
+ stream.read(reinterpret_cast(&poly_modulus_degree64), sizeof(uint64_t));
+ uint64_t coeff_mod_count64 = 0;
+ stream.read(reinterpret_cast(&coeff_mod_count64), sizeof(uint64_t));
+ double scale = 0;
+ stream.read(reinterpret_cast(&scale), sizeof(double));
+
+ // Load the data
+ IntArray new_data(data_.pool());
+ new_data.load(stream);
+ if (unsigned_neq(new_data.size(),
+ mul_safe(size64, poly_modulus_degree64, coeff_mod_count64)))
+ {
+ throw invalid_argument("ciphertext data is invalid");
+ }
+
+ // Set values
+ parms_id_ = parms_id;
+ is_ntt_form_ = (is_ntt_form_byte == SEAL_BYTE(0)) ? false : true;
+ size_ = safe_cast(size64);
+ poly_modulus_degree_ = safe_cast(poly_modulus_degree64);
+ coeff_mod_count_ = safe_cast(coeff_mod_count64);
+ scale_ = scale;
+
+ // Set the data
+ data_.swap_with(new_data);
+ }
+ catch (const exception &)
+ {
+ stream.exceptions(old_except_mask);
+ throw;
+ }
+
+ stream.exceptions(old_except_mask);
+ }
+}
diff --git a/src/seal/ciphertext.h b/src/seal/ciphertext.h
new file mode 100644
index 000000000..008aae4b6
--- /dev/null
+++ b/src/seal/ciphertext.h
@@ -0,0 +1,642 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include "seal/util/defines.h"
+#include "seal/context.h"
+#include "seal/memorymanager.h"
+#include "seal/intarray.h"
+
+namespace seal
+{
+ /**
+ Class to store a ciphertext element. The data for a ciphertext consists
+ of two or more polynomials, which are in SEAL stored in a CRT form with
+ respect to the factors of the coefficient modulus. This data itself is
+ not meant to be modified directly by the user, but is instead operated
+ on by functions in the Evaluator class. The size of the backing array of
+ a ciphertext depends on the encryption parameters and the size of the
+ ciphertext (at least 2). If the degree of the poly_modulus encryption
+ parameter is N, and the number of primes in the coeff_modulus encryption
+ parameter is K, then the ciphertext backing array requires precisely
+ 8*N*K*size bytes of memory. A ciphertext also carries with it the
+ parms_id of its associated encryption parameters, which is used to check
+ the validity of the ciphertext for homomorphic operations and decryption.
+
+ @par Memory Management
+ The size of a ciphertext refers to the number of polynomials it contains,
+ whereas its capacity refers to the number of polynomials that fit in the
+ current memory allocation. In high-performance applications unnecessary
+ re-allocations should be avoided by reserving enough memory for the
+ ciphertext to begin with either by providing the desired capacity to the
+ constructor as an extra argument, or by calling the reserve function at
+ any time.
+
+ @par Thread Safety
+ In general, reading from ciphertext is thread-safe as long as no other
+ thread is concurrently mutating it. This is due to the underlying data
+ structure storing the ciphertext not being thread-safe.
+
+ @see Plaintext for the class that stores plaintexts.
+ */
+ class Ciphertext
+ {
+ public:
+ using ct_coeff_type = std::uint64_t;
+
+ using size_type = IntArray::size_type;
+
+ /**
+ Constructs an empty ciphertext allocating no memory.
+
+ @param[in] pool The MemoryPoolHandle pointing to a valid memory pool
+ @throws std::invalid_argument if pool is uninitialized
+ */
+ Ciphertext(MemoryPoolHandle pool = MemoryManager::GetPool()) :
+ data_(std::move(pool))
+ {
+ }
+
+ /**
+ Constructs an empty ciphertext with capacity 2. In addition to the
+ capacity, the allocation size is determined by the highest-level
+ parameters associated to the given SEALContext.
+
+ @param[in] context The SEALContext
+ @param[in] pool The MemoryPoolHandle pointing to a valid memory pool
+ @throws std::invalid_argument if the context is not set or encryption
+ parameters are not valid
+ @throws std::invalid_argument if pool is uninitialized
+ */
+ explicit Ciphertext(std::shared_ptr context,
+ MemoryPoolHandle pool = MemoryManager::GetPool()) :
+ data_(std::move(pool))
+ {
+ // Allocate memory but don't resize
+ reserve(std::move(context), 2);
+ }
+
+ /**
+ Constructs an empty ciphertext with capacity 2. In addition to the
+ capacity, the allocation size is determined by the encryption parameters
+ with given parms_id.
+
+ @param[in] context The SEALContext
+ @param[in] parms_id The parms_id corresponding to the encryption
+ parameters to be used
+ @param[in] pool The MemoryPoolHandle pointing to a valid memory pool
+ @throws std::invalid_argument if the context is not set or encryption
+ parameters are not valid
+ @throws std::invalid_argument if parms_id is not valid for the encryption
+ parameters
+ @throws std::invalid_argument if pool is uninitialized
+ */
+ explicit Ciphertext(std::shared_ptr context,
+ parms_id_type parms_id,
+ MemoryPoolHandle pool = MemoryManager::GetPool()) :
+ data_(std::move(pool))
+ {
+ // Allocate memory but don't resize
+ reserve(std::move(context), parms_id, 2);
+ }
+
+ /**
+ Constructs an empty ciphertext with given capacity. In addition to
+ the capacity, the allocation size is determined by the given
+ encryption parameters.
+
+ @param[in] context The SEALContext
+ @param[in] parms_id The parms_id corresponding to the encryption
+ parameters to be used
+ @param[in] size_capacity The capacity
+ @param[in] pool The MemoryPoolHandle pointing to a valid memory pool
+ @throws std::invalid_argument if the context is not set or encryption
+ parameters are not valid
+ @throws std::invalid_argument if parms_id is not valid for the encryption
+ parameters
+ @throws std::invalid_argument if size_capacity is less than 2 or too large
+ @throws std::invalid_argument if pool is uninitialized
+ */
+ explicit Ciphertext(std::shared_ptr context,
+ parms_id_type parms_id, size_type size_capacity,
+ MemoryPoolHandle pool = MemoryManager::GetPool()) :
+ data_(std::move(pool))
+ {
+ // Allocate memory but don't resize
+ reserve(std::move(context), parms_id, size_capacity);
+ }
+
+ /**
+ Constructs a new ciphertext by copying a given one.
+
+ @param[in] copy The ciphertext to copy from
+ */
+ Ciphertext(const Ciphertext ©) = default;
+
+ /**
+ Creates a new ciphertext by moving a given one.
+
+ @param[in] source The ciphertext to move from
+ */
+ Ciphertext(Ciphertext &&source) = default;
+
+ /**
+ Allocates enough memory to accommodate the backing array of a ciphertext
+ with given capacity. In addition to the capacity, the allocation size is
+ determined by the encryption parameters corresponing to the given
+ parms_id.
+
+ @param[in] context The SEALContext
+ @param[in] parms_id The parms_id corresponding to the encryption
+ parameters to be used
+ @param[in] size_capacity The capacity
+ @throws std::invalid_argument if the context is not set or encryption
+ parameters are not valid
+ @throws std::invalid_argument if parms_id is not valid for the encryption
+ parameters
+ @throws std::invalid_argument if size_capacity is less than 2 or too large
+ */
+ void reserve(std::shared_ptr context,
+ parms_id_type parms_id, size_type size_capacity);
+
+ /**
+ Allocates enough memory to accommodate the backing array of a ciphertext
+ with given capacity. In addition to the capacity, the allocation size is
+ determined by the highest-level parameters associated to the given
+ SEALContext.
+
+ @param[in] context The SEALContext
+ @param[in] size_capacity The capacity
+ @throws std::invalid_argument if the context is not set or encryption
+ parameters are not valid
+ @throws std::invalid_argument if size_capacity is less than 2 or too large
+ */
+ inline void reserve(std::shared_ptr context,
+ size_type size_capacity)
+ {
+ // Verify parameters
+ if (!context)
+ {
+ throw std::invalid_argument("invalid context");
+ }
+ auto parms_id = context->first_parms_id();
+ reserve(std::move(context), parms_id, size_capacity);
+ }
+
+ /**
+ Allocates enough memory to accommodate the backing array of a ciphertext
+ with given capacity. In addition to the capacity, the allocation size is
+ determined by the current encryption parameters.
+
+ @param[in] size_capacity The capacity
+ @throws std::invalid_argument if size_capacity is less than 2 or too large
+ @throws std::logic_error if the encryption parameters are not
+ */
+ inline void reserve(size_type size_capacity)
+ {
+ // Note: poly_modulus_degree_ and coeff_mod_count_ are either valid
+ // or coeff_mod_count_ is zero (in which case no memory is allocated).
+ reserve_internal(size_capacity, poly_modulus_degree_,
+ coeff_mod_count_);
+ }
+
+ /**
+ Resizes the ciphertext to given size, reallocating if the capacity
+ of the ciphertext is too small. The ciphertext parameters are
+ determined by the given SEALContext and parms_id.
+
+ This function is mainly intended for internal use and is called
+ automatically by functions such as Evaluator::multiply and
+ Evaluator::relinearize. A normal user should never have a reason
+ to manually resize a ciphertext.
+
+ @param[in] context The SEALContext
+ @param[in] parms_id The parms_id corresponding to the encryption
+ parameters to be used
+ @param[in] size The new size
+ @throws std::invalid_argument if the context is not set or encryption
+ parameters are not valid
+ @throws std::invalid_argument if parms_id is not valid for the encryption
+ parameters
+ @throws std::invalid_argument if size is less than 2 or too large
+ */
+ void resize(std::shared_ptr context,
+ parms_id_type parms_id, size_type size);
+
+ /**
+ Resizes the ciphertext to given size, reallocating if the capacity
+ of the ciphertext is too small. The ciphertext parameters are
+ determined by the highest-level parameters associated to the given
+ SEALContext.
+
+ This function is mainly intended for internal use and is called
+ automatically by functions such as Evaluator::multiply and
+ Evaluator::relinearize. A normal user should never have a reason
+ to manually resize a ciphertext.
+
+ @param[in] context The SEALContext
+ @param[in] size The new size
+ @throws std::invalid_argument if the context is not set or encryption
+ parameters are not valid
+ @throws std::invalid_argument if size is less than 2 or too large
+ */
+ inline void resize(std::shared_ptr context,
+ size_type size)
+ {
+ // Verify parameters
+ if (!context)
+ {
+ throw std::invalid_argument("invalid context");
+ }
+ auto parms_id = context->first_parms_id();
+ resize(std::move(context), parms_id, size);
+ }
+
+ /**
+ Resizes the ciphertext to given size, reallocating if the capacity
+ of the ciphertext is too small.
+
+ This function is mainly intended for internal use and is called
+ automatically by functions such as Evaluator::multiply and
+ Evaluator::relinearize. A normal user should never have a reason
+ to manually resize a ciphertext.
+
+ @param[in] size The new size
+ @throws std::invalid_argument if size is less than 2 or too large
+ */
+ inline void resize(size_type size)
+ {
+ // Note: poly_modulus_degree_ and coeff_mod_count_ are either valid
+ // or coeff_mod_count_ is zero (in which case no memory is allocated).
+ resize_internal(size, poly_modulus_degree_, coeff_mod_count_);
+ }
+
+ /**
+ Resets the ciphertext. This function releases any memory allocated
+ by the ciphertext, returning it to the memory pool. It also sets all
+ encryption parameter specific size information to zero.
+ */
+ inline void release() noexcept
+ {
+ parms_id_ = parms_id_zero;
+ is_ntt_form_ = false;
+ size_capacity_ = 2;
+ size_ = 0;
+ poly_modulus_degree_ = 0;
+ coeff_mod_count_ = 0;
+ scale_ = 1.0;
+ data_.release();
+ }
+
+ /**
+ Copies a given ciphertext to the current one.
+
+ @param[in] assign The ciphertext to copy from
+ */
+ Ciphertext &operator =(const Ciphertext &assign);
+
+ /**
+ Moves a given ciphertext to the current one.
+
+ @param[in] assign The ciphertext to move from
+ */
+ Ciphertext &operator =(Ciphertext &&assign) = default;
+
+ /**
+ Returns a pointer to the beginning of the ciphertext data.
+ */
+ inline ct_coeff_type *data() noexcept
+ {
+ return data_.begin();
+ }
+
+ /**
+ Returns a const pointer to the beginning of the ciphertext data.
+ */
+ inline const ct_coeff_type *data() const noexcept
+ {
+ return data_.cbegin();
+ }
+#ifdef SEAL_USE_MSGSL_MULTISPAN
+ /**
+ Returns the ciphertext data.
+ */
+ inline gsl::multi_span<
+ ct_coeff_type,
+ gsl::dynamic_range,
+ gsl::dynamic_range,
+ gsl::dynamic_range> data_span()
+ {
+ return gsl::as_multi_span<
+ ct_coeff_type,
+ gsl::dynamic_range,
+ gsl::dynamic_range,
+ gsl::dynamic_range>(
+ data_.begin(),
+ util::safe_cast(size_),
+ util::safe_cast(coeff_mod_count_),
+ util::safe_cast(poly_modulus_degree_));
+ }
+
+ /**
+ Returns the backing array storing all of the coefficient values.
+ */
+ inline gsl::multi_span<
+ const ct_coeff_type,
+ gsl::dynamic_range,
+ gsl::dynamic_range,
+ gsl::dynamic_range> data_span() const
+ {
+ return gsl::as_multi_span<
+ const ct_coeff_type,
+ gsl::dynamic_range,
+ gsl::dynamic_range,
+ gsl::dynamic_range>(
+ data_.cbegin(),
+ util::safe_cast(size_),
+ util::safe_cast(coeff_mod_count_),
+ util::safe_cast(poly_modulus_degree_));
+ }
+#endif
+ /**
+ Returns a pointer to a particular polynomial in the ciphertext
+ data. Note that SEAL stores each polynomial in the ciphertext
+ modulo all of the K primes in the coefficient modulus. The pointer
+ returned by this function is to the beginning (constant coefficient)
+ of the first one of these K polynomials.
+
+ @param[in] poly_index The index of the polynomial in the ciphertext
+ @throws std::out_of_range if poly_index is less than 0 or bigger
+ than the size of the ciphertext
+ */
+ inline ct_coeff_type *data(size_type poly_index)
+ {
+ auto poly_uint64_count = util::mul_safe(
+ poly_modulus_degree_, coeff_mod_count_);
+ if (poly_uint64_count == 0)
+ {
+ return nullptr;
+ }
+ if (poly_index >= size_)
+ {
+ throw std::out_of_range("poly_index must be within [0, size)");
+ }
+ return data_.begin() + util::safe_cast(
+ util::mul_safe(poly_index, poly_uint64_count));
+ }
+
+ /**
+ Returns a const pointer to a particular polynomial in the
+ ciphertext data. Note that SEAL stores each polynomial in the
+ ciphertext modulo all of the K primes in the coefficient modulus.
+ The pointer returned by this function is to the beginning
+ (constant coefficient) of the first one of these K polynomials.
+
+ @param[in] poly_index The index of the polynomial in the ciphertext
+ @throws std::out_of_range if poly_index is out of range
+ */
+ inline const ct_coeff_type *data(size_type poly_index) const
+ {
+ auto poly_uint64_count = util::mul_safe(
+ poly_modulus_degree_, coeff_mod_count_);
+ if (poly_uint64_count == 0)
+ {
+ return nullptr;
+ }
+ if (poly_index >= size_)
+ {
+ throw std::out_of_range("poly_index must be within [0, size)");
+ }
+ return data_.cbegin() + util::safe_cast(
+ util::mul_safe(poly_index, poly_uint64_count));
+ }
+
+ /**
+ Returns a reference to a polynomial coefficient at a particular
+ index in the ciphertext data. If the polynomial modulus has degree N,
+ and the number of primes in the coefficient modulus is K, then the
+ ciphertext contains size*N*K coefficients. Thus, the coeff_index has
+ a range of [0, size*N*K).
+
+ @param[in] coeff_index The index of the coefficient
+ @throws std::out_of_range if coeff_index is out of range
+ */
+ inline ct_coeff_type &operator [](size_type coeff_index)
+ {
+ return data_.at(coeff_index);
+ }
+
+ /**
+ Returns a const reference to a polynomial coefficient at a particular
+ index in the ciphertext data. If the polynomial modulus has degree N,
+ and the number of primes in the coefficient modulus is K, then the
+ ciphertext contains size*N*K coefficients. Thus, the coeff_index has
+ a range of [0, size*N*K).
+
+ @param[in] coeff_index The index of the coefficient
+ @throws std::out_of_range if coeff_index is out of range
+ */
+ inline const ct_coeff_type &operator [](size_type coeff_index) const
+ {
+ return data_.at(coeff_index);
+ }
+
+ /**
+ Returns the number of primes in the coefficient modulus of the
+ associated encryption parameters. This directly affects the
+ allocation size of the ciphertext.
+ */
+ inline size_type coeff_mod_count() const noexcept
+ {
+ return coeff_mod_count_;
+ }
+
+ /**
+ Returns the degree of the polynomial modulus of the associated
+ encryption parameters. This directly affects the allocation size
+ of the ciphertext.
+ */
+ inline size_type poly_modulus_degree() const noexcept
+ {
+ return poly_modulus_degree_;
+ }
+
+ /**
+ Returns the capacity of the allocation. This means the largest size
+ of the ciphertext that can be stored in the current allocation with
+ the current encryption parameters.
+ */
+ inline size_type size_capacity() const noexcept
+ {
+ return size_capacity_;
+ }
+
+ /**
+ Returns the size of the ciphertext.
+ */
+ inline size_type size() const noexcept
+ {
+ return size_;
+ }
+
+ /**
+ Returns the total size of the current allocation in 64-bit words.
+ */
+ inline size_type uint64_count_capacity() const noexcept
+ {
+ return data_.capacity();
+ }
+
+ /**
+ Returns the total size of the current ciphertext in 64-bit words.
+ */
+ inline size_type uint64_count() const noexcept
+ {
+ return data_.size();
+ }
+
+ /**
+ Check whether the current ciphertext is valid for a given SEALContext.
+ If the given SEALContext is not set, the encryption parameters are invalid,
+ or the ciphertext data does not match the SEALContext, this function
+ returns false. Otherwise, returns true.
+
+ @param[in] context The SEALContext
+ */
+ bool is_valid_for(std::shared_ptr context) const noexcept;
+
+ /**
+ Saves the ciphertext to an output stream. The output is in binary format
+ and not human-readable. The output stream must have the "binary" flag set.
+
+ @param[in] stream The stream to save the ciphertext to
+ @throws std::exception if the ciphertext could not be written to stream
+ */
+ void save(std::ostream &stream) const;
+
+ /**
+ Loads a ciphertext from an input stream overwriting the current ciphertext.
+ No checking of the validity of the ciphertext data against encryption
+ parameters is performed. This function should not be used unless the
+ ciphertext comes from a fully trusted source.
+
+ @param[in] stream The stream to load the ciphertext from
+ @throws std::exception if a valid ciphertext could not be read from stream
+ */
+ void unsafe_load(std::istream &stream);
+
+ /**
+ Loads a ciphertext from an input stream overwriting the current ciphertext.
+ The loaded ciphertext is verified to be valid for the given SEALContext.
+
+ @param[in] context The SEALContext
+ @param[in] stream The stream to load the ciphertext from
+ @throws std::invalid_argument if the context is not set or encryption
+ parameters are not valid
+ @throws std::exception if a valid ciphertext could not be read from stream
+ @throws std::invalid_argument if the loaded ciphertext is invalid for the
+ context
+ */
+ inline void load(std::shared_ptr context,
+ std::istream &stream)
+ {
+ unsafe_load(stream);
+ if (!is_valid_for(std::move(context)))
+ {
+ throw std::invalid_argument("ciphertext data is invalid");
+ }
+ }
+
+ /**
+ Returns whether the ciphertext is in NTT form.
+ */
+ inline bool is_ntt_form() const noexcept
+ {
+ return is_ntt_form_;
+ }
+
+ /**
+ Returns whether the ciphertext is in NTT form.
+ */
+ inline bool &is_ntt_form() noexcept
+ {
+ return is_ntt_form_;
+ }
+
+ /**
+ Returns a reference to parms_id.
+
+ @see EncryptionParameters for more information about parms_id.
+ */
+ inline auto &parms_id() noexcept
+ {
+ return parms_id_;
+ }
+
+ /**
+ Returns a const reference to parms_id.
+
+ @see EncryptionParameters for more information about parms_id.
+ */
+ inline auto &parms_id() const noexcept
+ {
+ return parms_id_;
+ }
+
+ /**
+ Returns a reference to the scale. This is only needed when using the
+ CKKS encryption scheme. The user should have little or no reason to ever
+ change the scale by hand.
+ */
+ inline auto &scale() noexcept
+ {
+ return scale_;
+ }
+
+ /**
+ Returns a constant reference to the scale. This is only needed when
+ using the CKKS encryption scheme.
+ */
+ inline auto &scale() const noexcept
+ {
+ return scale_;
+ }
+
+ /**
+ Returns the currently used MemoryPoolHandle.
+ */
+ inline MemoryPoolHandle pool() const noexcept
+ {
+ return data_.pool();
+ }
+
+ private:
+ void reserve_internal(size_type size_capacity,
+ size_type poly_modulus_degree, size_type coeff_mod_count);
+
+ void resize_internal(size_type size, size_type poly_modulus_degree,
+ size_type coeff_mod_count);
+
+ parms_id_type parms_id_ = parms_id_zero;
+
+ bool is_ntt_form_ = false;
+
+ size_type size_capacity_ = 2;
+
+ size_type size_ = 0;
+
+ size_type poly_modulus_degree_ = 0;
+
+ size_type coeff_mod_count_ = 0;
+
+ double scale_ = 1.0;
+
+ IntArray data_;
+ };
+}
diff --git a/src/seal/ckks.cpp b/src/seal/ckks.cpp
new file mode 100644
index 000000000..c4f3ab695
--- /dev/null
+++ b/src/seal/ckks.cpp
@@ -0,0 +1,274 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+#include
+#include
+#include
+#include
+#include "seal/ckks.h"
+
+using namespace std;
+using namespace seal::util;
+
+namespace seal
+{
+ // For C++14 compatibility need to define static constexpr
+ // member variables with no initialization here.
+ constexpr double CKKSEncoder::PI_;
+
+ CKKSEncoder::CKKSEncoder(shared_ptr context) :
+ context_(context)
+ {
+ // Verify parameters
+ if (!context_)
+ {
+ throw invalid_argument("invalid context");
+ }
+ if (!context_->parameters_set())
+ {
+ throw invalid_argument("encryption parameters are not set correctly");
+ }
+
+ auto &context_data = *context_->context_data();
+ if (context_data.parms().scheme() != scheme_type::CKKS)
+ {
+ throw invalid_argument("unsupported scheme");
+ }
+
+ size_t coeff_count = context_data.parms().poly_modulus_degree();
+ slots_ = coeff_count >> 1;
+ int logn = get_power_of_two(coeff_count);
+
+ matrix_reps_index_map_ = allocate_uint(coeff_count, pool_);
+
+ // Copy from the matrix to the value vectors
+ uint64_t gen = 3;
+ uint64_t pos = 1;
+ uint64_t m = coeff_count << 1;
+ for (size_t i = 0; i < slots_; i++)
+ {
+ // Position in normal bit order
+ uint64_t index1 = (pos - 1) >> 1;
+ uint64_t index2 = (m - pos - 1) >> 1;
+
+ // Set the bit-reversed locations
+ matrix_reps_index_map_[i] = reverse_bits(index1, logn);
+ matrix_reps_index_map_[slots_ | i] = reverse_bits(index2, logn);
+
+ // Next primitive root
+ pos *= gen;
+ pos &= (m - 1);
+ }
+
+ roots_ = allocate>(coeff_count, pool_);
+ inv_roots_ = allocate>(coeff_count, pool_);
+ complex psi{ cos((2 * PI_) / static_cast(m)),
+ sin((2 * PI_) / static_cast(m)) };
+ for (size_t i = 0; i < coeff_count; i++)
+ {
+ roots_[i] = pow(psi, static_cast(reverse_bits(i, logn)));
+ inv_roots_[i] = 1.0 / roots_[i];
+ }
+ }
+
+ void CKKSEncoder::encode_internal(double value, parms_id_type parms_id,
+ double scale, Plaintext &destination, MemoryPoolHandle pool)
+ {
+ // Verify parameters.
+ auto context_data_ptr = context_->context_data(parms_id);
+ if (!context_data_ptr)
+ {
+ throw invalid_argument("parms_id is not valid for encryption parameters");
+ }
+ if (!pool)
+ {
+ throw invalid_argument("pool is uninitialized");
+ }
+
+ auto &context_data = *context_data_ptr;
+ auto &parms = context_data.parms();
+ auto &coeff_modulus = parms.coeff_modulus();
+ size_t coeff_mod_count = coeff_modulus.size();
+ size_t coeff_count = parms.poly_modulus_degree();
+
+ // Quick sanity check
+ if (!product_fits_in(coeff_mod_count, coeff_count))
+ {
+ throw logic_error("invalid parameters");
+ }
+
+ // Check that scale is positive and not too large
+ if (scale <= 0 || (static_cast(log2(scale)) >=
+ context_data.total_coeff_modulus_bit_count()))
+ {
+ throw invalid_argument("scale out of bounds");
+ }
+
+ // Compute the scaled value
+ value *= scale;
+
+ int coeff_bit_count = static_cast(log2(fabs(value))) + 2;
+ if (coeff_bit_count >= context_data.total_coeff_modulus_bit_count())
+ {
+ throw invalid_argument("encoded value is too large");
+ }
+
+ double two_pow_64 = pow(2.0, 64);
+
+ // Resize destination to appropriate size
+ // Need to first set parms_id to zero, otherwise resize
+ // will throw an exception.
+ destination.parms_id() = parms_id_zero;
+ destination.resize(coeff_count * coeff_mod_count);
+
+ double coeffd = round(value);
+ bool is_negative = signbit(coeffd);
+ coeffd = fabs(coeffd);
+
+ // Use faster decomposition methods when possible
+ if (coeff_bit_count <= 64)
+ {
+ uint64_t coeffu = static_cast(fabs(coeffd));
+
+ if (is_negative)
+ {
+ for (size_t j = 0; j < coeff_mod_count; j++)
+ {
+ fill_n(destination.data() + (j * coeff_count), coeff_count,
+ negate_uint_mod(coeffu % coeff_modulus[j].value(),
+ coeff_modulus[j]));
+ }
+ }
+ else
+ {
+ for (size_t j = 0; j < coeff_mod_count; j++)
+ {
+ fill_n(destination.data() + (j * coeff_count), coeff_count,
+ coeffu % coeff_modulus[j].value());
+ }
+ }
+ }
+ else if (coeff_bit_count <= 128)
+ {
+ uint64_t coeffu[2]{
+ static_cast(fmod(coeffd, two_pow_64)),
+ static_cast(coeffd / two_pow_64) };
+
+ if (is_negative)
+ {
+ for (size_t j = 0; j < coeff_mod_count; j++)
+ {
+ fill_n(destination.data() + (j * coeff_count), coeff_count,
+ negate_uint_mod(barrett_reduce_128(
+ coeffu, coeff_modulus[j]), coeff_modulus[j]));
+ }
+ }
+ else
+ {
+ for (size_t j = 0; j < coeff_mod_count; j++)
+ {
+ fill_n(destination.data() + (j * coeff_count), coeff_count,
+ barrett_reduce_128(coeffu, coeff_modulus[j]));
+ }
+ }
+ }
+ else
+ {
+ // Slow case
+ auto coeffu(allocate_uint(coeff_mod_count, pool));
+ auto decomp_coeffu(allocate_uint(coeff_mod_count, pool));
+
+ // We are at this point guaranteed to fit in the allocated space
+ set_zero_uint(coeff_mod_count, coeffu.get());
+ auto coeffu_ptr = coeffu.get();
+ while (coeffd >= 1)
+ {
+ *coeffu_ptr++ = static_cast(fmod(coeffd, two_pow_64));
+ coeffd /= two_pow_64;
+ }
+
+ // Next decompose this coefficient
+ decompose_single_coeff(context_data, coeffu.get(), decomp_coeffu.get(), pool);
+
+ // Finally replace the sign if necessary
+ if (is_negative)
+ {
+ for (size_t j = 0; j < coeff_mod_count; j++)
+ {
+ fill_n(destination.data() + (j * coeff_count), coeff_count,
+ negate_uint_mod(decomp_coeffu[j], coeff_modulus[j]));
+ }
+ }
+ else
+ {
+ for (size_t j = 0; j < coeff_mod_count; j++)
+ {
+ fill_n(destination.data() + (j * coeff_count), coeff_count,
+ decomp_coeffu[j]);
+ }
+ }
+ }
+
+ destination.parms_id() = parms_id;
+ destination.scale() = scale;
+ }
+
+ void CKKSEncoder::encode_internal(int64_t value, parms_id_type parms_id,
+ Plaintext &destination)
+ {
+ // Verify parameters.
+ auto context_data_ptr = context_->context_data(parms_id);
+ if (!context_data_ptr)
+ {
+ throw invalid_argument("parms_id is not valid for encryption parameters");
+ }
+
+ auto &context_data = *context_data_ptr;
+ auto &parms = context_data.parms();
+ auto &coeff_modulus = parms.coeff_modulus();
+ size_t coeff_mod_count = coeff_modulus.size();
+ size_t coeff_count = parms.poly_modulus_degree();
+
+ // Quick sanity check
+ if (!product_fits_in(coeff_mod_count, coeff_count))
+ {
+ throw logic_error("invalid parameters");
+ }
+
+ int coeff_bit_count = get_significant_bit_count(
+ static_cast(llabs(value))) + 2;
+ if (coeff_bit_count >= context_data.total_coeff_modulus_bit_count())
+ {
+ throw invalid_argument("encoded value is too large");
+ }
+
+ // Resize destination to appropriate size
+ // Need to first set parms_id to zero, otherwise resize
+ // will throw an exception.
+ destination.parms_id() = parms_id_zero;
+ destination.resize(coeff_count * coeff_mod_count);
+
+ if (value < 0)
+ {
+ for (size_t j = 0; j < coeff_mod_count; j++)
+ {
+ uint64_t tmp = static_cast(value);
+ tmp += coeff_modulus[j].value();
+ tmp %= coeff_modulus[j].value();
+ fill_n(destination.data() + (j * coeff_count), coeff_count, tmp);
+ }
+ }
+ else
+ {
+ for (size_t j = 0; j < coeff_mod_count; j++)
+ {
+ uint64_t tmp = static_cast(value);
+ tmp %= coeff_modulus[j].value();
+ fill_n(destination.data() + (j * coeff_count), coeff_count, tmp);
+ }
+ }
+
+ destination.parms_id() = parms_id;
+ destination.scale() = 1.0;
+ }
+}
diff --git a/src/seal/ckks.h b/src/seal/ckks.h
new file mode 100644
index 000000000..9e2a943c3
--- /dev/null
+++ b/src/seal/ckks.h
@@ -0,0 +1,745 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include "seal/plaintext.h"
+#include "seal/context.h"
+#include "seal/util/common.h"
+#include "seal/util/uintcore.h"
+#include "seal/util/uintarithsmallmod.h"
+
+namespace seal
+{
+ template::value ||
+ std::is_same>::value>>
+ inline T_out from_complex(std::complex in);
+
+ template<>
+ inline double from_complex(std::complex in)
+ {
+ return in.real();
+ }
+
+ template<>
+ inline std::complex from_complex(std::complex in)
+ {
+ return in;
+ }
+
+ /**
+ Provides functionality for encoding vectors of complex or real numbers into plaintext
+ polynomials to be encrypted and computed on using the CKKS scheme. If the polynomial
+ modulus degree is N, then CKKSEncoder converts vectors of N/2 complex numbers into
+ plaintext elements. Homomorphic operations performed on such encrypted vectors are
+ applied coefficient (slot-)wise, enabling powerful SIMD functionality for computations
+ that are vectorizable. This functionality is often called "batching" in the homomorphic
+ encryption literature.
+
+ @par Mathematical Background
+ Mathematically speaking, if the polynomial modulus is X^N+1, N is a power of two, the
+ CKKSEncoder implements an approximation of the canonical embedding of the ring of
+ integers Z[X]/(X^N+1) into C^(N/2), where C denotes the complex numbers. The Galois
+ group of the extension is (Z/2NZ)* ~= Z/2Z x Z/(N/2) whose action on the primitive roots
+ of unity modulo coeff_modulus is easy to describe. Since the batching slots correspond
+ 1-to-1 to the primitive roots of unity, applying Galois automorphisms on the plaintext
+ acts by permuting the slots. By applying generators of the two cyclic subgroups of the
+ Galois group, we can effectively enable cyclic rotations and complex conjugations of
+ the encrypted complex vectors.
+ */
+ class CKKSEncoder
+ {
+ public:
+ /**
+ Creates a CKKSEncoder instance initialized with the specified SEALContext.
+
+ @param[in] context The SEALContext
+ @throws std::invalid_argument if the context is not set or encryption parameters
+ are not valid
+ @throws std::invalid_argument if scheme is not scheme_type::CKKS
+ */
+ CKKSEncoder(std::shared_ptr context);
+
+ /**
+ Encodes double-precision floating-point real or complex numbers into a plaintext
+ polynomial. Dynamic memory allocations in the process are allocated from the
+ memory pool pointed to by the given MemoryPoolHandle.
+
+ @tparam T Vector value type (double or std::complex)
+ @param[in] values The vector of double-precision floating-point numbers
+ (of type T) to encode
+ @param[in] parms_id parms_id determining the encryption parameters to be used
+ by the result plaintext
+ @param[in] scale Scaling parameter defining encoding precision
+ @param[out] destination The plaintext polynomial to overwrite with the result
+ @param[in] pool The MemoryPoolHandle pointing to a valid memory pool
+ @throws std::invalid_argument if values has invalid size
+ @throws std::invalid_argument if parms_id is not valid for the encryption
+ parameters
+ @throws std::invalid_argument if scale is not strictly positive
+ @throws std::invalid_argument if encoding is too large for the encryption
+ parameters
+ @throws std::invalid_argument if pool is uninitialized
+ */
+ template::value ||
+ std::is_same>::value>>
+ inline void encode(const std::vector &values,
+ parms_id_type parms_id, double scale, Plaintext &destination,
+ MemoryPoolHandle pool = MemoryManager::GetPool())
+ {
+ encode_internal(values, parms_id, scale, destination, std::move(pool));
+ }
+
+ /**
+ Encodes double-precision floating-point real or complex numbers into
+ a plaintext polynomial. The encryption parameters used are the top level
+ parameters for the given context. Dynamic memory allocations in the process
+ are allocated from the memory pool pointed to by the given MemoryPoolHandle.
+
+ @tparam T Vector value type (double or std::complex)
+ @param[in] values The vector of double-precision floating-point numbers
+ (of type T) to encode
+ @param[in] scale Scaling parameter defining encoding precision
+ @param[out] destination The plaintext polynomial to overwrite with the result
+ @param[in] pool The MemoryPoolHandle pointing to a valid memory pool
+ @throws std::invalid_argument if values has invalid size
+ @throws std::invalid_argument if scale is not strictly positive
+ @throws std::invalid_argument if encoding is too large for the encryption
+ parameters
+ @throws std::invalid_argument if pool is uninitialized
+ */
+ template::value ||
+ std::is_same>::value>>
+ inline void encode(const std::vector &values,
+ double scale, Plaintext &destination,
+ MemoryPoolHandle pool = MemoryManager::GetPool())
+ {
+ encode(values, context_->first_parms_id(), scale,
+ destination, std::move(pool));
+ }
+
+ /**
+ Encodes a double-precision floating-point number into a plaintext polynomial.
+ Dynamic memory allocations in the process are allocated from the memory pool
+ pointed to by the given MemoryPoolHandle.
+
+ @param[in] value The double-precision floating-point number to encode
+ @param[in] parms_id parms_id determining the encryption parameters to be used
+ by the result plaintext
+ @param[in] scale Scaling parameter defining encoding precision
+ @param[out] destination The plaintext polynomial to overwrite with the result
+ @param[in] pool The MemoryPoolHandle pointing to a valid memory pool
+ @throws std::invalid_argument if parms_id is not valid for the encryption
+ parameters
+ @throws std::invalid_argument if scale is not strictly positive
+ @throws std::invalid_argument if encoding is too large for the encryption
+ parameters
+ @throws std::invalid_argument if pool is uninitialized
+ */
+ inline void encode(double value, parms_id_type parms_id,
+ double scale, Plaintext &destination,
+ MemoryPoolHandle pool = MemoryManager::GetPool())
+ {
+ encode_internal(value, parms_id, scale, destination, std::move(pool));
+ }
+
+ /**
+ Encodes a double-precision floating-point number into a plaintext polynomial.
+ The encryption parameters used are the top level parameters for the given context.
+ Dynamic memory allocations in the process are allocated from the memory pool
+ pointed to by the given MemoryPoolHandle.
+
+ @param[in] value The double-precision floating-point number to encode
+ @param[in] scale Scaling parameter defining encoding precision
+ @param[out] destination The plaintext polynomial to overwrite with the result
+ @param[in] pool The MemoryPoolHandle pointing to a valid memory pool
+ @throws std::invalid_argument if scale is not strictly positive
+ @throws std::invalid_argument if encoding is too large for the encryption
+ parameters
+ @throws std::invalid_argument if pool is uninitialized
+ */
+ inline void encode(double value,
+ double scale, Plaintext &destination,
+ MemoryPoolHandle pool = MemoryManager::GetPool())
+ {
+ encode(value, context_->first_parms_id(), scale,
+ destination, std::move(pool));
+ }
+
+ /**
+ Encodes a double-precision complex number into a plaintext polynomial. Dynamic
+ memory allocations in the process are allocated from the memory pool pointed to
+ by the given MemoryPoolHandle.
+
+ @param[in] value The double-precision complex number to encode
+ @param[in] parms_id parms_id determining the encryption parameters to be used
+ by the result plaintext
+ @param[in] scale Scaling parameter defining encoding precision
+ @param[out] destination The plaintext polynomial to overwrite with the result
+ @param[in] pool The MemoryPoolHandle pointing to a valid memory pool
+ @throws std::invalid_argument if parms_id is not valid for the encryption
+ parameters
+ @throws std::invalid_argument if scale is not strictly positive
+ @throws std::invalid_argument if encoding is too large for the encryption
+ parameters
+ @throws std::invalid_argument if pool is uninitialized
+ */
+ inline void encode(std::complex value,
+ parms_id_type parms_id, double scale, Plaintext &destination,
+ MemoryPoolHandle pool = MemoryManager::GetPool())
+ {
+ encode_internal(value, parms_id, scale, destination, std::move(pool));
+ }
+
+ /**
+ Encodes a double-precision complex number into a plaintext polynomial. The
+ encryption parameters used are the top level parameters for the given context.
+ Dynamic memory allocations in the process are allocated from the memory pool
+ pointed to by the given MemoryPoolHandle.
+
+ @param[in] value The double-precision complex number to encode
+ @param[in] scale Scaling parameter defining encoding precision
+ @param[out] destination The plaintext polynomial to overwrite with the result
+ @param[in] pool The MemoryPoolHandle pointing to a valid memory pool
+ @throws std::invalid_argument if scale is not strictly positive
+ @throws std::invalid_argument if encoding is too large for the encryption
+ parameters
+ @throws std::invalid_argument if pool is uninitialized
+ */
+ inline void encode(std::complex value,
+ double scale, Plaintext &destination,
+ MemoryPoolHandle pool = MemoryManager::GetPool())
+ {
+ encode(value, context_->first_parms_id(), scale,
+ destination, std::move(pool));
+ }
+
+ /**
+ Encodes an integer number into a plaintext polynomial without any scaling.
+
+ @param[in] value The integer number to encode
+ @param[in] parms_id parms_id determining the encryption parameters to be used
+ by the result plaintext
+ @param[out] destination The plaintext polynomial to overwrite with the result
+ @throws std::invalid_argument if parms_id is not valid for the encryption
+ parameters
+ */
+ inline void encode(std::int64_t value,
+ parms_id_type parms_id, Plaintext &destination)
+ {
+ encode_internal(value, parms_id, destination);
+ }
+
+ /**
+ Encodes an integer number into a plaintext polynomial without any scaling. The
+ encryption parameters used are the top level parameters for the given context.
+
+ @param[in] value The integer number to encode
+ @param[out] destination The plaintext polynomial to overwrite with the result
+ */
+ inline void encode(std::int64_t value, Plaintext &destination)
+ {
+ encode(value, context_->first_parms_id(), destination);
+ }
+
+ /**
+ Decodes a plaintext polynomial into double-precision floating-point real or
+ complex numbers. Dynamic memory allocations in the process are allocated from
+ the memory pool pointed to by the given MemoryPoolHandle.
+
+ @tparam T Vector value type (double or std::complex)
+ @param[in] plain The plaintext to decode
+ @param[out] destination The vector to be overwritten with the values in the slots
+ @param[in] pool The MemoryPoolHandle pointing to a valid memory pool
+ @throws std::invalid_argument if plain is not in NTT form or is invalid for the
+ encryption parameters
+ @throws std::invalid_argument if pool is uninitialized
+ */
+ template::value ||
+ std::is_same>::value>>
+ inline void decode(const Plaintext &plain, std::vector &destination,
+ MemoryPoolHandle pool = MemoryManager::GetPool())
+ {
+ decode_internal(plain, destination, std::move(pool));
+ }
+
+ /**
+ Returns the number of complex numbers encoded.
+ */
+ inline std::size_t slot_count() const noexcept
+ {
+ return slots_;
+ }
+
+ private:
+ // This is the same function as in evaluator.h
+ inline void decompose_single_coeff(
+ const SEALContext::ContextData &context_data, const std::uint64_t *value,
+ std::uint64_t *destination, util::MemoryPool &pool)
+ {
+ auto &parms = context_data.parms();
+ auto &coeff_modulus = parms.coeff_modulus();
+ std::size_t coeff_mod_count = coeff_modulus.size();
+#ifdef SEAL_DEBUG
+ if (value == nullptr)
+ {
+ throw std::invalid_argument("value cannot be null");
+ }
+ if (destination == nullptr)
+ {
+ throw std::invalid_argument("destination cannot be null");
+ }
+ if (destination == value)
+ {
+ throw std::invalid_argument("value cannot be the same as destination");
+ }
+#endif
+ if (coeff_mod_count == 1)
+ {
+ util::set_uint_uint(value, coeff_mod_count, destination);
+ return;
+ }
+
+ auto value_copy(util::allocate_uint(coeff_mod_count, pool));
+ for (std::size_t j = 0; j < coeff_mod_count; j++)
+ {
+ //destination[j] = util::modulo_uint(
+ // value, coeff_mod_count, coeff_modulus_[j], pool);
+
+ // Manually inlined for efficiency
+ // Make a fresh copy of value
+ util::set_uint_uint(value, coeff_mod_count, value_copy.get());
+
+ // Starting from the top, reduce always 128-bit blocks
+ for (std::size_t k = coeff_mod_count - 1; k--; )
+ {
+ value_copy[k] = util::barrett_reduce_128(
+ value_copy.get() + k, coeff_modulus[j]);
+ }
+ destination[j] = value_copy[0];
+ }
+ }
+
+ template::value ||
+ std::is_same>::value>>
+ void encode_internal(const std::vector &values,
+ parms_id_type parms_id, double scale, Plaintext &destination,
+ MemoryPoolHandle pool)
+ {
+ // Verify parameters.
+ auto context_data_ptr = context_->context_data(parms_id);
+ if (!context_data_ptr)
+ {
+ throw std::invalid_argument("parms_id is not valid for encryption parameters");
+ }
+ if (values.size() > slots_)
+ {
+ throw std::invalid_argument("values has invalid size");
+ }
+ if (!pool)
+ {
+ throw std::invalid_argument("pool is uninitialized");
+ }
+
+ auto &context_data = *context_data_ptr;
+ auto &parms = context_data.parms();
+ auto &coeff_modulus = parms.coeff_modulus();
+ std::size_t coeff_mod_count = coeff_modulus.size();
+ std::size_t coeff_count = parms.poly_modulus_degree();
+
+ // Quick sanity check
+ if (!util::product_fits_in(coeff_mod_count, coeff_count))
+ {
+ throw std::logic_error("invalid parameters");
+ }
+
+ // Check that scale is positive and not too large
+ if (scale <= 0 || (static_cast(log2(scale)) + 1 >=
+ context_data.total_coeff_modulus_bit_count()))
+ {
+ throw std::invalid_argument("scale out of bounds");
+ }
+
+ auto &small_ntt_tables = context_data.small_ntt_tables();
+
+ // input_size is guaranteed to be no bigger than slots_
+ std::size_t input_size = values.size();
+ std::size_t n = util::mul_safe(slots_, std::size_t(2));
+
+ auto conj_values = util::allocate>(n, pool, 0);
+ for (std::size_t i = 0; i < input_size; i++)
+ {
+ conj_values[matrix_reps_index_map_[i]] = values[i];
+ conj_values[matrix_reps_index_map_[i + slots_]] = std::conj(values[i]);
+ }
+
+ int logn = util::get_power_of_two(n);
+ std::size_t tt = 1;
+ for (int i = 0; i < logn; i++)
+ {
+ std::size_t mm = std::size_t(1) << (logn - i);
+ std::size_t k_start = 0;
+ std::size_t h = mm / 2;
+
+ for (std::size_t j = 0; j < h; j++)
+ {
+ std::size_t k_end = k_start + tt;
+ auto s = inv_roots_[h + j];
+
+ for (std::size_t k = k_start; k < k_end; k++)
+ {
+ auto u = conj_values[k];
+ auto v = conj_values[k + tt];
+ conj_values[k] = u + v;
+ conj_values[k + tt] = (u - v) * s;
+ }
+
+ k_start += 2 * tt;
+ }
+ tt *= 2;
+ }
+
+ double n_inv = double(1.0) / static_cast(n);
+
+ // Put the scale in at this point
+ n_inv *= scale;
+
+ int max_coeff_bit_count = 1;
+ for (std::size_t i = 0; i < n; i++)
+ {
+ // Multiply by scale and n_inv (see above)
+ conj_values[i] *= n_inv;
+
+ // Verify that the values are not too large to fit in coeff_modulus
+ // Note that we have an extra + 1 for the sign bit
+ max_coeff_bit_count = std::max(max_coeff_bit_count,
+ static_cast(std::log2(std::fabs(conj_values[i].real()))) + 2);
+ }
+ if (max_coeff_bit_count >= context_data.total_coeff_modulus_bit_count())
+ {
+ throw std::invalid_argument("encoded values are too large");
+ }
+
+ double two_pow_64 = std::pow(2.0, 64);
+
+ // Resize destination to appropriate size
+ // Need to first set parms_id to zero, otherwise resize
+ // will throw an exception.
+ destination.parms_id() = parms_id_zero;
+ destination.resize(util::mul_safe(coeff_count, coeff_mod_count));
+
+ // Use faster decomposition methods when possible
+ if (max_coeff_bit_count <= 64)
+ {
+ for (std::size_t i = 0; i < n; i++)
+ {
+ double coeffd = std::round(conj_values[i].real());
+ bool is_negative = std::signbit(coeffd);
+
+ std::uint64_t coeffu =
+ static_cast(std::fabs(coeffd));
+
+ if (is_negative)
+ {
+ for (std::size_t j = 0; j < coeff_mod_count; j++)
+ {
+ destination[i + (j * coeff_count)] = util::negate_uint_mod(
+ coeffu % coeff_modulus[j].value(), coeff_modulus[j]);
+ }
+ }
+ else
+ {
+ for (std::size_t j = 0; j < coeff_mod_count; j++)
+ {
+ destination[i + (j * coeff_count)] =
+ coeffu % coeff_modulus[j].value();
+ }
+ }
+ }
+ }
+ else if (max_coeff_bit_count <= 128)
+ {
+ for (std::size_t i = 0; i < n; i++)
+ {
+ double coeffd = std::round(conj_values[i].real());
+ bool is_negative = std::signbit(coeffd);
+ coeffd = std::fabs(coeffd);
+
+ std::uint64_t coeffu[2]{
+ static_cast(std::fmod(coeffd, two_pow_64)),
+ static_cast(coeffd / two_pow_64) };
+
+ if (is_negative)
+ {
+ for (std::size_t j = 0; j < coeff_mod_count; j++)
+ {
+ destination[i + (j * coeff_count)] =
+ util::negate_uint_mod(util::barrett_reduce_128(
+ coeffu, coeff_modulus[j]), coeff_modulus[j]);
+ }
+ }
+ else
+ {
+ for (std::size_t j = 0; j < coeff_mod_count; j++)
+ {
+ destination[i + (j * coeff_count)] =
+ util::barrett_reduce_128(coeffu, coeff_modulus[j]);
+ }
+ }
+ }
+ }
+ else
+ {
+ // Slow case
+ auto coeffu(util::allocate_uint(coeff_mod_count, pool));
+ auto decomp_coeffu(util::allocate_uint(coeff_mod_count, pool));
+ for (std::size_t i = 0; i < n; i++)
+ {
+ double coeffd = std::round(conj_values[i].real());
+ bool is_negative = std::signbit(coeffd);
+ coeffd = std::fabs(coeffd);
+
+ // We are at this point guaranteed to fit in the allocated space
+ util::set_zero_uint(coeff_mod_count, coeffu.get());
+ auto coeffu_ptr = coeffu.get();
+ while (coeffd >= 1)
+ {
+ *coeffu_ptr++ = static_cast(
+ std::fmod(coeffd, two_pow_64));
+ coeffd /= two_pow_64;
+ }
+
+ // Next decompose this coefficient
+ decompose_single_coeff(context_data, coeffu.get(),
+ decomp_coeffu.get(), pool);
+
+ // Finally replace the sign if necessary
+ if (is_negative)
+ {
+ for (std::size_t j = 0; j < coeff_mod_count; j++)
+ {
+ destination[i + (j * coeff_count)] =
+ util::negate_uint_mod(decomp_coeffu[j], coeff_modulus[j]);
+ }
+ }
+ else
+ {
+ for (std::size_t j = 0; j < coeff_mod_count; j++)
+ {
+ destination[i + (j * coeff_count)] = decomp_coeffu[j];
+ }
+ }
+ }
+ }
+
+ // Transform to NTT domain
+ for (std::size_t i = 0; i < coeff_mod_count; i++)
+ {
+ util::ntt_negacyclic_harvey(
+ destination.data(i * coeff_count), small_ntt_tables[i]);
+ }
+
+ destination.parms_id() = parms_id;
+ destination.scale() = scale;
+ }
+
+ template::value ||
+ std::is_same>::value>>
+ void decode_internal(const Plaintext &plain, std::vector &destination,
+ MemoryPoolHandle pool)
+ {
+ // Verify parameters.
+ if (!plain.is_ntt_form())
+ {
+ throw std::invalid_argument("plain is not in NTT form");
+ }
+ if (!pool)
+ {
+ throw std::invalid_argument("pool is uninitialized");
+ }
+
+ auto context_data_ptr = context_->context_data(plain.parms_id());
+ if (!context_data_ptr)
+ {
+ throw std::invalid_argument("parms_id is not valid for encryption parameters");
+ }
+
+ auto &parms = context_data_ptr->parms();
+ auto &coeff_modulus = parms.coeff_modulus();
+ std::size_t coeff_mod_count = coeff_modulus.size();
+ std::size_t coeff_count = parms.poly_modulus_degree();
+ std::size_t rns_poly_uint64_count =
+ util::mul_safe(coeff_count, coeff_mod_count);
+
+ auto &small_ntt_tables = context_data_ptr->small_ntt_tables();
+
+ // Check that scale is positive and not too large
+ if (plain.scale() <= 0 || (static_cast(log2(plain.scale())) >=
+ context_data_ptr->total_coeff_modulus_bit_count()))
+ {
+ throw std::invalid_argument("scale out of bounds");
+ }
+
+ auto decryption_modulus = context_data_ptr->total_coeff_modulus();
+ auto upper_half_threshold = context_data_ptr->upper_half_threshold();
+
+ auto &inv_coeff_products_mod_coeff_array =
+ context_data_ptr->base_converter()->get_inv_coeff_mod_coeff_array();
+ auto coeff_products_array =
+ context_data_ptr->base_converter()->get_coeff_products_array();
+
+ int logn = util::get_power_of_two(coeff_count);
+
+ // Quick sanity check
+ if ((logn < 0) || (coeff_count < SEAL_POLY_MOD_DEGREE_MIN) ||
+ (coeff_count > SEAL_POLY_MOD_DEGREE_MAX))
+ {
+ throw std::logic_error("invalid parameters");
+ }
+
+ double inv_scale = double(1.0) / plain.scale();
+
+ // Create mutable copy of input
+ auto plain_copy = util::allocate_uint(rns_poly_uint64_count, pool);
+ util::set_uint_uint(plain.data(), rns_poly_uint64_count, plain_copy.get());
+
+ // Array to keep number bigger than std::uint64_t
+ auto temp(util::allocate_uint(coeff_mod_count, pool));
+
+ // destination mod q
+ auto wide_tmp_dest(util::allocate_zero_uint(rns_poly_uint64_count, pool));
+
+ // Transform each polynomial from NTT domain
+ for (std::size_t i = 0; i < coeff_mod_count; i++)
+ {
+ util::inverse_ntt_negacyclic_harvey(
+ plain_copy.get() + (i * coeff_count), small_ntt_tables[i]);
+ }
+
+ auto res = util::allocate>(coeff_count, pool);
+
+ double two_pow_64 = std::pow(2.0, 64);
+ for (std::size_t i = 0; i < coeff_count; i++)
+ {
+ for (std::size_t j = 0; j < coeff_mod_count; j++)
+ {
+ std::uint64_t tmp = util::multiply_uint_uint_mod(
+ plain_copy[(j * coeff_count) + i],
+ inv_coeff_products_mod_coeff_array[j], // (qi/q * plain[i]) mod qi
+ coeff_modulus[j]);
+ util::multiply_uint_uint64(
+ coeff_products_array + (j * coeff_mod_count),
+ coeff_mod_count, tmp, coeff_mod_count, temp.get());
+ util::add_uint_uint_mod(temp.get(),
+ wide_tmp_dest.get() + (i * coeff_mod_count),
+ decryption_modulus, coeff_mod_count,
+ wide_tmp_dest.get() + (i * coeff_mod_count));
+ }
+
+ double res_accum = 0.0;
+ if (util::is_greater_than_or_equal_uint_uint(
+ wide_tmp_dest.get() + (i * coeff_mod_count),
+ upper_half_threshold, coeff_mod_count))
+ {
+ for (std::size_t j = 0; j < coeff_mod_count; j++)
+ {
+ double diff = 0.0;
+ if (wide_tmp_dest[i * coeff_mod_count + j] > decryption_modulus[j])
+ {
+ diff = static_cast(wide_tmp_dest[i * coeff_mod_count + j]
+ - decryption_modulus[j]);
+ }
+ else
+ {
+ diff = -static_cast(decryption_modulus[j]
+ - wide_tmp_dest[i * coeff_mod_count + j]);
+ }
+ res_accum += diff * pow(two_pow_64, j);
+ }
+ }
+ else
+ {
+ for (std::size_t j = 0; j < coeff_mod_count; j++)
+ {
+ res_accum += static_cast(
+ wide_tmp_dest[i * coeff_mod_count + j]) * pow(two_pow_64, j);
+ }
+ }
+
+ res[i] = res_accum * inv_scale;
+ }
+
+ std::size_t tt = coeff_count;
+ for (int i = 0; i < logn; i++)
+ {
+ std::size_t mm = std::size_t(1) << i;
+ tt >>= 1;
+
+ for (std::size_t j = 0; j < mm; j++)
+ {
+ std::size_t j1 = 2 * j * tt;
+ std::size_t j2 = j1 + tt - 1;
+ auto s = roots_[mm + j];
+
+ for (std::size_t k = j1; k < j2 + 1; k++)
+ {
+ auto u = res[k];
+ auto v = res[k + tt] * s;
+ res[k] = u + v;
+ res[k + tt] = u - v;
+ }
+ }
+ }
+
+ destination.clear();
+ destination.reserve(slots_);
+ for (std::size_t i = 0; i < slots_; i++)
+ {
+ destination.emplace_back(
+ from_complex(res[matrix_reps_index_map_[i]]));
+ }
+ }
+
+ void encode_internal(double value, parms_id_type parms_id,
+ double scale, Plaintext &destination, MemoryPoolHandle pool);
+
+ inline void encode_internal(std::complex value,
+ parms_id_type parms_id, double scale, Plaintext &destination,
+ MemoryPoolHandle pool)
+ {
+ encode_internal(std::vector>(1, value),
+ parms_id, scale, destination, std::move(pool));
+ }
+
+ void encode_internal(std::int64_t value,
+ parms_id_type parms_id, Plaintext &destination);
+
+ MemoryPoolHandle pool_ = MemoryManager::GetPool();
+
+ static constexpr double PI_ = 3.14159265358979323846;
+
+ static const double two_pow_64_;
+
+ std::shared_ptr context_{ nullptr };
+
+ std::size_t slots_;
+
+ util::Pointer