blob: bf33156756c03b75e74e0f57c290f2b6297ef53a [file]
/*
* Copyright 2023 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_DFT_TRANSFORMATIONS_H_
#define RLWE_DFT_TRANSFORMATIONS_H_
#include <complex>
#include <utility>
#include <vector>
#include "absl/status/status.h"
#include "ntt_parameters.h"
#include "status_macros.h"
// This file implements various discrete Fourier transformations to be used by
// arithmetic in polynomial rings.
namespace rlwe {
// Performs `log_len` iterations of the Cooley-Tukey butterfly on `coeffs`
// in-place, which has length 2^log_len.
//
// This is an implementation of the FFT from [Sei18, Sec. 2].
// [Sei18] https://eprint.iacr.org/2018/039
// All polynomial arithmetic performed is modulo (x^n+1) for n a power of two,
// with the coefficients operated on modulo a prime modulus.
//
// Let psi be a primitive 2n-th root of the unity, i.e., psi is a 2n-th root
// of unity such that psi^n = -1. Hence it holds that
// x^n+1 = x^n-psi^n = (x^n/2-psi^n/2)*(x^n/2+psi^n/2)
//
//
// If f = f_0 + f_1*x + ... + f_{n-1}*x^(n-1) is the polynomial to transform,
// the i-th coefficient of the polynomial mod x^n/2-psi^n/2 can thus be
// computed as
// f'_i = f_i + psi^(n/2)*f_(n/2+i),
// and the i-th coefficient of the polynomial mod x^n/2+psi^n/2 can thus be
// computed as
// f''_i = f_i - psi^(n/2)*f_(n/2+i)
// This operation is called the Cooley-Tukey butterfly and is done
// iteratively during the NTT.
//
// The FFT can thus be performed in-place and after the k-th level, it
// produces the vector of polynomials with pairs of coefficients
// f mod (x^(n/2^(k+1))-psi^brv[2^k+1]), f mod (x^(n/2^(k+1))+psi^brv[2^k+1])
// where brv maps a log(n)-bit number to its bitreversal.
template <typename ModularInt>
void IterativeCooleyTukey(std::vector<ModularInt>& coeffs, int log_len,
const NttParameters<ModularInt>& ntt_params,
const typename ModularInt::Params& mod_params) {
int index_psi = 1;
for (int i = log_len - 1; i >= 0; i--) {
// Layer i.
const int half_m = 1 << i;
const int m = half_m << 1;
for (int k = 0; k < coeffs.size(); k += m) {
const auto& [psi_constant, psi_constant_barrett] =
ntt_params.psis_bitrev_constant[index_psi];
for (int j = 0; j < half_m; j++) {
// The Cooley-Tukey butterfly operation.
ModularInt t = coeffs[k + j + half_m].MulConstant(
psi_constant, psi_constant_barrett, &mod_params);
ModularInt u = coeffs[k + j];
// Every odd layer, we perform *lazy operations* (i.e., we do not
// reduce in [0, modulus] but keeps the result of the operations in
// [0, 2*modulus]). At the beginning of the even layers, the input
// will therefore be in [0, 2*modulus] and therefore after applying
// addition or subtraction, the result will be in [0, 4*modulus].
// Since 4*modulus fits in a Int, this enables the computation to
// remains correct: we hence perform the regular modular addition and
// subtraction which reduce the values to [0, modulus] at the end of
// the even layers.
if (i & 1) {
coeffs[k + j].LazyAddInPlace(t, &mod_params);
// Note that the variable t is reduced in [0, modulus], which is
// required by the LazySubInPlace function.
coeffs[k + j + half_m] = std::move(u.LazySubInPlace(t, &mod_params));
} else {
coeffs[k + j].AddInPlace(t, &mod_params);
// Note that the variable t is reduced in [0, modulus], which is
// required by the SubInPlace function.
coeffs[k + j + half_m] = std::move(u.SubInPlace(t, &mod_params));
}
}
index_psi++;
}
}
}
// Performs `log_len` iterations of the Gentleman-Sande butterfly on `coeffs`
// in-place, which has length 2^log_len.
//
// This implements the backward NTT transformation, which is computed similarly
// by iteratively inverting the NTT representation. For instance, using the same
// notation as the forward NTT above,
// f'_i + f''_i = 2f_i and psi^(-n/2)*(f'_i-f''_i) = 2c_(n/2+i).
//
// In particular, the butterfly operation differs from the Cooley-Tukey
// butterfly used during the forward transform in that addition and
// substraction come before multiplying with a power of the root of unity.
// This butterfly operation is called the Gentleman-Sande butterfly.
//
// Note: To complete the inverse NTT transformation, a normalization step by
// the inverse of n=2^log_len (the factor 2 obtained at each level of the
// butterfly) is required after calling this function.
template <typename ModularInt>
absl::Status IterativeGentlemanSande(
std::vector<ModularInt>& coeffs, int log_len,
const NttParameters<ModularInt>& ntt_params,
const typename ModularInt::Params& mod_params) {
int index_psi_inv = 0;
for (int i = 0; i < log_len; i++) {
const int half_m = 1 << i;
const int m = half_m << 1;
for (int k = 0; k < coeffs.size(); k += m) {
if (index_psi_inv >= ntt_params.psis_inv_bitrev_constant.size()) {
return absl::InvalidArgumentError("Not enough psis provided.");
}
const auto& [psi_inv_constant, psi_inv_constant_barrett] =
ntt_params.psis_inv_bitrev_constant[index_psi_inv];
for (int j = 0; j < half_m; j++) {
// The Gentleman-Sande butterfly operation.
if (k + j + half_m >= coeffs.size()) {
return absl::InvalidArgumentError(
"Vector too short for applying iterative Gentleman-Sande.");
}
ModularInt t = coeffs[k + j + half_m];
ModularInt u = coeffs[k + j];
coeffs[k + j].AddInPlace(t, &mod_params);
// Since the input to MulConstantInPlace can be in [0, 2*modulus], we
// only perform a lazy subtraction.
coeffs[k + j + half_m] = std::move(
u.LazySubInPlace(t, &mod_params)
.MulConstantInPlace(psi_inv_constant, psi_inv_constant_barrett,
&mod_params));
}
index_psi_inv++;
}
}
return absl::OkStatus();
}
// Performs the forward NTT transformation from R_q = Z[X]/(q, X^N+1) to Z_q^N
// in-place, where q is a prime and N is a power of two such q == 1 (mod 2N).
// The input polynomial is represented by its coefficient vector `coeffs`.
template <typename ModularInt>
absl::Status ForwardNumberTheoreticTransform(
std::vector<ModularInt>& coeffs,
const NttParameters<ModularInt>& ntt_params,
const typename ModularInt::Params& mod_params) {
// Check to ensure that the coefficient vector is of the correct length.
int log_len = log2(coeffs.size());
int expected_len = 1 << log_len;
if (coeffs.size() != expected_len) {
return absl::InvalidArgumentError(absl::StrCat(
"`coeffs` must have contain ", expected_len, " elements."));
}
// This is a simple wrapper of the Cooley-Tukey FFT impl for now, but we may
// consider optimized implementation using different hardware acceleration
// in the future.
IterativeCooleyTukey(coeffs, log_len, ntt_params, mod_params);
return absl::OkStatus();
}
// Performs the backward NTT transformation from Z_q^N to R_q = Z[X]/(q, X^N+1)
// in-place, where q is a prime and N is a power of two such q == 1 (mod 2N).
// The coefficients of the output polynomial is written to `coeffs`.
template <typename ModularInt>
absl::Status InverseNumberTheoreticTransform(
std::vector<ModularInt>& coeffs,
const NttParameters<ModularInt>& ntt_params,
const typename ModularInt::Params& mod_params) {
int log_len = log2(coeffs.size());
int expected_len = 1 << log_len;
if (coeffs.size() != expected_len) {
return absl::InvalidArgumentError(absl::StrCat(
"`coeffs` must have contain ", expected_len, " elements."));
}
RLWE_RETURN_IF_ERROR(
IterativeGentlemanSande(coeffs, log_len, ntt_params, mod_params));
// Normalize the result by multiplying by the inverse of n.
ModularInt n_inv = ntt_params.n_inv_ptr.value();
for (auto& coeff : coeffs) {
coeff.MulInPlace(n_inv, &mod_params);
}
return absl::OkStatus();
}
// Performs the special log_len iterations of the Cooley-Tukey butterfly on
// `coeffs` in-place, which has length 2^log_len.
// This computes the DFT transform phi: coeffs -> {coeffs(psi^(4k+1))}_j
// where coeffs are coefficients of a polynomial of degree 2^log_len - 1, and
// {psi_j}_j are primitive 2N'th roots of unity for N = 2^(log_len + 1), i.e.
// psi = exp(PI * I / (2N)).
absl::Status IterativeHalfCooleyTukey(
std::vector<std::complex<double>>& coeffs,
const std::vector<std::complex<double>>& psis_bitrev);
// Performs the special log_len iterations of the Gentleman-Sande butterfly on
// `coeffs` in-place, which has length 2^log_len.
// This computes the inverse of the transform phi, where phi is the DFT
// phi: coeffs -> {coeffs(psi_j)}_{j = 4*k+1}.
absl::Status IterativeHalfGentlemanSande(
std::vector<std::complex<double>>& coeffs,
const std::vector<std::complex<double>>& psis_bitrev_inv);
} // namespace rlwe
#endif // RLWE_DFT_TRANSFORMATIONS_H_