Exemple #1
0
    ChooserPoly ChooserEvaluator::exponentiate(const ChooserPoly &operand, 
        uint64_t exponent, int decomposition_bit_count)
    {
        if (operand.max_coeff_count_ <= 0 || operand.comp_ == nullptr)
        {
            throw invalid_argument("operand is not correctly initialized");
        }
        if (exponent == 0)
        {
            throw invalid_argument("exponent cannot be 0");
        }

        // Check that decomposition_bit_count is in correct interval
        if (decomposition_bit_count < SEAL_DBC_MIN || 
            decomposition_bit_count > SEAL_DBC_MAX)
        {
            throw invalid_argument("decomposition_bit_count is not in the valid range");
        }

        if (operand.max_abs_value_ == 0)
        {
            return ChooserPoly(1, 0, 
                new ExponentiateComputation(*operand.comp_, 
                    exponent, 
                    decomposition_bit_count));
        }

        // There is no known closed formula for the growth factor, but we use the
        // asymptotic approximation k^n * sqrt[6/((k-1)*(k+1)*Pi*n)], where 
        // k = max_coeff_count_, n = exponent.
        uint64_t growth_factor = static_cast<uint64_t>(
            pow(operand.max_coeff_count_, exponent) * 
            sqrt(6 / ((operand.max_coeff_count_ - 1) * 
            (operand.max_coeff_count_ + 1) * 3.1415 * exponent)));

        uint64_t result_bit_count = 
            exponent * get_significant_bit_count(operand.max_abs_value_) + 
            get_significant_bit_count(growth_factor) + 1 + 
            get_significant_bit_count(growth_factor);
        if (result_bit_count > bits_per_uint64)
        {
            throw invalid_argument("polynomial coefficients too large");
        }
        uint64_t result_max_abs_value = exponentiate_uint64(
            operand.max_abs_value_, exponent) * growth_factor;

        uint64_t result_coeff_count = exponent * (operand.max_coeff_count_ - 1) + 1;
        if(result_coeff_count > numeric_limits<int>::max())
        {
            throw invalid_argument("polynomial is too long");
        }

        return ChooserPoly(static_cast<int>(result_coeff_count),
            result_max_abs_value, 
            new ExponentiateComputation(*operand.comp_, 
                exponent, 
                decomposition_bit_count));
    }
    ChooserPoly ChooserEvaluator::multiply_many(const vector<ChooserPoly> &operands)
    {
        if (operands.empty())
        {
            throw invalid_argument("operands vector can not be empty");
        }

        int prod_max_coeff_count = 1;
        uint64_t growth_factor = 1;
        int prod_max_abs_value_bit_count = 1;
        vector<Computation*> comps;
        for (vector<ChooserPoly>::size_type i = 0; i < operands.size(); ++i)
        {
            // Throw if any of the operands is not initialized correctly
            if (operands[i].max_coeff_count_ <= 0 || operands[i].comp_ == nullptr)
            {
                throw invalid_argument("input operand is not correctly initialized");
            }

            // Return early if the product is trivially zero
            if (operands[i].max_abs_value_.is_zero())
            {
                return ChooserPoly(1, 0, new MultiplyManyComputation(comps));
            }

            prod_max_coeff_count += operands[i].max_coeff_count_ - 1;
            prod_max_abs_value_bit_count += operands[i].max_abs_value().significant_bit_count();

            growth_factor *= (i == 0 ? 1 : min(operands[i].max_coeff_count_, prod_max_coeff_count));

            comps.push_back(operands[i].comp_);
        }

        prod_max_abs_value_bit_count += get_significant_bit_count(growth_factor);
        int prod_max_abs_value_uint64_count = divide_round_up(prod_max_abs_value_bit_count, bits_per_uint64);

        Pointer prod_max_abs_value(allocate_zero_uint(prod_max_abs_value_uint64_count, pool_));
        *prod_max_abs_value.get() = growth_factor;
        for (vector<ChooserPoly>::size_type i = 0; i < operands.size(); ++i)
        {
            ConstPointer temp_pointer(duplicate_uint_if_needed(prod_max_abs_value.get(), prod_max_abs_value_uint64_count, prod_max_abs_value_uint64_count, true, pool_));
            multiply_uint_uint(temp_pointer.get(), prod_max_abs_value_uint64_count, operands[i].max_abs_value_.pointer(), operands[i].max_abs_value_.uint64_count(), prod_max_abs_value_uint64_count, prod_max_abs_value.get());
        }

        return ChooserPoly(prod_max_coeff_count, BigUInt(prod_max_abs_value_bit_count, prod_max_abs_value.get()), new MultiplyManyComputation(comps));
    }
Exemple #3
0
        int get_significant_bit_count_uint(const uint64_t *operand, int uint64_count)
        {
#ifdef _DEBUG
            if (operand == nullptr && uint64_count > 0)
            {
                throw invalid_argument("operand");
            }
            if (uint64_count < 0)
            {
                throw invalid_argument("uint64_count");
            }
#endif
            operand += uint64_count;
            for (int long_index = uint64_count - 1; long_index >= 0; --long_index)
            {
                uint64_t value = *--operand;
                if (value != 0)
                {
                    return get_significant_bit_count(value) + long_index * bits_per_uint64;
                }
            }
            return 0;
        }
 ChooserEncoder::ChooserEncoder(uint64_t base) : encoder_(BigUInt(get_significant_bit_count(base), base), base)
 {
 }
    ChooserPoly ChooserEvaluator::exponentiate(const ChooserPoly &operand, uint64_t exponent)
    {
        if (operand.max_coeff_count_ <= 0 || operand.comp_ == nullptr)
        {
            throw invalid_argument("operand is not correctly initialized");
        }
        if (exponent == 0 && operand.max_abs_value_.is_zero())
        {
            throw invalid_argument("undefined operation");
        }
        if (exponent == 0)
        {
            return ChooserPoly(1, 1, new ExponentiateComputation(*operand.comp_, exponent));
        }
        if (operand.max_abs_value_.is_zero())
        {
            return ChooserPoly(1, 0, new ExponentiateComputation(*operand.comp_, exponent));
        }

        // There is no known closed formula for the growth factor, but we use the asymptotic approximation
        // k^n * sqrt[6/((k-1)*(k+1)*Pi*n)], where k = max_coeff_count_, n = exponent.
        uint64_t growth_factor = static_cast<uint64_t>(pow(operand.max_coeff_count_, exponent) * sqrt(6 / ((operand.max_coeff_count_ - 1) * (operand.max_coeff_count_ + 1) * 3.1415 * exponent)));

        int result_bit_count = static_cast<int>(exponent) * operand.max_abs_value_.significant_bit_count() + get_significant_bit_count(growth_factor) + 1;
        int result_uint64_count = divide_round_up(result_bit_count, bits_per_uint64);

        Pointer result_max_abs_value(allocate_uint(result_uint64_count, pool_));

        util::exponentiate_uint(operand.max_abs_value_.pointer(), operand.max_abs_value_.uint64_count(), &exponent, 1, result_uint64_count, result_max_abs_value.get(), pool_);

        ConstPointer temp_pointer(duplicate_uint_if_needed(result_max_abs_value.get(), result_uint64_count, result_uint64_count, true, pool_));
        multiply_uint_uint(&growth_factor, 1, temp_pointer.get(), result_uint64_count, result_uint64_count, result_max_abs_value.get());

        return ChooserPoly(static_cast<int>(exponent) * (operand.max_coeff_count_ - 1) + 1, BigUInt(result_bit_count, result_max_abs_value.get()), new ExponentiateComputation(*operand.comp_, exponent));
    }
    ChooserPoly ChooserEvaluator::multiply_plain(const ChooserPoly &operand, int plain_max_coeff_count, const BigUInt &plain_max_abs_value)
    {
        if (operand.max_coeff_count_ <= 0 || operand.comp_ == nullptr)
        {
            throw invalid_argument("operand is not correctly initialized");
        }
        if (plain_max_coeff_count <= 0)
        {
            throw invalid_argument("plain_max_coeff_count must be positive");
        }
        if (plain_max_abs_value.is_zero())
        {
            return ChooserPoly(1, 0, new MultiplyPlainComputation(*operand.comp_, plain_max_coeff_count, plain_max_abs_value));
        }
        if (operand.max_abs_value_.is_zero())
        {
            return ChooserPoly(1, 0, new MultiplyPlainComputation(*operand.comp_, plain_max_coeff_count, plain_max_abs_value));
        }

        uint64_t growth_factor = min(operand.max_coeff_count_, plain_max_coeff_count);
        int prod_bit_count = operand.max_abs_value_.significant_bit_count() + plain_max_abs_value.significant_bit_count() + get_significant_bit_count(growth_factor) + 1;
        int prod_uint64_count = divide_round_up(prod_bit_count, bits_per_uint64);

        Pointer prod_max_abs_value(allocate_zero_uint(prod_uint64_count, pool_));
        ConstPointer wide_operand_max_abs_value(duplicate_uint_if_needed(operand.max_abs_value_.pointer(), operand.max_abs_value_.uint64_count(), prod_uint64_count, false, pool_));

        multiply_uint_uint(&growth_factor, 1, plain_max_abs_value.pointer(), plain_max_abs_value.uint64_count(), prod_uint64_count, prod_max_abs_value.get());
        ConstPointer temp_pointer(duplicate_uint_if_needed(prod_max_abs_value.get(), prod_uint64_count, prod_uint64_count, true, pool_));
        multiply_uint_uint(wide_operand_max_abs_value.get(), prod_uint64_count, temp_pointer.get(), prod_uint64_count, prod_uint64_count, prod_max_abs_value.get());

        return ChooserPoly(operand.max_coeff_count_ + plain_max_coeff_count - 1, BigUInt(prod_bit_count, prod_max_abs_value.get()), new MultiplyPlainComputation(*operand.comp_, plain_max_coeff_count, plain_max_abs_value));
    }
    ChooserPoly ChooserEvaluator::add_many(const std::vector<ChooserPoly> &operands)
    {
        if (operands.empty())
        {
            throw invalid_argument("operands vector can not be empty");
        }

        int sum_max_coeff_count = operands[0].max_coeff_count_;
        vector<ChooserPoly>::size_type largest_abs_value_index = 0;
        for (vector<ChooserPoly>::size_type i = 0; i < operands.size(); ++i)
        {
            // Throw if any of the operands is not initialized correctly
            if (operands[i].max_coeff_count_ <= 0 || operands[i].comp_ == nullptr)
            {
                throw invalid_argument("input operand is not correctly initialized");
            }

            if (operands[i].max_coeff_count_ > sum_max_coeff_count)
            {
                sum_max_coeff_count = operands[i].max_coeff_count_;
            }
            if (compare_uint_uint(operands[i].max_abs_value_.pointer(), operands[i].max_abs_value_.uint64_count(), operands[largest_abs_value_index].max_abs_value_.pointer(), operands[largest_abs_value_index].max_abs_value_.uint64_count() > 0))
            {
                largest_abs_value_index = i;
            }
        }

        int sum_max_abs_value_bit_count = operands[largest_abs_value_index].max_abs_value_.significant_bit_count() + get_significant_bit_count(operands.size());
        int sum_max_abs_value_uint64_count = divide_round_up(sum_max_abs_value_bit_count, bits_per_uint64);
        Pointer sum_max_abs_value(allocate_zero_uint(sum_max_abs_value_uint64_count, pool_));

        vector<Computation*> comps;
        for (vector<ChooserPoly>::size_type i = 0; i < operands.size(); ++i)
        {
            add_uint_uint(operands[i].max_abs_value_.pointer(), operands[i].max_abs_value_.uint64_count(), sum_max_abs_value.get(), sum_max_abs_value_uint64_count, false, sum_max_abs_value_uint64_count, sum_max_abs_value.get());
            comps.push_back(operands[i].comp_);
        }

        return ChooserPoly(sum_max_coeff_count, BigUInt(sum_max_abs_value_bit_count, sum_max_abs_value.get()), new AddManyComputation(comps));
    }
Exemple #8
0
    bool ChooserEvaluator::select_parameters(
        const std::vector<ChooserPoly> &operands, 
        int budget_gap, double noise_standard_deviation, 
        const map<int, vector<SmallModulus> > &coeff_modulus_options, 
        EncryptionParameters &destination)
    {
        if (budget_gap < 0)
        {
            throw std::invalid_argument("budget_gap cannot be negative");
        }
        if (noise_standard_deviation < 0)
        {
            throw invalid_argument("noise_standard_deviation can not be negative");
        }
        if (coeff_modulus_options.size() == 0)
        {
            throw invalid_argument("parameter_options must contain at least one entry");
        }
        if (operands.empty())
        {
            throw invalid_argument("operands cannot be empty");
        }

        int largest_bit_count = 0;
        int largest_coeff_count = 0;
        for (size_t i = 0; i < operands.size(); i++)
        {
            if (operands[i].comp_ == nullptr)
            {
                throw logic_error("no operation history to simulate");
            }
            int current_bit_count = get_significant_bit_count(operands[i].max_abs_value_);
            largest_bit_count = (current_bit_count > largest_bit_count) ? 
                current_bit_count : largest_bit_count;

            int current_coeff_count = operands[i].max_coeff_count_;
            largest_coeff_count = (current_coeff_count > largest_coeff_count) ? 
                current_coeff_count : largest_coeff_count;
        }

        // We restrict to plain moduli that are powers of two. Here largest_bit_count 
        // is the largest positive coefficient that we can expect to appear. Thus, we 
        // need one more bit.
        uint64_t new_plain_modulus;
        if (largest_bit_count >= SEAL_USER_MODULO_BIT_BOUND)
        {
            // The plain_modulus needed is too big
            return false;
        }
        new_plain_modulus = 1ULL << largest_bit_count;
        destination.set_plain_modulus(new_plain_modulus);

        bool found_good_parms = false;
        map<int, vector<SmallModulus> >::const_iterator iter = coeff_modulus_options.begin();
        while (iter != coeff_modulus_options.end() && !found_good_parms)
        {
            int dimension = iter->first;
            if (dimension < 512 || (dimension & (dimension - 1)) != 0)
            {
                throw invalid_argument("coeff_modulus_options keys invalid");
            }

            int coeff_bit_count = 0;
            for(auto mod : iter->second)
            {
                coeff_bit_count += mod.bit_count();
            }

            if (dimension > largest_coeff_count && 
                coeff_bit_count > destination.plain_modulus().bit_count())
            {
                // Set the polynomial
                destination.set_coeff_modulus(iter->second);
                BigPoly new_poly_modulus(dimension + 1, 1);
                new_poly_modulus.set_zero();
                new_poly_modulus[0] = 1;
                new_poly_modulus[dimension] = 1;
                destination.set_poly_modulus(new_poly_modulus);

                // The bound needed for GapSVP->search-LWE reduction
                //parms.noise_standard_deviation() = round(sqrt(dimension / (2 * 3.1415)) + 0.5);

                // Use constant (small) standard deviation.
                destination.set_noise_standard_deviation(noise_standard_deviation);

                found_good_parms = true;
                for (size_t i = 0; i < operands.size(); i++)
                {
                    // If one of the operands does not decrypt, set found_good_parms to false.
                    found_good_parms = operands[i].simulate(destination).decrypts(budget_gap) ? 
                        found_good_parms : false;
                }
            }

            // This dimension/coeff_modulus are to small. Move on to the next pair.
            iter++;
        }

        if (!found_good_parms)
        {
            destination = EncryptionParameters();
        }

        return found_good_parms;
    }
Exemple #9
0
    ChooserPoly ChooserEvaluator::multiply_many(
        const vector<ChooserPoly> &operands, int decomposition_bit_count)
    {
        if (operands.empty())
        {
            throw invalid_argument("operands vector can not be empty");
        }

        // Check that decomposition_bit_count is in correct interval
        if (decomposition_bit_count < SEAL_DBC_MIN || 
            decomposition_bit_count > SEAL_DBC_MAX)
        {
            throw invalid_argument("decomposition_bit_count is not in the valid range");
        }

        int prod_max_coeff_count = 1;
        uint64_t growth_factor = 1;
        int prod_max_abs_value_bit_count = 1;
        vector<Computation*> comps;
        for (size_t i = 0; i < operands.size(); i++)
        {
            // Throw if any of the operands is not initialized correctly
            if (operands[i].max_coeff_count_ <= 0 || operands[i].comp_ == nullptr)
            {
                throw invalid_argument("input operand is not correctly initialized");
            }

            // Return early if the product is trivially zero
            if (operands[i].max_abs_value_ == 0)
            {
                return ChooserPoly(1, 0, 
                    new MultiplyManyComputation(comps, 
                        decomposition_bit_count));
            }

            prod_max_coeff_count += operands[i].max_coeff_count_ - 1;
            prod_max_abs_value_bit_count += 
                get_significant_bit_count(operands[i].max_abs_value_);

            growth_factor *= (i == 0 ? 1 : 
                min(operands[i].max_coeff_count_, prod_max_coeff_count));

            comps.emplace_back(operands[i].comp_);
        }

        prod_max_abs_value_bit_count += get_significant_bit_count(growth_factor);
        if (prod_max_abs_value_bit_count >= bits_per_uint64)
        {
            throw invalid_argument("polynomial coefficients too large");
        }

        uint64_t prod_max_abs_value = growth_factor;
        for (size_t i = 0; i < operands.size(); i++)
        {
            prod_max_abs_value *= operands[i].max_abs_value_;
        }

        return ChooserPoly(prod_max_coeff_count, 
            prod_max_abs_value, 
            new MultiplyManyComputation(comps, 
                decomposition_bit_count));
    }
    Evaluator::Evaluator(const EncryptionParameters &parms, const EvaluationKeys &evaluation_keys) :
        poly_modulus_(parms.poly_modulus()), coeff_modulus_(parms.coeff_modulus()), plain_modulus_(parms.plain_modulus()),
        decomposition_bit_count_(parms.decomposition_bit_count()), evaluation_keys_(evaluation_keys), mode_(parms.mode())
    {
        // Verify required parameters are non-zero and non-nullptr.
        if (poly_modulus_.is_zero())
        {
            throw invalid_argument("poly_modulus cannot be zero");
        }
        if (coeff_modulus_.is_zero())
        {
            throw invalid_argument("coeff_modulus cannot be zero");
        }
        if (plain_modulus_.is_zero())
        {
            throw invalid_argument("plain_modulus cannot be zero");
        }
        if (decomposition_bit_count_ <= 0)
        {
            throw invalid_argument("decomposition_bit_count must be positive");
        }
        if (evaluation_keys_.count() == 0)
        {
            throw invalid_argument("evaluation_keys cannot be empty");
        }

        // Verify parameters.
        if (plain_modulus_ >= coeff_modulus_)
        {
            throw invalid_argument("plain_modulus must be smaller than coeff_modulus");
        }
        if (!are_poly_coefficients_less_than(poly_modulus_, coeff_modulus_))
        {
            throw invalid_argument("poly_modulus cannot have coefficients larger than coeff_modulus");
        }

        // Resize encryption parameters to consistent size.
        int coeff_count = poly_modulus_.significant_coeff_count();
        int coeff_bit_count = coeff_modulus_.significant_bit_count();
        int coeff_uint64_count = divide_round_up(coeff_bit_count, bits_per_uint64);
        if (poly_modulus_.coeff_count() != coeff_count || poly_modulus_.coeff_bit_count() != coeff_bit_count)
        {
            poly_modulus_.resize(coeff_count, coeff_bit_count);
        }
        if (coeff_modulus_.bit_count() != coeff_bit_count)
        {
            coeff_modulus_.resize(coeff_bit_count);
        }
        if (plain_modulus_.bit_count() != coeff_bit_count)
        {
            plain_modulus_.resize(coeff_bit_count);
        }
        if (decomposition_bit_count_ > coeff_bit_count)
        {
            decomposition_bit_count_ = coeff_bit_count;
        }

        // Determine correct number of evaluation keys.
        int evaluation_key_count = 0;
        Pointer evaluation_factor(allocate_uint(coeff_uint64_count, pool_));
        set_uint(1, coeff_uint64_count, evaluation_factor.get());
        while (!is_zero_uint(evaluation_factor.get(), coeff_uint64_count) && is_less_than_uint_uint(evaluation_factor.get(), coeff_modulus_.pointer(), coeff_uint64_count))
        {
            left_shift_uint(evaluation_factor.get(), decomposition_bit_count_, coeff_uint64_count, evaluation_factor.get());
            evaluation_key_count++;
        }

        // Verify evaluation keys.
        if (evaluation_keys_.count() != evaluation_key_count)
        {
            throw invalid_argument("evaluation_keys is not valid for encryption parameters");
        }
        for (int i = 0; i < evaluation_keys_.count(); ++i)
        {
            BigPoly &evaluation_key = evaluation_keys_[i];
            if (evaluation_key.coeff_count() != coeff_count || evaluation_key.coeff_bit_count() != coeff_bit_count ||
                evaluation_key.significant_coeff_count() == coeff_count || !are_poly_coefficients_less_than(evaluation_key, coeff_modulus_))
            {
                throw invalid_argument("evaluation_keys is not valid for encryption parameters");
            }
        }

        // Calculate coeff_modulus / plain_modulus.
        coeff_div_plain_modulus_.resize(coeff_bit_count);
        Pointer temp(allocate_uint(coeff_uint64_count, pool_));
        divide_uint_uint(coeff_modulus_.pointer(), plain_modulus_.pointer(), coeff_uint64_count, coeff_div_plain_modulus_.pointer(), temp.get(), pool_);

        // Calculate (plain_modulus + 1) / 2.
        plain_upper_half_threshold_.resize(coeff_bit_count);
        half_round_up_uint(plain_modulus_.pointer(), coeff_uint64_count, plain_upper_half_threshold_.pointer());

        // Calculate coeff_modulus - plain_modulus.
        plain_upper_half_increment_.resize(coeff_bit_count);
        sub_uint_uint(coeff_modulus_.pointer(), plain_modulus_.pointer(), coeff_uint64_count, plain_upper_half_increment_.pointer());

        // Calculate (plain_modulus + 1) / 2 * coeff_div_plain_modulus.
        upper_half_threshold_.resize(coeff_bit_count);
        multiply_truncate_uint_uint(plain_upper_half_threshold_.pointer(), coeff_div_plain_modulus_.pointer(), coeff_uint64_count, upper_half_threshold_.pointer());

        // Calculate upper_half_increment.
        upper_half_increment_.resize(coeff_bit_count);
        multiply_truncate_uint_uint(plain_modulus_.pointer(), coeff_div_plain_modulus_.pointer(), coeff_uint64_count, upper_half_increment_.pointer());
        sub_uint_uint(coeff_modulus_.pointer(), upper_half_increment_.pointer(), coeff_uint64_count, upper_half_increment_.pointer());

        // Widen coeff modulus.
        int product_coeff_bit_count = coeff_bit_count + coeff_bit_count + get_significant_bit_count(static_cast<uint64_t>(coeff_count));
        int plain_modulus_bit_count = plain_modulus_.significant_bit_count();
        int wide_bit_count = product_coeff_bit_count + plain_modulus_bit_count;
        int wide_uint64_count = divide_round_up(wide_bit_count, bits_per_uint64);
        wide_coeff_modulus_.resize(wide_bit_count);
        wide_coeff_modulus_ = coeff_modulus_;

        // Calculate wide_coeff_modulus_ / 2.
        wide_coeff_modulus_div_two_.resize(wide_bit_count);
        right_shift_uint(wide_coeff_modulus_.pointer(), 1, wide_uint64_count, wide_coeff_modulus_div_two_.pointer());

        // Initialize moduli.
        polymod_ = PolyModulus(poly_modulus_.pointer(), coeff_count, coeff_uint64_count);
        if (mode_ == TEST_MODE)
        {
            mod_ = Modulus(plain_modulus_.pointer(), coeff_uint64_count, pool_);
        }
        else
        {
            mod_ = Modulus(coeff_modulus_.pointer(), coeff_uint64_count, pool_);
        }
    }
    void Evaluator::multiply(const uint64_t *encrypted1, const uint64_t *encrypted2, uint64_t *destination)
    {
        // Extract encryption parameters.
        int coeff_count = poly_modulus_.coeff_count();
        int coeff_bit_count = poly_modulus_.coeff_bit_count();
        int coeff_uint64_count = divide_round_up(coeff_bit_count, bits_per_uint64);

        // Clear destatintion.
        set_zero_poly(coeff_count, coeff_uint64_count, destination);

        // Determine if FFT can be used.
        bool use_fft = polymod_.coeff_count_power_of_two() >= 0 && polymod_.is_one_zero_one();

        if (use_fft)
        {
            // Use FFT to multiply polynomials.

            // Allocate polynomial to store product of two polynomials, with poly but no coeff modulo yet (and signed).
            int product_coeff_bit_count = coeff_bit_count + coeff_bit_count + get_significant_bit_count(static_cast<uint64_t>(coeff_count)) + 2;
            int product_coeff_uint64_count = divide_round_up(product_coeff_bit_count, bits_per_uint64);
            Pointer product(allocate_poly(coeff_count, product_coeff_uint64_count, pool_));

            // Use FFT to multiply polynomials.
            set_zero_uint(product_coeff_uint64_count, get_poly_coeff(product.get(), coeff_count - 1, product_coeff_uint64_count));
            fftmultiply_poly_poly_polymod(encrypted1, encrypted2, polymod_.coeff_count_power_of_two(), coeff_uint64_count, product_coeff_uint64_count, product.get(), pool_);

            // For each coefficient in product, multiply by plain_modulus and divide by coeff_modulus and then modulo by coeff_modulus.
            int plain_modulus_bit_count = plain_modulus_.significant_bit_count();
            int plain_modulus_uint64_count = divide_round_up(plain_modulus_bit_count, bits_per_uint64);
            int intermediate_bit_count = product_coeff_bit_count + plain_modulus_bit_count - 1;
            int intermediate_uint64_count = divide_round_up(intermediate_bit_count, bits_per_uint64);
            Pointer intermediate(allocate_uint(intermediate_uint64_count, pool_));
            Pointer quotient(allocate_uint(intermediate_uint64_count, pool_));
            for (int coeff_index = 0; coeff_index < coeff_count; ++coeff_index)
            {
                uint64_t *product_coeff = get_poly_coeff(product.get(), coeff_index, product_coeff_uint64_count);
                bool coeff_is_negative = is_high_bit_set_uint(product_coeff, product_coeff_uint64_count);
                if (coeff_is_negative)
                {
                    negate_uint(product_coeff, product_coeff_uint64_count, product_coeff);
                }
                multiply_uint_uint(product_coeff, product_coeff_uint64_count, plain_modulus_.pointer(), plain_modulus_uint64_count, intermediate_uint64_count, intermediate.get());
                add_uint_uint(intermediate.get(), wide_coeff_modulus_div_two_.pointer(), intermediate_uint64_count, intermediate.get());
                divide_uint_uint_inplace(intermediate.get(), wide_coeff_modulus_.pointer(), intermediate_uint64_count, quotient.get(), pool_);
                modulo_uint_inplace(quotient.get(), intermediate_uint64_count, mod_, pool_);
                uint64_t *dest_coeff = get_poly_coeff(destination, coeff_index, coeff_uint64_count);
                if (coeff_is_negative)
                {
                    negate_uint_mod(quotient.get(), coeff_modulus_.pointer(), coeff_uint64_count, dest_coeff);
                }
                else
                {
                    set_uint_uint(quotient.get(), coeff_uint64_count, dest_coeff);
                }
            }
        }
        else
        {
            // Use normal multiplication to multiply polynomials.

            // Allocate polynomial to store product of two polynomials, with no poly or coeff modulo yet.
            int product_coeff_count = coeff_count + coeff_count - 1;
            int product_coeff_bit_count = coeff_bit_count + coeff_bit_count + get_significant_bit_count(static_cast<uint64_t>(coeff_count));
            int product_coeff_uint64_count = divide_round_up(product_coeff_bit_count, bits_per_uint64);
            Pointer product(allocate_poly(product_coeff_count, product_coeff_uint64_count, pool_));

            // Multiply polynomials.
            multiply_poly_poly(encrypted1, coeff_count, coeff_uint64_count, encrypted2, coeff_count, coeff_uint64_count, product_coeff_count, product_coeff_uint64_count, product.get(), pool_);

            // For each coefficient in product, multiply by plain_modulus and divide by coeff_modulus and then modulo by coeff_modulus.
            int plain_modulus_bit_count = plain_modulus_.significant_bit_count();
            int plain_modulus_uint64_count = divide_round_up(plain_modulus_bit_count, bits_per_uint64);
            int intermediate_bit_count = product_coeff_bit_count + plain_modulus_bit_count;
            int intermediate_uint64_count = divide_round_up(intermediate_bit_count, bits_per_uint64);
            Pointer intermediate(allocate_uint(intermediate_uint64_count, pool_));
            Pointer quotient(allocate_uint(intermediate_uint64_count, pool_));
            Pointer productmoded(allocate_poly(product_coeff_count, coeff_uint64_count, pool_));
            for (int coeff_index = 0; coeff_index < product_coeff_count; ++coeff_index)
            {
                const uint64_t *product_coeff = get_poly_coeff(product.get(), coeff_index, product_coeff_uint64_count);
                multiply_uint_uint(product_coeff, product_coeff_uint64_count, plain_modulus_.pointer(), plain_modulus_uint64_count, intermediate_uint64_count, intermediate.get());
                add_uint_uint(intermediate.get(), wide_coeff_modulus_div_two_.pointer(), intermediate_uint64_count, intermediate.get());
                divide_uint_uint_inplace(intermediate.get(), wide_coeff_modulus_.pointer(), intermediate_uint64_count, quotient.get(), pool_);
                modulo_uint_inplace(quotient.get(), intermediate_uint64_count, mod_, pool_);
                uint64_t *productmoded_coeff = get_poly_coeff(productmoded.get(), coeff_index, coeff_uint64_count);
                set_uint_uint(quotient.get(), coeff_uint64_count, productmoded_coeff);
            }

            // Perform polynomial modulo.
            modulo_poly_inplace(productmoded.get(), product_coeff_count, polymod_, mod_, pool_);

            // Copy to destination.
            set_poly_poly(productmoded.get(), coeff_count, coeff_uint64_count, destination);
        }
    }