in native/src/seal/evaluator.cpp [1545:1671]
void Evaluator::multiply_plain_normal(Ciphertext &encrypted, const Plaintext &plain, MemoryPoolHandle pool) const
{
// Extract encryption parameters.
auto &context_data = *context_.get_context_data(encrypted.parms_id());
auto &parms = context_data.parms();
auto &coeff_modulus = parms.coeff_modulus();
size_t coeff_count = parms.poly_modulus_degree();
size_t coeff_modulus_size = coeff_modulus.size();
uint64_t plain_upper_half_threshold = context_data.plain_upper_half_threshold();
auto plain_upper_half_increment = context_data.plain_upper_half_increment();
auto ntt_tables = iter(context_data.small_ntt_tables());
size_t encrypted_size = encrypted.size();
size_t plain_coeff_count = plain.coeff_count();
size_t plain_nonzero_coeff_count = plain.nonzero_coeff_count();
// Size check
if (!product_fits_in(encrypted_size, coeff_count, coeff_modulus_size))
{
throw logic_error("invalid parameters");
}
double new_scale = encrypted.scale() * plain.scale();
if (!is_scale_within_bounds(new_scale, context_data))
{
throw invalid_argument("scale out of bounds");
}
/*
Optimizations for constant / monomial multiplication can lead to the presence of a timing side-channel in
use-cases where the plaintext data should also be kept private.
*/
if (plain_nonzero_coeff_count == 1)
{
// Multiplying by a monomial?
size_t mono_exponent = plain.significant_coeff_count() - 1;
if (plain[mono_exponent] >= plain_upper_half_threshold)
{
if (!context_data.qualifiers().using_fast_plain_lift)
{
// Allocate temporary space for a single RNS coefficient
SEAL_ALLOCATE_GET_COEFF_ITER(temp, coeff_modulus_size, pool);
// We need to adjust the monomial modulo each coeff_modulus prime separately when the coeff_modulus
// primes may be larger than the plain_modulus. We add plain_upper_half_increment (i.e., q-t) to
// the monomial to ensure it is smaller than coeff_modulus and then do an RNS multiplication. Note
// that in this case plain_upper_half_increment contains a multi-precision integer, so after the
// addition we decompose the multi-precision integer into RNS components, and then multiply.
add_uint(plain_upper_half_increment, coeff_modulus_size, plain[mono_exponent], temp);
context_data.rns_tool()->base_q()->decompose(temp, pool);
negacyclic_multiply_poly_mono_coeffmod(
encrypted, encrypted_size, temp, mono_exponent, coeff_modulus, encrypted, pool);
}
else
{
// Every coeff_modulus prime is larger than plain_modulus, so there is no need to adjust the
// monomial. Instead, just do an RNS multiplication.
negacyclic_multiply_poly_mono_coeffmod(
encrypted, encrypted_size, plain[mono_exponent], mono_exponent, coeff_modulus, encrypted, pool);
}
}
else
{
// The monomial represents a positive number, so no RNS multiplication is needed.
negacyclic_multiply_poly_mono_coeffmod(
encrypted, encrypted_size, plain[mono_exponent], mono_exponent, coeff_modulus, encrypted, pool);
}
// Set the scale
encrypted.scale() = new_scale;
return;
}
// Generic case: any plaintext polynomial
// Allocate temporary space for an entire RNS polynomial
auto temp(allocate_zero_poly(coeff_count, coeff_modulus_size, pool));
if (!context_data.qualifiers().using_fast_plain_lift)
{
StrideIter<uint64_t *> temp_iter(temp.get(), coeff_modulus_size);
SEAL_ITERATE(iter(plain.data(), temp_iter), plain_coeff_count, [&](auto I) {
auto plain_value = get<0>(I);
if (plain_value >= plain_upper_half_threshold)
{
add_uint(plain_upper_half_increment, coeff_modulus_size, plain_value, get<1>(I));
}
else
{
*get<1>(I) = plain_value;
}
});
context_data.rns_tool()->base_q()->decompose_array(temp_iter, coeff_count, pool);
}
else
{
// Note that in this case plain_upper_half_increment holds its value in RNS form modulo the coeff_modulus
// primes.
RNSIter temp_iter(temp.get(), coeff_count);
SEAL_ITERATE(iter(temp_iter, plain_upper_half_increment), coeff_modulus_size, [&](auto I) {
SEAL_ITERATE(iter(get<0>(I), plain.data()), plain_coeff_count, [&](auto J) {
get<0>(J) =
SEAL_COND_SELECT(get<1>(J) >= plain_upper_half_threshold, get<1>(J) + get<1>(I), get<1>(J));
});
});
}
// Need to multiply each component in encrypted with temp; first step is to transform to NTT form
RNSIter temp_iter(temp.get(), coeff_count);
ntt_negacyclic_harvey(temp_iter, coeff_modulus_size, ntt_tables);
SEAL_ITERATE(iter(encrypted), encrypted_size, [&](auto I) {
SEAL_ITERATE(iter(I, temp_iter, coeff_modulus, ntt_tables), coeff_modulus_size, [&](auto J) {
// Lazy reduction
ntt_negacyclic_harvey_lazy(get<0>(J), get<3>(J));
dyadic_product_coeffmod(get<0>(J), get<1>(J), coeff_count, get<2>(J), get<0>(J));
inverse_ntt_negacyclic_harvey(get<0>(J), get<3>(J));
});
});
// Set the scale
encrypted.scale() = new_scale;
}