Commit c6c9d2f5 authored by Hoang Gia NGUYEN's avatar Hoang Gia NGUYEN
Browse files

first commit

parent fe2d6195
Pipeline #60 failed with stages
in 0 seconds

Too many changes to show.

To preserve performance only 275 of 275+ files are displayed.
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the MIT license. -->
<resources>
<color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#D81B60</color>
</resources>
<!-- Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the MIT license. -->
<resources>
<string name="app_name">SEAL</string>
</resources>
<!-- Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the MIT license. -->
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.6.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT license.
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT license.
#Mon Apr 06 10:08:19 PDT 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
include ':app'
rootProject.name='SEAL'
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT license.
# Exports target SEAL::seal
#
# Creates variables:
# SEAL_FOUND : If a static Microsoft SEAL library was found
# SEAL_SHARED_FOUND : If a shared Microsoft SEAL library was found
# SEAL_C_FOUND : If a Microsoft SEAL C export library was found
# SEAL_VERSION : The full version number
# SEAL_VERSION_MAJOR : The major version number
# SEAL_VERSION_MINOR : The minor version number
# SEAL_VERSION_PATCH : The patch version number
# SEAL_DEBUG : Set to non-zero value if library is compiled with extra debugging code (very slow!)
# SEAL_USE_CXX17 : Set to non-zero value if library is compiled as C++17 instead of C++14
# SEAL_ENFORCE_HE_STD_SECURITY : Set to non-zero value if library 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 library is compiled with Microsoft GSL support
# SEAL_USE_ZLIB : Set to non-zero value if library is compiled with zlib support
@PACKAGE_INIT@
include(CMakeFindDependencyMacro)
macro(warning_when_not_quiet msg)
if(NOT SEAL_FIND_QUIETLY)
message(WARNING ${msg})
endif()
endmacro()
macro(status_when_not_quiet msg)
if(NOT SEAL_FIND_QUIETLY)
message(STATUS ${msg})
endif()
endmacro()
macro(find_seal_dependency dep)
find_dependency(${dep})
if(NOT ${dep}_FOUND)
warning_when_not_quiet("Could not find dependency `${dep}` required by this configuration")
set(SEAL_FOUND FALSE)
return()
endif()
endmacro()
set(SEAL_FOUND FALSE)
set(SEAL_SHARED_FOUND FALSE)
set(SEAL_C_FOUND FALSE)
set(SEAL_VERSION @SEAL_VERSION@)
set(SEAL_VERSION_MAJOR @SEAL_VERSION_MAJOR@)
set(SEAL_VERSION_MINOR @SEAL_VERSION_MINOR@)
set(SEAL_VERSION_PATCH @SEAL_VERSION_PATCH@)
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@)
set(SEAL_USE_ZLIB @SEAL_USE_ZLIB@)
# Add the current directory to the module search path
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR})
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
find_seal_dependency(Threads)
include(${CMAKE_CURRENT_LIST_DIR}/SEALTargets.cmake)
if(TARGET SEAL::seal)
set(SEAL_FOUND TRUE)
endif()
if(TARGET SEAL::seal_shared)
set(SEAL_SHARED_FOUND TRUE)
endif()
if(TARGET SEAL::sealc)
set(SEAL_C_FOUND TRUE)
endif()
if(SEAL_FOUND)
status_when_not_quiet("Microsoft SEAL -> Version ${SEAL_VERSION} detected")
if(SEAL_DEBUG)
status_when_not_quiet("Performance warning: Microsoft SEAL compiled in debug mode")
endif()
set(SEAL_TARGETS_AVAILABLE "Microsoft SEAL -> Targets available: SEAL::seal")
if(SEAL_SHARED_FOUND)
string(APPEND SEAL_TARGETS_AVAILABLE ", SEAL::seal_shared")
endif()
if(SEAL_C_FOUND)
string(APPEND SEAL_TARGETS_AVAILABLE ", SEAL::sealc")
endif()
status_when_not_quiet(${SEAL_TARGETS_AVAILABLE})
else()
warning_when_not_quiet("Microsoft SEAL -> NOT FOUND")
endif()
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using Microsoft.Research.SEAL;
namespace SEALNetExamples
{
partial class Examples
{
private static void ExampleBFVBasics()
{
Utilities.PrintExampleBanner("Example: BFV Basics");
/*
In this example, we demonstrate performing simple computations (a polynomial
evaluation) on encrypted integers using the BFV encryption scheme.
The first task is to set up an instance of the EncryptionParameters class.
It is critical to understand how the different parameters behave, how they
affect the encryption scheme, performance, and the security level. There are
three encryption parameters that are necessary to set:
- PolyModulusDegree (degree of polynomial modulus);
- CoeffModulus ([ciphertext] coefficient modulus);
- PlainModulus (plaintext modulus; only for the BFV scheme).
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.
*/
using EncryptionParameters parms = new EncryptionParameters(SchemeType.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-two
cyclotomic polynomial; it is not necessary to understand what this means.
Larger PolyModulusDegree makes ciphertext sizes larger and all operations
slower, but enables more complicated encrypted computations. Recommended
values are 1024, 2048, 4096, 8192, 16384, 32768, but it is also possible
to go beyond this range.
In this example we use a relatively small polynomial modulus. Anything
smaller than this will enable only very restricted encrypted computations.
*/
ulong polyModulusDegree = 4096;
parms.PolyModulusDegree = polyModulusDegree;
/*
Next we set the [ciphertext] `coefficient modulus' (CoeffModulus). This
parameter is a large integer, which is a product of distinct prime numbers,
numbers, each represented by an instance of the Modulus class. The
bit-length of CoeffModulus means the sum of the bit-lengths of its prime
factors.
A larger CoeffModulus implies a larger noise budget, hence more encrypted
computation capabilities. However, an upper bound for the total bit-length
of the CoeffModulus is determined by the PolyModulusDegree, as follows:
+----------------------------------------------------+
| PolyModulusDegree | max CoeffModulus bit-length |
+---------------------+------------------------------+
| 1024 | 27 |
| 2048 | 54 |
| 4096 | 109 |
| 8192 | 218 |
| 16384 | 438 |
| 32768 | 881 |
+---------------------+------------------------------+
These numbers can also be found in native/src/seal/util/hestdparms.h encoded
in the function SEAL_HE_STD_PARMS_128_TC, and can also be obtained from the
function
CoeffModulus.MaxBitCount(polyModulusDegree).
For example, if PolyModulusDegree is 4096, the coeff_modulus could consist
of three 36-bit primes (108 bits).
Microsoft SEAL comes with helper functions for selecting the CoeffModulus.
For new users the easiest way is to simply use
CoeffModulus.BFVDefault(polyModulusDegree),
which returns IEnumerable<Modulus> consisting of a generally good choice
for the given PolyModulusDegree.
*/
parms.CoeffModulus = CoeffModulus.BFVDefault(polyModulusDegree);
/*
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 and the consumption
of noise budget in 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(CoeffModulus/PlainModulus) (bits)
and the noise budget consumption in a homomorphic multiplication is of the
form log2(PlainModulus) + (other terms).
The plaintext modulus is specific to the BFV scheme, and cannot be set when
using the CKKS scheme.
*/
parms.PlainModulus = new Modulus(1024);
/*
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.
C# 8.0 introduced the `using` declaration for local variables. This is a very
convenient addition to the language: it causes the Dispose method for the
object to be called at the end of the enclosing scope (in this case the end
of this function), hence automatically releasing the native resources held by
the object. This is helpful, because releasing the native resources returns
the allocated memory to the memory pool, speeding up further allocations.
Another way would be to call GC::Collect() at a convenient point in the code,
but this may be less optimal as it may still cause unnecessary allocations
of memory if native resources were not released early enough. In this program
we call GC::Collect() after every example (see Examples.cs) to make sure
everything is returned to the memory pool at latest before running the next
example.
*/
using SEALContext context = new SEALContext(parms);
/*
Print the parameters that we have chosen.
*/
Utilities.PrintLine();
Console.WriteLine("Set encryption parameters and print");
Utilities.PrintParameters(context);
/*
When parameters are used to create SEALContext, Microsoft SEAL will first
validate those parameters. The parameters chosen here are valid.
*/
Console.WriteLine("Parameter validation (success): {0}", context.ParameterErrorMessage());
Console.WriteLine();
Console.WriteLine("~~~~~~ A naive way to calculate 4(x^2+1)(x+1)^2. ~~~~~~");
/*
The encryption schemes in Microsoft SEAL are public key encryption schemes.
For users unfamiliar with this terminology, a public key encryption scheme
has a separate public key for encrypting data, and a separate secret key for
decrypting data. This way multiple parties can encrypt data using the same
shared public key, but only the proper recipient of the data can decrypt it
with the secret key.
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 immediately be
read to local variables.
*/
using KeyGenerator keygen = new KeyGenerator(context);
using PublicKey publicKey = keygen.PublicKey;
using SecretKey secretKey = keygen.SecretKey;
/*
To be able to encrypt we need to construct an instance of Encryptor. Note
that the Encryptor only requires the public key, as expected.
*/
using Encryptor encryptor = new Encryptor(context, publicKey);
/*
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.
*/
using Evaluator evaluator = new 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.
*/
using Decryptor decryptor = new Decryptor(context, secretKey);
/*
As an example, we evaluate the degree 4 polynomial
4x^4 + 8x^3 + 8x^2 + 8x + 4
over an encrypted x = 6. The coefficients of the polynomial can be considered
as plaintext inputs, as we will see below. The computation is done modulo the
plain_modulus 1024.
While this examples is simple and easy to understand, it does not have much
practical value. In later examples we will demonstrate how to compute more
efficiently on encrypted integers and real or complex numbers.
Plaintexts in the BFV scheme are polynomials of degree less than the degree
of the polynomial modulus, and coefficients integers modulo the plaintext
modulus. For readers with background in ring theory, the plaintext space is
the polynomial quotient ring Z_T[X]/(X^N + 1), where N is PolyModulusDegree
and T is PlainModulus.
To get started, we create a plaintext containing the constant 6. For the
plaintext element we use a constructor that takes the desired polynomial as
a string with coefficients represented as hexadecimal numbers.
*/
Utilities.PrintLine();
int x = 6;
using Plaintext xPlain = new Plaintext(x.ToString());
Console.WriteLine($"Express x = {x} as a plaintext polynomial 0x{xPlain}.");
/*
We then encrypt the plaintext, producing a ciphertext.
*/
Utilities.PrintLine();
using Ciphertext xEncrypted = new Ciphertext();
Console.WriteLine("Encrypt xPlain to xEncrypted.");
encryptor.Encrypt(xPlain, xEncrypted);
/*
In Microsoft SEAL, a valid ciphertext consists of two or more polynomials
whose coefficients are integers modulo the product of the primes in the
coeff_modulus. The number of polynomials in a ciphertext is called its `size'
and is given by Ciphertext.Size. A freshly encrypted ciphertext always has
size 2.
*/
Console.WriteLine($" + size of freshly encrypted x: {xEncrypted.Size}");
/*
There is plenty of noise budget left in this freshly encrypted ciphertext.
*/
Console.WriteLine(" + noise budget in freshly encrypted x: {0} bits",
decryptor.InvariantNoiseBudget(xEncrypted));
/*
We decrypt the ciphertext and print the resulting plaintext in order to
demonstrate correctness of the encryption.
*/
using Plaintext xDecrypted = new Plaintext();
Console.Write(" + decryption of encrypted_x: ");
decryptor.Decrypt(xEncrypted, xDecrypted);
Console.WriteLine($"0x{xDecrypted} ...... Correct.");
/*
When using Microsoft SEAL, it is typically advantageous to compute in a way
that minimizes the longest chain of sequential multiplications. In other
words, encrypted computations are best evaluated in a way that minimizes
the multiplicative depth of the computation, because the total noise budget
consumption is proportional to the multiplicative depth. For example, for
our example computation it is advantageous to factorize the polynomial as
4x^4 + 8x^3 + 8x^2 + 8x + 4 = 4(x + 1)^2 * (x^2 + 1)
to obtain a simple depth 2 representation. Thus, we compute (x + 1)^2 and
(x^2 + 1) separately, before multiplying them, and multiplying by 4.
First, we compute x^2 and add a plaintext "1". We can clearly see from the
print-out that multiplication has consumed a lot of noise budget. The user
can vary the plain_modulus parameter to see its effect on the rate of noise
budget consumption.
*/
Utilities.PrintLine();
Console.WriteLine("Compute xSqPlusOne (x^2+1).");
using Ciphertext xSqPlusOne = new Ciphertext();
evaluator.Square(xEncrypted, xSqPlusOne);
using Plaintext plainOne = new Plaintext("1");
evaluator.AddPlainInplace(xSqPlusOne, plainOne);
/*
Encrypted 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 perform a squaring, and observe both size growth and noise budget
consumption.
*/
Console.WriteLine($" + size of xSqPlusOne: {xSqPlusOne.Size}");
Console.WriteLine(" + noise budget in xSqPlusOne: {0} bits",
decryptor.InvariantNoiseBudget(xSqPlusOne));
/*
Even though the size has grown, decryption works as usual as long as noise
budget has not reached 0.
*/
using Plaintext decryptedResult = new Plaintext();
Console.Write(" + decryption of xSqPlusOne: ");
decryptor.Decrypt(xSqPlusOne, decryptedResult);
Console.WriteLine($"0x{decryptedResult} ...... Correct.");
/*
Next, we compute (x + 1)^2.
*/
Utilities.PrintLine();
Console.WriteLine("Compute xPlusOneSq ((x+1)^2).");
using Ciphertext xPlusOneSq = new Ciphertext();
evaluator.AddPlain(xEncrypted, plainOne, xPlusOneSq);
evaluator.SquareInplace(xPlusOneSq);
Console.WriteLine($" + size of xPlusOneSq: {xPlusOneSq.Size}");
Console.WriteLine(" + noise budget in xPlusOneSq: {0} bits",
decryptor.InvariantNoiseBudget(xPlusOneSq));
Console.Write(" + decryption of xPlusOneSq: ");
decryptor.Decrypt(xPlusOneSq, decryptedResult);
Console.WriteLine($"0x{decryptedResult} ...... Correct.");
/*
Finally, we multiply (x^2 + 1) * (x + 1)^2 * 4.
*/
Utilities.PrintLine();
Console.WriteLine("Compute encryptedResult (4(x^2+1)(x+1)^2).");
using Ciphertext encryptedResult = new Ciphertext();
using Plaintext plainFour = new Plaintext("4");
evaluator.MultiplyPlainInplace(xSqPlusOne, plainFour);
evaluator.Multiply(xSqPlusOne, xPlusOneSq, encryptedResult);
Console.WriteLine($" + size of encrypted_result: {encryptedResult.Size}");
Console.WriteLine(" + noise budget in encrypted_result: {0} bits",
decryptor.InvariantNoiseBudget(encryptedResult));
Console.WriteLine("NOTE: Decryption can be incorrect if noise budget is zero.");
Console.WriteLine();
Console.WriteLine("~~~~~~ A better way to calculate 4(x^2+1)(x+1)^2. ~~~~~~");
/*
Noise budget has reached 0, which means that decryption cannot be expected
to give the correct result. This is because both ciphertexts xSqPlusOne and
xPlusOneSq consist of 3 polynomials due to the previous squaring operations,
and homomorphic operations on large ciphertexts consume much more noise budget
than computations on small ciphertexts. Computing on smaller ciphertexts is
also computationally significantly cheaper.
`Relinearization' is an operation that reduces the size of a ciphertext after
multiplication back to the initial size, 2. Thus, relinearizing one or both
input ciphertexts before the next multiplication can have a huge positive
impact on both noise growth and performance, even though relinearization has
a significant computational cost itself. It is only possible to relinearize
size 3 ciphertexts down to size 2, so often the user would want to relinearize
after each multiplication to keep the ciphertext sizes at 2.
Relinearization requires special `relinearization keys', which can be thought
of as a kind of public key. Relinearization keys can easily be created with
the KeyGenerator.
Relinearization is used similarly in both the BFV and the CKKS schemes, but
in this example we continue using BFV. We repeat our computation from before,
but this time relinearize after every multiplication.
Here we use the function KeyGenerator.RelinKeysLocal(). In production code
it is much better to use KeyGenerator.RelinKeys() instead. We will explain
and discuss these differences in `6_Serialization.cs'.
*/
Utilities.PrintLine();
Console.WriteLine("Generate locally usable relinearization keys.");
using RelinKeys relinKeys = keygen.RelinKeysLocal();
/*
We now repeat the computation relinearizing after each multiplication.
*/
Utilities.PrintLine();
Console.WriteLine("Compute and relinearize xSquared (x^2),");
Console.WriteLine(new string(' ', 13) + "then compute xSqPlusOne (x^2+1)");
using Ciphertext xSquared = new Ciphertext();
evaluator.Square(xEncrypted, xSquared);
Console.WriteLine($" + size of xSquared: {xSquared.Size}");
evaluator.RelinearizeInplace(xSquared, relinKeys);
Console.WriteLine(" + size of xSquared (after relinearization): {0}",
xSquared.Size);
evaluator.AddPlain(xSquared, plainOne, xSqPlusOne);
Console.WriteLine(" + noise budget in xSqPlusOne: {0} bits",
decryptor.InvariantNoiseBudget(xSqPlusOne));
Console.Write(" + decryption of xSqPlusOne: ");
decryptor.Decrypt(xSqPlusOne, decryptedResult);
Console.WriteLine($"0x{decryptedResult} ...... Correct.");
Utilities.PrintLine();
using Ciphertext xPlusOne = new Ciphertext();
Console.WriteLine("Compute xPlusOne (x+1),");
Console.WriteLine(new string(' ', 13) +
"then compute and relinearize xPlusOneSq ((x+1)^2).");
evaluator.AddPlain(xEncrypted, plainOne, xPlusOne);
evaluator.Square(xPlusOne, xPlusOneSq);
Console.WriteLine($" + size of xPlusOneSq: {xPlusOneSq.Size}");
evaluator.RelinearizeInplace(xPlusOneSq, relinKeys);
Console.WriteLine(" + noise budget in xPlusOneSq: {0} bits",
decryptor.InvariantNoiseBudget(xPlusOneSq));
Console.Write(" + decryption of xPlusOneSq: ");
decryptor.Decrypt(xPlusOneSq, decryptedResult);
Console.WriteLine($"0x{decryptedResult} ...... Correct.");
Utilities.PrintLine();
Console.WriteLine("Compute and relinearize encryptedResult (4(x^2+1)(x+1)^2).");
evaluator.MultiplyPlainInplace(xSqPlusOne, plainFour);
evaluator.Multiply(xSqPlusOne, xPlusOneSq, encryptedResult);
Console.WriteLine($" + size of encryptedResult: {encryptedResult.Size}");
evaluator.RelinearizeInplace(encryptedResult, relinKeys);
Console.WriteLine(" + size of encryptedResult (after relinearization): {0}",
encryptedResult.Size);
Console.WriteLine(" + noise budget in encryptedResult: {0} bits",
decryptor.InvariantNoiseBudget(encryptedResult));
Console.WriteLine();
Console.WriteLine("NOTE: Notice the increase in remaining noise budget.");
/*
Relinearization clearly improved our noise consumption. We have still plenty
of noise budget left, so we can expect the correct answer when decrypting.
*/
Utilities.PrintLine();
Console.WriteLine("Decrypt encrypted_result (4(x^2+1)(x+1)^2).");
decryptor.Decrypt(encryptedResult, decryptedResult);
Console.WriteLine(" + decryption of 4(x^2+1)(x+1)^2 = 0x{0} ...... Correct.",
decryptedResult);
/*
For x=6, 4(x^2+1)(x+1)^2 = 7252. Since the plaintext modulus is set to 1024,
this result is computed in integers modulo 1024. Therefore the expected output
should be 7252 % 1024 == 84, or 0x54 in hexadecimal.
*/
/*
Sometimes we create customized encryption parameters which turn out to be invalid.
Microsoft SEAL can interpret the reason why parameters are considered invalid.
Here we simply reduce the polynomial modulus degree to make the parameters not
compliant with the HomomorphicEncryption.org security standard.
*/
Utilities.PrintLine();
Console.WriteLine("An example of invalid parameters");
parms.PolyModulusDegree = 2048;
using SEALContext new_context = new SEALContext(parms);
Utilities.PrintParameters(context);
Console.WriteLine("Parameter validation (failed): {0}", new_context.ParameterErrorMessage());
/*
This information is helpful to fix invalid encryption parameters.
*/
}
}
}
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Collections.Generic;
using Microsoft.Research.SEAL;
namespace SEALNetExamples
{
partial class Examples
{
/*
In `1_BFV_Basics.cs' we showed how to perform a very simple computation using the
BFV scheme. The computation was performed modulo the PlainModulus parameter, and
utilized only one coefficient from a BFV plaintext polynomial. This approach has
two notable problems:
(1) Practical applications typically use integer or real number arithmetic,
not modular arithmetic;
(2) We used only one coefficient of the plaintext polynomial. This is really
wasteful, as the plaintext polynomial is large and will in any case be
encrypted in its entirety.
For (1), one may ask why not just increase the PlainModulus parameter until no
overflow occurs, and the computations behave as in integer arithmetic. The problem
is that increasing PlainModulus increases noise budget consumption, and decreases
the initial noise budget too.
In these examples we will discuss other ways of laying out data into plaintext
elements (encoding) that allow more computations without data type overflow, and
can allow the full plaintext polynomial to be utilized.
*/
private static void ExampleIntegerEncoder()
{
Utilities.PrintExampleBanner("Example: Encoders / Integer Encoder");
/*
[IntegerEncoder] (For BFV scheme only)
The IntegerEncoder encodes integers to BFV plaintext polynomials as follows.
First, a binary expansion of the integer is computed. Next, a polynomial is
created with the bits as coefficients. For example, the integer
26 = 2^4 + 2^3 + 2^1
is encoded as the polynomial 1x^4 + 1x^3 + 1x^1. Conversely, plaintext
polynomials are decoded by evaluating them at x=2. For negative numbers the
IntegerEncoder simply stores all coefficients as either 0 or -1, where -1 is
represented by the unsigned integer PlainModulus - 1 in memory.
Since encrypted computations operate on the polynomials rather than on the
encoded integers themselves, the polynomial coefficients will grow in the
course of such computations. For example, computing the sum of the encrypted
encoded integer 26 with itself will result in an encrypted polynomial with
larger coefficients: 2x^4 + 2x^3 + 2x^1. Squaring the encrypted encoded
integer 26 results also in increased coefficients due to cross-terms, namely,
(1x^4 + 1x^3 + 1x^1)^2 = 1x^8 + 2x^7 + 1x^6 + 2x^5 + 2x^4 + 1x^2;
further computations will quickly increase the coefficients much more.
Decoding will still work correctly in this case (evaluating the polynomial
at x=2), but since the coefficients of plaintext polynomials are really
integers modulo plain_modulus, implicit reduction modulo plain_modulus may
yield unexpected results. For example, adding 1x^4 + 1x^3 + 1x^1 to itself
plain_modulus many times will result in the constant polynomial 0, which is
clearly not equal to 26 * plain_modulus. It can be difficult to predict when
such overflow will take place especially when computing several sequential
multiplications.
The IntegerEncoder is easy to understand and use for simple computations,
and can be a good tool to experiment with for users new to Microsoft SEAL.
However, advanced users will probably prefer more efficient approaches,
such as the BatchEncoder or the CKKSEncoder.
*/
using EncryptionParameters parms = new EncryptionParameters(SchemeType.BFV);
ulong polyModulusDegree = 4096;
parms.PolyModulusDegree = polyModulusDegree;
parms.CoeffModulus = CoeffModulus.BFVDefault(polyModulusDegree);
/*
There is no hidden logic behind our choice of the plain_modulus. The only
thing that matters is that the plaintext polynomial coefficients will not
exceed this value at any point during our computation; otherwise the result
will be incorrect.
*/
parms.PlainModulus = new Modulus(512);
using SEALContext context = new SEALContext(parms);
Utilities.PrintParameters(context);
Console.WriteLine();
using KeyGenerator keygen = new KeyGenerator(context);
using PublicKey publicKey = keygen.PublicKey;
using SecretKey secretKey = keygen.SecretKey;
using Encryptor encryptor = new Encryptor(context, publicKey);
using Evaluator evaluator = new Evaluator(context);
using Decryptor decryptor = new Decryptor(context, secretKey);
/*
We create an IntegerEncoder.
*/
using IntegerEncoder encoder = new IntegerEncoder(context);
/*
First, we encode two integers as plaintext polynomials. Note that encoding
is not encryption: at this point nothing is encrypted.
*/
int value1 = 5;
using Plaintext plain1 = encoder.Encode(value1);
Utilities.PrintLine();
Console.WriteLine($"Encode {value1} as polynomial {plain1} (plain1),");
int value2 = -7;
using Plaintext plain2 = encoder.Encode(value2);
Console.WriteLine(new string(' ', 13)
+ $"Encode {value2} as polynomial {plain2} (plain2),");
/*
Now we can encrypt the plaintext polynomials.
*/
using Ciphertext encrypted1 = new Ciphertext();
using Ciphertext encrypted2 = new Ciphertext();
Utilities.PrintLine();
Console.WriteLine("Encrypt plain1 to encrypted1 and plain2 to encrypted2.");
encryptor.Encrypt(plain1, encrypted1);
encryptor.Encrypt(plain2, encrypted2);
Console.WriteLine(" + Noise budget in encrypted1: {0} bits",
decryptor.InvariantNoiseBudget(encrypted1));
Console.WriteLine(" + Noise budget in encrypted2: {0} bits",
decryptor.InvariantNoiseBudget(encrypted2));
/*
As a simple example, we compute (-encrypted1 + encrypted2) * encrypted2.
*/
encryptor.Encrypt(plain2, encrypted2);
using Ciphertext encryptedResult = new Ciphertext();
Utilities.PrintLine();
Console.WriteLine("Compute encrypted_result = (-encrypted1 + encrypted2) * encrypted2.");
evaluator.Negate(encrypted1, encryptedResult);
evaluator.AddInplace(encryptedResult, encrypted2);
evaluator.MultiplyInplace(encryptedResult, encrypted2);
Console.WriteLine(" + Noise budget in encryptedResult: {0} bits",
decryptor.InvariantNoiseBudget(encryptedResult));
using Plaintext plainResult = new Plaintext();
Utilities.PrintLine();
Console.WriteLine("Decrypt encrypted_result to plain_result.");
decryptor.Decrypt(encryptedResult, plainResult);
/*
Print the result plaintext polynomial. The coefficients are not even close
to exceeding our plainModulus, 512.
*/
Console.WriteLine($" + Plaintext polynomial: {plainResult}");
/*
Decode to obtain an integer result.
*/
Utilities.PrintLine();
Console.WriteLine("Decode plain_result.");
Console.WriteLine(" + Decoded integer: {0} ...... Correct.",
encoder.DecodeInt32(plainResult));
}
private static void ExampleBatchEncoder()
{
Utilities.PrintExampleBanner("Example: Encoders / Batch Encoder");
/*
[BatchEncoder] (For BFV scheme only)
Let N denote the PolyModulusDegree and T denote the PlainModulus. Batching
allows the BFV plaintext polynomials to be viewed as 2-by-(N/2) matrices, with
each element an integer modulo T. In the matrix view, encrypted operations act
element-wise on encrypted matrices, allowing the user to obtain speeds-ups of
several orders of magnitude in fully vectorizable computations. Thus, in all
but the simplest computations, batching should be the preferred method to use
with BFV, and when used properly will result in implementations outperforming
anything done with the IntegerEncoder.
*/
using EncryptionParameters parms = new EncryptionParameters(SchemeType.BFV);
ulong polyModulusDegree = 8192;
parms.PolyModulusDegree = polyModulusDegree;
parms.CoeffModulus = CoeffModulus.BFVDefault(polyModulusDegree);
/*
To enable batching, we need to set the plain_modulus to be a prime number
congruent to 1 modulo 2*PolyModulusDegree. Microsoft SEAL provides a helper
method for finding such a prime. In this example we create a 20-bit prime
that supports batching.
*/
parms.PlainModulus = PlainModulus.Batching(polyModulusDegree, 20);
using SEALContext context = new SEALContext(parms);
Utilities.PrintParameters(context);
Console.WriteLine();
/*
We can verify that batching is indeed enabled by looking at the encryption
parameter qualifiers created by SEALContext.
*/
using var qualifiers = context.FirstContextData.Qualifiers;
Console.WriteLine($"Batching enabled: {qualifiers.UsingBatching}");
using KeyGenerator keygen = new KeyGenerator(context);
using PublicKey publicKey = keygen.PublicKey;
using SecretKey secretKey = keygen.SecretKey;
using RelinKeys relinKeys = keygen.RelinKeysLocal();
using Encryptor encryptor = new Encryptor(context, publicKey);
using Evaluator evaluator = new Evaluator(context);
using Decryptor decryptor = new Decryptor(context, secretKey);
/*
Batching is done through an instance of the BatchEncoder class.
*/
using BatchEncoder batchEncoder = new BatchEncoder(context);
/*
The total number of batching `slots' equals the PolyModulusDegree, N, and
these slots are organized into 2-by-(N/2) matrices that can be encrypted and
computed on. Each slot contains an integer modulo PlainModulus.
*/
ulong slotCount = batchEncoder.SlotCount;
ulong rowSize = slotCount / 2;
Console.WriteLine($"Plaintext matrix row size: {rowSize}");
/*
The matrix plaintext is simply given to BatchEncoder as a flattened vector
of numbers. The first `rowSize' many 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 ]
*/
ulong[] podMatrix = new ulong[slotCount];
podMatrix[0] = 0;
podMatrix[1] = 1;
podMatrix[2] = 2;
podMatrix[3] = 3;
podMatrix[rowSize] = 4;
podMatrix[rowSize + 1] = 5;
podMatrix[rowSize + 2] = 6;
podMatrix[rowSize + 3] = 7;
Console.WriteLine("Input plaintext matrix:");
Utilities.PrintMatrix(podMatrix, (int)rowSize);
/*
First we use BatchEncoder to encode the matrix into a plaintext polynomial.
*/
using Plaintext plainMatrix = new Plaintext();
Utilities.PrintLine();
Console.WriteLine("Encode plaintext matrix:");
batchEncoder.Encode(podMatrix, plainMatrix);
/*
We can instantly decode to verify correctness of the encoding. Note that no
encryption or decryption has yet taken place.
*/
List<ulong> podResult = new List<ulong>();
Console.WriteLine(" + Decode plaintext matrix ...... Correct.");
batchEncoder.Decode(plainMatrix, podResult);
Utilities.PrintMatrix(podResult, (int)rowSize);
/*
Next we encrypt the encoded plaintext.
*/
using Ciphertext encryptedMatrix = new Ciphertext();
Utilities.PrintLine();
Console.WriteLine("Encrypt plainMatrix to encryptedMatrix.");
encryptor.Encrypt(plainMatrix, encryptedMatrix);
Console.WriteLine(" + Noise budget in encryptedMatrix: {0} bits",
decryptor.InvariantNoiseBudget(encryptedMatrix));
/*
Operating on the ciphertext results in homomorphic operations being performed
simultaneously in all 8192 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 encode it into a plaintext.
*/
ulong[] podMatrix2 = new ulong[slotCount];
for (ulong i = 0; i < slotCount; i++)
{
podMatrix2[i] = (i % 2) + 1;
}
using Plaintext plainMatrix2 = new Plaintext();
batchEncoder.Encode(podMatrix2, plainMatrix2);
Console.WriteLine();
Console.WriteLine("Second input plaintext matrix:");
Utilities.PrintMatrix(podMatrix2, (int)rowSize);
/*
We now add the second (plaintext) matrix to the encrypted matrix, and square
the sum.
*/
Utilities.PrintLine();
Console.WriteLine("Sum, square, and relinearize.");
evaluator.AddPlainInplace(encryptedMatrix, plainMatrix2);
evaluator.SquareInplace(encryptedMatrix);
evaluator.RelinearizeInplace(encryptedMatrix, relinKeys);
/*
How much noise budget do we have left?
*/
Console.WriteLine(" + Noise budget in result: {0} bits",
decryptor.InvariantNoiseBudget(encryptedMatrix));
/*
We decrypt and decompose the plaintext to recover the result as a matrix.
*/
using Plaintext plainResult = new Plaintext();
Utilities.PrintLine();
Console.WriteLine("Decrypt and decode result.");
decryptor.Decrypt(encryptedMatrix, plainResult);
batchEncoder.Decode(plainResult, podResult);
Console.WriteLine(" + Result plaintext matrix ...... Correct.");
Utilities.PrintMatrix(podResult, (int)rowSize);
/*
Batching allows us to efficiently use the full plaintext polynomial when the
desired encrypted computation is highly parallelizable. However, it has not
solved the other problem mentioned in the beginning of this file: each slot
holds only an integer modulo plain_modulus, and unless plain_modulus is very
large, we can quickly encounter data type overflow and get unexpected results
when integer computations are desired. Note that overflow cannot be detected
in encrypted form. The CKKS scheme (and the CKKSEncoder) addresses the data
type overflow issue, but at the cost of yielding only approximate results.
*/
}
static private void ExampleCKKSEncoder()
{
Utilities.PrintExampleBanner("Example: Encoders / CKKS Encoder");
/*
[CKKSEncoder] (For CKKS scheme only)
In this example we demonstrate the Cheon-Kim-Kim-Song (CKKS) scheme for
computing on encrypted real or complex numbers. We start by creating
encryption parameters for the CKKS scheme. There are two important
differences compared to the BFV scheme:
(1) CKKS does not use the PlainModulus encryption parameter;
(2) Selecting the CoeffModulus in a specific way can be very important
when using the CKKS scheme. We will explain this further in the file
`CKKS_Basics.cs'. In this example we use CoeffModulus.Create to
generate 5 40-bit prime numbers.
*/
using EncryptionParameters parms = new EncryptionParameters(SchemeType.CKKS);
ulong polyModulusDegree = 8192;
parms.PolyModulusDegree = polyModulusDegree;
parms.CoeffModulus = CoeffModulus.Create(
polyModulusDegree, new int[]{ 40, 40, 40, 40, 40 });
/*
We create the SEALContext as usual and print the parameters.
*/
using SEALContext context = new SEALContext(parms);
Utilities.PrintParameters(context);
Console.WriteLine();
/*
Keys are created the same way as for the BFV scheme.
*/
using KeyGenerator keygen = new KeyGenerator(context);
using PublicKey publicKey = keygen.PublicKey;
using SecretKey secretKey = keygen.SecretKey;
using RelinKeys relinKeys = keygen.RelinKeysLocal();
/*
We also set up an Encryptor, Evaluator, and Decryptor as usual.
*/
using Encryptor encryptor = new Encryptor(context, publicKey);
using Evaluator evaluator = new Evaluator(context);
using Decryptor decryptor = new Decryptor(context, secretKey);
/*
To create CKKS plaintexts we need a special encoder: there is no other way
to create them. The IntegerEncoder and BatchEncoder cannot be used with the
CKKS scheme. The CKKSEncoder encodes vectors of real or complex numbers into
Plaintext objects, which can subsequently be encrypted. At a high level this
looks a lot like what BatchEncoder does for the BFV scheme, but the theory
behind it is completely different.
*/
using CKKSEncoder encoder = new CKKSEncoder(context);
/*
In CKKS the number of slots is PolyModulusDegree / 2 and each slot encodes
one real or complex number. This should be contrasted with BatchEncoder in
the BFV scheme, where the number of slots is equal to PolyModulusDegree
and they are arranged into a matrix with two rows.
*/
ulong slotCount = encoder.SlotCount;
Console.WriteLine($"Number of slots: {slotCount}");
/*
We create a small vector to encode; the CKKSEncoder will implicitly pad it
with zeros to full size (PolyModulusDegree / 2) when encoding.
*/
double[] input = new double[]{ 0.0, 1.1, 2.2, 3.3 };
Console.WriteLine("Input vector: ");
Utilities.PrintVector(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 plaintext elements are fundamentally polynomials with
integer coefficients. It is instructive to think of the scale as determining
the bit-precision of the encoding; naturally it will affect the precision of
the result.
In CKKS the message is stored modulo CoeffModulus (in BFV it is stored modulo
PlainModulus), so the scaled message must not get too close to the total size
of CoeffModulus. In this case our CoeffModulus is quite large (200 bits) so
we have little to worry about in this regard. For this simple example a 30-bit
scale is more than enough.
*/
using Plaintext plain = new Plaintext();
double scale = Math.Pow(2.0, 30);
Utilities.PrintLine();
Console.WriteLine("Encode input vector.");
encoder.Encode(input, scale, plain);
/*
We can instantly decode to check the correctness of encoding.
*/
List<double> output = new List<double>();
Console.WriteLine(" + Decode input vector ...... Correct.");
encoder.Decode(plain, output);
Utilities.PrintVector(output);
/*
The vector is encrypted the same was as in BFV.
*/
using Ciphertext encrypted = new Ciphertext();
Utilities.PrintLine();
Console.WriteLine("Encrypt input vector, square, and relinearize.");
encryptor.Encrypt(plain, encrypted);
/*
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 (PolyModulusDegree / 2); this is
because of the implicit zero-padding mentioned above.
*/
evaluator.SquareInplace(encrypted);
evaluator.RelinearizeInplace(encrypted, relinKeys);
/*
We notice that the scale in the result has increased. In fact, it is now
the square of the original scale: 2^60.
*/
Console.WriteLine(" + Scale in squared input: {0} ({1} bits)",
encrypted.Scale,
(int)Math.Ceiling(Math.Log(encrypted.Scale, newBase: 2)));
Utilities.PrintLine();
Console.WriteLine("Decrypt and decode.");
decryptor.Decrypt(encrypted, plain);
encoder.Decode(plain, output);
Console.WriteLine(" + Result vector ...... Correct.");
Utilities.PrintVector(output);
/*
The CKKS scheme allows the scale to be reduced between encrypted computations.
This is a fundamental and critical feature that makes CKKS very powerful and
flexible. We will discuss it in great detail in `3_Levels.cs' and later in
`4_CKKS_Basics.cs'.
*/
}
private static void ExampleEncoders()
{
Utilities.PrintExampleBanner("Example: Encoders");
/*
Run all encoder examples.
*/
ExampleIntegerEncoder();
ExampleBatchEncoder();
ExampleCKKSEncoder();
}
}
}
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using Microsoft.Research.SEAL;
namespace SEALNetExamples
{
partial class Examples
{
private static void ExampleLevels()
{
Utilities.PrintExampleBanner("Example: Levels");
/*
In this examples we describe the concept of `levels' in BFV and CKKS and the
related objects that represent them in Microsoft SEAL.
In Microsoft SEAL a set of encryption parameters (excluding the random number
generator) is identified uniquely by a 256-bit hash of the parameters. This
hash is called the `ParmsId' and can be easily accessed and printed at any
time. The hash will change as soon as any of the parameters is changed.
When a SEALContext is created from a given EncryptionParameters instance,
Microsoft 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 the last coefficient modulus prime from the
previous set; this continues until the parameter set is no longer valid
(e.g., PlainModulus is larger than the remaining CoeffModulus). 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.
Each set of parameters in the chain involves unique pre-computations performed
when the SEALContext is created, and stored in a SEALContext.ContextData
object. The chain is basically a linked list of SEALContext.ContextData
objects, and can easily be accessed through the SEALContext at any time. Each
node can be identified by the ParmsId of its specific encryption parameters
(PolyModulusDegree remains the same but CoeffModulus varies).
*/
using EncryptionParameters parms = new EncryptionParameters(SchemeType.BFV);
ulong polyModulusDegree = 8192;
parms.PolyModulusDegree = polyModulusDegree;
/*
In this example we use a custom CoeffModulus, consisting of 5 primes of
sizes 50, 30, 30, 50, and 50 bits. Note that this is still OK according to
the explanation in `1_BFV_Basics.cs'. Indeed,
CoeffModulus.MaxBitCount(polyModulusDegree)
returns 218 (greater than 50+30+30+50+50=210).
Due to the modulus switching chain, the order of the 5 primes is significant.
The last prime has a special meaning and we call it the `special prime'. Thus,
the first parameter set in the modulus switching chain is the only one that
involves the special prime. All key objects, such as SecretKey, are created
at this highest level. All data objects, such as Ciphertext, can be only at
lower levels. The special modulus should be as large as the largest of the
other primes in the CoeffModulus, although this is not a strict requirement.
special prime +---------+
|
v
CoeffModulus: { 50, 30, 30, 50, 50 } +---+ Level 4 (all keys; `key level')
|
|
CoeffModulus: { 50, 30, 30, 50 } +---+ Level 3 (highest `data level')
|
|
CoeffModulus: { 50, 30, 30 } +---+ Level 2
|
|
CoeffModulus: { 50, 30 } +---+ Level 1
|
|
CoeffModulus: { 50 } +---+ Level 0 (lowest level)
*/
parms.CoeffModulus = CoeffModulus.Create(
polyModulusDegree, new int[] { 50, 30, 30, 50, 50 });
/*
In this example the PlainModulus does not play much of a role; we choose
some reasonable value.
*/
parms.PlainModulus = PlainModulus.Batching(polyModulusDegree, 20);
using SEALContext context = new SEALContext(parms);
Utilities.PrintParameters(context);
/*
There are convenience method for accessing the SEALContext.ContextData for
some of the most important levels:
SEALContext.KeyContextData: access to key level ContextData
SEALContext.FirstContextData: access to highest data level ContextData
SEALContext.LastContextData: access to lowest level ContextData
We iterate over the chain and print the ParmsId for each set of parameters.
*/
Console.WriteLine();
Utilities.PrintLine();
Console.WriteLine("Print the modulus switching chain.");
/*
First print the key level parameter information.
*/
SEALContext.ContextData contextData = context.KeyContextData;
Console.WriteLine("----> Level (chain index): {0} ...... KeyContextData",
contextData.ChainIndex);
Console.WriteLine($" ParmsId: {contextData.ParmsId}");
Console.Write(" CoeffModulus primes: ");
foreach (Modulus prime in contextData.Parms.CoeffModulus)
{
Console.Write($"{Utilities.ULongToString(prime.Value)} ");
}
Console.WriteLine();
Console.WriteLine("\\");
Console.Write(" \\--> ");
/*
Next iterate over the remaining (data) levels.
*/
contextData = context.FirstContextData;
while (null != contextData)
{
Console.Write($"Level (chain index): {contextData.ChainIndex}");
if (contextData.ParmsId.Equals(context.FirstParmsId))
{
Console.WriteLine(" ...... FirstContextData");
}
else if (contextData.ParmsId.Equals(context.LastParmsId))
{
Console.WriteLine(" ...... LastContextData");
}
else
{
Console.WriteLine();
}
Console.WriteLine($" ParmsId: {contextData.ParmsId}");
Console.Write(" CoeffModulus primes: ");
foreach (Modulus prime in contextData.Parms.CoeffModulus)
{
Console.Write($"{Utilities.ULongToString(prime.Value)} ");
}
Console.WriteLine();
Console.WriteLine("\\");
Console.Write(" \\--> ");
/*
Step forward in the chain.
*/
contextData = contextData.NextContextData;
}
Console.WriteLine("End of chain reached");
Console.WriteLine();
/*
We create some keys and check that indeed they appear at the highest level.
*/
using KeyGenerator keygen = new KeyGenerator(context);
using PublicKey publicKey = keygen.PublicKey;
using SecretKey secretKey = keygen.SecretKey;
using RelinKeys relinKeys = keygen.RelinKeysLocal();
/*
In this example we create a local version of the GaloisKeys object using
KeyGenerator.GaloisKeysLocal(). In a production setting where the Galois
keys would need to be communicated to a server, it would be much better to
use KeyGenerator.GaloisKeys(), which outputs a Serializable<GaloisKeys>
object for compressed serialization.
*/
using GaloisKeys galoisKeys = keygen.GaloisKeysLocal();
Utilities.PrintLine();
Console.WriteLine("Print the parameter IDs of generated elements.");
Console.WriteLine($" + publicKey: {publicKey.ParmsId}");
Console.WriteLine($" + secretKey: {secretKey.ParmsId}");
Console.WriteLine($" + relinKeys: {relinKeys.ParmsId}");
Console.WriteLine($" + galoisKeys: {galoisKeys.ParmsId}");
using Encryptor encryptor = new Encryptor(context, publicKey);
using Evaluator evaluator = new Evaluator(context);
using Decryptor decryptor = new Decryptor(context, secretKey);
/*
In the BFV scheme plaintexts do not carry a ParmsId, but ciphertexts do. Note
how the freshly encrypted ciphertext is at the highest data level.
*/
using Plaintext plain = new Plaintext("1x^3 + 2x^2 + 3x^1 + 4");
using Ciphertext encrypted = new Ciphertext();
encryptor.Encrypt(plain, encrypted);
Console.WriteLine($" + plain: {plain.ParmsId} (not set in BFV)");
Console.WriteLine($" + encrypted: {encrypted.ParmsId}");
Console.WriteLine();
/*
`Modulus switching' is a technique of changing the ciphertext parameters down
in the chain. The function Evaluator.ModSwitchToNext always switches to the
next level down the chain, whereas Evaluator.ModSwitchTo switches to a parameter
set down the chain corresponding to a given ParmsId. However, it is impossible
to switch up in the chain.
*/
Utilities.PrintLine();
Console.WriteLine("Perform modulus switching on encrypted and print.");
contextData = context.FirstContextData;
Console.Write("----> ");
while (null != contextData.NextContextData)
{
Console.WriteLine($"Level (chain index): {contextData.ChainIndex}");
Console.WriteLine($" ParmsId of encrypted: {contextData.ParmsId}");
Console.WriteLine(" Noise budget at this level: {0} bits",
decryptor.InvariantNoiseBudget(encrypted));
Console.WriteLine("\\");
Console.Write(" \\--> ");
evaluator.ModSwitchToNextInplace(encrypted);
contextData = contextData.NextContextData;
}
Console.WriteLine($"Level (chain index): {contextData.ChainIndex}");
Console.WriteLine($" ParmsId of encrypted: {contextData.ParmsId}");
Console.WriteLine(" Noise budget at this level: {0} bits",
decryptor.InvariantNoiseBudget(encrypted));
Console.WriteLine("\\");
Console.Write(" \\--> ");
Console.WriteLine("End of chain reached");
Console.WriteLine();
/*
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. Decryption still works.
*/
Utilities.PrintLine();
Console.WriteLine("Decrypt still works after modulus switching.");
decryptor.Decrypt(encrypted, plain);
Console.WriteLine($" + Decryption of encrypted: {plain} ...... Correct.");
Console.WriteLine();
/*
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 further 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 an issue at all, if we do things
right, as we will see below.
First we recreate the original ciphertext and perform some computations.
*/
Console.WriteLine("Computation is more efficient with modulus switching.");
Utilities.PrintLine();
Console.WriteLine("Compute the eight power.");
encryptor.Encrypt(plain, encrypted);
Console.WriteLine(" + Noise budget fresh: {0} bits",
decryptor.InvariantNoiseBudget(encrypted));
evaluator.SquareInplace(encrypted);
evaluator.RelinearizeInplace(encrypted, relinKeys);
Console.WriteLine(" + Noise budget of the 2nd power: {0} bits",
decryptor.InvariantNoiseBudget(encrypted));
evaluator.SquareInplace(encrypted);
evaluator.RelinearizeInplace(encrypted, relinKeys);
Console.WriteLine(" + Noise budget of the 4th power: {0} bits",
decryptor.InvariantNoiseBudget(encrypted));
/*
Surprisingly, in this case modulus switching has no effect at all on the
noise budget.
*/
evaluator.ModSwitchToNextInplace(encrypted);
Console.WriteLine(" + Noise budget after modulus switching: {0} bits",
decryptor.InvariantNoiseBudget(encrypted));
/*
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
smaller parameters. We see from the print-out that the next modulus switch
should be done ideally when the noise budget is down to around 25 bits.
*/
evaluator.SquareInplace(encrypted);
evaluator.RelinearizeInplace(encrypted, relinKeys);
Console.WriteLine(" + Noise budget of the 8th power: {0} bits",
decryptor.InvariantNoiseBudget(encrypted));
evaluator.ModSwitchToNextInplace(encrypted);
Console.WriteLine(" + Noise budget after modulus switching: {0} bits",
decryptor.InvariantNoiseBudget(encrypted));
/*
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.
*/
decryptor.Decrypt(encrypted, plain);
Console.WriteLine(" + Decryption of the 8th power (hexadecimal) ...... Correct.");
Console.WriteLine($" {plain}");
Console.WriteLine();
/*
In BFV modulus switching is not necessary and in some cases the user might
not want to create the modulus switching chain, except for the highest two
levels. This can be done by passing a bool `false' to SEALContext constructor.
*/
using SEALContext context2 = new SEALContext(parms, expandModChain: false);
/*
We can check that indeed the modulus switching chain has been created only
for the highest two levels (key level and highest data level). The following
loop should execute only once.
*/
Console.WriteLine("Optionally disable modulus switching chain expansion.");
Utilities.PrintLine();
Console.WriteLine("Print the modulus switching chain.");
Console.Write("----> ");
for (contextData = context2.KeyContextData; null != contextData;
contextData = contextData.NextContextData)
{
Console.WriteLine($"Level (chain index): {contextData.ChainIndex}");
Console.WriteLine($" ParmsId of encrypted: {contextData.ParmsId}");
Console.Write(" CoeffModulus primes: ");
foreach (Modulus prime in contextData.Parms.CoeffModulus)
{
Console.Write($"{Utilities.ULongToString(prime.Value)} ");
}
Console.WriteLine();
Console.WriteLine("\\");
Console.Write(" \\--> ");
}
Console.WriteLine("End of chain reached");
Console.WriteLine();
/*
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.
*/
}
}
}
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Collections.Generic;
using Microsoft.Research.SEAL;
namespace SEALNetExamples
{
partial class Examples
{
private static void ExampleCKKSBasics()
{
Utilities.PrintExampleBanner("Example: CKKS Basics");
/*
In this example we demonstrate evaluating a polynomial function
PI*x^3 + 0.4*x + 1
on encrypted floating-point input data x for a set of 4096 equidistant points
in the interval [0, 1]. This example demonstrates many of the main features
of the CKKS scheme, but also the challenges in using it.
We start by setting up the CKKS scheme.
*/
using EncryptionParameters parms = new EncryptionParameters(SchemeType.CKKS);
/*
We saw in `2_Encoders.cs' that multiplication in CKKS causes scales in
ciphertexts to grow. The scale of any ciphertext must not get too close to
the total size of CoeffModulus, or else the ciphertext simply runs out of
room to store the scaled-up plaintext. The CKKS scheme provides a `rescale'
functionality that can reduce the scale, and stabilize the scale expansion.
Rescaling is a kind of modulus switch operation (recall `3_Levels.cs').
As modulus switching, it removes the last of the primes from CoeffModulus,
but as a side-effect it scales down the ciphertext by the removed prime.
Usually we want to have perfect control over how the scales are changed,
which is why for the CKKS scheme it is more common to use carefully selected
primes for the CoeffModulus.
More precisely, suppose that the scale in a CKKS ciphertext is S, and the
last prime in the current CoeffModulus (for the ciphertext) is P. Rescaling
to the next level changes the scale to S/P, and removes the prime P from the
CoeffModulus, as usual in modulus switching. The number of primes limits
how many rescalings can be done, and thus limits the multiplicative depth of
the computation.
It is possible to choose the initial scale freely. One good strategy can be
to is to set the initial scale S and primes P_i in the CoeffModulus to be
very close to each other. If ciphertexts have scale S before multiplication,
they have scale S^2 after multiplication, and S^2/P_i after rescaling. If all
P_i are close to S, then S^2/P_i is close to S again. This way we stabilize the
scales to be close to S throughout the computation. Generally, for a circuit
of depth D, we need to rescale D times, i.e., we need to be able to remove D
primes from the coefficient modulus. Once we have only one prime left in the
coeff_modulus, the remaining prime must be larger than S by a few bits to
preserve the pre-decimal-point value of the plaintext.
Therefore, a generally good strategy is to choose parameters for the CKKS
scheme as follows:
(1) Choose a 60-bit prime as the first prime in CoeffModulus. This will
give the highest precision when decrypting;
(2) Choose another 60-bit prime as the last element of CoeffModulus, as
this will be used as the special prime and should be as large as the
largest of the other primes;
(3) Choose the intermediate primes to be close to each other.
We use CoeffModulus.Create to generate primes of the appropriate size. Note
that our CoeffModulus is 200 bits total, which is below the bound for our
PolyModulusDegree: CoeffModulus.MaxBitCount(8192) returns 218.
*/
ulong polyModulusDegree = 8192;
parms.PolyModulusDegree = polyModulusDegree;
parms.CoeffModulus = CoeffModulus.Create(
polyModulusDegree, new int[]{ 60, 40, 40, 60 });
/*
We choose the initial scale to be 2^40. At the last level, this leaves us
60-40=20 bits of precision before the decimal point, and enough (roughly
10-20 bits) of precision after the decimal point. Since our intermediate
primes are 40 bits (in fact, they are very close to 2^40), we can achieve
scale stabilization as described above.
*/
double scale = Math.Pow(2.0, 40);
using SEALContext context = new SEALContext(parms);
Utilities.PrintParameters(context);
Console.WriteLine();
using KeyGenerator keygen = new KeyGenerator(context);
using PublicKey publicKey = keygen.PublicKey;
using SecretKey secretKey = keygen.SecretKey;
using RelinKeys relinKeys = keygen.RelinKeysLocal();
using Encryptor encryptor = new Encryptor(context, publicKey);
using Evaluator evaluator = new Evaluator(context);
using Decryptor decryptor = new Decryptor(context, secretKey);
using CKKSEncoder encoder = new CKKSEncoder(context);
ulong slotCount = encoder.SlotCount;
Console.WriteLine($"Number of slots: {slotCount}");
List<double> input = new List<double>((int)slotCount);
double currPoint = 0, stepSize = 1.0 / (slotCount - 1);
for (ulong i = 0; i < slotCount; i++, currPoint += stepSize)
{
input.Add(currPoint);
}
Console.WriteLine("Input vector:");
Utilities.PrintVector(input, 3, 7);
Console.WriteLine("Evaluating polynomial PI*x^3 + 0.4x + 1 ...");
/*
We create plaintexts 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.
*/
using Plaintext plainCoeff3 = new Plaintext(),
plainCoeff1 = new Plaintext(),
plainCoeff0 = new Plaintext();
encoder.Encode(3.14159265, scale, plainCoeff3);
encoder.Encode(0.4, scale, plainCoeff1);
encoder.Encode(1.0, scale, plainCoeff0);
using Plaintext xPlain = new Plaintext();
Utilities.PrintLine();
Console.WriteLine("Encode input vectors.");
encoder.Encode(input, scale, xPlain);
using Ciphertext x1Encrypted = new Ciphertext();
encryptor.Encrypt(xPlain, x1Encrypted);
/*
To compute x^3 we first compute x^2 and relinearize. However, the scale has
now grown to 2^80.
*/
using Ciphertext x3Encrypted = new Ciphertext();
Utilities.PrintLine();
Console.WriteLine("Compute x^2 and relinearize:");
evaluator.Square(x1Encrypted, x3Encrypted);
evaluator.RelinearizeInplace(x3Encrypted, relinKeys);
Console.WriteLine(" + Scale of x^2 before rescale: {0} bits",
Math.Log(x3Encrypted.Scale, newBase: 2));
/*
Now rescale; in addition to a modulus switch, the scale is reduced down by
a factor equal to the prime that was switched away (40-bit prime). Hence, the
new scale should be close to 2^40. Note, however, that the scale is not equal
to 2^40: this is because the 40-bit prime is only close to 2^40.
*/
Utilities.PrintLine();
Console.WriteLine("Rescale x^2.");
evaluator.RescaleToNextInplace(x3Encrypted);
Console.WriteLine(" + Scale of x^2 after rescale: {0} bits",
Math.Log(x3Encrypted.Scale, newBase: 2));
/*
Now x3Encrypted is at a different level than x1Encrypted, which prevents us
from multiplying them to compute x^3. We could simply switch x1Encrypted to
the next parameters in the modulus switching chain. However, since we still
need to multiply the x^3 term with PI (plainCoeff3), we instead compute PI*x
first and multiply that with x^2 to obtain PI*x^3. To this end, we compute
PI*x and rescale it back from scale 2^80 to something close to 2^40.
*/
Utilities.PrintLine();
Console.WriteLine("Compute and rescale PI*x.");
using Ciphertext x1EncryptedCoeff3 = new Ciphertext();
evaluator.MultiplyPlain(x1Encrypted, plainCoeff3, x1EncryptedCoeff3);
Console.WriteLine(" + Scale of PI*x before rescale: {0} bits",
Math.Log(x1EncryptedCoeff3.Scale, newBase: 2));
evaluator.RescaleToNextInplace(x1EncryptedCoeff3);
Console.WriteLine(" + Scale of PI*x after rescale: {0} bits",
Math.Log(x1EncryptedCoeff3.Scale, newBase: 2));
/*
Since x3Encrypted and x1EncryptedCoeff3 have the same exact scale and use
the same encryption parameters, we can multiply them together. We write the
result to x3Encrypted, relinearize, and rescale. Note that again the scale
is something close to 2^40, but not exactly 2^40 due to yet another scaling
by a prime. We are down to the last level in the modulus switching chain.
*/
Utilities.PrintLine();
Console.WriteLine("Compute, relinearize, and rescale (PI*x)*x^2.");
evaluator.MultiplyInplace(x3Encrypted, x1EncryptedCoeff3);
evaluator.RelinearizeInplace(x3Encrypted, relinKeys);
Console.WriteLine(" + Scale of PI*x^3 before rescale: {0} bits",
Math.Log(x3Encrypted.Scale, newBase: 2));
evaluator.RescaleToNextInplace(x3Encrypted);
Console.WriteLine(" + Scale of PI*x^3 after rescale: {0} bits",
Math.Log(x3Encrypted.Scale, newBase: 2));
/*
Next we compute the degree one term. All this requires is one MultiplyPlain
with plainCoeff1. We overwrite x1Encrypted with the result.
*/
Utilities.PrintLine();
Console.WriteLine("Compute and rescale 0.4*x.");
evaluator.MultiplyPlainInplace(x1Encrypted, plainCoeff1);
Console.WriteLine(" + Scale of 0.4*x before rescale: {0} bits",
Math.Log(x1Encrypted.Scale, newBase: 2));
evaluator.RescaleToNextInplace(x1Encrypted);
Console.WriteLine(" + Scale of 0.4*x after rescale: {0} bits",
Math.Log(x1Encrypted.Scale, newBase: 2));
/*
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.
Encrypted addition and subtraction require that the scales of the inputs are
the same, and also that the encryption parameters (ParmsId) match. If there
is a mismatch, Evaluator will throw an exception.
*/
Console.WriteLine();
Utilities.PrintLine();
Console.WriteLine("Parameters used by all three terms are different:");
Console.WriteLine(" + Modulus chain index for x3Encrypted: {0}",
context.GetContextData(x3Encrypted.ParmsId).ChainIndex);
Console.WriteLine(" + Modulus chain index for x1Encrypted: {0}",
context.GetContextData(x1Encrypted.ParmsId).ChainIndex);
Console.WriteLine(" + Modulus chain index for plainCoeff0: {0}",
context.GetContextData(plainCoeff0.ParmsId).ChainIndex);
Console.WriteLine();
/*
Let us carefully consider what the scales are at this point. We denote the
primes in coeff_modulus as P_0, P_1, P_2, P_3, in this order. P_3 is used as
the special modulus and is not involved in rescalings. After the computations
above the scales in ciphertexts are:
- Product x^2 has scale 2^80 and is at level 2;
- Product PI*x has scale 2^80 and is at level 2;
- We rescaled both down to scale 2^80/P2 and level 1;
- Product PI*x^3 has scale (2^80/P_2)^2;
- We rescaled it down to scale (2^80/P_2)^2/P_1 and level 0;
- Product 0.4*x has scale 2^80;
- We rescaled it down to scale 2^80/P_2 and level 1;
- The contant term 1 has scale 2^40 and is at level 2.
Although the scales of all three terms are approximately 2^40, their exact
values are different, hence they cannot be added together.
*/
Utilities.PrintLine();
Console.WriteLine("The exact scales of all three terms are different:");
Console.WriteLine(" + Exact scale in PI*x^3: {0:0.0000000000}", x3Encrypted.Scale);
Console.WriteLine(" + Exact scale in 0.4*x: {0:0.0000000000}", x1Encrypted.Scale);
Console.WriteLine(" + Exact scale in 1: {0:0.0000000000}", plainCoeff0.Scale);
Console.WriteLine();
/*
There are many ways to fix this problem. Since P_2 and P_1 are really close
to 2^40, we can simply "lie" to Microsoft SEAL and set the scales to be the
same. For example, changing the scale of PI*x^3 to 2^40 simply means that we
scale the value of PI*x^3 by 2^120/(P_2^2*P_1), which is very close to 1.
This should not result in any noticeable error.
Another option would be to encode 1 with scale 2^80/P_2, do a MultiplyPlain
with 0.4*x, and finally rescale. In this case we would need to additionally
make sure to encode 1 with appropriate encryption parameters (ParmsId).
In this example we will use the first (simplest) approach and simply change
the scale of PI*x^3 and 0.4*x to 2^40.
*/
Utilities.PrintLine();
Console.WriteLine("Normalize scales to 2^40.");
x3Encrypted.Scale = Math.Pow(2.0, 40);
x1Encrypted.Scale = Math.Pow(2.0, 40);
/*
We still have a problem with mismatching encryption parameters. This is easy
to fix by using traditional modulus switching (no rescaling). CKKS supports
modulus switching just like the BFV scheme, allowing us to switch away parts
of the coefficient modulus when it is simply not needed.
*/
Utilities.PrintLine();
Console.WriteLine("Normalize encryption parameters to the lowest level.");
ParmsId lastParmsId = x3Encrypted.ParmsId;
evaluator.ModSwitchToInplace(x1Encrypted, lastParmsId);
evaluator.ModSwitchToInplace(plainCoeff0, lastParmsId);
/*
All three ciphertexts are now compatible and can be added.
*/
Utilities.PrintLine();
Console.WriteLine("Compute PI*x^3 + 0.4*x + 1.");
using Ciphertext encryptedResult = new Ciphertext();
evaluator.Add(x3Encrypted, x1Encrypted, encryptedResult);
evaluator.AddPlainInplace(encryptedResult, plainCoeff0);
/*
First print the true result.
*/
using Plaintext plainResult = new Plaintext();
Utilities.PrintLine();
Console.WriteLine("Decrypt and decode PI * x ^ 3 + 0.4x + 1.");
Console.WriteLine(" + Expected result:");
List<double> trueResult = new List<double>(input.Count);
foreach (double x in input)
{
trueResult.Add((3.14159265 * x * x + 0.4) * x + 1);
}
Utilities.PrintVector(trueResult, 3, 7);
/*
We decrypt, decode, and print the result.
*/
decryptor.Decrypt(encryptedResult, plainResult);
List<double> result = new List<double>();
encoder.Decode(plainResult, result);
Console.WriteLine(" + Computed result ...... Correct.");
Utilities.PrintVector(result, 3, 7);
/*
While we did not show any computations on complex numbers in these examples,
the CKKSEncoder would allow us to have done that just as easily. Additions
and multiplications of complex numbers behave just as one would expect.
*/
}
}
}
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using Microsoft.Research.SEAL;
using System.Collections.Generic;
namespace SEALNetExamples
{
partial class Examples
{
/*
Both the BFV scheme (with BatchEncoder) as well as the CKKS scheme support native
vectorized computations on encrypted numbers. In addition to computing slot-wise,
it is possible to rotate the encrypted vectors cyclically.
*/
private static void ExampleRotationBFV()
{
Utilities.PrintExampleBanner("Example: Rotation / Rotation in BFV");
using EncryptionParameters parms = new EncryptionParameters(SchemeType.BFV);
ulong polyModulusDegree = 8192;
parms.PolyModulusDegree = polyModulusDegree;
parms.CoeffModulus = CoeffModulus.BFVDefault(polyModulusDegree);
parms.PlainModulus = PlainModulus.Batching(polyModulusDegree, 20);
using SEALContext context = new SEALContext(parms);
Utilities.PrintParameters(context);
Console.WriteLine();
using KeyGenerator keygen = new KeyGenerator(context);
using PublicKey publicKey = keygen.PublicKey;
using SecretKey secretKey = keygen.SecretKey;
using RelinKeys relinKeys = keygen.RelinKeysLocal();
using Encryptor encryptor = new Encryptor(context, publicKey);
using Evaluator evaluator = new Evaluator(context);
using Decryptor decryptor = new Decryptor(context, secretKey);
using BatchEncoder batchEncoder = new BatchEncoder(context);
ulong slotCount = batchEncoder.SlotCount;
ulong rowSize = slotCount / 2;
Console.WriteLine($"Plaintext matrix row size: {rowSize}");
ulong[] podMatrix = new ulong[slotCount];
podMatrix[0] = 0;
podMatrix[1] = 1;
podMatrix[2] = 2;
podMatrix[3] = 3;
podMatrix[rowSize] = 4;
podMatrix[rowSize + 1] = 5;
podMatrix[rowSize + 2] = 6;
podMatrix[rowSize + 3] = 7;
Console.WriteLine("Input plaintext matrix:");
Utilities.PrintMatrix(podMatrix, (int)rowSize);
Console.WriteLine();
/*
First we use BatchEncoder to encode the matrix into a plaintext. We encrypt
the plaintext as usual.
*/
Utilities.PrintLine();
using Plaintext plainMatrix = new Plaintext();
Console.WriteLine("Encode and encrypt.");
batchEncoder.Encode(podMatrix, plainMatrix);
using Ciphertext encryptedMatrix = new Ciphertext();
encryptor.Encrypt(plainMatrix, encryptedMatrix);
Console.WriteLine(" + Noise budget in fresh encryption: {0} bits",
decryptor.InvariantNoiseBudget(encryptedMatrix));
Console.WriteLine();
/*
Rotations require yet another type of special key called `Galois keys'. These
are easily obtained from the KeyGenerator.
*/
using GaloisKeys galKeys = keygen.GaloisKeysLocal();
/*
Now rotate both matrix rows 3 steps to the left, decrypt, decode, and print.
*/
Utilities.PrintLine();
Console.WriteLine("Rotate rows 3 steps left.");
evaluator.RotateRowsInplace(encryptedMatrix, 3, galKeys);
using Plaintext plainResult = new Plaintext();
Console.WriteLine(" + Noise budget after rotation: {0} bits",
decryptor.InvariantNoiseBudget(encryptedMatrix));
Console.WriteLine(" + Decrypt and decode ...... Correct.");
decryptor.Decrypt(encryptedMatrix, plainResult);
List<ulong> podResult = new List<ulong>();
batchEncoder.Decode(plainResult, podResult);
Utilities.PrintMatrix(podResult, (int)rowSize);
/*
We can also rotate the columns, i.e., swap the rows.
*/
Utilities.PrintLine();
Console.WriteLine("Rotate columns.");
evaluator.RotateColumnsInplace(encryptedMatrix, galKeys);
Console.WriteLine(" + Noise budget after rotation: {0} bits",
decryptor.InvariantNoiseBudget(encryptedMatrix));
Console.WriteLine(" + Decrypt and decode ...... Correct.");
decryptor.Decrypt(encryptedMatrix, plainResult);
batchEncoder.Decode(plainResult, podResult);
Utilities.PrintMatrix(podResult, (int)rowSize);
/*
Finally, we rotate the rows 4 steps to the right, decrypt, decode, and print.
*/
Utilities.PrintLine();
Console.WriteLine("Rotate rows 4 steps right.");
evaluator.RotateRowsInplace(encryptedMatrix, -4, galKeys);
Console.WriteLine(" + Noise budget after rotation: {0} bits",
decryptor.InvariantNoiseBudget(encryptedMatrix));
Console.WriteLine(" + Decrypt and decode ...... Correct.");
decryptor.Decrypt(encryptedMatrix, plainResult);
batchEncoder.Decode(plainResult, podResult);
Utilities.PrintMatrix(podResult, (int)rowSize);
/*
Note that rotations do not consume any noise budget. However, this is only
the case when the special prime is at least as large as the other primes. The
same holds for relinearization. Microsoft SEAL does not require that the
special prime is of any particular size, so ensuring this is the case is left
for the user to do.
*/
}
private static void ExampleRotationCKKS()
{
Utilities.PrintExampleBanner("Example: Rotation / Rotation in CKKS");
using EncryptionParameters parms = new EncryptionParameters(SchemeType.CKKS);
ulong polyModulusDegree = 8192;
parms.PolyModulusDegree = polyModulusDegree;
parms.CoeffModulus = CoeffModulus.Create(
polyModulusDegree, new int[] { 40, 40, 40, 40, 40 });
using SEALContext context = new SEALContext(parms);
Utilities.PrintParameters(context);
Console.WriteLine();
using KeyGenerator keygen = new KeyGenerator(context);
using PublicKey publicKey = keygen.PublicKey;
using SecretKey secretKey = keygen.SecretKey;
using RelinKeys relinKeys = keygen.RelinKeysLocal();
using GaloisKeys galKeys = keygen.GaloisKeysLocal();
using Encryptor encryptor = new Encryptor(context, publicKey);
using Evaluator evaluator = new Evaluator(context);
using Decryptor decryptor = new Decryptor(context, secretKey);
using CKKSEncoder ckksEncoder = new CKKSEncoder(context);
ulong slotCount = ckksEncoder.SlotCount;
Console.WriteLine($"Number of slots: {slotCount}");
List<double> input = new List<double>((int)slotCount);
double currPoint = 0, stepSize = 1.0 / (slotCount - 1);
for (ulong i = 0; i < slotCount; i++, currPoint += stepSize)
{
input.Add(currPoint);
}
Console.WriteLine("Input vector:");
Utilities.PrintVector(input, 3, 7);
double scale = Math.Pow(2.0, 50);
Utilities.PrintLine();
Console.WriteLine("Encode and encrypt.");
using Plaintext plain = new Plaintext();
ckksEncoder.Encode(input, scale, plain);
using Ciphertext encrypted = new Ciphertext();
encryptor.Encrypt(plain, encrypted);
using Ciphertext rotated = new Ciphertext();
Utilities.PrintLine();
Console.WriteLine("Rotate 2 steps left.");
evaluator.RotateVector(encrypted, 2, galKeys, rotated);
Console.WriteLine(" + Decrypt and decode ...... Correct.");
decryptor.Decrypt(encrypted, plain);
List<double> result = new List<double>();
ckksEncoder.Decode(plain, result);
Utilities.PrintVector(result, 3, 7);
/*
With the CKKS scheme it is also possible to evaluate a complex conjugation on
a vector of encrypted complex numbers, using Evaluator.ComplexConjugate. This
is in fact a kind of rotation, and requires also Galois keys.
*/
}
private static void ExampleRotation()
{
Utilities.PrintExampleBanner("Example: Rotation");
/*
Run all rotation examples.
*/
ExampleRotationBFV();
ExampleRotationCKKS();
}
}
}
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.IO;
using System.Collections.Generic;
using Microsoft.Research.SEAL;
namespace SEALNetExamples
{
partial class Examples
{
/*
In this example we show how serialization works in Microsoft SEAL. Specifically,
we present important concepts that enable the user to optimize the data size when
communicating ciphertexts and keys for outsourced computation. Unlike the previous
examples, we organize this one in a client-server style for maximal clarity. The
server selects encryption parameters, the client generates keys, the server does
the encrypted computation, and the client decrypts.
*/
private static void ExampleSerialization()
{
Utilities.PrintExampleBanner("Example: Serialization");
/*
We require ZLIB support for this example to be available.
*/
if (!Serialization.IsSupportedComprMode(ComprModeType.Deflate))
{
Console.WriteLine("ZLIB support is not enabled; this example is not available.");
Console.WriteLine();
return;
}
/*
To simulate client-server interaction, we set up a shared C# stream. In real
use-cases this can be a network stream, a filestream, or any shared resource.
It is critical to note that all data serialized by Microsoft SEAL is in binary
form, so it is not meaningful to print the data as ASCII characters. Encodings
such as Base64 would increase the data size, which is already a bottleneck in
homomorphic encryption. Hence, serialization into text is not supported or
recommended.
In this example we use a couple of shared MemoryStreams.
*/
MemoryStream parmsStream = new MemoryStream();
MemoryStream dataStream = new MemoryStream();
MemoryStream skStream = new MemoryStream();
/*
The server first determines the computation and sets encryption parameters
accordingly.
*/
{
ulong polyModulusDegree = 8192;
using EncryptionParameters parms = new EncryptionParameters(SchemeType.CKKS);
parms.PolyModulusDegree = polyModulusDegree;
parms.CoeffModulus = CoeffModulus.Create(
polyModulusDegree, new int[]{ 50, 20, 50 });
/*
Serialization of the encryption parameters to our shared stream is very
simple with the EncryptionParameters.Save function.
*/
long size = parms.Save(parmsStream);
/*
Seek the parmsStream head back to beginning of the stream.
*/
parmsStream.Seek(0, SeekOrigin.Begin);
/*
The return value of this function is the actual byte count of data written
to the stream.
*/
Utilities.PrintLine();
Console.WriteLine($"EncryptionParameters: wrote {size} bytes");
/*
Before moving on, we will take some time to discuss further options in
serialization. These will become particularly important when the user
needs to optimize communication and storage sizes.
*/
/*
It is possible to enable or disable ZLIB ("deflate") compression for
serialization by providing EncryptionParameters.Save with the desired
compression mode as in the following examples:
long size = parms.Save(sharedStream, ComprModeType.None);
long size = parms.Save(sharedStream, ComprModeType.Deflate);
If Microsoft SEAL is compiled with ZLIB support, the default is to use
ComprModeType.Deflate, so to instead disable compression one would use
the first version of the two.
*/
/*
In many cases, when working with fixed size memory, it is necessary
to know ahead of time an upper bound on the serialized data size to
allocate enough memory. This information is returned by the
EncryptionParameters.SaveSize function. This function accepts the
desired compression mode, with ComprModeType.Deflate being the default
when Microsoft SEAL is compiled with ZLIB support.
In more detail, the output of EncryptionParameters.SaveSize is as follows:
- Exact buffer size required for ComprModeType.None;
- Upper bound on the size required for ComprModeType.Deflate.
As we can see from the print-out, the sizes returned by these functions
are significantly larger than the compressed size written into the shared
stream in the beginning. This is normal: compression yielded a significant
improvement in the data size, yet it is hard to estimate the size of the
compressed data.
*/
Utilities.PrintLine();
Console.Write("EncryptionParameters: data size upper bound (ComprModeType.None): ");
Console.WriteLine(parms.SaveSize(ComprModeType.None));
Console.Write(" ");
Console.Write("EncryptionParameters: data size upper bound (ComprModeType.Deflate): ");
Console.WriteLine(parms.SaveSize(ComprModeType.Deflate));
/*
As an example, we now serialize the encryption parameters to a fixed
size buffer.
*/
MemoryStream buffer = new MemoryStream(new byte[parms.SaveSize()]);
parms.Save(buffer);
/*
To illustrate deserialization, we load back the encryption parameters
from our buffer into another instance of EncryptionParameters. First
we need to seek our stream back to the beginning.
*/
buffer.Seek(0, SeekOrigin.Begin);
using EncryptionParameters parms2 = new EncryptionParameters();
parms2.Load(buffer);
/*
We can check that the saved and loaded encryption parameters indeed match.
*/
Utilities.PrintLine();
Console.WriteLine($"EncryptionParameters: parms == parms2: {parms.Equals(parms2)}");
}
/*
Client starts by loading the encryption parameters, sets up the SEALContext,
and creates the required keys.
*/
{
using EncryptionParameters parms = new EncryptionParameters();
parms.Load(parmsStream);
/*
Seek the parmsStream head back to beginning of the stream because we
will use the same stream to read the parameters repeatedly.
*/
parmsStream.Seek(0, SeekOrigin.Begin);
using SEALContext context = new SEALContext(parms);
using KeyGenerator keygen = new KeyGenerator(context);
using SecretKey sk = keygen.SecretKey;
using PublicKey pk = keygen.PublicKey;
/*
We need to save the secret key so we can decrypt later.
*/
sk.Save(skStream);
skStream.Seek(0, SeekOrigin.Begin);
/*
In this example we will also use relinearization keys. For realinearization
and Galois keys the KeyGenerator.RelinKeys and KeyGenerator.GaloisKeys
functions return special Serializable<T> objects. These objects are meant
to be serialized and never used locally. On the other hand, for local use
of RelinKeys and GaloisKeys, the functions KeyGenerator.RelinKeysLocal
and KeyGenerator.GaloisKeysLocal can be used to create the RelinKeys
and GaloisKeys objects directly. The difference is that the Serializable<T>
objects contain a partly seeded version of the RelinKeys (or GaloisKeys)
that will result in a significantly smaller size when serialized. Using
this method has no impact on security. Such seeded RelinKeys (GaloisKeys)
must be expanded before being used in computations; this is automatically
done by deserialization.
*/
using Serializable<RelinKeys> rlk = keygen.RelinKeys();
/*
Before continuing, we demonstrate the significant space saving from this
method.
*/
long sizeRlk = rlk.Save(dataStream);
using RelinKeys rlkLocal = keygen.RelinKeysLocal();
long sizeRlkLocal = rlkLocal.Save(dataStream);
/*
Now compare the serialized sizes of rlk and rlkLocal.
*/
Utilities.PrintLine();
Console.WriteLine($"Serializable<RelinKeys>: wrote {sizeRlk} bytes");
Console.Write(" ");
Console.WriteLine($"RelinKeys (local): wrote {sizeRlkLocal} bytes");
/*
Seek back in dataStream to where rlk data ended, i.e., sizeRlkLocal
bytes backwards from current position.
*/
dataStream.Seek(-sizeRlkLocal, SeekOrigin.Current);
/*
Next set up the CKKSEncoder and Encryptor, and encrypt some numbers.
*/
double scale = Math.Pow(2.0, 20);
CKKSEncoder encoder = new CKKSEncoder(context);
using Plaintext plain1 = new Plaintext(),
plain2 = new Plaintext();
encoder.Encode(2.3, scale, plain1);
encoder.Encode(4.5, scale, plain2);
using Encryptor encryptor = new Encryptor(context, pk);
using Ciphertext encrypted1 = new Ciphertext(),
encrypted2 = new Ciphertext();
encryptor.Encrypt(plain1, encrypted1);
encryptor.Encrypt(plain2, encrypted2);
/*
Now, we could serialize both encrypted1 and encrypted2 to dataStream
using Ciphertext.Save. However, for this example, we demonstrate another
size-saving trick that can come in handy.
As you noticed, we set up the Encryptor using the public key. Clearly this
indicates that the CKKS scheme is a public-key encryption scheme. However,
both BFV and CKKS can operate also in a symmetric-key mode. This can be
beneficial when the public-key functionality is not exactly needed, like
in simple outsourced computation scenarios. The benefit is that in these
cases it is possible to produce ciphertexts that are partly seeded, hence
significantly smaller. Such ciphertexts must be expanded before being used
in computations; this is automatically done by deserialization.
To use symmetric-key encryption, we need to set up the Encryptor with the
secret key instead.
*/
using Encryptor symEncryptor = new Encryptor(context, sk);
using Serializable<Ciphertext> symEncrypted1 = symEncryptor.EncryptSymmetric(plain1);
using Serializable<Ciphertext> symEncrypted2 = symEncryptor.EncryptSymmetric(plain2);
/*
Before continuing, we demonstrate the significant space saving from this
method.
*/
long sizeSymEncrypted1 = symEncrypted1.Save(dataStream);
long sizeEncrypted1 = encrypted1.Save(dataStream);
/*
Now compare the serialized sizes of encrypted1 and symEncrypted1.
*/
Utilities.PrintLine();
Console.Write("Serializable<Ciphertext> (symmetric-key): ");
Console.WriteLine($"wrote {sizeSymEncrypted1} bytes");
Console.Write(" ");
Console.WriteLine($"Ciphertext (public-key): wrote {sizeEncrypted1} bytes");
/*
Seek back in dataStream to where symEncrypted1 data ended, i.e.,
sizeEncrypted1 bytes backwards from current position and write
symEncrypted2 right after symEncrypted1.
*/
dataStream.Seek(-sizeEncrypted1, SeekOrigin.Current);
symEncrypted2.Save(dataStream);
dataStream.Seek(0, SeekOrigin.Begin);
/*
We have seen how using KeyGenerator.RelinKeys (KeyGenerator.GaloisKeys)
can result in huge space savings over the local variants when the objects
are not needed for local use. We have seen how symmetric-key encryption
can be used to achieve much smaller ciphertext sizes when the public-key
functionality is not needed.
We would also like to draw attention to the fact there we could easily
serialize multiple Microsoft SEAL objects sequentially in a stream. Each
object writes its own size into the stream, so deserialization knows
exactly how many bytes to read. We will see this working next.
Finally, we would like to point out that none of these methods provide any
space savings unless Microsoft SEAL is compiled with ZLIB support, or when
serialized with ComprModeType.None.
*/
}
/*
The server can now compute on the encrypted data. We will recreate the
SEALContext and set up an Evaluator here.
*/
{
using EncryptionParameters parms = new EncryptionParameters();
parms.Load(parmsStream);
parmsStream.Seek(0, SeekOrigin.Begin);
using SEALContext context = new SEALContext(parms);
using Evaluator evaluator = new Evaluator(context);
/*
Next we need to load relinearization keys and the ciphertexts from our
dataStream.
*/
using RelinKeys rlk = new RelinKeys();
using Ciphertext encrypted1 = new Ciphertext(),
encrypted2 = new Ciphertext();
/*
Deserialization is as easy as serialization.
*/
rlk.Load(context, dataStream);
encrypted1.Load(context, dataStream);
encrypted2.Load(context, dataStream);
/*
Compute the product, rescale, and relinearize.
*/
using Ciphertext encryptedProd = new Ciphertext();
evaluator.Multiply(encrypted1, encrypted2, encryptedProd);
evaluator.RelinearizeInplace(encryptedProd, rlk);
evaluator.RescaleToNextInplace(encryptedProd);
/*
We use dataStream to communicate encryptedProd back to the client. There
is no way to save the encryptedProd as Serializable<Ciphertext> even
though it is still a symmetric-key encryption: only freshly encrypted
ciphertexts can be seeded. Note how the size of the result ciphertext is
smaller than the size of a fresh ciphertext because it is at a lower level
due to the rescale operation.
*/
dataStream.Seek(0, SeekOrigin.Begin);
long sizeEncryptedProd = encryptedProd.Save(dataStream);
dataStream.Seek(0, SeekOrigin.Begin);
Utilities.PrintLine();
Console.Write($"Ciphertext (symmetric-key): ");
Console.WriteLine($"wrote {sizeEncryptedProd} bytes");
}
/*
In the final step the client decrypts the result.
*/
{
using EncryptionParameters parms = new EncryptionParameters();
parms.Load(parmsStream);
parmsStream.Seek(0, SeekOrigin.Begin);
using SEALContext context = new SEALContext(parms);
/*
Load back the secret key from skStream.
*/
using SecretKey sk = new SecretKey();
sk.Load(context, skStream);
using Decryptor decryptor = new Decryptor(context, sk);
using CKKSEncoder encoder = new CKKSEncoder(context);
using Ciphertext encryptedResult = new Ciphertext();
encryptedResult.Load(context, dataStream);
using Plaintext plainResult = new Plaintext();
decryptor.Decrypt(encryptedResult, plainResult);
List<double> result = new List<double>();
encoder.Decode(plainResult, result);
Utilities.PrintLine();
Console.WriteLine("Result: ");
Utilities.PrintVector(result, 3, 7);
}
/*
Finally, we give a little bit more explanation of the structure of data
serialized by Microsoft SEAL. Serialized data always starts with a 16-byte
SEALHeader struct, as defined in dotnet/src/Serialization.cs, and is
followed by the possibly compressed data for the object.
A SEALHeader contains the following data:
[offset 0] 2-byte magic number 0xA15E (Serialization.SEALMagic)
[offset 2] 1-byte indicating the header size in bytes (always 16)
[offset 3] 1-byte indicating the Microsoft SEAL major version number
[offset 4] 1-byte indicating the Microsoft SEAL minor version number
[offset 5] 1-byte indicating the compression mode type
[offset 6] 2-byte reserved field (unused)
[offset 8] 8-byte size in bytes of the serialized data, including the header
Currently Microsoft SEAL supports only little-endian systems.
As an example, we demonstrate the SEALHeader created by saving a plaintext.
Note that the SEALHeader is never compressed, so there is no need to specify
the compression mode.
*/
using Plaintext pt = new Plaintext("1x^2 + 3");
MemoryStream stream = new MemoryStream();
long dataSize = pt.Save(stream);
/*
Seek the stream head back to beginning of the stream.
*/
stream.Seek(0, SeekOrigin.Begin);
/*
We can now load just the SEALHeader back from the stream as follows.
*/
Serialization.SEALHeader header = new Serialization.SEALHeader();
Serialization.LoadHeader(stream, header);
/*
Now confirm that the size of data written to stream matches with what is
indicated by the SEALHeader.
*/
Utilities.PrintLine();
Console.WriteLine($"Size written to stream: {dataSize} bytes");
Console.Write(" ");
Console.WriteLine($"Size indicated in SEALHeader: {header.Size} bytes");
Console.WriteLine();
}
}
}
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using Microsoft.Research.SEAL;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace SEALNetExamples
{
partial class Examples
{
private static void BFVPerformanceTest(SEALContext context)
{
Stopwatch timer;
Utilities.PrintParameters(context);
Console.WriteLine();
using EncryptionParameters parms = context.FirstContextData.Parms;
using Modulus plainModulus = parms.PlainModulus;
ulong polyModulusDegree = parms.PolyModulusDegree;
Console.Write("Generating secret/public keys: ");
using KeyGenerator keygen = new KeyGenerator(context);
Console.WriteLine("Done");
using SecretKey secretKey = keygen.SecretKey;
using PublicKey publicKey = keygen.PublicKey;
Func<RelinKeys> GetRelinKeys = () => {
if (context.UsingKeyswitching)
{
/*
Generate relinearization keys.
*/
Console.Write("Generating relinearization keys: ");
timer = Stopwatch.StartNew();
RelinKeys result = keygen.RelinKeysLocal();
int micros = (int)(timer.Elapsed.TotalMilliseconds * 1000);
Console.WriteLine($"Done [{micros} microseconds]");
return result;
}
else
{
return null;
}
};
Func<GaloisKeys> GetGaloisKeys = () => {
if (context.UsingKeyswitching)
{
if (!context.KeyContextData.Qualifiers.UsingBatching)
{
Console.WriteLine("Given encryption parameters do not support batching.");
return null;
}
/*
Generate Galois keys. In larger examples the Galois keys can use a lot of
memory, which can be a problem in constrained systems. The user should
try some of the larger runs of the test and observe their effect on the
memory pool allocation size. The key generation can also take a long time,
as can be observed from the print-out.
*/
Console.Write($"Generating Galois keys: ");
timer = Stopwatch.StartNew();
GaloisKeys result = keygen.GaloisKeysLocal();
int micros = (int)(timer.Elapsed.TotalMilliseconds * 1000);
Console.WriteLine($"Done [{micros} microseconds]");
return result;
}
else
{
return null;
}
};
using RelinKeys relinKeys = GetRelinKeys();
using GaloisKeys galKeys = GetGaloisKeys();
using Encryptor encryptor = new Encryptor(context, publicKey);
using Decryptor decryptor = new Decryptor(context, secretKey);
using Evaluator evaluator = new Evaluator(context);
using BatchEncoder batchEncoder = new BatchEncoder(context);
using IntegerEncoder encoder = new IntegerEncoder(context);
/*
These will hold the total times used by each operation.
*/
Stopwatch timeBatchSum = new Stopwatch();
Stopwatch timeUnbatchSum = new Stopwatch();
Stopwatch timeEncryptSum = new Stopwatch();
Stopwatch timeDecryptSum = new Stopwatch();
Stopwatch timeAddSum = new Stopwatch();
Stopwatch timeMultiplySum = new Stopwatch();
Stopwatch timeMultiplyPlainSum = new Stopwatch();
Stopwatch timeSquareSum = new Stopwatch();
Stopwatch timeRelinearizeSum = new Stopwatch();
Stopwatch timeRotateRowsOneStepSum = new Stopwatch();
Stopwatch timeRotateRowsRandomSum = new Stopwatch();
Stopwatch timeRotateColumnsSum = new Stopwatch();
/*
How many times to run the test?
*/
int count = 10;
/*
Populate a vector of values to batch.
*/
ulong slotCount = batchEncoder.SlotCount;
ulong[] podValues = new ulong[slotCount];
Random rnd = new Random();
for (ulong i = 0; i < batchEncoder.SlotCount; i++)
{
podValues[i] = (ulong)rnd.Next() % plainModulus.Value;
}
Console.Write("Running tests ");
for (int i = 0; i < count; i++)
{
/*
[Batching]
There is nothing unusual here. We batch our random plaintext matrix
into the polynomial. Note how the plaintext we create is of the exactly
right size so unnecessary reallocations are avoided.
*/
using Plaintext plain = new Plaintext(parms.PolyModulusDegree, 0);
timeBatchSum.Start();
batchEncoder.Encode(podValues, plain);
timeBatchSum.Stop();
/*
[Unbatching]
We unbatch what we just batched.
*/
List<ulong> podList = new List<ulong>((int)slotCount);
timeUnbatchSum.Start();
batchEncoder.Decode(plain, podList);
timeUnbatchSum.Stop();
if (!podList.SequenceEqual(podValues))
{
throw new InvalidOperationException("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.
*/
using Ciphertext encrypted = new Ciphertext(context);
timeEncryptSum.Start();
encryptor.Encrypt(plain, encrypted);
timeEncryptSum.Stop();
/*
[Decryption]
We decrypt what we just encrypted.
*/
using Plaintext plain2 = new Plaintext(polyModulusDegree, 0);
timeDecryptSum.Start();
decryptor.Decrypt(encrypted, plain2);
timeDecryptSum.Stop();
if (!plain2.Equals(plain))
{
throw new InvalidOperationException("Encrypt/decrypt failed. Something is wrong.");
}
/*
[Add]
We create two ciphertexts and perform a few additions with them.
*/
using Ciphertext encrypted1 = new Ciphertext(context);
encryptor.Encrypt(encoder.Encode(i), encrypted1);
using Ciphertext encrypted2 = new Ciphertext(context);
encryptor.Encrypt(encoder.Encode(i + 1), encrypted2);
timeAddSum.Start();
evaluator.AddInplace(encrypted1, encrypted1);
evaluator.AddInplace(encrypted2, encrypted2);
evaluator.AddInplace(encrypted1, encrypted2);
timeAddSum.Stop();
/*
[Multiply]
We multiply two ciphertexts. 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);
timeMultiplySum.Start();
evaluator.MultiplyInplace(encrypted1, encrypted2);
timeMultiplySum.Stop();
/*
[Multiply Plain]
We multiply a ciphertext with a random plaintext. Recall that
MultiplyPlain does not change the size of the ciphertext so we use
encrypted2 here.
*/
timeMultiplyPlainSum.Start();
evaluator.MultiplyPlainInplace(encrypted2, plain);
timeMultiplyPlainSum.Stop();
/*
[Square]
We continue to use encrypted2. Now we square it; this should be
faster than generic homomorphic multiplication.
*/
timeSquareSum.Start();
evaluator.SquareInplace(encrypted2);
timeSquareSum.Stop();
if (context.UsingKeyswitching)
{
/*
[Relinearize]
Time to get back to encrypted1. 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.
*/
timeRelinearizeSum.Start();
evaluator.RelinearizeInplace(encrypted1, relinKeys);
timeRelinearizeSum.Stop();
/*
[Rotate Rows One Step]
We rotate matrix rows by one step left and measure the time.
*/
timeRotateRowsOneStepSum.Start();
evaluator.RotateRowsInplace(encrypted, 1, galKeys);
evaluator.RotateRowsInplace(encrypted, -1, galKeys);
timeRotateRowsOneStepSum.Stop();
/*
[Rotate Rows Random]
We rotate matrix rows by a random number of steps. This is much more
expensive than rotating by just one step.
*/
int rowSize = (int)batchEncoder.SlotCount / 2;
int randomRotation = rnd.Next() % rowSize;
timeRotateRowsRandomSum.Start();
evaluator.RotateRowsInplace(encrypted, randomRotation, galKeys);
timeRotateRowsRandomSum.Stop();
/*
[Rotate Columns]
Nothing surprising here.
*/
timeRotateColumnsSum.Start();
evaluator.RotateColumnsInplace(encrypted, galKeys);
timeRotateColumnsSum.Stop();
}
/*
Print a dot to indicate progress.
*/
Console.Write(".");
Console.Out.Flush();
}
Console.WriteLine(" Done");
Console.WriteLine();
Console.Out.Flush();
int avgBatch = (int)(timeBatchSum.Elapsed.TotalMilliseconds * 1000 / count);
int avgUnbatch = (int)(timeUnbatchSum.Elapsed.TotalMilliseconds * 1000 / count);
int avgEncrypt = (int)(timeEncryptSum.Elapsed.TotalMilliseconds * 1000 / count);
int avgDecrypt = (int)(timeDecryptSum.Elapsed.TotalMilliseconds * 1000 / count);
int avgAdd = (int)(timeAddSum.Elapsed.TotalMilliseconds * 1000 / (3 * count));
int avgMultiply = (int)(timeMultiplySum.Elapsed.TotalMilliseconds * 1000 / count);
int avgMultiplyPlain = (int)(timeMultiplyPlainSum.Elapsed.TotalMilliseconds * 1000 / count);
int avgSquare = (int)(timeSquareSum.Elapsed.TotalMilliseconds * 1000 / count);
int avgRelinearize = (int)(timeRelinearizeSum.Elapsed.TotalMilliseconds * 1000 / count);
int avgRotateRowsOneStep = (int)(timeRotateRowsOneStepSum.Elapsed.TotalMilliseconds * 1000 / (2 * count));
int avgRotateRowsRandom = (int)(timeRotateRowsRandomSum.Elapsed.TotalMilliseconds * 1000 / count);
int avgRotateColumns = (int)(timeRotateColumnsSum.Elapsed.TotalMilliseconds * 1000 / count);
Console.WriteLine($"Average batch: {avgBatch} microseconds");
Console.WriteLine($"Average unbatch: {avgUnbatch} microseconds");
Console.WriteLine($"Average encrypt: {avgEncrypt} microseconds");
Console.WriteLine($"Average decrypt: {avgDecrypt} microseconds");
Console.WriteLine($"Average add: {avgAdd} microseconds");
Console.WriteLine($"Average multiply: {avgMultiply} microseconds");
Console.WriteLine($"Average multiply plain: {avgMultiplyPlain} microseconds");
Console.WriteLine($"Average square: {avgSquare} microseconds");
if (context.UsingKeyswitching)
{
Console.WriteLine($"Average relinearize: {avgRelinearize} microseconds");
Console.WriteLine($"Average rotate rows one step: {avgRotateRowsOneStep} microseconds");
Console.WriteLine($"Average rotate rows random: {avgRotateRowsRandom} microseconds");
Console.WriteLine($"Average rotate columns: {avgRotateColumns} microseconds");
}
Console.Out.Flush();
}
private static void CKKSPerformanceTest(SEALContext context)
{
Stopwatch timer;
Utilities.PrintParameters(context);
Console.WriteLine();
using EncryptionParameters parms = context.FirstContextData.Parms;
ulong polyModulusDegree = parms.PolyModulusDegree;
Console.Write("Generating secret/public keys: ");
using KeyGenerator keygen = new KeyGenerator(context);
Console.WriteLine("Done");
using SecretKey secretKey = keygen.SecretKey;
using PublicKey publicKey = keygen.PublicKey;
Func<RelinKeys> GetRelinKeys = () => {
if (context.UsingKeyswitching)
{
/*
Generate relinearization keys.
*/
Console.Write("Generating relinearization keys: ");
timer = Stopwatch.StartNew();
RelinKeys result = keygen.RelinKeysLocal();
int micros = (int)(timer.Elapsed.TotalMilliseconds * 1000);
Console.WriteLine($"Done [{micros} microseconds]");
return result;
}
else
{
return null;
}
};
Func<GaloisKeys> GetGaloisKeys = () => {
if (context.UsingKeyswitching)
{
if (!context.KeyContextData.Qualifiers.UsingBatching)
{
Console.WriteLine("Given encryption parameters do not support batching.");
return null;
}
/*
Generate Galois keys. In larger examples the Galois keys can use a lot of
memory, which can be a problem in constrained systems. The user should
try some of the larger runs of the test and observe their effect on the
memory pool allocation size. The key generation can also take a long time,
as can be observed from the print-out.
*/
Console.Write($"Generating Galois keys: ");
timer = Stopwatch.StartNew();
GaloisKeys result = keygen.GaloisKeysLocal();
int micros = (int)(timer.Elapsed.TotalMilliseconds * 1000);
Console.WriteLine($"Done [{micros} microseconds]");
return result;
}
else
{
return null;
}
};
using RelinKeys relinKeys = GetRelinKeys();
using GaloisKeys galKeys = GetGaloisKeys();
using Encryptor encryptor = new Encryptor(context, publicKey);
using Decryptor decryptor = new Decryptor(context, secretKey);
using Evaluator evaluator = new Evaluator(context);
using CKKSEncoder ckksEncoder = new CKKSEncoder(context);
Stopwatch timeEncodeSum = new Stopwatch();
Stopwatch timeDecodeSum = new Stopwatch();
Stopwatch timeEncryptSum = new Stopwatch();
Stopwatch timeDecryptSum = new Stopwatch();
Stopwatch timeAddSum = new Stopwatch();
Stopwatch timeMultiplySum = new Stopwatch();
Stopwatch timeMultiplyPlainSum = new Stopwatch();
Stopwatch timeSquareSum = new Stopwatch();
Stopwatch timeRelinearizeSum = new Stopwatch();
Stopwatch timeRescaleSum = new Stopwatch();
Stopwatch timeRotateOneStepSum = new Stopwatch();
Stopwatch timeRotateRandomSum = new Stopwatch();
Stopwatch timeConjugateSum = new Stopwatch();
Random rnd = new Random();
/*
How many times to run the test?
*/
int count = 10;
/*
Populate a vector of floating-point values to batch.
*/
ulong slotCount = ckksEncoder.SlotCount;
double[] podValues = new double[slotCount];
for (ulong i = 0; i < slotCount; i++)
{
podValues[i] = 1.001 * i;
}
Console.Write("Running tests ");
for (int i = 0; i < count; i++)
{
/*
[Encoding]
For scale we use the square root of the last CoeffModulus prime
from parms.
*/
double scale = Math.Sqrt(parms.CoeffModulus.Last().Value);
using Plaintext plain = new Plaintext(parms.PolyModulusDegree *
(ulong)parms.CoeffModulus.Count(), 0);
timeEncodeSum.Start();
ckksEncoder.Encode(podValues, scale, plain);
timeEncodeSum.Stop();
/*
[Decoding]
*/
List<double> podList = new List<double>((int)slotCount);
timeDecodeSum.Start();
ckksEncoder.Decode(plain, podList);
timeDecodeSum.Stop();
/*
[Encryption]
*/
using Ciphertext encrypted = new Ciphertext(context);
timeEncryptSum.Start();
encryptor.Encrypt(plain, encrypted);
timeEncryptSum.Stop();
/*
[Decryption]
*/
using Plaintext plain2 = new Plaintext(polyModulusDegree, 0);
timeDecryptSum.Start();
decryptor.Decrypt(encrypted, plain2);
timeDecryptSum.Stop();
/*
[Add]
*/
using Ciphertext encrypted1 = new Ciphertext(context);
ckksEncoder.Encode(i + 1, plain);
encryptor.Encrypt(plain, encrypted1);
using Ciphertext encrypted2 = new Ciphertext(context);
ckksEncoder.Encode(i + 1, plain2);
encryptor.Encrypt(plain2, encrypted2);
timeAddSum.Start();
evaluator.AddInplace(encrypted1, encrypted2);
evaluator.AddInplace(encrypted2, encrypted2);
evaluator.AddInplace(encrypted1, encrypted2);
timeAddSum.Stop();
/*
[Multiply]
*/
encrypted1.Reserve(3);
timeMultiplySum.Start();
evaluator.MultiplyInplace(encrypted1, encrypted2);
timeMultiplySum.Stop();
/*
[Multiply Plain]
*/
timeMultiplyPlainSum.Start();
evaluator.MultiplyPlainInplace(encrypted2, plain);
timeMultiplyPlainSum.Stop();
/*
[Square]
*/
timeSquareSum.Start();
evaluator.SquareInplace(encrypted2);
timeSquareSum.Stop();
if (context.UsingKeyswitching)
{
/*
[Relinearize]
*/
timeRelinearizeSum.Start();
evaluator.RelinearizeInplace(encrypted1, relinKeys);
timeRelinearizeSum.Stop();
/*
[Rescale]
*/
timeRescaleSum.Start();
evaluator.RescaleToNextInplace(encrypted1);
timeRescaleSum.Stop();
/*
[Rotate Vector]
*/
timeRotateOneStepSum.Start();
evaluator.RotateVectorInplace(encrypted, 1, galKeys);
evaluator.RotateVectorInplace(encrypted, -1, galKeys);
timeRotateOneStepSum.Stop();
/*
[Rotate Vector Random]
*/
int randomRotation = rnd.Next() % (int)ckksEncoder.SlotCount;
timeRotateRandomSum.Start();
evaluator.RotateVectorInplace(encrypted, randomRotation, galKeys);
timeRotateRandomSum.Stop();
/*
[Complex Conjugate]
*/
timeConjugateSum.Start();
evaluator.ComplexConjugateInplace(encrypted, galKeys);
timeConjugateSum.Stop();
}
/*
Print a dot to indicate progress.
*/
Console.Write(".");
Console.Out.Flush();
}
Console.WriteLine(" Done");
Console.WriteLine();
Console.Out.Flush();
int avgEncode = (int)(timeEncodeSum.Elapsed.TotalMilliseconds * 1000 / count);
int avgDecode = (int)(timeDecodeSum.Elapsed.TotalMilliseconds * 1000 / count);
int avgEncrypt = (int)(timeEncryptSum.Elapsed.TotalMilliseconds * 1000 / count);
int avgDecrypt = (int)(timeDecryptSum.Elapsed.TotalMilliseconds * 1000 / count);
int avgAdd = (int)(timeAddSum.Elapsed.TotalMilliseconds * 1000 / (3 * count));
int avgMultiply = (int)(timeMultiplySum.Elapsed.TotalMilliseconds * 1000 / count);
int avgMultiplyPlain = (int)(timeMultiplyPlainSum.Elapsed.TotalMilliseconds * 1000 / count);
int avgSquare = (int)(timeSquareSum.Elapsed.TotalMilliseconds * 1000 / count);
int avgRelinearize = (int)(timeRelinearizeSum.Elapsed.TotalMilliseconds * 1000 / count);
int avgRescale = (int)(timeRescaleSum.Elapsed.TotalMilliseconds * 1000 / count);
int avgRotateOneStep = (int)(timeRotateOneStepSum.Elapsed.TotalMilliseconds * 1000 / (2 * count));
int avgRotateRandom = (int)(timeRotateRandomSum.Elapsed.TotalMilliseconds * 1000 / count);
int avgConjugate = (int)(timeConjugateSum.Elapsed.TotalMilliseconds * 1000 / count);
Console.WriteLine($"Average encode: {avgEncode} microseconds");
Console.WriteLine($"Average decode: {avgDecode} microseconds");
Console.WriteLine($"Average encrypt: {avgEncrypt} microseconds");
Console.WriteLine($"Average decrypt: {avgDecrypt} microseconds");
Console.WriteLine($"Average add: {avgAdd} microseconds");
Console.WriteLine($"Average multiply: {avgMultiply} microseconds");
Console.WriteLine($"Average multiply plain: {avgMultiplyPlain} microseconds");
Console.WriteLine($"Average square: {avgSquare} microseconds");
if (context.UsingKeyswitching)
{
Console.WriteLine($"Average relinearize: {avgRelinearize} microseconds");
Console.WriteLine($"Average rescale: {avgRescale} microseconds");
Console.WriteLine($"Average rotate vector one step: {avgRotateOneStep} microseconds");
Console.WriteLine($"Average rotate vector random: {avgRotateRandom} microseconds");
Console.WriteLine($"Average complex conjugate: {avgConjugate} microseconds");
}
Console.Out.Flush();
}
private static void ExampleBFVPerformanceDefault()
{
Utilities.PrintExampleBanner("BFV Performance Test with Degrees: 4096, 8192, and 16384");
using EncryptionParameters parms = new EncryptionParameters(SchemeType.BFV);
ulong polyModulusDegree = 4096;
parms.PolyModulusDegree = polyModulusDegree;
parms.CoeffModulus = CoeffModulus.BFVDefault(polyModulusDegree);
parms.PlainModulus = new Modulus(786433);
using (SEALContext context = new SEALContext(parms))
{
BFVPerformanceTest(context);
}
Console.WriteLine();
polyModulusDegree = 8192;
parms.PolyModulusDegree = polyModulusDegree;
parms.CoeffModulus = CoeffModulus.BFVDefault(polyModulusDegree);
parms.PlainModulus = new Modulus(786433);
using (SEALContext context = new SEALContext(parms))
{
BFVPerformanceTest(context);
}
Console.WriteLine();
polyModulusDegree = 16384;
parms.PolyModulusDegree = polyModulusDegree;
parms.CoeffModulus = CoeffModulus.BFVDefault(polyModulusDegree);
parms.PlainModulus = new Modulus(786433);
using (SEALContext context = new SEALContext(parms))
{
BFVPerformanceTest(context);
}
/*
Comment out the following to run the biggest example.
*/
//Console.WriteLine();
//polyModulusDegree = 32768;
//parms.PolyModulusDegree = polyModulusDegree;
//parms.CoeffModulus = CoeffModulus.BFVDefault(polyModulusDegree);
//parms.PlainModulus = new Modulus(786433);
//using (SEALContext context = new SEALContext(parms))
//{
// BFVPerformanceTest(context);
//}
}
private static void ExampleBFVPerformanceCustom()
{
Console.Write("> Set PolyModulusDegree (1024, 2048, 4096, 8192, 16384, or 32768): ");
string input = Console.ReadLine();
if (!ulong.TryParse(input, out ulong polyModulusDegree))
{
Console.WriteLine("Invalid option.");
return;
}
if (polyModulusDegree < 1024 || polyModulusDegree > 32768 ||
(polyModulusDegree & (polyModulusDegree - 1)) != 0)
{
Console.WriteLine("Invalid option.");
return;
}
string banner = $"BFV Performance Test with Degree: {polyModulusDegree}";
Utilities.PrintExampleBanner(banner);
using EncryptionParameters parms = new EncryptionParameters(SchemeType.BFV)
{
PolyModulusDegree = polyModulusDegree,
CoeffModulus = CoeffModulus.BFVDefault(polyModulusDegree)
};
if (polyModulusDegree == 1024)
{
parms.PlainModulus = new Modulus(12289);
}
else
{
parms.PlainModulus = new Modulus(786433);
}
using (SEALContext context = new SEALContext(parms))
{
BFVPerformanceTest(context);
}
}
private static void ExampleCKKSPerformanceDefault()
{
Utilities.PrintExampleBanner("CKKS Performance Test with Degrees: 4096, 8192, and 16384");
// It is not recommended to use BFVDefault primes in CKKS. However, for performance
// test, BFVDefault primes are good enough.
using EncryptionParameters parms = new EncryptionParameters(SchemeType.CKKS);
ulong polyModulusDegree = 4096;
parms.PolyModulusDegree = polyModulusDegree;
parms.CoeffModulus = CoeffModulus.BFVDefault(polyModulusDegree);
using (SEALContext context = new SEALContext(parms))
{
CKKSPerformanceTest(context);
}
Console.WriteLine();
polyModulusDegree = 8192;
parms.PolyModulusDegree = polyModulusDegree;
parms.CoeffModulus = CoeffModulus.BFVDefault(polyModulusDegree);
using (SEALContext context = new SEALContext(parms))
{
CKKSPerformanceTest(context);
}
Console.WriteLine();
polyModulusDegree = 16384;
parms.PolyModulusDegree = polyModulusDegree;
parms.CoeffModulus = CoeffModulus.BFVDefault(polyModulusDegree);
using (SEALContext context = new SEALContext(parms))
{
CKKSPerformanceTest(context);
}
/*
Comment out the following to run the biggest example.
*/
//Console.WriteLine();
//polyModulusDegree = 32768;
//parms.PolyModulusDegree = polyModulusDegree;
//parms.CoeffModulus = CoeffModulus.BFVDefault(polyModulusDegree);
//using (SEALContext context = new SEALContext(parms))
//{
// CKKSPerformanceTest(context);
//}
}
private static void ExampleCKKSPerformanceCustom()
{
Console.Write("> Set PolyModulusDegree (1024, 2048, 4096, 8192, 16384, or 32768): ");
string input = Console.ReadLine();
if (!ulong.TryParse(input, out ulong polyModulusDegree))
{
Console.WriteLine("Invalid option.");
return;
}
if (polyModulusDegree < 1024 || polyModulusDegree > 32768 ||
(polyModulusDegree & (polyModulusDegree - 1)) != 0)
{
Console.WriteLine("Invalid option.");
return;
}
string banner = $"CKKS Performance Test with Degree: {polyModulusDegree}";
Utilities.PrintExampleBanner(banner);
using EncryptionParameters parms = new EncryptionParameters(SchemeType.CKKS)
{
PolyModulusDegree = polyModulusDegree,
CoeffModulus = CoeffModulus.BFVDefault(polyModulusDegree)
};
using (SEALContext context = new SEALContext(parms))
{
CKKSPerformanceTest(context);
}
}
private static void ExamplePerformanceTest()
{
Utilities.PrintExampleBanner("Example: Performance Test");
if (!Stopwatch.IsHighResolution)
{
Console.WriteLine("WARNING: High resolution stopwatch not available in this machine.");
Console.WriteLine(" Timings might not be accurate.");
}
while (true)
{
Console.WriteLine();
Console.WriteLine("Select a scheme (and optionally PolyModulusDegree):");
Console.WriteLine(" 1. BFV with default degrees");
Console.WriteLine(" 2. BFV with a custom degree");
Console.WriteLine(" 3. CKKS with default degrees");
Console.WriteLine(" 4. CKKS with a custom degree");
Console.WriteLine(" 0. Back to main menu");
Console.WriteLine();
ConsoleKeyInfo key;
do
{
Console.Write("> Run performance test (1 ~ 4) or go back (0): ");
key = Console.ReadKey();
Console.WriteLine();
} while (key.KeyChar < '0' || key.KeyChar > '4');
switch (key.Key)
{
case ConsoleKey.D1:
ExampleBFVPerformanceDefault();
break;
case ConsoleKey.D2:
ExampleBFVPerformanceCustom();
break;
case ConsoleKey.D3:
ExampleCKKSPerformanceDefault();
break;
case ConsoleKey.D4:
ExampleCKKSPerformanceCustom();
break;
case ConsoleKey.D0:
Console.WriteLine();
return;
default:
Console.WriteLine(" [Beep~~] Invalid option: type 0 ~ 4");
break;
}
}
}
}
}
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Research.SEAL;
using System;
namespace SEALNetExamples
{
partial class Examples
{
static void Main(string[] args)
{
Console.WriteLine("Microsoft SEAL version: " + SEALVersion.Version);
while (true)
{
Console.WriteLine("+---------------------------------------------------------+");
Console.WriteLine("| The following examples should be executed while reading |");
Console.WriteLine("| comments in associated files in dotnet/examples/. |");
Console.WriteLine("+---------------------------------------------------------+");
Console.WriteLine("| Examples | Source Files |");
Console.WriteLine("+----------------------------+----------------------------+");
Console.WriteLine("| 1. BFV Basics | 1_BFV_Basics.cs |");
Console.WriteLine("| 2. Encoders | 2_Encoders.cs |");
Console.WriteLine("| 3. Levels | 3_Levels.cs |");
Console.WriteLine("| 4. CKKS Basics | 4_CKKS_Basics.cs |");
Console.WriteLine("| 5. Rotation | 5_Rotation.cs |");
Console.WriteLine("| 6. Serialization | 6_Serialization.cs |");
Console.WriteLine("| 7. Performance Test | 7_Performance.cs |");
Console.WriteLine("+----------------------------+----------------------------+");
/*
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.
*/
ulong megabytes = MemoryManager.GetPool().AllocByteCount >> 20;
Console.WriteLine("[{0,7} MB] Total allocation from the memory pool", megabytes);
ConsoleKeyInfo key;
do
{
Console.WriteLine();
Console.Write("> Run example (1 ~ 7) or exit (0): ");
key = Console.ReadKey();
Console.WriteLine();
} while (key.KeyChar < '0' || key.KeyChar > '7');
switch (key.Key)
{
case ConsoleKey.D1:
ExampleBFVBasics();
break;
case ConsoleKey.D2:
ExampleEncoders();
break;
case ConsoleKey.D3:
ExampleLevels();
break;
case ConsoleKey.D4:
ExampleCKKSBasics();
break;
case ConsoleKey.D5:
ExampleRotation();
break;
case ConsoleKey.D6:
ExampleSerialization();
break;
case ConsoleKey.D7:
ExamplePerformanceTest();
break;
case ConsoleKey.D0:
return;
default:
Console.WriteLine(" [Beep~~] Invalid option: type 0 ~ 7");
break;
}
/*
We may want to force a garbage collection after each example to accurately show memory pool use.
*/
GC.Collect();
}
}
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<Authors>Microsoft Research</Authors>
<Company>Microsoft Corporation</Company>
<Description>.NET wrapper examples for Microsoft SEAL</Description>
<Copyright>Microsoft Corporation 2020</Copyright>
</PropertyGroup>
<PropertyGroup Condition="'$(Platform)'=='AnyCPU'">
<PlatformTarget>x64</PlatformTarget>
<OutputPath>$(ProjectDir)../../bin/dotnet/$(Configuration)</OutputPath>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="$(ProjectDir)../src/SEALNet.csproj" />
</ItemGroup>
<ItemGroup>
<SEALCBinaryFiles Condition="$([MSBuild]::IsOsPlatform(Windows))" Include="$(ProjectDir)..\..\lib\x64\$(Configuration)\sealc.dll" />
<SEALCBinaryFiles Condition="$([MSBuild]::IsOsPlatform(Linux))" Include="$(ProjectDir)../../lib/libsealc.so.*" />
<SEALCBinaryFiles Condition="$([MSBuild]::IsOsPlatform(OSX))" Include="$(ProjectDir)../../lib/libsealc*.dylib" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Copy SourceFiles="@(SEALCBinaryFiles)" DestinationFolder="$(TargetDir)" />
</Target>
</Project>
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment