blob: 2abc1dd46a63f90bfe2700ab98e4f3409b1ad019 [file]
/*
* Copyright 2017 Google LLC.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef RLWE_POLYNOMIAL_H_
#define RLWE_POLYNOMIAL_H_
#include <cmath>
#include <vector>
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "constants.h"
#include "dft_transformations.h"
#include "dft_transformations_hwy.h"
#include "ntt_parameters.h"
#include "opt/constant_polynomial.h"
#include "prng/prng.h"
#include "serialization.pb.h"
#include "status_macros.h"
#include "statusor.h"
namespace rlwe {
// A polynomial in NTT form. The length of the polynomial must be a power of 2.
template <typename ModularInt>
class Polynomial {
using ModularIntParams = typename ModularInt::Params;
public:
// Default constructor.
Polynomial() = default;
// Copy constructor.
Polynomial(const Polynomial& p) = default;
Polynomial& operator=(const Polynomial& that) = default;
// Basic constructor.
explicit Polynomial(std::vector<ModularInt> poly_coeffs)
: log_len_(log2(poly_coeffs.size())), coeffs_(std::move(poly_coeffs)) {}
// Create an empty polynomial of the specified length. The length must be
// a power of 2.
explicit Polynomial(int len, const ModularIntParams* params)
: Polynomial(
std::vector<ModularInt>(len, ModularInt::ImportZero(params))) {}
// This is an implementation of the FFT from [Sei18, Sec. 2].
// [Sei18] https://eprint.iacr.org/2018/039
// For any polynomial f(X) in R_q = Z[X]/(q, X^n+1) for n a power of two and q
// a prime modulus such that q == 1 (mod 2n), the NTT form of f(X) is the
// vector
// NTT(f) = (f(psi), f(psi^3), .., f(psi^(n/2-1))),
// where psi is a primitive 2n-th root of unity modulo q. By Chinese Remainder
// Theorem, the map f -> NTT(f) is an isomorphism between R_q and Z_q^n, and
// it can be computed using FFT (in bit reversed order).
// For details see dft_transformations.h.
static Polynomial ConvertToNtt(std::vector<ModularInt> poly_coeffs,
const NttParameters<ModularInt>* ntt_params,
const ModularIntParams* modular_params) {
if (!ForwardNumberTheoreticTransform(poly_coeffs, *ntt_params,
*modular_params)
.ok()) {
// An error value.
return Polynomial();
}
return Polynomial(std::move(poly_coeffs));
}
static rlwe::StatusOr<Polynomial> ConvertToNttFast(
std::vector<ModularInt> poly_coeffs,
const NttParameters<ModularInt>* ntt_params,
const ModularIntParams* modular_params) {
RLWE_RETURN_IF_ERROR(internal::ForwardNumberTheoreticTransformHwy(
poly_coeffs, *ntt_params, *modular_params));
return Polynomial(std::move(poly_coeffs));
}
// Deprecated ConvertToNtt function taking NttParameters by constant reference
ABSL_DEPRECATED("Use ConvertToNtt function with NttParameters pointer above.")
static Polynomial ConvertToNtt(std::vector<ModularInt> poly_coeffs,
const NttParameters<ModularInt>& ntt_params,
const ModularIntParams* modular_params) {
return ConvertToNtt(std::move(poly_coeffs), &ntt_params, modular_params);
}
// The inverse NTT transform is computed similarly by iteratively inverting
// the NTT representation. For instance, using the same notation as above,
// f'_i + f''_i = 2f_i and psi^(-n/2)*(f'_i-f''_i) = 2c_(n/2+i).
//
// For details see dft_transformations.h.
//
// Returns a copy of the polynomial on errors.
std::vector<ModularInt> InverseNtt(
const NttParameters<ModularInt>* ntt_params,
const ModularIntParams* modular_params) const {
if (ntt_params == nullptr || modular_params == nullptr ||
!ntt_params->n_inv_ptr.has_value()) {
return coeffs_;
}
std::vector<ModularInt> coeffs_copy = coeffs_;
if (!InverseNumberTheoreticTransform(coeffs_copy, *ntt_params,
*modular_params)
.ok()) {
return coeffs_;
}
return coeffs_copy;
}
// Deprecated InverseNtt function taking NttParameters by constant reference
ABSL_DEPRECATED("Use InverseNtt function with NttParameters pointer above.")
std::vector<ModularInt> InverseNtt(
const NttParameters<ModularInt>& ntt_params,
const ModularIntParams* modular_params) const {
return InverseNtt(&ntt_params, modular_params);
}
// Specifies whether the Polynomial is valid.
bool IsValid() const { return !coeffs_.empty(); }
// Scalar multiply.
rlwe::StatusOr<Polynomial> Mul(const ModularInt& scalar,
const ModularIntParams* modular_params) const {
Polynomial output = *this;
RLWE_RETURN_IF_ERROR(output.MulInPlace(scalar, modular_params));
return output;
}
// Scalar multiply in place.
absl::Status MulInPlace(const ModularInt& scalar,
const ModularIntParams* modular_params) {
return ModularInt::BatchMulInPlace(&coeffs_, scalar, modular_params);
}
// Coordinate-wise multiplication.
rlwe::StatusOr<Polynomial> Mul(const Polynomial& that,
const ModularIntParams* modular_params) const {
Polynomial output = *this;
RLWE_RETURN_IF_ERROR(output.MulInPlace(that, modular_params));
return output;
}
// Coordinate-wise multiplication in place.
absl::Status MulInPlace(const Polynomial& that,
const ModularIntParams* modular_params) {
return ModularInt::BatchMulInPlace(&coeffs_, that.coeffs_, modular_params);
}
// Fused Multiply Add in place: this += a * b.
absl::Status FusedMulAddInPlace(const Polynomial& a, const Polynomial& b,
const ModularIntParams* modular_params) {
return ModularInt::BatchFusedMulAddInPlace(&coeffs_, a.coeffs_, b.coeffs_,
modular_params);
}
// Fused Multiply Add in place, where the multiplication is with a constant
// polynomial.
absl::Status FusedMulConstantAddInPlace(
const Polynomial& a, const ConstantPolynomial<ModularInt>& b,
const ModularIntParams* modular_params) {
return ModularInt::BatchFusedMulConstantAddInPlace(
&coeffs_, a.coeffs_, b.coeffs_constant_, b.coeffs_constant_barrett_,
modular_params);
}
// Negation.
Polynomial Negate(const ModularIntParams* modular_params) const {
Polynomial output = *this;
output.NegateInPlace(modular_params);
return output;
}
// Negation in place.
Polynomial& NegateInPlace(const ModularIntParams* modular_params) {
for (auto& coeff : coeffs_) {
coeff.NegateInPlace(modular_params);
}
return *this;
}
// Coordinate-wise addition.
rlwe::StatusOr<Polynomial> Add(const Polynomial& that,
const ModularIntParams* modular_params) const {
Polynomial output = *this;
RLWE_RETURN_IF_ERROR(output.AddInPlace(that, modular_params));
return output;
}
// Coordinate-wise substraction.
rlwe::StatusOr<Polynomial> Sub(const Polynomial& that,
const ModularIntParams* modular_params) const {
Polynomial output = *this;
RLWE_RETURN_IF_ERROR(output.SubInPlace(that, modular_params));
return output;
}
// Coordinate-wise addition in place.
absl::Status AddInPlace(const Polynomial& that,
const ModularIntParams* modular_params) {
return ModularInt::BatchAddInPlace(&coeffs_, that.coeffs_, modular_params);
}
// Coordinate-wise substraction in place.
absl::Status SubInPlace(const Polynomial& that,
const ModularIntParams* modular_params) {
return ModularInt::BatchSubInPlace(&coeffs_, that.coeffs_, modular_params);
}
// Substitute: Given an Polynomial representing p(x), returns an
// Polynomial representing p(x^power). Power must be an odd non-negative
// integer less than 2 * Len().
rlwe::StatusOr<Polynomial> Substitute(
const int power, const NttParameters<ModularInt>* ntt_params,
const ModularIntParams* modulus_params) const {
// The NTT representation consists in the evaluations of the polynomial at
// roots psi^brv[n/2], psi^brv[n/2+1], ..., psi^brv[n/2+n/2-1],
// psi^(n/2+brv[n/2+1]), ..., psi^(n/2+brv[n/2+n/2-1]).
// Let f(x) be the original polynomial, and out(x) be the polynomial after
// the substitution. Note that (psi^i)^power = psi^{(i * power) % (2 * n).
if (0 > power || (power % 2) == 0 || power >= 2 * Len()) {
return absl::InvalidArgumentError(
absl::StrCat("Substitution power must be a non-negative odd "
"integer less than 2*n."));
}
Polynomial out = *this;
// Get the index of the psi^power evaluation
int psi_power_index = (power - 1) / 2;
// Update the coefficients one by one: remember that they are stored in
// bitreversed order.
for (int i = 0; i < Len(); i++) {
if (ntt_params->bitrevs[psi_power_index] >= coeffs_.size()) {
return absl::InternalError(absl::StrFormat(
"Index %d out-of-bounds in coeffs_ of size %d.",
ntt_params->bitrevs[psi_power_index], coeffs_.size()));
}
out.coeffs_[ntt_params->bitrevs[i]] =
coeffs_[ntt_params->bitrevs[psi_power_index]];
// Each time the index increases by 1, the psi_power_index increases by
// power mod the length.
psi_power_index = (psi_power_index + power) % Len();
}
return out;
}
// Deprecated Substitute function taking NttParameters by constant reference
ABSL_DEPRECATED("Use Substitute function with NttParameters pointer above.")
rlwe::StatusOr<Polynomial> Substitute(
const int power, const NttParameters<ModularInt>& ntt_params,
const ModularIntParams* modulus_params) const {
return Substitute(power, &ntt_params, modulus_params);
}
// Boolean comparison.
bool operator==(const Polynomial& that) const {
if (Len() != that.Len()) {
return false;
}
for (int i = 0; i < Len(); i++) {
if (coeffs_[i] != that.coeffs_[i]) {
return false;
}
}
return true;
}
bool operator!=(const Polynomial& that) const { return !(*this == that); }
int LogLen() const { return log_len_; }
int Len() const { return coeffs_.size(); }
// Accessor for coefficients.
const std::vector<ModularInt>& Coeffs() const { return coeffs_; }
rlwe::StatusOr<SerializedNttPolynomial> Serialize(
const ModularIntParams* modular_params) const {
SerializedNttPolynomial output;
RLWE_ASSIGN_OR_RETURN(*(output.mutable_coeffs()),
ModularInt::SerializeVector(coeffs_, modular_params));
output.set_num_coeffs(coeffs_.size());
return output;
}
static rlwe::StatusOr<Polynomial> Deserialize(
const SerializedNttPolynomial& serialized,
const ModularIntParams* modular_params) {
if (serialized.num_coeffs() <= 0) {
return absl::InvalidArgumentError(
"Number of serialized coefficients must be positive.");
} else if (serialized.num_coeffs() > static_cast<int>(kMaxNumCoeffs)) {
return absl::InvalidArgumentError(absl::StrCat(
"Number of serialized coefficients, ", serialized.num_coeffs(),
", must be less than ", kMaxNumCoeffs, "."));
}
Polynomial output(serialized.num_coeffs(), modular_params);
RLWE_ASSIGN_OR_RETURN(
output.coeffs_,
ModularInt::DeserializeVector(serialized.num_coeffs(),
serialized.coeffs(), modular_params));
return output;
}
// Compute a ConstantPolynomial representation of the coefficients for faster
// multiplication.
StatusOr<ConstantPolynomial<ModularInt>> ComputeConstantRepresentation(
const ModularIntParams* modular_params) const {
std::vector<typename ModularInt::Int> coeffs_constant,
coeffs_constant_barrett;
coeffs_constant.reserve(coeffs_.size());
coeffs_constant_barrett.reserve(coeffs_.size());
for (const ModularInt& coeff : coeffs_) {
const auto [constant, constant_barrett] =
coeff.GetConstant(modular_params);
coeffs_constant.push_back(constant);
coeffs_constant_barrett.push_back(constant_barrett);
}
return ConstantPolynomial<ModularInt>::Create(
std::move(coeffs_constant), std::move(coeffs_constant_barrett));
}
// Coordinate-wise multiplication by a constant polynomial.
rlwe::StatusOr<Polynomial> MulConstant(
const ConstantPolynomial<ModularInt>& that,
const ModularIntParams* modular_params) const {
Polynomial output(*this);
RLWE_RETURN_IF_ERROR(output.MulConstantInPlace(that, modular_params));
return output;
}
// Coordinate-wise multiplication in place by a constant polynomial.
absl::Status MulConstantInPlace(const ConstantPolynomial<ModularInt>& that,
const ModularIntParams* modular_params) {
return ModularInt::BatchMulConstantInPlace(&coeffs_, that.coeffs_constant_,
that.coeffs_constant_barrett_,
modular_params);
}
private:
// Instance variables.
size_t log_len_;
std::vector<ModularInt> coeffs_;
};
template <typename ModularInt, typename Prng = rlwe::SecurePrng>
rlwe::StatusOr<Polynomial<ModularInt>> SamplePolynomialFromPrng(
int num_coeffs, Prng* prng,
const typename ModularInt::Params* modulus_params) {
// Sample a from the uniform distribution. Since a is uniformly distributed,
// it can be generated directly in NTT form since the NTT transformation is
// an automorphism.
if (num_coeffs < 1) {
return absl::InvalidArgumentError(
"SamplePolynomialFromPrng: number of coefficients must be a "
"non-negative integer.");
}
std::vector<ModularInt> a_ntt_coeffs;
a_ntt_coeffs.reserve(num_coeffs);
for (int i = 0; i < num_coeffs; i++) {
RLWE_ASSIGN_OR_RETURN(ModularInt coefficient,
ModularInt::ImportRandom(prng, modulus_params));
a_ntt_coeffs.push_back(std::move(coefficient));
}
return Polynomial<ModularInt>(std::move(a_ntt_coeffs));
}
} // namespace rlwe
#endif // RLWE_POLYNOMIAL_H_