chromium/third_party/shell-encryption/src/testing/coefficient_polynomial_ciphertext_test.cc

/*
 * 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.
 */

#include "testing/coefficient_polynomial_ciphertext.h"

#include <vector>

#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "constants.h"
#include "montgomery.h"
#include "ntt_parameters.h"
#include "polynomial.h"
#include "symmetric_encryption.h"
#include "testing/coefficient_polynomial.h"
#include "testing/status_matchers.h"
#include "testing/status_testing.h"
#include "testing/testing_prng.h"
#include "testing/testing_utils.h"

namespace {

using ::rlwe::testing::StatusIs;
using ::testing::HasSubstr;

unsigned int seed = 0;

// Useful typedefs.
using uint_m = rlwe::MontgomeryInt<rlwe::Uint32>;
using Polynomial = rlwe::Polynomial<uint_m>;
using CoefficientPolynomial = rlwe::testing::CoefficientPolynomial<uint_m>;
using Ciphertext = rlwe::SymmetricRlweCiphertext<uint_m>;
using PolynomialCiphertext =
    rlwe::testing::CoefficientPolynomialCiphertext<uint_m>;
using Key = rlwe::SymmetricRlweKey<uint_m>;

// Set constants.
const uint_m::Int kDefaultLogT = 2;
const uint_m::Int kDefaultT = (1 << kDefaultLogT) + 1;
const uint_m::Int kDefaultVariance = 8;
const uint_m::Int kCoeffs = rlwe::kNewhopeDegreeBound;
const uint_m::Int kLogCoeffs = rlwe::kNewhopeLogDegreeBound;

class PolynomialCiphertextTest : public ::testing::Test {
 protected:
  void SetUp() override {
    ASSERT_OK_AND_ASSIGN(params14_,
                         rlwe::testing::ConstructMontgomeryIntParams());
    ASSERT_OK_AND_ASSIGN(auto ntt_params,
                         rlwe::InitializeNttParameters<uint_m>(
                             rlwe::testing::kLogCoeffs, params14_.get()));
    ntt_params_ = absl::make_unique<const rlwe::NttParameters<uint_m>>(
        std::move(ntt_params));
    ASSERT_OK_AND_ASSIGN(
        auto error_params,
        rlwe::ErrorParams<uint_m>::Create(kDefaultLogT, kDefaultVariance,
                                          params14_.get(), ntt_params_.get()));
    error_params_ =
        absl::make_unique<const rlwe::ErrorParams<uint_m>>(error_params);
  }

  // Sample a random key.
  rlwe::StatusOr<Key> SampleKey(uint_m::Int variance = kDefaultVariance,
                                uint_m::Int log_t = kDefaultLogT) {
    RLWE_ASSIGN_OR_RETURN(std::string prng_seed,
                          rlwe::SingleThreadPrng::GenerateSeed());
    RLWE_ASSIGN_OR_RETURN(auto prng, rlwe::SingleThreadPrng::Create(prng_seed));
    return Key::Sample(kLogCoeffs, variance, log_t, params14_.get(),
                       ntt_params_.get(), prng.get());
  }

  // Encrypt a plaintext.
  rlwe::StatusOr<Ciphertext> Encrypt(
      const Key& key, const std::vector<uint_m::Int>& plaintext) {
    RLWE_ASSIGN_OR_RETURN(auto mp, rlwe::testing::ConvertToMontgomery<uint_m>(
                                       plaintext, params14_.get()));
    auto plaintext_ntt =
        Polynomial::ConvertToNtt(mp, ntt_params_.get(), key.ModulusParams());
    RLWE_ASSIGN_OR_RETURN(std::string prng_seed,
                          rlwe::SingleThreadPrng::GenerateSeed());
    RLWE_ASSIGN_OR_RETURN(auto prng, rlwe::SingleThreadPrng::Create(prng_seed));
    return rlwe::Encrypt<uint_m>(key, plaintext_ntt, error_params_.get(),
                                 prng.get());
  }

  std::unique_ptr<const uint_m::Params> params14_;
  std::unique_ptr<const rlwe::NttParameters<uint_m>> ntt_params_;
  std::unique_ptr<const rlwe::ErrorParams<uint_m>> error_params_;
};

TEST_F(PolynomialCiphertextTest, CanDecryptAfterConversion) {
  ASSERT_OK_AND_ASSIGN(auto key, SampleKey());
  auto plaintext = rlwe::testing::SamplePlaintext<uint_m>();
  ASSERT_OK_AND_ASSIGN(auto ciphertext, Encrypt(key, plaintext));
  ASSERT_OK_AND_ASSIGN(auto coefficient_ciphertext,
                       PolynomialCiphertext::ConvertToCoefficients(
                           ciphertext, ntt_params_.get()));
  auto ntt_ciphertext = coefficient_ciphertext.ConvertToNtt(ntt_params_.get());
  ASSERT_OK_AND_ASSIGN(auto coefficient_decrypted,
                       rlwe::Decrypt<uint_m>(key, ntt_ciphertext));

  EXPECT_EQ(plaintext, coefficient_decrypted);
}

TEST_F(PolynomialCiphertextTest, CoefficientHomomorphicAdd) {
  // Ensure PolynomialCiphertext adds.
  ASSERT_OK_AND_ASSIGN(auto key, SampleKey());

  std::vector<uint_m::Int> plaintext1 =
      rlwe::testing::SamplePlaintext<uint_m>();
  std::vector<uint_m::Int> plaintext2 =
      rlwe::testing::SamplePlaintext<uint_m>();

  ASSERT_OK_AND_ASSIGN(auto ciphertext1, Encrypt(key, plaintext1));
  ASSERT_OK_AND_ASSIGN(auto ciphertext2, Encrypt(key, plaintext2));

  // Homomorphic add in the polynomial domain.
  ASSERT_OK_AND_ASSIGN(auto polynomial1,
                       PolynomialCiphertext::ConvertToCoefficients(
                           ciphertext1, ntt_params_.get()));
  ASSERT_OK_AND_ASSIGN(auto polynomial2,
                       PolynomialCiphertext::ConvertToCoefficients(
                           ciphertext2, ntt_params_.get()));
  ASSERT_OK_AND_ASSIGN(auto polynomial3, polynomial1 + polynomial2);

  // Decrypt result.
  auto ciphertext3 = polynomial3.ConvertToNtt(ntt_params_.get());
  ASSERT_OK_AND_ASSIGN(std::vector<uint_m::Int> decrypted,
                       rlwe::Decrypt<uint_m>(key, ciphertext3));

  for (unsigned int j = 0; j < plaintext1.size(); j++) {
    EXPECT_EQ((plaintext1[j] + plaintext2[j]) % kDefaultT, decrypted[j]);
  }
}

TEST_F(PolynomialCiphertextTest, HomomorphicAddDifferentComponents) {
  // Ensure PolynomialCiphertext adds.
  uint_m::Int log_t = 1;
  uint_m::Int variance = 4;
  ASSERT_OK_AND_ASSIGN(auto key, SampleKey(variance, log_t));

  std::vector<uint_m::Int> plaintext1 =
      rlwe::testing::SamplePlaintext<uint_m>(key.Len(), (1 << log_t) + 1);
  ASSERT_OK_AND_ASSIGN(auto mp1, rlwe::testing::ConvertToMontgomery<uint_m>(
                                     plaintext1, params14_.get()));
  CoefficientPolynomial plaintext1_poly(mp1, params14_.get());
  std::vector<uint_m::Int> plaintext2 =
      rlwe::testing::SamplePlaintext<uint_m>(key.Len(), (1 << log_t) + 1);
  ASSERT_OK_AND_ASSIGN(auto mp2, rlwe::testing::ConvertToMontgomery<uint_m>(
                                     plaintext2, params14_.get()));
  CoefficientPolynomial plaintext2_poly(mp2, params14_.get());

  // Create ciphertexts of different lengths.
  ASSERT_OK_AND_ASSIGN(auto two_component_ciphertext, Encrypt(key, plaintext1));
  ASSERT_OK_AND_ASSIGN(auto intermediate, Encrypt(key, plaintext2));
  ASSERT_OK_AND_ASSIGN(auto three_component_ciphertext,
                       intermediate* two_component_ciphertext);

  // Homomorphic add in the polynomial domain.
  ASSERT_OK_AND_ASSIGN(auto polynomial1,
                       PolynomialCiphertext::ConvertToCoefficients(
                           two_component_ciphertext, ntt_params_.get()));
  ASSERT_OK_AND_ASSIGN(auto polynomial2,
                       PolynomialCiphertext::ConvertToCoefficients(
                           three_component_ciphertext, ntt_params_.get()));
  ASSERT_OK_AND_ASSIGN(auto polynomial3, polynomial1 + polynomial2);

  // Decrypt result.
  auto ciphertext3 = polynomial3.ConvertToNtt(ntt_params_.get());
  ASSERT_OK_AND_ASSIGN(std::vector<uint_m::Int> decrypted,
                       rlwe::Decrypt<uint_m>(key, ciphertext3));

  // Create expected result.
  ASSERT_OK_AND_ASSIGN(auto product, plaintext2_poly* plaintext1_poly);
  ASSERT_OK_AND_ASSIGN(CoefficientPolynomial expected_poly,
                       plaintext1_poly + product);
  std::vector<uint_m::Int> expected =
      rlwe::RemoveError<uint_m>(expected_poly.Coeffs(), params14_->modulus,
                                (1 << log_t) + 1, params14_.get());

  EXPECT_EQ(expected, decrypted);
}

TEST_F(PolynomialCiphertextTest, MonomialOutOfRange) {
  ASSERT_OK_AND_ASSIGN(auto key, SampleKey());

  std::vector<uint_m::Int> plaintext = rlwe::testing::SamplePlaintext<uint_m>();
  ASSERT_OK_AND_ASSIGN(auto encrypt, Encrypt(key, plaintext));
  ASSERT_OK_AND_ASSIGN(auto coeffs, PolynomialCiphertext::ConvertToCoefficients(
                                        encrypt, ntt_params_.get()));
  EXPECT_THAT(coeffs.MonomialAbsorb(2 * key.Len()),
              StatusIs(::absl::StatusCode::kInvalidArgument,
                       HasSubstr("Monomial to absorb must have non-negative "
                                 "degree less than 2n.")));
}

TEST_F(PolynomialCiphertextTest, CoefficientMonomialAbsorb) {
  // Ensure that an absorb for monomials works correctly.
  ASSERT_OK_AND_ASSIGN(auto key, SampleKey());

  // Create a random plaintext and a random monomial.
  std::vector<uint_m::Int> plaintext = rlwe::testing::SamplePlaintext<uint_m>();
  ASSERT_OK_AND_ASSIGN(auto mp, rlwe::testing::ConvertToMontgomery<uint_m>(
                                    plaintext, params14_.get()));
  CoefficientPolynomial plaintext_polynomial(mp, params14_.get());
  std::vector<uint_m::Int> monomial(kCoeffs);
  int monomial_index = rand_r(&seed) % (2 * key.Len());
  monomial[monomial_index] = 1;

  // Create our expected value.
  ASSERT_OK_AND_ASSIGN(
      CoefficientPolynomial expected_polynomial,
      plaintext_polynomial.MonomialMultiplication(monomial_index));
  std::vector<uint_m::Int> expected =
      rlwe::RemoveError<uint_m>(expected_polynomial.Coeffs(),
                                params14_->modulus, kDefaultT, params14_.get());

  // Encrypt and absorb in the polynomial domain, then decrypt.
  ASSERT_OK_AND_ASSIGN(auto encrypt, Encrypt(key, plaintext));
  ASSERT_OK_AND_ASSIGN(auto coeffs, PolynomialCiphertext::ConvertToCoefficients(
                                        encrypt, ntt_params_.get()));
  ASSERT_OK_AND_ASSIGN(auto polynomial, coeffs.MonomialAbsorb(monomial_index));
  auto ciphertext = polynomial.ConvertToNtt(ntt_params_.get());
  ASSERT_OK_AND_ASSIGN(std::vector<uint_m::Int> decrypted,
                       rlwe::Decrypt<uint_m>(key, ciphertext));

  EXPECT_EQ(expected, decrypted);
}

TEST_F(PolynomialCiphertextTest, Substitution) {
  int subtitution_power = 3;
  ASSERT_OK_AND_ASSIGN(auto key, SampleKey());
  auto plaintext = rlwe::testing::SamplePlaintext<uint_m>();

  // Create the expected polynomial output by substituting the plaintext.
  ASSERT_OK_AND_ASSIGN(auto mp, rlwe::testing::ConvertToMontgomery<uint_m>(
                                    plaintext, params14_.get()));
  CoefficientPolynomial polynomial_coeffs(mp, params14_.get());
  ASSERT_OK_AND_ASSIGN(CoefficientPolynomial polynomial_expected,
                       polynomial_coeffs.Substitute(subtitution_power));
  std::vector<uint_m::Int> expected =
      rlwe::RemoveError<uint_m>(polynomial_expected.Coeffs(),
                                params14_->modulus, kDefaultT, params14_.get());

  // Encrypt and substitute the ciphertext. Decrypt with a substituted key.
  ASSERT_OK_AND_ASSIGN(auto encrypt, Encrypt(key, plaintext));
  ASSERT_OK_AND_ASSIGN(auto coeffs, PolynomialCiphertext::ConvertToCoefficients(
                                        encrypt, ntt_params_.get()));

  ASSERT_OK_AND_ASSIGN(auto polynomial, coeffs.Substitute(subtitution_power));
  auto ciphertext = polynomial.ConvertToNtt(ntt_params_.get());
  ASSERT_OK_AND_ASSIGN(auto sub_key, key.Substitute(subtitution_power));
  ASSERT_OK_AND_ASSIGN(std::vector<uint_m::Int> decrypted,
                       rlwe::Decrypt<uint_m>(sub_key, ciphertext));

  EXPECT_EQ(expected, decrypted);
  EXPECT_EQ(ciphertext.PowerOfS(), subtitution_power);
}

TEST_F(PolynomialCiphertextTest, SubstitutionFailsOnEvenPower) {
  ASSERT_OK_AND_ASSIGN(auto key, SampleKey());
  auto plaintext = rlwe::testing::SamplePlaintext<uint_m>();

  ASSERT_OK_AND_ASSIGN(auto encrypt, Encrypt(key, plaintext));
  ASSERT_OK_AND_ASSIGN(auto coeffs, PolynomialCiphertext::ConvertToCoefficients(
                                        encrypt, ntt_params_.get()));
  EXPECT_THAT(coeffs.Substitute(2),
              StatusIs(::absl::StatusCode::kInvalidArgument,
                       HasSubstr("power must be a non-negative odd integer")));
}

TEST_F(PolynomialCiphertextTest, PowersOfSMustMatchOnAdd) {
  int subtitution_power = 5;
  ASSERT_OK_AND_ASSIGN(auto key, SampleKey());

  std::vector<uint_m::Int> plaintext1 =
      rlwe::testing::SamplePlaintext<uint_m>();
  std::vector<uint_m::Int> plaintext2 =
      rlwe::testing::SamplePlaintext<uint_m>();

  ASSERT_OK_AND_ASSIGN(auto ciphertext1, Encrypt(key, plaintext1));
  ASSERT_OK_AND_ASSIGN(auto ciphertext2, Encrypt(key, plaintext2));

  // Add fails when only one ciphertext is substituted.
  ASSERT_OK_AND_ASSIGN(auto polynomial1,
                       PolynomialCiphertext::ConvertToCoefficients(
                           ciphertext1, ntt_params_.get()));
  ASSERT_OK_AND_ASSIGN(auto polynomial2,
                       PolynomialCiphertext::ConvertToCoefficients(
                           ciphertext2, ntt_params_.get()));
  ASSERT_OK_AND_ASSIGN(auto polynomial2_sub,
                       polynomial2.Substitute(subtitution_power));

  EXPECT_THAT(polynomial1 + polynomial2_sub,
              StatusIs(::absl::StatusCode::kInvalidArgument,
                       HasSubstr("must be encrypted with the same key")));
}

TEST_F(PolynomialCiphertextTest, AddOnSubstitutedCiphertexts) {
  int subtitution_power = 5;
  ASSERT_OK_AND_ASSIGN(auto key, SampleKey());

  std::vector<uint_m::Int> plaintext1 =
      rlwe::testing::SamplePlaintext<uint_m>();
  std::vector<uint_m::Int> plaintext2 =
      rlwe::testing::SamplePlaintext<uint_m>();

  // Create the expected polynomial output by substituting the plaintext.
  ASSERT_OK_AND_ASSIGN(auto mp1, rlwe::testing::ConvertToMontgomery<uint_m>(
                                     plaintext1, params14_.get()));
  CoefficientPolynomial polynomial1_coeffs(mp1, params14_.get());
  ASSERT_OK_AND_ASSIGN(auto mp2, rlwe::testing::ConvertToMontgomery<uint_m>(
                                     plaintext2, params14_.get()));
  CoefficientPolynomial polynomial2_coeffs(mp2, params14_.get());
  ASSERT_OK_AND_ASSIGN(auto sum, polynomial1_coeffs + polynomial2_coeffs);
  ASSERT_OK_AND_ASSIGN(CoefficientPolynomial polynomial_expected,
                       sum.Substitute(subtitution_power));
  std::vector<uint_m::Int> expected =
      rlwe::RemoveError<uint_m>(polynomial_expected.Coeffs(),
                                params14_->modulus, kDefaultT, params14_.get());

  ASSERT_OK_AND_ASSIGN(auto ciphertext1, Encrypt(key, plaintext1));
  ASSERT_OK_AND_ASSIGN(auto ciphertext2, Encrypt(key, plaintext2));

  // Add succeeds and decrypts when both ciphertexts are substituted.
  ASSERT_OK_AND_ASSIGN(auto coeffs1,
                       PolynomialCiphertext::ConvertToCoefficients(
                           ciphertext1, ntt_params_.get()));
  ASSERT_OK_AND_ASSIGN(auto polynomial1_sub,
                       coeffs1.Substitute(subtitution_power));
  ASSERT_OK_AND_ASSIGN(auto coeffs2,
                       PolynomialCiphertext::ConvertToCoefficients(
                           ciphertext2, ntt_params_.get()));
  ASSERT_OK_AND_ASSIGN(auto polynomial2_sub,
                       coeffs2.Substitute(subtitution_power));
  ASSERT_OK_AND_ASSIGN(PolynomialCiphertext result,
                       polynomial1_sub + polynomial2_sub);

  // Decrypt result.
  auto ciphertext_result = result.ConvertToNtt(ntt_params_.get());
  ASSERT_OK_AND_ASSIGN(auto key_sub, key.Substitute(subtitution_power));
  ASSERT_OK_AND_ASSIGN(std::vector<uint_m::Int> decrypted,
                       rlwe::Decrypt<uint_m>(key_sub, ciphertext_result));

  EXPECT_EQ(expected, decrypted);
  EXPECT_EQ(result.PowerOfS(), subtitution_power);
}

TEST_F(PolynomialCiphertextTest, AbsorbOnSubstitutedCiphertexts) {
  int monomial = 3;
  int subtitution_power = 5;

  ASSERT_OK_AND_ASSIGN(auto key, SampleKey());

  // Create a polynomial p(x).
  std::vector<uint_m::Int> plaintext = rlwe::testing::SamplePlaintext<uint_m>();

  // Create the expected polynomial output by absorbing in the p(x^sub).
  ASSERT_OK_AND_ASSIGN(auto mp, rlwe::testing::ConvertToMontgomery<uint_m>(
                                    plaintext, params14_.get()));
  CoefficientPolynomial polynomial_coeffs(mp, params14_.get());
  ASSERT_OK_AND_ASSIGN(auto coeffs,
                       polynomial_coeffs.Substitute(subtitution_power));
  ASSERT_OK_AND_ASSIGN(CoefficientPolynomial coeff_expected,
                       coeffs.MonomialMultiplication(monomial));
  std::vector<uint_m::Int> expected = rlwe::RemoveError<uint_m>(
      coeff_expected.Coeffs(), params14_->modulus, kDefaultT, params14_.get());

  ASSERT_OK_AND_ASSIGN(auto ciphertext, Encrypt(key, plaintext));

  // Absorb x^monomial in the substituted ciphertext.
  ASSERT_OK_AND_ASSIGN(auto coeffs2,
                       PolynomialCiphertext::ConvertToCoefficients(
                           ciphertext, ntt_params_.get()));
  ASSERT_OK_AND_ASSIGN(auto sub_coeffs, coeffs2.Substitute(subtitution_power));
  ASSERT_OK_AND_ASSIGN(auto coeff_result, sub_coeffs.MonomialAbsorb(monomial));

  // Decrypt result.
  auto ntt_result = coeff_result.ConvertToNtt(ntt_params_.get());
  ASSERT_OK_AND_ASSIGN(auto key_sub, key.Substitute(subtitution_power));
  ASSERT_OK_AND_ASSIGN(std::vector<uint_m::Int> decrypted,
                       rlwe::Decrypt<uint_m>(key_sub, ntt_result));

  // Expect that the ciphertext x^monomial c(x^sub) decrypts to x^monomial
  // p(x^sub).
  EXPECT_EQ(expected, decrypted);
  EXPECT_EQ(coeff_result.PowerOfS(), subtitution_power);
}

TEST_F(PolynomialCiphertextTest, RepeatedSubstitution) {
  // Verifies PowerOfS is updated correctly after repeated substitutions, and
  // the ciphertext can still be decrypted correctly.
  ASSERT_OK_AND_ASSIGN(auto key, SampleKey());
  auto plaintext = rlwe::testing::SamplePlaintext<uint_m>();
  ASSERT_OK_AND_ASSIGN(auto encrypt, Encrypt(key, plaintext));
  ASSERT_OK_AND_ASSIGN(
      auto polynomial,
      PolynomialCiphertext::ConvertToCoefficients(encrypt, ntt_params_.get()));

  EXPECT_EQ(polynomial.PowerOfS(), 1);

  // Creates an encryption of p(x^3).
  ASSERT_OK_AND_ASSIGN(auto polynomial3, polynomial.Substitute(3));
  EXPECT_EQ(polynomial3.PowerOfS(), 3);

  ASSERT_OK_AND_ASSIGN(auto polynomial9, polynomial3.Substitute(3));
  EXPECT_EQ(polynomial9.PowerOfS(), 9 % kCoeffs);

  // Substitutes the inverse of 3 mod 1024 to retrieve an encryption of the
  // original polynomial: p((x^3)^683) =p(x).
  ASSERT_OK_AND_ASSIGN(auto polynomial_wraparound, polynomial3.Substitute(683));
  auto ciphertext_wraparound =
      polynomial_wraparound.ConvertToNtt(ntt_params_.get());
  ASSERT_OK_AND_ASSIGN(std::vector<uint_m::Int> decrypted,
                       rlwe::Decrypt<uint_m>(key, ciphertext_wraparound));

  // Verifies that a polynomial after repeated substitutions decrypts correctly.
  EXPECT_EQ(polynomial_wraparound.PowerOfS(), 1);
  EXPECT_EQ(decrypted, plaintext);
}

TEST_F(PolynomialCiphertextTest, NttConversionsPreservePowerOfS) {
  // Ensures that NTT conversion to / from polynomial ciphertexts preserves the
  // power of s index.
  ASSERT_OK_AND_ASSIGN(auto key, SampleKey());
  auto plaintext = rlwe::testing::SamplePlaintext<uint_m>();
  ASSERT_OK_AND_ASSIGN(auto ciphertext, Encrypt(key, plaintext));

  EXPECT_EQ(ciphertext.PowerOfS(), 1);

  ASSERT_OK_AND_ASSIGN(auto coeffs, PolynomialCiphertext::ConvertToCoefficients(
                                        ciphertext, ntt_params_.get()));
  ASSERT_OK_AND_ASSIGN(auto polynomial, coeffs.Substitute(3));

  EXPECT_EQ(polynomial.PowerOfS(), 3);

  ASSERT_OK_AND_ASSIGN(
      auto ntt_converted_polynomial,
      PolynomialCiphertext::ConvertToCoefficients(
          polynomial.ConvertToNtt(ntt_params_.get()), ntt_params_.get()));

  EXPECT_EQ(ntt_converted_polynomial.PowerOfS(), 3);
}

TEST_F(PolynomialCiphertextTest, SubstitutionCommutesWithAbsorb) {
  // Checks if a monomial absorb can be factored out of a Substitution. In other
  // words, this checks that for a ciphertext c and a plaintext p, we can either
  // Absorb a plaintext and Substitute, or Substitute and Absorb the substituted
  // plaintext: (c.Absorb(p)).Substitute(power) ==
  // (c.Substitute(power)).Absorb(p.Substitute(power)).
  int monomial = kCoeffs - 1;
  int substitution_power = kCoeffs + 1;
  // For x^monomial, (x^monomial).Substitute(power) = x^{monomial_substituted}.
  int monomial_substituted = (monomial * substitution_power) % (2 * kCoeffs);

  ASSERT_OK_AND_ASSIGN(auto key, SampleKey());

  // Create a polynomial p(x).
  std::vector<uint_m::Int> plaintext = rlwe::testing::SamplePlaintext<uint_m>();

  // Create the expected polynomial by first applying the absorb and then
  // substitution.
  ASSERT_OK_AND_ASSIGN(auto mp, rlwe::testing::ConvertToMontgomery<uint_m>(
                                    plaintext, params14_.get()));
  CoefficientPolynomial polynomial_coeffs(mp, params14_.get());
  ASSERT_OK_AND_ASSIGN(auto prod,
                       polynomial_coeffs.MonomialMultiplication(monomial));
  ASSERT_OK_AND_ASSIGN(CoefficientPolynomial coeff_expected,
                       prod.Substitute(substitution_power));
  std::vector<uint_m::Int> expected = rlwe::RemoveError<uint_m>(
      coeff_expected.Coeffs(), params14_->modulus, kDefaultT, params14_.get());

  ASSERT_OK_AND_ASSIGN(auto ciphertext, Encrypt(key, plaintext));

  // Takes the ciphertext and FIRST applies the substitution, and then follows
  // with an absorb of the corresponding power.
  ASSERT_OK_AND_ASSIGN(auto coeffs, PolynomialCiphertext::ConvertToCoefficients(
                                        ciphertext, ntt_params_.get()));
  ASSERT_OK_AND_ASSIGN(auto sub_coeffs, coeffs.Substitute(substitution_power));
  ASSERT_OK_AND_ASSIGN(auto coeff_result,
                       sub_coeffs.MonomialAbsorb(monomial_substituted));

  // Decrypt result.
  auto ntt_result = coeff_result.ConvertToNtt(ntt_params_.get());
  ASSERT_OK_AND_ASSIGN(auto sub_key, key.Substitute(substitution_power));
  ASSERT_OK_AND_ASSIGN(std::vector<uint_m::Int> decrypted,
                       rlwe::Decrypt<uint_m>(sub_key, ntt_result));

  EXPECT_EQ(expected, decrypted);
  EXPECT_EQ(coeff_result.PowerOfS(), substitution_power);
}

}  // namespace