blob: 6a82be65d40b867add16fc07d95c1317bd500604 [file]
/*
* Copyright 2018 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.
*/
// The constants in this file represent an expected bound on the size of certain
// NTT polynomials. The size is defined as the l-infinity norm over all
// coefficients, in other words, the size of the largest coefficient. Each bound
// is chosen to be 6 * sqrt(V), where V is the NTT coefficients' variance. Even
// after union-bounding over all N coefficients, this provides a
// high-probability bound for the l-infinity norm of the NTT polynomial.
#ifndef RLWE_ERROR_H_
#define RLWE_ERROR_H_
#include <cmath>
#include "montgomery.h"
#include "ntt_parameters.h"
#include "statusor.h"
namespace rlwe {
// A class that stores the error constants. This class only accurate computes
// error when the plaintext modulus is sufficiently small (less than 64 bits).
template <typename ModularInt>
class ErrorParams {
public:
static rlwe::StatusOr<ErrorParams> Create(
const int log_t, Uint64 variance,
const typename ModularInt::Params* params,
const rlwe::NttParameters<ModularInt>* ntt_params) {
if (log_t > static_cast<int>(params->log_modulus) - 1) {
return absl::InvalidArgumentError(
absl::StrCat("The value log_t, ", log_t,
", must be smaller than log_modulus - 1, ",
params->log_modulus - 1, "."));
} else if (log_t <= 0) {
return absl::InvalidArgumentError(
absl::StrCat("The value log_t, ", log_t, ", must be positive."));
}
if (variance > kMaxVariance) {
return absl::InvalidArgumentError(absl::StrCat(
"The variance, ", variance, ", must be at most ", kMaxVariance, "."));
}
return ErrorParams(log_t, variance, params, ntt_params);
}
// Accessors for constants.
double B_plaintext() const { return b_plaintext_; }
double B_encryption() const { return b_encryption_; }
double B_scale() const { return b_scale_; }
// A polynomial consisting of error terms is added to the ciphertext during
// relinearization. The noise of a ciphertext increases additively by the size
// of the polynomial, which depends on the decomposition modulus of the
// key-switching matrix and the number of ciphertext components applied on.
double B_relinearize(int num_components,
int log_decomposition_modulus) const {
// The number of digits needed to represent integers mod modulus in base
// decomposition modulus.
int num_digits = (log_decomposition_modulus + log_modulus_ - 1) /
log_decomposition_modulus;
double decomposition_modulus = static_cast<double>(
static_cast<typename ModularInt::Int>(1) << log_decomposition_modulus);
return (8.0 / sqrt(3.0)) * ExportDoubleT() * num_digits * sigma_ *
dimension_ * decomposition_modulus * num_components;
}
// Returns the "size" of the error term introduced during relinearization
// using the auxiliary modulus technique. The error term can be expressed as
// p^(-1) * (t * sum(ei * ci, i=1..k) + d0 + s * d1), where p is the auxiliary
// modulus, ci is a ciphertext component wrt modulus q, ei is a fresh error
// term in the relinearization key, k = `num_components` is the number of key
// components of a relinearization key, s is the secret key polynomial, and
// d0, d1 are small conversion errors.
// We bound the coefficients of c_i by the ciphertext modulus q, and d0, d1
// by t * p; thus in the NTT domain, the norm of the entire error term is
// bounded by t * (N * q / p * 6 * sigma * k + sqrt(N) * 6 * sigma).
double B_aux_mod_relinearize(
int num_components,
const typename ModularInt::Params& mod_params_aux) const {
double p =
static_cast<double>(ModularInt::ExportUInt64(mod_params_aux.modulus));
double f = std::pow(2, log_modulus_);
f /= p;
return ExportDoubleT() * 6 * sigma_ *
(dimension_ * f * num_components + sqrt(dimension_));
}
// This represents the "size" of a freshly encrypted ciphertext using a public
// key, where the public key's error term, the public-key encryption's random
// element and error terms are all sampled from a centered binomial
// distribution with the specified standard deviation `sigma`. The error in a
// fresh public-key encryption is t * (v * e + e' + s * e''), where s, v, e,
// e', e'' are all sampled from the same error distribution of variance
// sigma^2. In the NTT domain, the norm of this error term is bounded by t *
// (72 * N * sigma^2 + 6 * sqrt(N) * sigma). Then adding the bound on the
// message t * sqrt(3 * N) and we get the bound on the error and message.
rlwe::StatusOr<double> B_publickey_encryption(int dimension,
int variance) const {
if (variance <= 0) {
return absl::InvalidArgumentError(
absl::StrCat("The variance, ", variance, ", must be positive."));
}
double sigma = sqrt(static_cast<double>(variance));
return ExportDoubleT() * (sqrt(dimension) * (6 * sigma + sqrt(3)) +
72 * dimension * variance);
}
private:
// Constructor to set up the params.
ErrorParams(const int log_t, Uint64 variance,
const typename ModularInt::Params* params,
const rlwe::NttParameters<ModularInt>* ntt_params)
: t_(params->One()) {
t_ = (params->One() << log_t) + params->One();
dimension_ = ntt_params->number_coeffs;
sigma_ = sqrt(variance);
log_modulus_ = params->log_modulus;
// Set error constants.
b_plaintext_ = B_plaintext(dimension_);
b_encryption_ = B_encryption(dimension_, sigma_);
b_scale_ = B_scale(dimension_);
}
// This represents the "size" of an NTT coefficient of a randomly sampled
// plaintext polynomial. The ciphertext error grows multiplicatively by this
// constant under an absorb. Assume the plaintext polynomial has coefficients
// chosen uniformly at random from the range [0, t), where t is the plaintext
// modulus. Then the variance of a coefficient is V = t ^ 2 / 12. In the NTT
// domain, the variance is (dimension * t ^ 2 / 12).
double B_plaintext(int dimension) {
// Return 6 * sqrt(V) where V is the variance of a coefficient in the NTT
// domain.
return ExportDoubleT() * sqrt(3.0 * dimension);
}
// This represents the "size" of a freshly encrypted ciphertext with a secret
// key and error sampled from a centered binomial distribution with the
// specified variance. The error and message have size |m + et|. Like
// B_plaintext, the variance of a coefficient of m is V = t ^ 2 / 12, and the
// variance of a coefficient of e is sigma ^ 2. In the NTT domain we can bound
// the coefficient's variance by (dimension * (t ^ 2 / 12 + t * sigma)). The
// bound 6 * sqrt(V) is then t * sqrt(dimension) * (sqrt(3.0) + 6.0 * sigma).
double B_encryption(int dimension, double sigma) {
return ExportDoubleT() * sqrt(dimension) * (sqrt(3.0) + 6.0 * sigma);
}
// When modulus switching a ciphertext from a modulus q to a smaller modulus
// p, the polynomial is scaled by (p / q) and a small rounding polynomial is
// added so that the result is the closest integer polynomial with c' = c mod
// t. The rounding polynomial's size contributes additively to the ciphertext
// error, and its size is given by this constant.
double B_scale(int dimension) {
return ExportDoubleT() *
(sqrt(3.0 * dimension) + 8.0 * dimension * sqrt(1 / 3.0));
}
// This returns the least 64 bits of t_. If t_ is much larger than 64 bits,
// this will return inaccurate error estimates.
double ExportDoubleT() const {
return static_cast<double>(ModularInt::ExportUInt64(t_));
}
double b_plaintext_;
double b_encryption_;
double b_scale_;
int log_modulus_;
typename ModularInt::Int t_;
int dimension_;
double sigma_;
};
} // namespace rlwe
#endif // RLWE_ERROR_H_