SEAL開源庫源碼12
文章目錄
- SEAL開源庫源碼12
- 5_ckks_basics.cpp
-
- 6_rotation.cpp
-
- example_rotation_bfv 函數
- example_rotation_ckks 函數
- example_rotation 函數
- 7_serialization.cpp
-
- 8_performance.cpp
-
- bfv_performance_test 函數
- ckks_performance_test 函數
- bgv_performance_test 函數
- example_bfv_performance_default 函數
- example_bfv_performance_custom 函數
- example_ckks_performance_default 函數
- example_ckks_performance_custom 函數
- example_bgv_performance_default 函數
- example_bgv_performance_custom 函數
- example_performance_test 函數
5_ckks_basics.cpp
example_ckks_basics 函數
void example_ckks_basics()
{
print_example_banner("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.
*/
EncryptionParameters parms(scheme_type::ckks);
/*
We saw in `2_encoders.cpp' 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 coeff_modulus, 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.cpp').
As modulus switching, it removes the last of the primes from coeff_modulus,
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 coeff_modulus.
More precisely, suppose that the scale in a CKKS ciphertext is S, and the
last prime in the current coeff_modulus (for the ciphertext) is P. Rescaling
to the next level changes the scale to S/P, and removes the prime P from the
coeff_modulus, 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 coeff_modulus 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 coeff_modulus. This will
give the highest precision when decrypting;
(2) Choose another 60-bit prime as the last element of coeff_modulus, 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 coeff_modulus is 200 bits total, which is below the bound for our
poly_modulus_degree: CoeffModulus::MaxBitCount(8192) returns 218.
*/
size_t poly_modulus_degree = 8192;
parms.set_poly_modulus_degree(poly_modulus_degree);
parms.set_coeff_modulus(CoeffModulus::Create(poly_modulus_degree, { 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 = pow(2.0, 40);
SEALContext context(parms);
print_parameters(context);
cout << endl;
KeyGenerator keygen(context);
auto secret_key = keygen.secret_key();
PublicKey public_key;
keygen.create_public_key(public_key);
RelinKeys relin_keys;
keygen.create_relin_keys(relin_keys);
GaloisKeys gal_keys;
keygen.create_galois_keys(gal_keys);
Encryptor encryptor(context, public_key);
Evaluator evaluator(context);
Decryptor decryptor(context, secret_key);
CKKSEncoder encoder(context);
size_t slot_count = encoder.slot_count();
cout << "Number of slots: " << slot_count << endl;
vector<double> input;
input.reserve(slot_count);
double curr_point = 0;
double step_size = 1.0 / (static_cast<double>(slot_count) - 1);
for (size_t i = 0; i < slot_count; i++)
{
input.push_back(curr_point);
curr_point += step_size;
}
cout << "Input vector: " << endl;
print_vector(input, 3, 7);
cout << "Evaluating polynomial PI*x^3 + 0.4x + 1 ..." << endl;
/*
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.
*/
Plaintext plain_coeff3, plain_coeff1, plain_coeff0;
encoder.encode(3.14159265, scale, plain_coeff3);
encoder.encode(0.4, scale, plain_coeff1);
encoder.encode(1.0, scale, plain_coeff0);
Plaintext x_plain;
print_line(__LINE__);
cout << "Encode input vectors." << endl;
encoder.encode(input, scale, x_plain);
Ciphertext x1_encrypted;
encryptor.encrypt(x_plain, x1_encrypted);
/*
To compute x^3 we first compute x^2 and relinearize. However, the scale has
now grown to 2^80.
*/
Ciphertext x3_encrypted;
print_line(__LINE__);
cout << "Compute x^2 and relinearize:" << endl;
evaluator.square(x1_encrypted, x3_encrypted);
evaluator.relinearize_inplace(x3_encrypted, relin_keys);
cout << " + Scale of x^2 before rescale: " << log2(x3_encrypted.scale()) << " bits" << endl;
/*
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.
*/
print_line(__LINE__);
cout << "Rescale x^2." << endl;
evaluator.rescale_to_next_inplace(x3_encrypted);
cout << " + Scale of x^2 after rescale: " << log2(x3_encrypted.scale()) << " bits" << endl;
/*
Now x3_encrypted is at a different level than x1_encrypted, which prevents us
from multiplying them to compute x^3. We could simply switch x1_encrypted to
the next parameters in the modulus switching chain. However, since we still
need to multiply the x^3 term with PI (plain_coeff3), we instead compute PI*x
first and multiply that with x^2 to obtain PI*x^3. To this end, we compute
PI*x and rescale it back from scale 2^80 to something close to 2^40.
*/
print_line(__LINE__);
cout << "Compute and rescale PI*x." << endl;
Ciphertext x1_encrypted_coeff3;
evaluator.multiply_plain(x1_encrypted, plain_coeff3, x1_encrypted_coeff3);
cout << " + Scale of PI*x before rescale: " << log2(x1_encrypted_coeff3.scale()) << " bits" << endl;
evaluator.rescale_to_next_inplace(x1_encrypted_coeff3);
cout << " + Scale of PI*x after rescale: " << log2(x1_encrypted_coeff3.scale()) << " bits" << endl;
/*
Since x3_encrypted and x1_encrypted_coeff3 have the same exact scale and use
the same encryption parameters, we can multiply them together. We write the
result to x3_encrypted, 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.
*/
print_line(__LINE__);
cout << "Compute, relinearize, and rescale (PI*x)*x^2." << endl;
evaluator.multiply_inplace(x3_encrypted, x1_encrypted_coeff3);
evaluator.relinearize_inplace(x3_encrypted, relin_keys);
cout << " + Scale of PI*x^3 before rescale: " << log2(x3_encrypted.scale()) << " bits" << endl;
evaluator.rescale_to_next_inplace(x3_encrypted);
cout << " + Scale of PI*x^3 after rescale: " << log2(x3_encrypted.scale()) << " bits" << endl;
/*
Next we compute the degree one term. All this requires is one multiply_plain
with plain_coeff1. We overwrite x1_encrypted with the result.
*/
print_line(__LINE__);
cout << "Compute and rescale 0.4*x." << endl;
evaluator.multiply_plain_inplace(x1_encrypted, plain_coeff1);
cout << " + Scale of 0.4*x before rescale: " << log2(x1_encrypted.scale()) << " bits" << endl;
evaluator.rescale_to_next_inplace(x1_encrypted);
cout << " + Scale of 0.4*x after rescale: " << log2(x1_encrypted.scale()) << " bits" << endl;
/*
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 (parms_id) match. If there
is a mismatch, Evaluator will throw an exception.
*/
cout << endl;
print_line(__LINE__);
cout << "Parameters used by all three terms are different." << endl;
cout << " + Modulus chain index for x3_encrypted: "
<< context.get_context_data(x3_encrypted.parms_id())->chain_index() << endl;
cout << " + Modulus chain index for x1_encrypted: "
<< context.get_context_data(x1_encrypted.parms_id())->chain_index() << endl;
cout << " + Modulus chain index for plain_coeff0: "
<< context.get_context_data(plain_coeff0.parms_id())->chain_index() << endl;
cout << endl;
/*
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/P_2 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.
*/
print_line(__LINE__);
cout << "The exact scales of all three terms are different:" << endl;
ios old_fmt(nullptr);
old_fmt.copyfmt(cout);
cout << fixed << setprecision(10);
cout << " + Exact scale in PI*x^3: " << x3_encrypted.scale() << endl;
cout << " + Exact scale in 0.4*x: " << x1_encrypted.scale() << endl;
cout << " + Exact scale in 1: " << plain_coeff0.scale() << endl;
cout << endl;
cout.copyfmt(old_fmt);
/*
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 multiply_plain
with 0.4*x, and finally rescale. In this case we would need to additionally
make sure to encode 1 with appropriate encryption parameters (parms_id).
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.
*/
print_line(__LINE__);
cout << "Normalize scales to 2^40." << endl;
x3_encrypted.scale() = pow(2.0, 40);
x1_encrypted.scale() = 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.
*/
print_line(__LINE__);
cout << "Normalize encryption parameters to the lowest level." << endl;
parms_id_type last_parms_id = x3_encrypted.parms_id();
evaluator.mod_switch_to_inplace(x1_encrypted, last_parms_id);
evaluator.mod_switch_to_inplace(plain_coeff0, last_parms_id);
/*
All three ciphertexts are now compatible and can be added.
*/
print_line(__LINE__);
cout << "Compute PI*x^3 + 0.4*x + 1." << endl;
Ciphertext encrypted_result;
evaluator.add(x3_encrypted, x1_encrypted, encrypted_result);
evaluator.add_plain_inplace(encrypted_result, plain_coeff0);
/*
First print the true result.
*/
Plaintext plain_result;
print_line(__LINE__);
cout << "Decrypt and decode PI*x^3 + 0.4x + 1." << endl;
cout << " + Expected result:" << endl;
vector<double> true_result;
for (size_t i = 0; i < input.size(); i++)
{
double x = input[i];
true_result.push_back((3.14159265 * x * x + 0.4) * x + 1);
}
print_vector(true_result, 3, 7);
/*
Decrypt, decode, and print the result.
*/
decryptor.decrypt(encrypted_result, plain_result);
vector<double> result;
encoder.decode(plain_result, result);
cout << " + Computed result ...... Correct." << endl;
print_vector(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.
*/
}
6_rotation.cpp
example_rotation_bfv 函數
/*
Both the BFV and BGV schemes (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.
Simply changing `scheme_type::bfv` to `scheme_type::bgv` will make this example work for
the BGV scheme.
*/
void example_rotation_bfv()
{
print_example_banner("Example: Rotation / Rotation in BFV");
EncryptionParameters parms(scheme_type::bfv);
size_t poly_modulus_degree = 8192;
parms.set_poly_modulus_degree(poly_modulus_degree);
parms.set_coeff_modulus(CoeffModulus::BFVDefault(poly_modulus_degree));
parms.set_plain_modulus(PlainModulus::Batching(poly_modulus_degree, 20));
SEALContext context(parms);
print_parameters(context);
cout << endl;
KeyGenerator keygen(context);
SecretKey secret_key = keygen.secret_key();
PublicKey public_key;
keygen.create_public_key(public_key);
RelinKeys relin_keys;
keygen.create_relin_keys(relin_keys);
Encryptor encryptor(context, public_key);
Evaluator evaluator(context);
Decryptor decryptor(context, secret_key);
BatchEncoder batch_encoder(context);
size_t slot_count = batch_encoder.slot_count();
size_t row_size = slot_count / 2;
cout << "Plaintext matrix row size: " << row_size << endl;
vector<uint64_t> pod_matrix(slot_count, 0ULL);
pod_matrix[0] = 0ULL;
pod_matrix[1] = 1ULL;
pod_matrix[2] = 2ULL;
pod_matrix[3] = 3ULL;
pod_matrix[row_size] = 4ULL;
pod_matrix[row_size + 1] = 5ULL;
pod_matrix[row_size + 2] = 6ULL;
pod_matrix[row_size + 3] = 7ULL;
cout << "Input plaintext matrix:" << endl;
print_matrix(pod_matrix, row_size);
/*
First we use BatchEncoder to encode the matrix into a plaintext. We encrypt
the plaintext as usual.
*/
Plaintext plain_matrix;
print_line(__LINE__);
cout << "Encode and encrypt." << endl;
batch_encoder.encode(pod_matrix, plain_matrix);
Ciphertext encrypted_matrix;
encryptor.encrypt(plain_matrix, encrypted_matrix);
cout << " + Noise budget in fresh encryption: " << decryptor.invariant_noise_budget(encrypted_matrix) << " bits"
<< endl;
cout << endl;
/*
Rotations require yet another type of special key called `Galois keys'. These
are easily obtained from the KeyGenerator.
*/
GaloisKeys galois_keys;
keygen.create_galois_keys(galois_keys);
/*
Now rotate both matrix rows 3 steps to the left, decrypt, decode, and print.
*/
print_line(__LINE__);
cout << "Rotate rows 3 steps left." << endl;
evaluator.rotate_rows_inplace(encrypted_matrix, 3, galois_keys);
Plaintext plain_result;
cout << " + Noise budget after rotation: " << decryptor.invariant_noise_budget(encrypted_matrix) << " bits"
<< endl;
cout << " + Decrypt and decode ...... Correct." << endl;
decryptor.decrypt(encrypted_matrix, plain_result);
batch_encoder.decode(plain_result, pod_matrix);
print_matrix(pod_matrix, row_size);
/*
We can also rotate the columns, i.e., swap the rows.
*/
print_line(__LINE__);
cout << "Rotate columns." << endl;
evaluator.rotate_columns_inplace(encrypted_matrix, galois_keys);
cout << " + Noise budget after rotation: " << decryptor.invariant_noise_budget(encrypted_matrix) << " bits"
<< endl;
cout << " + Decrypt and decode ...... Correct." << endl;
decryptor.decrypt(encrypted_matrix, plain_result);
batch_encoder.decode(plain_result, pod_matrix);
print_matrix(pod_matrix, row_size);
/*
Finally, we rotate the rows 4 steps to the right, decrypt, decode, and print.
*/
print_line(__LINE__);
cout << "Rotate rows 4 steps right." << endl;
evaluator.rotate_rows_inplace(encrypted_matrix, -4, galois_keys);
cout << " + Noise budget after rotation: " << decryptor.invariant_noise_budget(encrypted_matrix) << " bits"
<< endl;
cout << " + Decrypt and decode ...... Correct." << endl;
decryptor.decrypt(encrypted_matrix, plain_result);
batch_encoder.decode(plain_result, pod_matrix);
print_matrix(pod_matrix, row_size);
/*
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.
*/
}
example_rotation_ckks 函數
void example_rotation_ckks()
{
print_example_banner("Example: Rotation / Rotation in CKKS");
/*
Rotations in the CKKS scheme work very similarly to rotations in BFV.
*/
EncryptionParameters parms(scheme_type::ckks);
size_t poly_modulus_degree = 8192;
parms.set_poly_modulus_degree(poly_modulus_degree);
parms.set_coeff_modulus(CoeffModulus::Create(poly_modulus_degree, { 40, 40, 40, 40, 40 }));
SEALContext context(parms);
print_parameters(context);
cout << endl;
KeyGenerator keygen(context);
SecretKey secret_key = keygen.secret_key();
PublicKey public_key;
keygen.create_public_key(public_key);
RelinKeys relin_keys;
keygen.create_relin_keys(relin_keys);
GaloisKeys galois_keys;
keygen.create_galois_keys(galois_keys);
Encryptor encryptor(context, public_key);
Evaluator evaluator(context);
Decryptor decryptor(context, secret_key);
CKKSEncoder ckks_encoder(context);
size_t slot_count = ckks_encoder.slot_count();
cout << "Number of slots: " << slot_count << endl;
vector<double> input;
input.reserve(slot_count);
double curr_point = 0;
double step_size = 1.0 / (static_cast<double>(slot_count) - 1);
for (size_t i = 0; i < slot_count; i++, curr_point += step_size)
{
input.push_back(curr_point);
}
cout << "Input vector:" << endl;
print_vector(input, 3, 7);
auto scale = pow(2.0, 50);
print_line(__LINE__);
cout << "Encode and encrypt." << endl;
Plaintext plain;
ckks_encoder.encode(input, scale, plain);
Ciphertext encrypted;
encryptor.encrypt(plain, encrypted);
Ciphertext rotated;
print_line(__LINE__);
cout << "Rotate 2 steps left." << endl;
evaluator.rotate_vector(encrypted, 2, galois_keys, rotated);
cout << " + Decrypt and decode ...... Correct." << endl;
decryptor.decrypt(rotated, plain);
vector<double> result;
ckks_encoder.decode(plain, result);
print_vector(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::complex_conjugate.
This is in fact a kind of rotation, and requires also Galois keys.
*/
}
example_rotation 函數
void example_rotation()
{
print_example_banner("Example: Rotation");
/*
Run all rotation examples.
*/
example_rotation_bfv();
example_rotation_ckks();
}
7_serialization.cpp
example_serialization 函數
/*
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.
*/
void example_serialization()
{
print_example_banner("Example: Serialization");
/*
We require ZLIB or Zstandard support for this example to be available.
*/
#if (!defined(SEAL_USE_ZSTD) && !defined(SEAL_USE_ZLIB))
cout << "Neither ZLIB nor Zstandard support is enabled; this example is not available." << endl;
cout << endl;
return;
#else
/*
We start by briefly discussing the Serializable<T> class template. This is
a wrapper class that can wrap any serializable class, which include:
- EncryptionParameters
- Modulus
- Plaintext and Ciphertext
- SecretKey, PublicKey, RelinKeys, and GaloisKeys
Serializable<T> provides minimal functionality needed to serialize the wrapped
object by simply forwarding the calls to corresponding functions of the wrapped
object of type T. The need for Serializable<T> comes from the fact that many
Microsoft SEAL objects consist of two parts, one of which is pseudorandom data
independent of the other part. Until the object is actually being used, the
pseudorandom part can be instead stored as a seed. We will call objects with
property `seedable'.
For example, GaloisKeys can often be very large in size, but in reality half
of the data is pseudorandom and can be stored as a seed. Since GaloisKeys are
never used by the party that generates them, so it makes sense to expand the
seed at the point deserialization. On the other hand, we cannot allow the user
to accidentally try to use an unexpanded GaloisKeys object, which is prevented
at by ensuring it is always wrapped in a Serializable<GaloisKeys> and can only
be serialized.
Only some Microsoft SEAL objects are seedable. Specifically, they are:
- PublicKey, RelinKeys, and GaloisKeys
- Ciphertext in secret-key mode (from Encryptor::encrypt_symmetric or
Encryptor::encrypt_zero_symmetric)
Importantly, ciphertexts in public-key mode are not seedable. Thus, it may
be beneficial to use Microsoft SEAL in secret-key mode whenever the public
key is not truly needed.
There are a handful of functions that output Serializable<T> objects:
- Encryptor::encrypt (and variants) output Serializable<Ciphertext>
- KeyGenerator::create_... output Serializable<T> for different key types
Note that Encryptor::encrypt is included in the above list, yet it produces
ciphertexts in public-key mode that are not seedable. This is for the sake of
consistency in the API for public-key and secret-key encryption. Functions
that output Serializable<T> objects also have overloads that take a normal
object of type T as a destination parameter, overwriting it. These overloads
can be convenient for local testing where no serialization is needed and the
object needs to be used at the point of construction. Such an object can no
longer be transformed back to a seeded state.
*/
/*
To simulate client-server interaction, we set up a shared C++ stream. In real
use-cases this can be a network buffer, 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.
We feel it is important to remind users that filestream serialization will
always require the ios::binary flag to signal that the serialized data is
binary data and not text. For example, an appropriate output filestream could
be set up as:
ofstream ofs("filename", ios::binary);
In this example we use an std::stringstream, where the ios::binary flag is
not needed. Note that the default constructor of std::stringstream opens the
stream with ios::in | ios::out so both reading and writing will be possible.
*/
stringstream parms_stream;
stringstream data_stream;
stringstream sk_stream;
/*
The server first determines the computation and sets encryption parameters
accordingly.
*/
{
EncryptionParameters parms(scheme_type::ckks);
size_t poly_modulus_degree = 8192;
parms.set_poly_modulus_degree(poly_modulus_degree);
parms.set_coeff_modulus(CoeffModulus::Create(poly_modulus_degree, { 50, 30, 50 }));
/*
Serialization of the encryption parameters to our shared stream is very
simple with the EncryptionParameters::save function.
*/
auto size = parms.save(parms_stream);
/*
The return value of this function is the actual byte count of data written
to the stream.
*/
print_line(__LINE__);
cout << "EncryptionParameters: wrote " << size << " bytes" << endl;
/*
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 compression for serialization by
providing EncryptionParameters::save with the desired compression mode as
in the following examples:
auto size = parms.save(shared_stream, compr_mode_type::none);
auto size = parms.save(shared_stream, compr_mode_type::zlib);
auto size = parms.save(shared_stream, compr_mode_type::zstd);
If Microsoft SEAL is compiled with Zstandard or ZLIB support, the default
is to use one of them. If available, Zstandard is preferred over ZLIB due
to its speed.
Compression can have a substantial impact on the serialized data size,
because ciphertext and key data consists of many uniformly random integers
modulo the coeff_modulus primes. Especially when using CKKS, the primes in
coeff_modulus can be relatively small compared to the 64-bit words used to
store the ciphertext and key data internally. Serialization writes full
64-bit words to the destination buffer or stream, possibly leaving in many
zero bytes corresponding to the high-order bytes of the 64-bit words. One
convenient way to get rid of these zeros is to apply a general-purpose
compression algorithm on the encrypted data. The compression rate can be
significant (up to 50-60%) when using CKKS with small primes.
*/
/*
It is also possible to serialize data directly to a buffer. For this, one
needs to know an upper bound for the required buffer size, which can be
obtained using the EncryptionParameters::save_size function. This function
also accepts the desired compression mode, or uses the default option
otherwise.
In more detail, the output of EncryptionParameters::save_size is as follows:
- Exact buffer size required for compr_mode_type::none;
- Upper bound on the size required for compr_mode_type::zlib or
compr_mode_type::zstd.
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, however, it is impossible to know ahead of
time the exact size of the compressed data. If compression is not used,
then the size is exactly determined by the encryption parameters.
*/
print_line(__LINE__);
cout << "EncryptionParameters: data size upper bound (compr_mode_type::none): "
<< parms.save_size(compr_mode_type::none) << endl;
cout << " "
<< "EncryptionParameters: data size upper bound (compression): "
<< parms.save_size(/* Serialization::compr_mode_default */) << endl;
/*
As an example, we now serialize the encryption parameters to a fixed size
buffer.
*/
vector<seal_byte> byte_buffer(static_cast<size_t>(parms.save_size()));
parms.save(reinterpret_cast<seal_byte *>(byte_buffer.data()), byte_buffer.size());
/*
To illustrate deserialization, we load back the encryption parameters
from our buffer into another instance of EncryptionParameters. Note how
EncryptionParameters::load in this case requires the size of the buffer,
which is larger than the actual data size of the compressed parameters.
The serialization format includes the true size of the data and the size
of the buffer is only used for a sanity check.
*/
EncryptionParameters parms2;
parms2.load(reinterpret_cast<const seal_byte *>(byte_buffer.data()), byte_buffer.size());
/*
We can check that the saved and loaded encryption parameters indeed match.
*/
print_line(__LINE__);
cout << "EncryptionParameters: parms == parms2: " << boolalpha << (parms == parms2) << endl;
/*
The functions presented and used here exist for all Microsoft SEAL objects
that are meaningful to serialize. However, it is important to understand
more advanced techniques that can be used for further compressing the data
size. We will present these techniques below.
*/
}
/*
Client starts by loading the encryption parameters, sets up the SEALContext,
and creates the required keys.
*/
{
EncryptionParameters parms;
parms.load(parms_stream);
/*
Seek the parms_stream get head back to beginning of the stream because we
will use the same stream to read the parameters repeatedly.
*/
parms_stream.seekg(0, parms_stream.beg);
SEALContext context(parms);
KeyGenerator keygen(context);
auto sk = keygen.secret_key();
PublicKey pk;
keygen.create_public_key(pk);
/*
We need to save the secret key so we can decrypt later.
*/
sk.save(sk_stream);
/*
As in previous examples, in this example we will encrypt in public-key
mode. If we want to send a public key over the network, we should instead
have created it as a seeded object as follows:
Serializable<PublicKey> pk = keygen.create_public_key();
In this example we will also use relinearization keys. These we will
absolutely want to create as seeded objects to minimize communication
cost, unlike in prior examples.
*/
Serializable<RelinKeys> rlk = keygen.create_relin_keys();
/*
To demonstrate the significant space saving from this method, we will
create another set of relinearization keys, this time fully expanded.
*/
RelinKeys rlk_big;
keygen.create_relin_keys(rlk_big);
/*
We serialize both relinearization keys to demonstrate the concrete size
difference. If compressed serialization is used, the compression rate
will be the same in both cases. We omit specifying the compression mode
to use the default, as determined by the Microsoft SEAL build system.
*/
auto size_rlk = rlk.save(data_stream);
auto size_rlk_big = rlk_big.save(data_stream);
print_line(__LINE__);
cout << "Serializable<RelinKeys>: wrote " << size_rlk << " bytes" << endl;
cout << " "
<< "RelinKeys wrote " << size_rlk_big << " bytes" << endl;
/*
Seek back in data_stream to where rlk data ended, i.e., size_rlk_big
bytes backwards from current position.
*/
data_stream.seekp(-size_rlk_big, data_stream.cur);
/*
Next set up the CKKSEncoder and Encryptor, and encrypt some numbers.
*/
double scale = pow(2.0, 30);
CKKSEncoder encoder(context);
Plaintext plain1, plain2;
encoder.encode(2.3, scale, plain1);
encoder.encode(4.5, scale, plain2);
Encryptor encryptor(context, pk);
/*
The client will not compute on ciphertexts that it creates, so it can
just as well create Serializable<Ciphertext> objects. In fact, we do
not even need to name those objects and instead immediately call
Serializable<Ciphertext>::save.
*/
auto size_encrypted1 = encryptor.encrypt(plain1).save(data_stream);
/*
As we discussed in the beginning of this example, ciphertexts can be
created in a seeded state in secret-key mode, providing a huge reduction
in the data size upon serialization. To do this, we need to provide the
Encryptor with the secret key in its constructor, or at a later point
with the Encryptor::set_secret_key function, and use the
Encryptor::encrypt_symmetric function to encrypt.
*/
encryptor.set_secret_key(sk);
auto size_sym_encrypted2 = encryptor.encrypt_symmetric(plain2).save(data_stream);
/*
The size reduction is substantial.
*/
print_line(__LINE__);
cout << "Serializable<Ciphertext> (public-key): wrote " << size_encrypted1 << " bytes" << endl;
cout << " "
<< "Serializable<Ciphertext> (seeded secret-key): wrote " << size_sym_encrypted2 << " bytes" << endl;
/*
We have seen how creating seeded objects can result in huge space
savings compared to creating unseeded objects. This is particularly
important when creating Galois keys, which can be very large. We have
seen how secret-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 below.
*/
}
/*
The server can now compute on the encrypted data. We will recreate the
SEALContext and set up an Evaluator here.
*/
{
EncryptionParameters parms;
parms.load(parms_stream);
parms_stream.seekg(0, parms_stream.beg);
SEALContext context(parms);
Evaluator evaluator(context);
/*
Next we need to load relinearization keys and the ciphertexts from our
data_stream.
*/
RelinKeys rlk;
Ciphertext encrypted1, encrypted2;
/*
Deserialization is as easy as serialization.
*/
rlk.load(context, data_stream);
encrypted1.load(context, data_stream);
encrypted2.load(context, data_stream);
/*
Compute the product, rescale, and relinearize.
*/
Ciphertext encrypted_prod;
evaluator.multiply(encrypted1, encrypted2, encrypted_prod);
evaluator.relinearize_inplace(encrypted_prod, rlk);
evaluator.rescale_to_next_inplace(encrypted_prod);
/*
we use data_stream to communicate encrypted_prod back to the client.
there is no way to save the encrypted_prod as a seeded object: only
freshly encrypted secret-key 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.
*/
data_stream.seekp(0, parms_stream.beg);
data_stream.seekg(0, parms_stream.beg);
auto size_encrypted_prod = encrypted_prod.save(data_stream);
print_line(__LINE__);
cout << "Ciphertext (secret-key): wrote " << size_encrypted_prod << " bytes" << endl;
}
/*
In the final step the client decrypts the result.
*/
{
EncryptionParameters parms;
parms.load(parms_stream);
parms_stream.seekg(0, parms_stream.beg);
SEALContext context(parms);
/*
Load back the secret key from sk_stream.
*/
SecretKey sk;
sk.load(context, sk_stream);
Decryptor decryptor(context, sk);
CKKSEncoder encoder(context);
Ciphertext encrypted_result;
encrypted_result.load(context, data_stream);
Plaintext plain_result;
decryptor.decrypt(encrypted_result, plain_result);
vector<double> result;
encoder.decode(plain_result, result);
print_line(__LINE__);
cout << "Decrypt the loaded ciphertext" << endl;
cout << " + Expected result:" << endl;
vector<double> true_result(encoder.slot_count(), 2.3 * 4.5);
print_vector(true_result, 3, 7);
cout << " + Computed result ...... Correct." << endl;
print_vector(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 native/src/seal/serialization.h, 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::seal_magic)
[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.
*/
Plaintext pt("1x^2 + 3");
stringstream stream;
auto data_size = pt.save(stream);
/*
We can now load just the SEALHeader back from the stream as follows.
*/
Serialization::SEALHeader header;
Serialization::LoadHeader(stream, header);
/*
Now confirm that the size of data written to stream matches with what is
indicated by the SEALHeader.
*/
print_line(__LINE__);
cout << "Size written to stream: " << data_size << " bytes" << endl;
cout << " "
<< "Size indicated in SEALHeader: " << header.size << " bytes" << endl;
cout << endl;
#endif
}
8_performance.cpp
bfv_performance_test 函數
void bfv_performance_test(SEALContext context)
{
chrono::high_resolution_clock::time_point time_start, time_end;
print_parameters(context);
cout << endl;
auto &parms = context.first_context_data()->parms();
auto &plain_modulus = parms.plain_modulus();
size_t poly_modulus_degree = parms.poly_modulus_degree();
cout << "Generating secret/public keys: ";
KeyGenerator keygen(context);
cout << "Done" << endl;
auto secret_key = keygen.secret_key();
PublicKey public_key;
keygen.create_public_key(public_key);
RelinKeys relin_keys;
GaloisKeys gal_keys;
chrono::microseconds time_diff;
if (context.using_keyswitching())
{
/*
Generate relinearization keys.
*/
cout << "Generating relinearization keys: ";
time_start = chrono::high_resolution_clock::now();
keygen.create_relin_keys(relin_keys);
time_end = chrono::high_resolution_clock::now();
time_diff = chrono::duration_cast<chrono::microseconds>(time_end - time_start);
cout << "Done [" << time_diff.count() << " microseconds]" << endl;
if (!context.key_context_data()->qualifiers().using_batching)
{
cout << "Given encryption parameters do not support batching." << endl;
return;
}
/*
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.
*/
cout << "Generating Galois keys: ";
time_start = chrono::high_resolution_clock::now();
keygen.create_galois_keys(gal_keys);
time_end = chrono::high_resolution_clock::now();
time_diff = chrono::duration_cast<chrono::microseconds>(time_end - time_start);
cout << "Done [" << time_diff.count() << " microseconds]" << endl;
}
Encryptor encryptor(context, public_key);
Decryptor decryptor(context, secret_key);
Evaluator evaluator(context);
BatchEncoder batch_encoder(context);
/*
These will hold the total times used by each operation.
*/
chrono::microseconds time_batch_sum(0);
chrono::microseconds time_unbatch_sum(0);
chrono::microseconds time_encrypt_sum(0);
chrono::microseconds time_decrypt_sum(0);
chrono::microseconds time_add_sum(0);
chrono::microseconds time_multiply_sum(0);
chrono::microseconds time_multiply_plain_sum(0);
chrono::microseconds time_square_sum(0);
chrono::microseconds time_relinearize_sum(0);
chrono::microseconds time_rotate_rows_one_step_sum(0);
chrono::microseconds time_rotate_rows_random_sum(0);
chrono::microseconds time_rotate_columns_sum(0);
chrono::microseconds time_serialize_sum(0);
#ifdef SEAL_USE_ZLIB
chrono::microseconds time_serialize_zlib_sum(0);
#endif
#ifdef SEAL_USE_ZSTD
chrono::microseconds time_serialize_zstd_sum(0);
#endif
/*
How many times to run the test?
*/
long long count = 10;
/*
Populate a vector of values to batch.
*/
size_t slot_count = batch_encoder.slot_count();
vector<uint64_t> pod_vector;
random_device rd;
for (size_t i = 0; i < slot_count; i++)
{
pod_vector.push_back(plain_modulus.reduce(rd()));
}
cout << "Running tests ";
for (size_t i = 0; i < static_cast<size_t>(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.
*/
Plaintext plain(poly_modulus_degree, 0);
Plaintext plain1(poly_modulus_degree, 0);
Plaintext plain2(poly_modulus_degree, 0);
time_start = chrono::high_resolution_clock::now();
batch_encoder.encode(pod_vector, plain);
time_end = chrono::high_resolution_clock::now();
time_batch_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
/*
[Unbatching]
We unbatch what we just batched.
*/
vector<uint64_t> pod_vector2(slot_count);
time_start = chrono::high_resolution_clock::now();
batch_encoder.decode(plain, pod_vector2);
time_end = chrono::high_resolution_clock::now();
time_unbatch_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
if (pod_vector2 != pod_vector)
{
throw runtime_error("Batch/unbatch failed. Something is wrong.");
}
/*
[Encryption]
We make sure our ciphertext is already allocated and large enough
to hold the encryption with these encryption parameters. We encrypt
our random batched matrix here.
*/
Ciphertext encrypted(context);
time_start = chrono::high_resolution_clock::now();
encryptor.encrypt(plain, encrypted);
time_end = chrono::high_resolution_clock::now();
time_encrypt_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
/*
[Decryption]
We decrypt what we just encrypted.
*/
time_start = chrono::high_resolution_clock::now();
decryptor.decrypt(encrypted, plain2);
time_end = chrono::high_resolution_clock::now();
time_decrypt_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
if (plain2 != plain)
{
throw runtime_error("Encrypt/decrypt failed. Something is wrong.");
}
/*
[Add]
We create two ciphertexts and perform a few additions with them.
*/
Ciphertext encrypted1(context);
batch_encoder.encode(vector<uint64_t>(slot_count, i), plain1);
encryptor.encrypt(plain1, encrypted1);
Ciphertext encrypted2(context);
batch_encoder.encode(vector<uint64_t>(slot_count, i + 1), plain2);
encryptor.encrypt(plain2, encrypted2);
time_start = chrono::high_resolution_clock::now();
evaluator.add_inplace(encrypted1, encrypted1);
evaluator.add_inplace(encrypted2, encrypted2);
evaluator.add_inplace(encrypted1, encrypted2);
time_end = chrono::high_resolution_clock::now();
time_add_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
/*
[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);
time_start = chrono::high_resolution_clock::now();
evaluator.multiply_inplace(encrypted1, encrypted2);
time_end = chrono::high_resolution_clock::now();
time_multiply_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
/*
[Multiply Plain]
We multiply a ciphertext with a random plaintext. Recall that
multiply_plain does not change the size of the ciphertext so we use
encrypted2 here.
*/
time_start = chrono::high_resolution_clock::now();
evaluator.multiply_plain_inplace(encrypted2, plain);
time_end = chrono::high_resolution_clock::now();
time_multiply_plain_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
/*
[Square]
We continue to use encrypted2. Now we square it; this should be
faster than generic homomorphic multiplication.
*/
time_start = chrono::high_resolution_clock::now();
evaluator.square_inplace(encrypted2);
time_end = chrono::high_resolution_clock::now();
time_square_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
if (context.using_keyswitching())
{
/*
[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.
*/
time_start = chrono::high_resolution_clock::now();
evaluator.relinearize_inplace(encrypted1, relin_keys);
time_end = chrono::high_resolution_clock::now();
time_relinearize_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
/*
[Rotate Rows One Step]
We rotate matrix rows by one step left and measure the time.
*/
time_start = chrono::high_resolution_clock::now();
evaluator.rotate_rows_inplace(encrypted, 1, gal_keys);
evaluator.rotate_rows_inplace(encrypted, -1, gal_keys);
time_end = chrono::high_resolution_clock::now();
time_rotate_rows_one_step_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
;
/*
[Rotate Rows Random]
We rotate matrix rows by a random number of steps. This is much more
expensive than rotating by just one step.
*/
size_t row_size = batch_encoder.slot_count() / 2;
// row_size is always a power of 2
int random_rotation = static_cast<int>(rd() & (row_size - 1));
time_start = chrono::high_resolution_clock::now();
evaluator.rotate_rows_inplace(encrypted, random_rotation, gal_keys);
time_end = chrono::high_resolution_clock::now();
time_rotate_rows_random_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
/*
[Rotate Columns]
Nothing surprising here.
*/
time_start = chrono::high_resolution_clock::now();
evaluator.rotate_columns_inplace(encrypted, gal_keys);
time_end = chrono::high_resolution_clock::now();
time_rotate_columns_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
}
/*
[Serialize Ciphertext]
*/
size_t buf_size = static_cast<size_t>(encrypted.save_size(compr_mode_type::none));
vector<seal_byte> buf(buf_size);
time_start = chrono::high_resolution_clock::now();
encrypted.save(buf.data(), buf_size, compr_mode_type::none);
time_end = chrono::high_resolution_clock::now();
time_serialize_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
#ifdef SEAL_USE_ZLIB
/*
[Serialize Ciphertext (ZLIB)]
*/
buf_size = static_cast<size_t>(encrypted.save_size(compr_mode_type::zlib));
buf.resize(buf_size);
time_start = chrono::high_resolution_clock::now();
encrypted.save(buf.data(), buf_size, compr_mode_type::zlib);
time_end = chrono::high_resolution_clock::now();
time_serialize_zlib_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
#endif
#ifdef SEAL_USE_ZSTD
/*
[Serialize Ciphertext (Zstandard)]
*/
buf_size = static_cast<size_t>(encrypted.save_size(compr_mode_type::zstd));
buf.resize(buf_size);
time_start = chrono::high_resolution_clock::now();
encrypted.save(buf.data(), buf_size, compr_mode_type::zstd);
time_end = chrono::high_resolution_clock::now();
time_serialize_zstd_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
#endif
/*
Print a dot to indicate progress.
*/
cout << ".";
cout.flush();
}
cout << " Done" << endl << endl;
cout.flush();
auto avg_batch = time_batch_sum.count() / count;
auto avg_unbatch = time_unbatch_sum.count() / count;
auto avg_encrypt = time_encrypt_sum.count() / count;
auto avg_decrypt = time_decrypt_sum.count() / count;
auto avg_add = time_add_sum.count() / (3 * count);
auto avg_multiply = time_multiply_sum.count() / count;
auto avg_multiply_plain = time_multiply_plain_sum.count() / count;
auto avg_square = time_square_sum.count() / count;
auto avg_relinearize = time_relinearize_sum.count() / count;
auto avg_rotate_rows_one_step = time_rotate_rows_one_step_sum.count() / (2 * count);
auto avg_rotate_rows_random = time_rotate_rows_random_sum.count() / count;
auto avg_rotate_columns = time_rotate_columns_sum.count() / count;
auto avg_serialize = time_serialize_sum.count() / count;
#ifdef SEAL_USE_ZLIB
auto avg_serialize_zlib = time_serialize_zlib_sum.count() / count;
#endif
#ifdef SEAL_USE_ZSTD
auto avg_serialize_zstd = time_serialize_zstd_sum.count() / count;
#endif
cout << "Average batch: " << avg_batch << " microseconds" << endl;
cout << "Average unbatch: " << avg_unbatch << " microseconds" << endl;
cout << "Average encrypt: " << avg_encrypt << " microseconds" << endl;
cout << "Average decrypt: " << avg_decrypt << " microseconds" << endl;
cout << "Average add: " << avg_add << " microseconds" << endl;
cout << "Average multiply: " << avg_multiply << " microseconds" << endl;
cout << "Average multiply plain: " << avg_multiply_plain << " microseconds" << endl;
cout << "Average square: " << avg_square << " microseconds" << endl;
if (context.using_keyswitching())
{
cout << "Average relinearize: " << avg_relinearize << " microseconds" << endl;
cout << "Average rotate rows one step: " << avg_rotate_rows_one_step << " microseconds" << endl;
cout << "Average rotate rows random: " << avg_rotate_rows_random << " microseconds" << endl;
cout << "Average rotate columns: " << avg_rotate_columns << " microseconds" << endl;
}
cout << "Average serialize ciphertext: " << avg_serialize << " microseconds" << endl;
#ifdef SEAL_USE_ZLIB
cout << "Average compressed (ZLIB) serialize ciphertext: " << avg_serialize_zlib << " microseconds" << endl;
#endif
#ifdef SEAL_USE_ZSTD
cout << "Average compressed (Zstandard) serialize ciphertext: " << avg_serialize_zstd << " microseconds" << endl;
#endif
cout.flush();
}
ckks_performance_test 函數
void ckks_performance_test(SEALContext context)
{
chrono::high_resolution_clock::time_point time_start, time_end;
print_parameters(context);
cout << endl;
auto &parms = context.first_context_data()->parms();
size_t poly_modulus_degree = parms.poly_modulus_degree();
cout << "Generating secret/public keys: ";
KeyGenerator keygen(context);
cout << "Done" << endl;
auto secret_key = keygen.secret_key();
PublicKey public_key;
keygen.create_public_key(public_key);
RelinKeys relin_keys;
GaloisKeys gal_keys;
chrono::microseconds time_diff;
if (context.using_keyswitching())
{
cout << "Generating relinearization keys: ";
time_start = chrono::high_resolution_clock::now();
keygen.create_relin_keys(relin_keys);
time_end = chrono::high_resolution_clock::now();
time_diff = chrono::duration_cast<chrono::microseconds>(time_end - time_start);
cout << "Done [" << time_diff.count() << " microseconds]" << endl;
if (!context.first_context_data()->qualifiers().using_batching)
{
cout << "Given encryption parameters do not support batching." << endl;
return;
}
cout << "Generating Galois keys: ";
time_start = chrono::high_resolution_clock::now();
keygen.create_galois_keys(gal_keys);
time_end = chrono::high_resolution_clock::now();
time_diff = chrono::duration_cast<chrono::microseconds>(time_end - time_start);
cout << "Done [" << time_diff.count() << " microseconds]" << endl;
}
Encryptor encryptor(context, public_key);
Decryptor decryptor(context, secret_key);
Evaluator evaluator(context);
CKKSEncoder ckks_encoder(context);
chrono::microseconds time_encode_sum(0);
chrono::microseconds time_decode_sum(0);
chrono::microseconds time_encrypt_sum(0);
chrono::microseconds time_decrypt_sum(0);
chrono::microseconds time_add_sum(0);
chrono::microseconds time_multiply_sum(0);
chrono::microseconds time_multiply_plain_sum(0);
chrono::microseconds time_square_sum(0);
chrono::microseconds time_relinearize_sum(0);
chrono::microseconds time_rescale_sum(0);
chrono::microseconds time_rotate_one_step_sum(0);
chrono::microseconds time_rotate_random_sum(0);
chrono::microseconds time_conjugate_sum(0);
chrono::microseconds time_serialize_sum(0);
#ifdef SEAL_USE_ZLIB
chrono::microseconds time_serialize_zlib_sum(0);
#endif
#ifdef SEAL_USE_ZSTD
chrono::microseconds time_serialize_zstd_sum(0);
#endif
/*
How many times to run the test?
*/
long long count = 10;
/*
Populate a vector of floating-point values to batch.
*/
vector<double> pod_vector;
random_device rd;
for (size_t i = 0; i < ckks_encoder.slot_count(); i++)
{
pod_vector.push_back(1.001 * static_cast<double>(i));
}
cout << "Running tests ";
for (long long i = 0; i < count; i++)
{
/*
[Encoding]
For scale we use the square root of the last coeff_modulus prime
from parms.
*/
Plaintext plain(parms.poly_modulus_degree() * parms.coeff_modulus().size(), 0);
/*
*/
double scale = sqrt(static_cast<double>(parms.coeff_modulus().back().value()));
time_start = chrono::high_resolution_clock::now();
ckks_encoder.encode(pod_vector, scale, plain);
time_end = chrono::high_resolution_clock::now();
time_encode_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
/*
[Decoding]
*/
vector<double> pod_vector2(ckks_encoder.slot_count());
time_start = chrono::high_resolution_clock::now();
ckks_encoder.decode(plain, pod_vector2);
time_end = chrono::high_resolution_clock::now();
time_decode_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
/*
[Encryption]
*/
Ciphertext encrypted(context);
time_start = chrono::high_resolution_clock::now();
encryptor.encrypt(plain, encrypted);
time_end = chrono::high_resolution_clock::now();
time_encrypt_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
/*
[Decryption]
*/
Plaintext plain2(poly_modulus_degree, 0);
time_start = chrono::high_resolution_clock::now();
decryptor.decrypt(encrypted, plain2);
time_end = chrono::high_resolution_clock::now();
time_decrypt_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
/*
[Add]
*/
Ciphertext encrypted1(context);
ckks_encoder.encode(i + 1, plain);
encryptor.encrypt(plain, encrypted1);
Ciphertext encrypted2(context);
ckks_encoder.encode(i + 1, plain2);
encryptor.encrypt(plain2, encrypted2);
time_start = chrono::high_resolution_clock::now();
evaluator.add_inplace(encrypted1, encrypted1);
evaluator.add_inplace(encrypted2, encrypted2);
evaluator.add_inplace(encrypted1, encrypted2);
time_end = chrono::high_resolution_clock::now();
time_add_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
/*
[Multiply]
*/
encrypted1.reserve(3);
time_start = chrono::high_resolution_clock::now();
evaluator.multiply_inplace(encrypted1, encrypted2);
time_end = chrono::high_resolution_clock::now();
time_multiply_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
/*
[Multiply Plain]
*/
time_start = chrono::high_resolution_clock::now();
evaluator.multiply_plain_inplace(encrypted2, plain);
time_end = chrono::high_resolution_clock::now();
time_multiply_plain_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
/*
[Square]
*/
time_start = chrono::high_resolution_clock::now();
evaluator.square_inplace(encrypted2);
time_end = chrono::high_resolution_clock::now();
time_square_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
if (context.using_keyswitching())
{
/*
[Relinearize]
*/
time_start = chrono::high_resolution_clock::now();
evaluator.relinearize_inplace(encrypted1, relin_keys);
time_end = chrono::high_resolution_clock::now();
time_relinearize_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
/*
[Rescale]
*/
time_start = chrono::high_resolution_clock::now();
evaluator.rescale_to_next_inplace(encrypted1);
time_end = chrono::high_resolution_clock::now();
time_rescale_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
/*
[Rotate Vector]
*/
time_start = chrono::high_resolution_clock::now();
evaluator.rotate_vector_inplace(encrypted, 1, gal_keys);
evaluator.rotate_vector_inplace(encrypted, -1, gal_keys);
time_end = chrono::high_resolution_clock::now();
time_rotate_one_step_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
/*
[Rotate Vector Random]
*/
// ckks_encoder.slot_count() is always a power of 2.
int random_rotation = static_cast<int>(rd() & (ckks_encoder.slot_count() - 1));
time_start = chrono::high_resolution_clock::now();
evaluator.rotate_vector_inplace(encrypted, random_rotation, gal_keys);
time_end = chrono::high_resolution_clock::now();
time_rotate_random_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
/*
[Complex Conjugate]
*/
time_start = chrono::high_resolution_clock::now();
evaluator.complex_conjugate_inplace(encrypted, gal_keys);
time_end = chrono::high_resolution_clock::now();
time_conjugate_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
}
/*
[Serialize Ciphertext]
*/
size_t buf_size = static_cast<size_t>(encrypted.save_size(compr_mode_type::none));
vector<seal_byte> buf(buf_size);
time_start = chrono::high_resolution_clock::now();
encrypted.save(buf.data(), buf_size, compr_mode_type::none);
time_end = chrono::high_resolution_clock::now();
time_serialize_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
#ifdef SEAL_USE_ZLIB
/*
[Serialize Ciphertext (ZLIB)]
*/
buf_size = static_cast<size_t>(encrypted.save_size(compr_mode_type::zlib));
buf.resize(buf_size);
time_start = chrono::high_resolution_clock::now();
encrypted.save(buf.data(), buf_size, compr_mode_type::zlib);
time_end = chrono::high_resolution_clock::now();
time_serialize_zlib_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
#endif
#ifdef SEAL_USE_ZSTD
/*
[Serialize Ciphertext (Zstandard)]
*/
buf_size = static_cast<size_t>(encrypted.save_size(compr_mode_type::zstd));
buf.resize(buf_size);
time_start = chrono::high_resolution_clock::now();
encrypted.save(buf.data(), buf_size, compr_mode_type::zstd);
time_end = chrono::high_resolution_clock::now();
time_serialize_zstd_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
#endif
/*
Print a dot to indicate progress.
*/
cout << ".";
cout.flush();
}
cout << " Done" << endl << endl;
cout.flush();
auto avg_encode = time_encode_sum.count() / count;
auto avg_decode = time_decode_sum.count() / count;
auto avg_encrypt = time_encrypt_sum.count() / count;
auto avg_decrypt = time_decrypt_sum.count() / count;
auto avg_add = time_add_sum.count() / (3 * count);
auto avg_multiply = time_multiply_sum.count() / count;
auto avg_multiply_plain = time_multiply_plain_sum.count() / count;
auto avg_square = time_square_sum.count() / count;
auto avg_relinearize = time_relinearize_sum.count() / count;
auto avg_rescale = time_rescale_sum.count() / count;
auto avg_rotate_one_step = time_rotate_one_step_sum.count() / (2 * count);
auto avg_rotate_random = time_rotate_random_sum.count() / count;
auto avg_conjugate = time_conjugate_sum.count() / count;
auto avg_serialize = time_serialize_sum.count() / count;
#ifdef SEAL_USE_ZLIB
auto avg_serialize_zlib = time_serialize_zlib_sum.count() / count;
#endif
#ifdef SEAL_USE_ZSTD
auto avg_serialize_zstd = time_serialize_zstd_sum.count() / count;
#endif
cout << "Average encode: " << avg_encode << " microseconds" << endl;
cout << "Average decode: " << avg_decode << " microseconds" << endl;
cout << "Average encrypt: " << avg_encrypt << " microseconds" << endl;
cout << "Average decrypt: " << avg_decrypt << " microseconds" << endl;
cout << "Average add: " << avg_add << " microseconds" << endl;
cout << "Average multiply: " << avg_multiply << " microseconds" << endl;
cout << "Average multiply plain: " << avg_multiply_plain << " microseconds" << endl;
cout << "Average square: " << avg_square << " microseconds" << endl;
if (context.using_keyswitching())
{
cout << "Average relinearize: " << avg_relinearize << " microseconds" << endl;
cout << "Average rescale: " << avg_rescale << " microseconds" << endl;
cout << "Average rotate vector one step: " << avg_rotate_one_step << " microseconds" << endl;
cout << "Average rotate vector random: " << avg_rotate_random << " microseconds" << endl;
cout << "Average complex conjugate: " << avg_conjugate << " microseconds" << endl;
}
cout << "Average serialize ciphertext: " << avg_serialize << " microseconds" << endl;
#ifdef SEAL_USE_ZLIB
cout << "Average compressed (ZLIB) serialize ciphertext: " << avg_serialize_zlib << " microseconds" << endl;
#endif
#ifdef SEAL_USE_ZSTD
cout << "Average compressed (Zstandard) serialize ciphertext: " << avg_serialize_zstd << " microseconds" << endl;
#endif
cout.flush();
}
bgv_performance_test 函數
void bgv_performance_test(SEALContext context)
{
chrono::high_resolution_clock::time_point time_start, time_end;
print_parameters(context);
cout << endl;
auto &parms = context.first_context_data()->parms();
auto &plain_modulus = parms.plain_modulus();
size_t poly_modulus_degree = parms.poly_modulus_degree();
cout << "Generating secret/public keys: ";
KeyGenerator keygen(context);
cout << "Done" << endl;
auto secret_key = keygen.secret_key();
PublicKey public_key;
keygen.create_public_key(public_key);
RelinKeys relin_keys;
GaloisKeys gal_keys;
chrono::microseconds time_diff;
if (context.using_keyswitching())
{
/*
Generate relinearization keys.
*/
cout << "Generating relinearization keys: ";
time_start = chrono::high_resolution_clock::now();
keygen.create_relin_keys(relin_keys);
time_end = chrono::high_resolution_clock::now();
time_diff = chrono::duration_cast<chrono::microseconds>(time_end - time_start);
cout << "Done [" << time_diff.count() << " microseconds]" << endl;
if (!context.key_context_data()->qualifiers().using_batching)
{
cout << "Given encryption parameters do not support batching." << endl;
return;
}
/*
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.
*/
cout << "Generating Galois keys: ";
time_start = chrono::high_resolution_clock::now();
keygen.create_galois_keys(gal_keys);
time_end = chrono::high_resolution_clock::now();
time_diff = chrono::duration_cast<chrono::microseconds>(time_end - time_start);
cout << "Done [" << time_diff.count() << " microseconds]" << endl;
}
Encryptor encryptor(context, public_key);
Decryptor decryptor(context, secret_key);
Evaluator evaluator(context);
BatchEncoder batch_encoder(context);
/*
These will hold the total times used by each operation.
*/
chrono::microseconds time_batch_sum(0);
chrono::microseconds time_unbatch_sum(0);
chrono::microseconds time_encrypt_sum(0);
chrono::microseconds time_decrypt_sum(0);
chrono::microseconds time_add_sum(0);
chrono::microseconds time_multiply_sum(0);
chrono::microseconds time_multiply_plain_sum(0);
chrono::microseconds time_square_sum(0);
chrono::microseconds time_relinearize_sum(0);
chrono::microseconds time_rotate_rows_one_step_sum(0);
chrono::microseconds time_rotate_rows_random_sum(0);
chrono::microseconds time_rotate_columns_sum(0);
chrono::microseconds time_serialize_sum(0);
#ifdef SEAL_USE_ZLIB
chrono::microseconds time_serialize_zlib_sum(0);
#endif
#ifdef SEAL_USE_ZSTD
chrono::microseconds time_serialize_zstd_sum(0);
#endif
/*
How many times to run the test?
*/
long long count = 10;
/*
Populate a vector of values to batch.
*/
size_t slot_count = batch_encoder.slot_count();
vector<uint64_t> pod_vector;
random_device rd;
for (size_t i = 0; i < slot_count; i++)
{
pod_vector.push_back(plain_modulus.reduce(rd()));
}
cout << "Running tests ";
for (size_t i = 0; i < static_cast<size_t>(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.
*/
Plaintext plain(poly_modulus_degree, 0);
Plaintext plain1(poly_modulus_degree, 0);
Plaintext plain2(poly_modulus_degree, 0);
time_start = chrono::high_resolution_clock::now();
batch_encoder.encode(pod_vector, plain);
time_end = chrono::high_resolution_clock::now();
time_batch_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
/*
[Unbatching]
We unbatch what we just batched.
*/
vector<uint64_t> pod_vector2(slot_count);
time_start = chrono::high_resolution_clock::now();
batch_encoder.decode(plain, pod_vector2);
time_end = chrono::high_resolution_clock::now();
time_unbatch_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
if (pod_vector2 != pod_vector)
{
throw runtime_error("Batch/unbatch failed. Something is wrong.");
}
/*
[Encryption]
We make sure our ciphertext is already allocated and large enough
to hold the encryption with these encryption parameters. We encrypt
our random batched matrix here.
*/
Ciphertext encrypted(context);
time_start = chrono::high_resolution_clock::now();
encryptor.encrypt(plain, encrypted);
time_end = chrono::high_resolution_clock::now();
time_encrypt_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
/*
[Decryption]
We decrypt what we just encrypted.
*/
time_start = chrono::high_resolution_clock::now();
decryptor.decrypt(encrypted, plain2);
time_end = chrono::high_resolution_clock::now();
time_decrypt_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
if (plain2 != plain)
{
throw runtime_error("Encrypt/decrypt failed. Something is wrong.");
}
/*
[Add]
We create two ciphertexts and perform a few additions with them.
*/
Ciphertext encrypted1(context);
batch_encoder.encode(vector<uint64_t>(slot_count, i), plain1);
encryptor.encrypt(plain1, encrypted1);
Ciphertext encrypted2(context);
batch_encoder.encode(vector<uint64_t>(slot_count, i + 1), plain2);
encryptor.encrypt(plain2, encrypted2);
time_start = chrono::high_resolution_clock::now();
evaluator.add_inplace(encrypted1, encrypted1);
evaluator.add_inplace(encrypted2, encrypted2);
evaluator.add_inplace(encrypted1, encrypted2);
time_end = chrono::high_resolution_clock::now();
time_add_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
/*
[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);
time_start = chrono::high_resolution_clock::now();
evaluator.multiply_inplace(encrypted1, encrypted2);
time_end = chrono::high_resolution_clock::now();
time_multiply_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
/*
[Multiply Plain]
We multiply a ciphertext with a random plaintext. Recall that
multiply_plain does not change the size of the ciphertext so we use
encrypted2 here.
*/
time_start = chrono::high_resolution_clock::now();
evaluator.multiply_plain_inplace(encrypted2, plain);
time_end = chrono::high_resolution_clock::now();
time_multiply_plain_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
/*
[Square]
We continue to use encrypted2. Now we square it; this should be
faster than generic homomorphic multiplication.
*/
time_start = chrono::high_resolution_clock::now();
evaluator.square_inplace(encrypted2);
time_end = chrono::high_resolution_clock::now();
time_square_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
if (context.using_keyswitching())
{
/*
[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.
*/
time_start = chrono::high_resolution_clock::now();
evaluator.relinearize_inplace(encrypted1, relin_keys);
time_end = chrono::high_resolution_clock::now();
time_relinearize_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
/*
[Rotate Rows One Step]
We rotate matrix rows by one step left and measure the time.
*/
time_start = chrono::high_resolution_clock::now();
evaluator.rotate_rows_inplace(encrypted, 1, gal_keys);
evaluator.rotate_rows_inplace(encrypted, -1, gal_keys);
time_end = chrono::high_resolution_clock::now();
time_rotate_rows_one_step_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
;
/*
[Rotate Rows Random]
We rotate matrix rows by a random number of steps. This is much more
expensive than rotating by just one step.
*/
size_t row_size = batch_encoder.slot_count() / 2;
// row_size is always a power of 2
int random_rotation = static_cast<int>(rd() & (row_size - 1));
time_start = chrono::high_resolution_clock::now();
evaluator.rotate_rows_inplace(encrypted, random_rotation, gal_keys);
time_end = chrono::high_resolution_clock::now();
time_rotate_rows_random_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
/*
[Rotate Columns]
Nothing surprising here.
*/
time_start = chrono::high_resolution_clock::now();
evaluator.rotate_columns_inplace(encrypted, gal_keys);
time_end = chrono::high_resolution_clock::now();
time_rotate_columns_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
}
/*
[Serialize Ciphertext]
*/
size_t buf_size = static_cast<size_t>(encrypted.save_size(compr_mode_type::none));
vector<seal_byte> buf(buf_size);
time_start = chrono::high_resolution_clock::now();
encrypted.save(buf.data(), buf_size, compr_mode_type::none);
time_end = chrono::high_resolution_clock::now();
time_serialize_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
#ifdef SEAL_USE_ZLIB
/*
[Serialize Ciphertext (ZLIB)]
*/
buf_size = static_cast<size_t>(encrypted.save_size(compr_mode_type::zlib));
buf.resize(buf_size);
time_start = chrono::high_resolution_clock::now();
encrypted.save(buf.data(), buf_size, compr_mode_type::zlib);
time_end = chrono::high_resolution_clock::now();
time_serialize_zlib_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
#endif
#ifdef SEAL_USE_ZSTD
/*
[Serialize Ciphertext (Zstandard)]
*/
buf_size = static_cast<size_t>(encrypted.save_size(compr_mode_type::zstd));
buf.resize(buf_size);
time_start = chrono::high_resolution_clock::now();
encrypted.save(buf.data(), buf_size, compr_mode_type::zstd);
time_end = chrono::high_resolution_clock::now();
time_serialize_zstd_sum += chrono::duration_cast<chrono::microseconds>(time_end - time_start);
#endif
/*
Print a dot to indicate progress.
*/
cout << ".";
cout.flush();
}
cout << " Done" << endl << endl;
cout.flush();
auto avg_batch = time_batch_sum.count() / count;
auto avg_unbatch = time_unbatch_sum.count() / count;
auto avg_encrypt = time_encrypt_sum.count() / count;
auto avg_decrypt = time_decrypt_sum.count() / count;
auto avg_add = time_add_sum.count() / (3 * count);
auto avg_multiply = time_multiply_sum.count() / count;
auto avg_multiply_plain = time_multiply_plain_sum.count() / count;
auto avg_square = time_square_sum.count() / count;
auto avg_relinearize = time_relinearize_sum.count() / count;
auto avg_rotate_rows_one_step = time_rotate_rows_one_step_sum.count() / (2 * count);
auto avg_rotate_rows_random = time_rotate_rows_random_sum.count() / count;
auto avg_rotate_columns = time_rotate_columns_sum.count() / count;
auto avg_serialize = time_serialize_sum.count() / count;
#ifdef SEAL_USE_ZLIB
auto avg_serialize_zlib = time_serialize_zlib_sum.count() / count;
#endif
#ifdef SEAL_USE_ZSTD
auto avg_serialize_zstd = time_serialize_zstd_sum.count() / count;
#endif
cout << "Average batch: " << avg_batch << " microseconds" << endl;
cout << "Average unbatch: " << avg_unbatch << " microseconds" << endl;
cout << "Average encrypt: " << avg_encrypt << " microseconds" << endl;
cout << "Average decrypt: " << avg_decrypt << " microseconds" << endl;
cout << "Average add: " << avg_add << " microseconds" << endl;
cout << "Average multiply: " << avg_multiply << " microseconds" << endl;
cout << "Average multiply plain: " << avg_multiply_plain << " microseconds" << endl;
cout << "Average square: " << avg_square << " microseconds" << endl;
if (context.using_keyswitching())
{
cout << "Average relinearize: " << avg_relinearize << " microseconds" << endl;
cout << "Average rotate rows one step: " << avg_rotate_rows_one_step << " microseconds" << endl;
cout << "Average rotate rows random: " << avg_rotate_rows_random << " microseconds" << endl;
cout << "Average rotate columns: " << avg_rotate_columns << " microseconds" << endl;
}
cout << "Average serialize ciphertext: " << avg_serialize << " microseconds" << endl;
#ifdef SEAL_USE_ZLIB
cout << "Average compressed (ZLIB) serialize ciphertext: " << avg_serialize_zlib << " microseconds" << endl;
#endif
#ifdef SEAL_USE_ZSTD
cout << "Average compressed (Zstandard) serialize ciphertext: " << avg_serialize_zstd << " microseconds" << endl;
#endif
cout.flush();
}
example_bfv_performance_default 函數
void example_bfv_performance_default()
{
print_example_banner("BFV Performance Test with Degrees: 4096, 8192, and 16384");
EncryptionParameters parms(scheme_type::bfv);
size_t poly_modulus_degree = 4096;
parms.set_poly_modulus_degree(poly_modulus_degree);
parms.set_coeff_modulus(CoeffModulus::BFVDefault(poly_modulus_degree));
parms.set_plain_modulus(786433);
bfv_performance_test(parms);
cout << endl;
poly_modulus_degree = 8192;
parms.set_poly_modulus_degree(poly_modulus_degree);
parms.set_coeff_modulus(CoeffModulus::BFVDefault(poly_modulus_degree));
parms.set_plain_modulus(786433);
bfv_performance_test(parms);
cout << endl;
poly_modulus_degree = 16384;
parms.set_poly_modulus_degree(poly_modulus_degree);
parms.set_coeff_modulus(CoeffModulus::BFVDefault(poly_modulus_degree));
parms.set_plain_modulus(786433);
bfv_performance_test(parms);
/*
Comment out the following to run the biggest example.
*/
// cout << endl;
// poly_modulus_degree = 32768;
// parms.set_poly_modulus_degree(poly_modulus_degree);
// parms.set_coeff_modulus(CoeffModulus::BFVDefault(poly_modulus_degree));
// parms.set_plain_modulus(786433);
// bfv_performance_test(parms);
}
example_bfv_performance_custom 函數
void example_bfv_performance_custom()
{
size_t poly_modulus_degree = 0;
cout << endl << "Set poly_modulus_degree (1024, 2048, 4096, 8192, 16384, or 32768): ";
if (!(cin >> poly_modulus_degree))
{
cout << "Invalid option." << endl;
cin.clear();
cin.ignore(numeric_limits<streamsize>::max(), '\n');
return;
}
if (poly_modulus_degree < 1024 || poly_modulus_degree > 32768 ||
(poly_modulus_degree & (poly_modulus_degree - 1)) != 0)
{
cout << "Invalid option." << endl;
return;
}
string banner = "BFV Performance Test with Degree: ";
print_example_banner(banner + to_string(poly_modulus_degree));
EncryptionParameters parms(scheme_type::bfv);
parms.set_poly_modulus_degree(poly_modulus_degree);
parms.set_coeff_modulus(CoeffModulus::BFVDefault(poly_modulus_degree));
if (poly_modulus_degree == 1024)
{
parms.set_plain_modulus(12289);
}
else
{
parms.set_plain_modulus(786433);
}
bfv_performance_test(parms);
}
example_ckks_performance_default 函數
void example_ckks_performance_default()
{
print_example_banner("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.
EncryptionParameters parms(scheme_type::ckks);
size_t poly_modulus_degree = 4096;
parms.set_poly_modulus_degree(poly_modulus_degree);
parms.set_coeff_modulus(CoeffModulus::BFVDefault(poly_modulus_degree));
ckks_performance_test(parms);
cout << endl;
poly_modulus_degree = 8192;
parms.set_poly_modulus_degree(poly_modulus_degree);
parms.set_coeff_modulus(CoeffModulus::BFVDefault(poly_modulus_degree));
ckks_performance_test(parms);
cout << endl;
poly_modulus_degree = 16384;
parms.set_poly_modulus_degree(poly_modulus_degree);
parms.set_coeff_modulus(CoeffModulus::BFVDefault(poly_modulus_degree));
ckks_performance_test(parms);
/*
Comment out the following to run the biggest example.
*/
// cout << endl;
// poly_modulus_degree = 32768;
// parms.set_poly_modulus_degree(poly_modulus_degree);
// parms.set_coeff_modulus(CoeffModulus::BFVDefault(poly_modulus_degree));
// ckks_performance_test(parms);
}
example_ckks_performance_custom 函數
void example_ckks_performance_custom()
{
size_t poly_modulus_degree = 0;
cout << endl << "Set poly_modulus_degree (1024, 2048, 4096, 8192, 16384, or 32768): ";
if (!(cin >> poly_modulus_degree))
{
cout << "Invalid option." << endl;
cin.clear();
cin.ignore(numeric_limits<streamsize>::max(), '\n');
return;
}
if (poly_modulus_degree < 1024 || poly_modulus_degree > 32768 ||
(poly_modulus_degree & (poly_modulus_degree - 1)) != 0)
{
cout << "Invalid option." << endl;
return;
}
string banner = "CKKS Performance Test with Degree: ";
print_example_banner(banner + to_string(poly_modulus_degree));
EncryptionParameters parms(scheme_type::ckks);
parms.set_poly_modulus_degree(poly_modulus_degree);
parms.set_coeff_modulus(CoeffModulus::BFVDefault(poly_modulus_degree));
ckks_performance_test(parms);
}
example_bgv_performance_default 函數
void example_bgv_performance_default()
{
print_example_banner("BGV Performance Test with Degrees: 4096, 8192, and 16384");
EncryptionParameters parms(scheme_type::bgv);
size_t poly_modulus_degree = 4096;
parms.set_poly_modulus_degree(poly_modulus_degree);
parms.set_coeff_modulus(CoeffModulus::BFVDefault(poly_modulus_degree));
parms.set_plain_modulus(786433);
bgv_performance_test(parms);
cout << endl;
poly_modulus_degree = 8192;
parms.set_poly_modulus_degree(poly_modulus_degree);
parms.set_coeff_modulus(CoeffModulus::BFVDefault(poly_modulus_degree));
parms.set_plain_modulus(786433);
bgv_performance_test(parms);
cout << endl;
poly_modulus_degree = 16384;
parms.set_poly_modulus_degree(poly_modulus_degree);
parms.set_coeff_modulus(CoeffModulus::BFVDefault(poly_modulus_degree));
parms.set_plain_modulus(786433);
bgv_performance_test(parms);
/*
Comment out the following to run the biggest example.
*/
// cout << endl;
// poly_modulus_degree = 32768;
// parms.set_poly_modulus_degree(poly_modulus_degree);
// parms.set_coeff_modulus(CoeffModulus::BFVDefault(poly_modulus_degree));
// parms.set_plain_modulus(786433);
// bgv_performance_test(parms);
}
example_bgv_performance_custom 函數
void example_bgv_performance_custom()
{
size_t poly_modulus_degree = 0;
cout << endl << "Set poly_modulus_degree (1024, 2048, 4096, 8192, 16384, or 32768): ";
if (!(cin >> poly_modulus_degree))
{
cout << "Invalid option." << endl;
cin.clear();
cin.ignore(numeric_limits<streamsize>::max(), '\n');
return;
}
if (poly_modulus_degree < 1024 || poly_modulus_degree > 32768 ||
(poly_modulus_degree & (poly_modulus_degree - 1)) != 0)
{
cout << "Invalid option." << endl;
return;
}
string banner = "BGV Performance Test with Degree: ";
print_example_banner(banner + to_string(poly_modulus_degree));
EncryptionParameters parms(scheme_type::bgv);
parms.set_poly_modulus_degree(poly_modulus_degree);
parms.set_coeff_modulus(CoeffModulus::BFVDefault(poly_modulus_degree));
if (poly_modulus_degree == 1024)
{
parms.set_plain_modulus(12289);
}
else
{
parms.set_plain_modulus(786433);
}
bgv_performance_test(parms);
}
example_performance_test 函數
/*
Prints a sub-menu to select the performance test.
*/
void example_performance_test()
{
print_example_banner("Example: Performance Test");
while (true)
{
cout << endl;
cout << "Select a scheme (and optionally poly_modulus_degree):" << endl;
cout << " 1. BFV with default degrees" << endl;
cout << " 2. BFV with a custom degree" << endl;
cout << " 3. CKKS with default degrees" << endl;
cout << " 4. CKKS with a custom degree" << endl;
cout << " 5. BGV with default degrees" << endl;
cout << " 6. BGV with a custom degree" << endl;
cout << " 0. Back to main menu" << endl;
int selection = 0;
cout << endl << "> Run performance test (1 ~ 6) or go back (0): ";
if (!(cin >> selection))
{
cout << "Invalid option." << endl;
cin.clear();
cin.ignore(numeric_limits<streamsize>::max(), '\n');
continue;
}
switch (selection)
{
case 1:
example_bfv_performance_default();
break;
case 2:
example_bfv_performance_custom();
break;
case 3:
example_ckks_performance_default();
break;
case 4:
example_ckks_performance_custom();
break;
case 5:
example_bgv_performance_default();
break;
case 6:
example_bgv_performance_custom();
break;
case 0:
cout << endl;
return;
default:
cout << "Invalid option." << endl;
}
}
}