#include "seal/ciphertext.h"
#include "seal/polyarray.h"
#include "seal/randomgen.h"
#include "seal/randomtostd.h"
#include "seal/util/clipnormal.h"
#include "seal/util/common.h"
#include "seal/util/globals.h"
#include "seal/util/ntt.h"
#include "seal/util/polyarithsmallmod.h"
#include "seal/util/polycore.h"
#include "seal/util/rlwe.h"
#include <optional>
using namespace std;
namespace seal
{
namespace util
{
void sample_poly_ternary(
shared_ptr<UniformRandomGenerator> prng, const EncryptionParameters &parms, uint64_t *destination)
{
auto coeff_modulus = parms.coeff_modulus();
size_t coeff_modulus_size = coeff_modulus.size();
size_t coeff_count = parms.poly_modulus_degree();
RandomToStandardAdapter engine(prng);
uniform_int_distribution<uint64_t> dist(0, 2);
SEAL_ITERATE(iter(destination), coeff_count, [&](auto &I) {
uint64_t rand = dist(engine);
uint64_t flag = static_cast<uint64_t>(-static_cast<int64_t>(rand == 0));
SEAL_ITERATE(
iter(StrideIter<uint64_t *>(&I, coeff_count), coeff_modulus), coeff_modulus_size,
[&](auto J) { *get<0>(J) = rand + (flag & get<1>(J).value()) - 1; });
});
}
void sample_poly_normal(
shared_ptr<UniformRandomGenerator> prng, const EncryptionParameters &parms, uint64_t *destination)
{
auto coeff_modulus = parms.coeff_modulus();
size_t coeff_modulus_size = coeff_modulus.size();
size_t coeff_count = parms.poly_modulus_degree();
if (are_close(global_variables::noise_max_deviation, 0.0))
{
set_zero_poly(coeff_count, coeff_modulus_size, destination);
return;
}
RandomToStandardAdapter engine(prng);
ClippedNormalDistribution dist(
0, global_variables::noise_standard_deviation, global_variables::noise_max_deviation);
SEAL_ITERATE(iter(destination), coeff_count, [&](auto &I) {
int64_t noise = static_cast<int64_t>(dist(engine));
uint64_t flag = static_cast<uint64_t>(-static_cast<int64_t>(noise < 0));
SEAL_ITERATE(
iter(StrideIter<uint64_t *>(&I, coeff_count), coeff_modulus), coeff_modulus_size,
[&](auto J) { *get<0>(J) = static_cast<uint64_t>(noise) + (flag & get<1>(J).value()); });
});
}
void sample_poly_cbd(
shared_ptr<UniformRandomGenerator> prng, const EncryptionParameters &parms, uint64_t *destination)
{
auto coeff_modulus = parms.coeff_modulus();
size_t coeff_modulus_size = coeff_modulus.size();
size_t coeff_count = parms.poly_modulus_degree();
if (are_close(global_variables::noise_max_deviation, 0.0))
{
set_zero_poly(coeff_count, coeff_modulus_size, destination);
return;
}
if (!are_close(global_variables::noise_standard_deviation, 3.2))
{
throw logic_error("centered binomial distribution only supports standard deviation 3.2; use rounded "
"Gaussian instead");
}
auto cbd = [&]() {
unsigned char x[6];
prng->generate(6, reinterpret_cast<seal_byte *>(x));
x[2] &= 0x1F;
x[5] &= 0x1F;
return hamming_weight(x[0]) + hamming_weight(x[1]) + hamming_weight(x[2]) - hamming_weight(x[3]) -
hamming_weight(x[4]) - hamming_weight(x[5]);
};
SEAL_ITERATE(iter(destination), coeff_count, [&](auto &I) {
int32_t noise = cbd();
uint64_t flag = static_cast<uint64_t>(-static_cast<int64_t>(noise < 0));
SEAL_ITERATE(
iter(StrideIter<uint64_t *>(&I, coeff_count), coeff_modulus), coeff_modulus_size,
[&](auto J) { *get<0>(J) = static_cast<uint64_t>(noise) + (flag & get<1>(J).value()); });
});
}
void sample_poly_uniform(
shared_ptr<UniformRandomGenerator> prng, const EncryptionParameters &parms, uint64_t *destination)
{
auto coeff_modulus = parms.coeff_modulus();
size_t coeff_modulus_size = coeff_modulus.size();
size_t coeff_count = parms.poly_modulus_degree();
size_t dest_byte_count = mul_safe(coeff_modulus_size, coeff_count, sizeof(uint64_t));
constexpr uint64_t max_random = static_cast<uint64_t>(0xFFFFFFFFFFFFFFFFULL);
prng->generate(dest_byte_count, reinterpret_cast<seal_byte *>(destination));
for (size_t j = 0; j < coeff_modulus_size; j++)
{
auto &modulus = coeff_modulus[j];
uint64_t max_multiple = max_random - barrett_reduce_64(max_random, modulus) - 1;
transform(destination, destination + coeff_count, destination, [&](uint64_t rand) {
while (rand >= max_multiple)
{
prng->generate(sizeof(uint64_t), reinterpret_cast<seal_byte *>(&rand));
}
return barrett_reduce_64(rand, modulus);
});
destination += coeff_count;
}
}
void sample_poly_uniform_seal_3_4(
shared_ptr<UniformRandomGenerator> prng, const EncryptionParameters &parms, uint64_t *destination)
{
auto coeff_modulus = parms.coeff_modulus();
size_t coeff_modulus_size = coeff_modulus.size();
size_t coeff_count = parms.poly_modulus_degree();
RandomToStandardAdapter engine(prng);
constexpr uint64_t max_random = static_cast<uint64_t>(0x7FFFFFFFFFFFFFFFULL);
for (size_t j = 0; j < coeff_modulus_size; j++)
{
auto &modulus = coeff_modulus[j];
uint64_t max_multiple = max_random - barrett_reduce_64(max_random, modulus) - 1;
for (size_t i = 0; i < coeff_count; i++)
{
uint64_t rand;
do
{
rand = (static_cast<uint64_t>(engine()) << 31) | (static_cast<uint64_t>(engine()) >> 1);
} while (rand >= max_multiple);
destination[i + j * coeff_count] = barrett_reduce_64(rand, modulus);
}
}
}
void sample_poly_uniform_seal_3_5(
shared_ptr<UniformRandomGenerator> prng, const EncryptionParameters &parms, uint64_t *destination)
{
auto coeff_modulus = parms.coeff_modulus();
size_t coeff_modulus_size = coeff_modulus.size();
size_t coeff_count = parms.poly_modulus_degree();
RandomToStandardAdapter engine(prng);
constexpr uint64_t max_random = static_cast<uint64_t>(0xFFFFFFFFFFFFFFFFULL);
for (size_t j = 0; j < coeff_modulus_size; j++)
{
auto &modulus = coeff_modulus[j];
uint64_t max_multiple = max_random - barrett_reduce_64(max_random, modulus) - 1;
for (size_t i = 0; i < coeff_count; i++)
{
uint64_t rand;
do
{
rand = (static_cast<uint64_t>(engine()) << 32) | static_cast<uint64_t>(engine());
} while (rand >= max_multiple);
destination[i + j * coeff_count] = barrett_reduce_64(rand, modulus);
}
}
}
void encrypt_zero_asymmetric(
const PublicKey &public_key,
const SEALContext &context,
parms_id_type parms_id,
bool is_ntt_form,
bool export_components,
Ciphertext &destination,
PolynomialArray &u_destination,
PolynomialArray &e_destination,
optional<prng_seed_type> seed
) {
#ifdef SEAL_DEBUG
if (!is_valid_for(public_key, context))
{
throw invalid_argument("public key is not valid for the encryption parameters");
}
#endif
MemoryPoolHandle pool = MemoryManager::GetPool(mm_prof_opt::mm_force_new, true);
auto &context_data = *context.get_context_data(parms_id);
auto &parms = context_data.parms();
auto &coeff_modulus = parms.coeff_modulus();
auto &plain_modulus = parms.plain_modulus();
size_t coeff_modulus_size = coeff_modulus.size();
size_t coeff_count = parms.poly_modulus_degree();
auto ntt_tables = context_data.small_ntt_tables();
size_t encrypted_size = public_key.data().size();
scheme_type type = parms.scheme();
destination.resize(context, parms_id, encrypted_size);
destination.is_ntt_form() = is_ntt_form;
destination.scale() = 1.0;
destination.correction_factor() = 1;
auto prng = seed.has_value() ? parms.random_generator()->create(seed.value())
: parms.random_generator()->create();
auto u(allocate_poly(coeff_count, coeff_modulus_size, pool));
sample_poly_ternary(prng, parms, u.get());
if (export_components) {
u_destination.reserve(1, coeff_count, coeff_modulus);
u_destination.insert_polynomial(0, u.get());
}
for (size_t i = 0; i < coeff_modulus_size; i++)
{
ntt_negacyclic_harvey(u.get() + i * coeff_count, ntt_tables[i]);
for (size_t j = 0; j < encrypted_size; j++)
{
dyadic_product_coeffmod(
u.get() + i * coeff_count, public_key.data().data(j) + i * coeff_count, coeff_count,
coeff_modulus[i], destination.data(j) + i * coeff_count);
if (!is_ntt_form)
{
inverse_ntt_negacyclic_harvey(destination.data(j) + i * coeff_count, ntt_tables[i]);
}
}
}
if (export_components) {
e_destination.reserve(encrypted_size, coeff_count, coeff_modulus);
}
for (size_t j = 0; j < encrypted_size; j++)
{
SEAL_NOISE_SAMPLER(prng, parms, u.get());
RNSIter gaussian_iter(u.get(), coeff_count);
if (export_components) {
e_destination.insert_polynomial(j, u.get());
}
if (type == scheme_type::bgv)
{
if (is_ntt_form)
{
ntt_negacyclic_harvey_lazy(gaussian_iter, coeff_modulus_size, ntt_tables);
}
multiply_poly_scalar_coeffmod(
gaussian_iter, coeff_modulus_size, plain_modulus.value(), coeff_modulus, gaussian_iter);
}
else
{
if (is_ntt_form)
{
ntt_negacyclic_harvey(gaussian_iter, coeff_modulus_size, ntt_tables);
}
}
RNSIter dst_iter(destination.data(j), coeff_count);
add_poly_coeffmod(gaussian_iter, dst_iter, coeff_modulus_size, coeff_modulus, dst_iter);
}
}
void encrypt_zero_symmetric(
const SecretKey &secret_key,
const SEALContext &context,
parms_id_type parms_id,
bool is_ntt_form,
bool save_seed,
Ciphertext &destination,
optional<prng_seed_type> seed
)
{
#ifdef SEAL_DEBUG
if (!is_valid_for(secret_key, context))
{
throw invalid_argument("secret key is not valid for the encryption parameters");
}
#endif
MemoryPoolHandle pool = MemoryManager::GetPool(mm_prof_opt::mm_force_new, true);
auto &context_data = *context.get_context_data(parms_id);
auto &parms = context_data.parms();
auto &coeff_modulus = parms.coeff_modulus();
auto &plain_modulus = parms.plain_modulus();
size_t coeff_modulus_size = coeff_modulus.size();
size_t coeff_count = parms.poly_modulus_degree();
auto ntt_tables = context_data.small_ntt_tables();
size_t encrypted_size = 2;
scheme_type type = parms.scheme();
size_t poly_uint64_count = mul_safe(coeff_count, coeff_modulus_size);
size_t prng_info_byte_count =
static_cast<size_t>(UniformRandomGeneratorInfo::SaveSize(compr_mode_type::none));
size_t prng_info_uint64_count =
divide_round_up(prng_info_byte_count, static_cast<size_t>(bytes_per_uint64));
if (save_seed && poly_uint64_count < prng_info_uint64_count + 1)
{
save_seed = false;
}
destination.resize(context, parms_id, encrypted_size);
destination.is_ntt_form() = is_ntt_form;
destination.scale() = 1.0;
destination.correction_factor() = 1;
auto bootstrap_prng = parms.random_generator()->create();
prng_seed_type public_prng_seed;
bootstrap_prng->generate(prng_seed_byte_count, reinterpret_cast<seal_byte *>(public_prng_seed.data()));
auto ciphertext_prng = UniformRandomGeneratorFactory::DefaultFactory()->create(public_prng_seed);
uint64_t *c0 = destination.data();
uint64_t *c1 = destination.data(1);
if (is_ntt_form || !save_seed)
{
sample_poly_uniform(ciphertext_prng, parms, c1);
}
else if (save_seed)
{
sample_poly_uniform(ciphertext_prng, parms, c1);
for (size_t i = 0; i < coeff_modulus_size; i++)
{
ntt_negacyclic_harvey(c1 + i * coeff_count, ntt_tables[i]);
}
}
auto noise(allocate_poly(coeff_count, coeff_modulus_size, pool));
SEAL_NOISE_SAMPLER(bootstrap_prng, parms, noise.get());
for (size_t i = 0; i < coeff_modulus_size; i++)
{
dyadic_product_coeffmod(
secret_key.data().data() + i * coeff_count, c1 + i * coeff_count, coeff_count, coeff_modulus[i],
c0 + i * coeff_count);
if (is_ntt_form)
{
ntt_negacyclic_harvey(noise.get() + i * coeff_count, ntt_tables[i]);
}
else
{
inverse_ntt_negacyclic_harvey(c0 + i * coeff_count, ntt_tables[i]);
}
if (type == scheme_type::bgv)
{
multiply_poly_scalar_coeffmod(
noise.get() + i * coeff_count, coeff_count, plain_modulus.value(), coeff_modulus[i],
noise.get() + i * coeff_count);
}
add_poly_coeffmod(
noise.get() + i * coeff_count, c0 + i * coeff_count, coeff_count, coeff_modulus[i],
c0 + i * coeff_count);
negate_poly_coeffmod(c0 + i * coeff_count, coeff_count, coeff_modulus[i], c0 + i * coeff_count);
}
if (!is_ntt_form && !save_seed)
{
for (size_t i = 0; i < coeff_modulus_size; i++)
{
inverse_ntt_negacyclic_harvey(c1 + i * coeff_count, ntt_tables[i]);
}
}
if (save_seed)
{
UniformRandomGeneratorInfo prng_info = ciphertext_prng->info();
c1[0] = static_cast<uint64_t>(0xFFFFFFFFFFFFFFFFULL);
prng_info.save(reinterpret_cast<seal_byte *>(c1 + 1), prng_info_byte_count, compr_mode_type::none);
}
}
} }