/*
* 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.
*/
#include "symmetric_encryption.h"
#include <algorithm>
#include <cstdint>
#include <functional>
#include <iostream>
#include <random>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "constants.h"
#include "context.h"
#include "montgomery.h"
#include "ntt_parameters.h"
#include "polynomial.h"
#include "prng/integral_prng_types.h"
#include "serialization.pb.h"
#include "status_macros.h"
#include "testing/parameters.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::Eq;
using ::testing::HasSubstr;
// Set constants.
const int kTestingRounds = 10;
// Tests symmetric-key encryption scheme, including the following homomorphic
// operations: addition, scalar multiplication by a polynomial (absorb), and
// multiplication. Substitutions are implemented in
// testing/coefficient_polynomial_ciphertext.h, and SymmetricRlweKey::Substitute
// and SymmetricRlweCiphertext::PowersOfS() (updated on substitution calls) are
// further tested in testing/coefficient_polynomial_ciphertext_test.cc.
template <typename ModularInt>
class SymmetricRlweEncryptionTest : public ::testing::Test {
public:
// Sample a random key.
rlwe::StatusOr<rlwe::SymmetricRlweKey<ModularInt>> SampleKey(
const rlwe::RlweContext<ModularInt>* context) {
RLWE_ASSIGN_OR_RETURN(std::string prng_seed,
rlwe::SingleThreadPrng::GenerateSeed());
RLWE_ASSIGN_OR_RETURN(auto prng, rlwe::SingleThreadPrng::Create(prng_seed));
return rlwe::SymmetricRlweKey<ModularInt>::Sample(
context->GetLogN(), context->GetVariance(), context->GetLogT(),
context->GetModulusParams(), context->GetNttParams(), prng.get());
}
// Encrypt a plaintext.
rlwe::StatusOr<rlwe::SymmetricRlweCiphertext<ModularInt>> Encrypt(
const rlwe::SymmetricRlweKey<ModularInt>& key,
const std::vector<typename ModularInt::Int>& plaintext,
const rlwe::RlweContext<ModularInt>* context) {
RLWE_ASSIGN_OR_RETURN(auto mont,
rlwe::testing::ConvertToMontgomery<ModularInt>(
plaintext, context->GetModulusParams()));
auto plaintext_ntt = rlwe::Polynomial<ModularInt>::ConvertToNtt(
mont, context->GetNttParams(), context->GetModulusParams());
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<ModularInt>(key, plaintext_ntt,
context->GetErrorParams(), prng.get());
}
};
TYPED_TEST_SUITE(SymmetricRlweEncryptionTest, rlwe::testing::ModularIntTypes);
// Ensure that RemoveError works correctly on negative numbers for several
// different values of t.
TYPED_TEST(SymmetricRlweEncryptionTest, RemoveErrorNegative) {
unsigned int seed = 0;
for (const auto& params :
rlwe::testing::ContextParameters<TypeParam>::Value()) {
ASSERT_OK_AND_ASSIGN(auto context,
rlwe::RlweContext<TypeParam>::Create(params));
for (int t = 2; t < 16; t++) {
for (int i = 0; i < kTestingRounds; i++) {
// Sample a plaintext in the range (modulus/2, modulus)
typename TypeParam::Int plaintext =
(rand_r(&seed) % (context->GetModulus() / 2)) +
context->GetModulus() / 2 + 1;
// Create a vector that exclusively contains the value "plaintext".
ASSERT_OK_AND_ASSIGN(
auto m_plaintext,
TypeParam::ImportInt(plaintext, context->GetModulusParams()));
std::vector<TypeParam> error_and_message(context->GetN(), m_plaintext);
auto result = rlwe::RemoveError<TypeParam>(error_and_message,
context->GetModulus(), t,
context->GetModulusParams());
// Compute the expected result using signed arithmetic. Derive its
// negative equivalent by subtracting out testing::kModulus and taking
// that negative value (mod t).
absl::int128 expected =
(static_cast<absl::int128>(plaintext) -
static_cast<absl::int128>(context->GetModulus())) %
t;
// Finally, turn any negative values into their positive equivalents
// (mod t).
if (expected < 0) {
expected += t;
}
for (unsigned int j = 0; j < context->GetN(); j++) {
EXPECT_EQ(expected, result[j]) << t << plaintext;
}
}
}
}
}
// Ensure that RemoveError works correctly on positive numbers for several
// different values of t.
TYPED_TEST(SymmetricRlweEncryptionTest, RemoveErrorPositive) {
for (const auto& params :
rlwe::testing::ContextParameters<TypeParam>::Value()) {
ASSERT_OK_AND_ASSIGN(auto context,
rlwe::RlweContext<TypeParam>::Create(params));
unsigned int seed = 0;
for (int t = 2; t < 16; t++) {
for (int i = 0; i < kTestingRounds; i++) {
// Sample a plaintext in the range (0, modulus/2)
typename TypeParam::Int plaintext =
rand_r(&seed) % (context->GetModulus() / 2);
// Create a vector that exclusively contains the value "plaintext".
ASSERT_OK_AND_ASSIGN(
auto m_plaintext,
TypeParam::ImportInt(plaintext, context->GetModulusParams()));
std::vector<TypeParam> error_and_message(context->GetN(), m_plaintext);
auto result = rlwe::RemoveError<TypeParam>(error_and_message,
context->GetModulus(), t,
context->GetModulusParams());
for (unsigned int j = 0; j < context->GetN(); j++) {
EXPECT_EQ(plaintext % t, result[j]);
}
}
}
}
}
// Ensure that the encryption scheme can decrypt its own ciphertexts.
TYPED_TEST(SymmetricRlweEncryptionTest, CanDecrypt) {
for (const auto& params :
rlwe::testing::ContextParameters<TypeParam>::Value()) {
ASSERT_OK_AND_ASSIGN(auto context,
rlwe::RlweContext<TypeParam>::Create(params));
for (unsigned int i = 0; i < kTestingRounds; i++) {
ASSERT_OK_AND_ASSIGN(auto key, this->SampleKey(context.get()));
auto plaintext = rlwe::testing::SamplePlaintext<TypeParam>(
context->GetN(), context->GetT());
ASSERT_OK_AND_ASSIGN(auto ciphertext,
this->Encrypt(key, plaintext, context.get()));
ASSERT_OK_AND_ASSIGN(auto decrypted,
rlwe::Decrypt<TypeParam>(key, ciphertext));
EXPECT_EQ(plaintext, decrypted);
}
}
}
// Accessing out of bounds raises errors
TYPED_TEST(SymmetricRlweEncryptionTest, OutOfBoundsIndex) {
for (const auto& params :
rlwe::testing::ContextParameters<TypeParam>::Value()) {
ASSERT_OK_AND_ASSIGN(auto context,
rlwe::RlweContext<TypeParam>::Create(params));
ASSERT_OK_AND_ASSIGN(auto key, this->SampleKey(context.get()));
auto plaintext = rlwe::testing::SamplePlaintext<TypeParam>(context->GetN(),
context->GetT());
ASSERT_OK_AND_ASSIGN(auto ciphertext,
this->Encrypt(key, plaintext, context.get()));
ASSERT_OK(ciphertext.Component(ciphertext.Len() - 1));
EXPECT_THAT(ciphertext.Component(ciphertext.Len()),
StatusIs(::absl::StatusCode::kInvalidArgument,
HasSubstr("Index out of range.")));
EXPECT_THAT(ciphertext.Component(-1),
StatusIs(::absl::StatusCode::kInvalidArgument,
HasSubstr("Index out of range.")));
}
}
// Check that the HE scheme is additively homomorphic.
TYPED_TEST(SymmetricRlweEncryptionTest, AdditivelyHomomorphic) {
for (const auto& params :
rlwe::testing::ContextParameters<TypeParam>::Value()) {
ASSERT_OK_AND_ASSIGN(auto context,
rlwe::RlweContext<TypeParam>::Create(params));
for (unsigned int i = 0; i < kTestingRounds; i++) {
ASSERT_OK_AND_ASSIGN(auto key, this->SampleKey(context.get()));
std::vector<typename TypeParam::Int> plaintext1 =
rlwe::testing::SamplePlaintext<TypeParam>(context->GetN(),
context->GetT());
std::vector<typename TypeParam::Int> plaintext2 =
rlwe::testing::SamplePlaintext<TypeParam>(context->GetN(),
context->GetT());
ASSERT_OK_AND_ASSIGN(auto ciphertext1,
this->Encrypt(key, plaintext1, context.get()));
ASSERT_OK_AND_ASSIGN(auto ciphertext2,
this->Encrypt(key, plaintext2, context.get()));
ASSERT_OK_AND_ASSIGN(auto ciphertext_add, ciphertext1 + ciphertext2);
ASSERT_OK_AND_ASSIGN(auto ciphertext_sub, ciphertext1 - ciphertext2);
ASSERT_OK_AND_ASSIGN(std::vector<typename TypeParam::Int> decrypted_add,
rlwe::Decrypt<TypeParam>(key, ciphertext_add));
ASSERT_OK_AND_ASSIGN(std::vector<typename TypeParam::Int> decrypted_sub,
rlwe::Decrypt<TypeParam>(key, ciphertext_sub));
for (unsigned int j = 0; j < plaintext1.size(); j++) {
EXPECT_EQ((plaintext1[j] + plaintext2[j]) % context->GetT(),
decrypted_add[j]);
EXPECT_EQ(
(context->GetT() + plaintext1[j] - plaintext2[j]) % context->GetT(),
decrypted_sub[j]);
// Check that the error grows additively.
EXPECT_EQ(ciphertext_add.Error(),
ciphertext1.Error() + ciphertext2.Error());
EXPECT_EQ(ciphertext_sub.Error(),
ciphertext1.Error() + ciphertext2.Error());
}
}
}
}
// Check that homomorphic addition can be performed in place.
TYPED_TEST(SymmetricRlweEncryptionTest, AddHomomorphicallyInPlace) {
for (const auto& params :
rlwe::testing::ContextParameters<TypeParam>::Value()) {
ASSERT_OK_AND_ASSIGN(auto context,
rlwe::RlweContext<TypeParam>::Create(params));
for (unsigned int i = 0; i < kTestingRounds; i++) {
ASSERT_OK_AND_ASSIGN(auto key, this->SampleKey(context.get()));
std::vector<typename TypeParam::Int> plaintext1 =
rlwe::testing::SamplePlaintext<TypeParam>(context->GetN(),
context->GetT());
std::vector<typename TypeParam::Int> plaintext2 =
rlwe::testing::SamplePlaintext<TypeParam>(context->GetN(),
context->GetT());
ASSERT_OK_AND_ASSIGN(auto ciphertext1_add,
this->Encrypt(key, plaintext1, context.get()));
ASSERT_OK_AND_ASSIGN(auto ciphertext1_sub,
this->Encrypt(key, plaintext1, context.get()));
ASSERT_OK_AND_ASSIGN(auto ciphertext2,
this->Encrypt(key, plaintext2, context.get()));
const double ciphertext1_add_error = ciphertext1_add.Error();
const double ciphertext1_sub_error = ciphertext1_sub.Error();
ASSERT_OK(ciphertext1_add.AddInPlace(ciphertext2));
ASSERT_OK(ciphertext1_sub.SubInPlace(ciphertext2));
ASSERT_OK_AND_ASSIGN(std::vector<typename TypeParam::Int> decrypted1_add,
rlwe::Decrypt<TypeParam>(key, ciphertext1_add));
ASSERT_OK_AND_ASSIGN(std::vector<typename TypeParam::Int> decrypted1_sub,
rlwe::Decrypt<TypeParam>(key, ciphertext1_sub));
for (unsigned int j = 0; j < plaintext1.size(); j++) {
EXPECT_EQ((plaintext1[j] + plaintext2[j]) % context->GetT(),
decrypted1_add[j]);
EXPECT_EQ(
(context->GetT() + plaintext1[j] - plaintext2[j]) % context->GetT(),
decrypted1_sub[j]);
// Check that the error grows additively.
EXPECT_EQ(ciphertext1_add.Error(),
ciphertext1_add_error + ciphertext2.Error());
EXPECT_EQ(ciphertext1_sub.Error(),
ciphertext1_sub_error + ciphertext2.Error());
}
}
}
}
// Check that homomorphic addition to a 0-ciphertext does not change the
// plaintext.
TYPED_TEST(SymmetricRlweEncryptionTest, AddToZero) {
for (const auto& params :
rlwe::testing::ContextParameters<TypeParam>::Value()) {
ASSERT_OK_AND_ASSIGN(auto context,
rlwe::RlweContext<TypeParam>::Create(params));
for (unsigned int i = 0; i < kTestingRounds; i++) {
ASSERT_OK_AND_ASSIGN(auto key, this->SampleKey(context.get()));
std::vector<typename TypeParam::Int> plaintext =
rlwe::testing::SamplePlaintext<TypeParam>(context->GetN(),
context->GetT());
rlwe::SymmetricRlweCiphertext<TypeParam> ciphertext1(
context->GetModulusParams(), context->GetErrorParams());
ASSERT_OK_AND_ASSIGN(auto ciphertext2,
this->Encrypt(key, plaintext, context.get()));
ASSERT_OK_AND_ASSIGN(auto ciphertext3, ciphertext1 + ciphertext2);
ASSERT_OK_AND_ASSIGN(std::vector<typename TypeParam::Int> decrypted,
rlwe::Decrypt<TypeParam>(key, ciphertext3));
EXPECT_EQ(plaintext, decrypted);
}
}
}
// Check that homomorphic absorption works.
TYPED_TEST(SymmetricRlweEncryptionTest, Absorb) {
for (const auto& params :
rlwe::testing::ContextParameters<TypeParam>::Value()) {
ASSERT_OK_AND_ASSIGN(auto context,
rlwe::RlweContext<TypeParam>::Create(params));
for (unsigned int i = 0; i < kTestingRounds; i++) {
ASSERT_OK_AND_ASSIGN(auto key, this->SampleKey(context.get()));
// Create the initial plaintexts.
std::vector<typename TypeParam::Int> plaintext =
rlwe::testing::SamplePlaintext<TypeParam>(context->GetN(),
context->GetT());
ASSERT_OK_AND_ASSIGN(auto m_plaintext,
rlwe::testing::ConvertToMontgomery<TypeParam>(
plaintext, context->GetModulusParams()));
rlwe::Polynomial<TypeParam> plaintext_ntt =
rlwe::Polynomial<TypeParam>::ConvertToNtt(
m_plaintext, context->GetNttParams(),
context->GetModulusParams());
std::vector<typename TypeParam::Int> to_absorb =
rlwe::testing::SamplePlaintext<TypeParam>(context->GetN(),
context->GetT());
ASSERT_OK_AND_ASSIGN(auto m_to_absorb,
rlwe::testing::ConvertToMontgomery<TypeParam>(
to_absorb, context->GetModulusParams()));
rlwe::Polynomial<TypeParam> to_absorb_ntt =
rlwe::Polynomial<TypeParam>::ConvertToNtt(
m_to_absorb, context->GetNttParams(),
context->GetModulusParams());
// Create our expected value.
ASSERT_OK_AND_ASSIGN(
rlwe::Polynomial<TypeParam> expected_ntt,
plaintext_ntt.Mul(to_absorb_ntt, context->GetModulusParams()));
std::vector<typename TypeParam::Int> expected =
rlwe::RemoveError<TypeParam>(
expected_ntt.InverseNtt(context->GetNttParams(),
context->GetModulusParams()),
context->GetModulus(), context->GetT(),
context->GetModulusParams());
// Encrypt, absorb, and decrypt.
ASSERT_OK_AND_ASSIGN(auto encrypt,
this->Encrypt(key, plaintext, context.get()));
ASSERT_OK_AND_ASSIGN(auto ciphertext, encrypt* to_absorb_ntt);
ASSERT_OK_AND_ASSIGN(std::vector<typename TypeParam::Int> decrypted,
rlwe::Decrypt<TypeParam>(key, ciphertext));
EXPECT_EQ(expected, decrypted);
// Check that the error is the product of an encryption and a plaintext.
EXPECT_EQ(ciphertext.Error(),
context->GetErrorParams()->B_encryption() *
context->GetErrorParams()->B_plaintext());
}
}
}
// Check that homomorphic absorption in place works.
TYPED_TEST(SymmetricRlweEncryptionTest, AbsorbInPlace) {
for (const auto& params :
rlwe::testing::ContextParameters<TypeParam>::Value()) {
ASSERT_OK_AND_ASSIGN(auto context,
rlwe::RlweContext<TypeParam>::Create(params));
for (unsigned int i = 0; i < kTestingRounds; i++) {
ASSERT_OK_AND_ASSIGN(auto key, this->SampleKey(context.get()));
// Create the initial plaintexts.
std::vector<typename TypeParam::Int> plaintext =
rlwe::testing::SamplePlaintext<TypeParam>(context->GetN(),
context->GetT());
ASSERT_OK_AND_ASSIGN(auto m_plaintext,
rlwe::testing::ConvertToMontgomery<TypeParam>(
plaintext, context->GetModulusParams()));
rlwe::Polynomial<TypeParam> plaintext_ntt =
rlwe::Polynomial<TypeParam>::ConvertToNtt(
m_plaintext, context->GetNttParams(),
context->GetModulusParams());
std::vector<typename TypeParam::Int> to_absorb =
rlwe::testing::SamplePlaintext<TypeParam>(context->GetN(),
context->GetT());
ASSERT_OK_AND_ASSIGN(auto m_to_absorb,
rlwe::testing::ConvertToMontgomery<TypeParam>(
to_absorb, context->GetModulusParams()));
rlwe::Polynomial<TypeParam> to_absorb_ntt =
rlwe::Polynomial<TypeParam>::ConvertToNtt(
m_to_absorb, context->GetNttParams(),
context->GetModulusParams());
// Create our expected value.
ASSERT_OK_AND_ASSIGN(
rlwe::Polynomial<TypeParam> expected_ntt,
plaintext_ntt.Mul(to_absorb_ntt, context->GetModulusParams()));
std::vector<typename TypeParam::Int> expected =
rlwe::RemoveError<TypeParam>(
expected_ntt.InverseNtt(context->GetNttParams(),
context->GetModulusParams()),
context->GetModulus(), context->GetT(),
context->GetModulusParams());
// Encrypt, absorb in place, and decrypt.
ASSERT_OK_AND_ASSIGN(auto ciphertext,
this->Encrypt(key, plaintext, context.get()));
ASSERT_OK(ciphertext.AbsorbInPlace(to_absorb_ntt));
ASSERT_OK_AND_ASSIGN(std::vector<typename TypeParam::Int> decrypted,
rlwe::Decrypt<TypeParam>(key, ciphertext));
EXPECT_EQ(expected, decrypted);
// Check that the error is the product of an encryption and a plaintext.
EXPECT_EQ(ciphertext.Error(),
context->GetErrorParams()->B_encryption() *
context->GetErrorParams()->B_plaintext());
}
}
}
// Check that homomorphic absorption of a scalar works.
TYPED_TEST(SymmetricRlweEncryptionTest, AbsorbScalar) {
for (const auto& params :
rlwe::testing::ContextParameters<TypeParam>::Value()) {
ASSERT_OK_AND_ASSIGN(auto context,
rlwe::RlweContext<TypeParam>::Create(params));
unsigned int seed = 0;
for (unsigned int i = 0; i < kTestingRounds; i++) {
ASSERT_OK_AND_ASSIGN(auto key, this->SampleKey(context.get()));
// Create the initial plaintexts.
std::vector<typename TypeParam::Int> plaintext =
rlwe::testing::SamplePlaintext<TypeParam>(context->GetN(),
context->GetT());
ASSERT_OK_AND_ASSIGN(auto m_plaintext,
rlwe::testing::ConvertToMontgomery<TypeParam>(
plaintext, context->GetModulusParams()));
rlwe::Polynomial<TypeParam> plaintext_ntt =
rlwe::Polynomial<TypeParam>::ConvertToNtt(
m_plaintext, context->GetNttParams(),
context->GetModulusParams());
ASSERT_OK_AND_ASSIGN(TypeParam to_absorb,
TypeParam::ImportInt(rand_r(&seed) % context->GetT(),
context->GetModulusParams()));
// Create our expected value.
ASSERT_OK_AND_ASSIGN(
rlwe::Polynomial<TypeParam> expected_ntt,
plaintext_ntt.Mul(to_absorb, context->GetModulusParams()));
std::vector<typename TypeParam::Int> expected =
rlwe::RemoveError<TypeParam>(
expected_ntt.InverseNtt(context->GetNttParams(),
context->GetModulusParams()),
context->GetModulus(), context->GetT(),
context->GetModulusParams());
// Encrypt, absorb, and decrypt.
ASSERT_OK_AND_ASSIGN(auto encrypt,
this->Encrypt(key, plaintext, context.get()));
ASSERT_OK_AND_ASSIGN(auto ciphertext, encrypt* to_absorb);
ASSERT_OK_AND_ASSIGN(std::vector<typename TypeParam::Int> decrypted,
rlwe::Decrypt<TypeParam>(key, ciphertext));
EXPECT_EQ(expected, decrypted);
// Expect the error to grow multiplicatively.
EXPECT_EQ(ciphertext.Error(), context->GetErrorParams()->B_encryption() *
static_cast<double>(to_absorb.ExportInt(
context->GetModulusParams())));
}
}
}
// Check that homomorphic absorption of a scalar in place works.
TYPED_TEST(SymmetricRlweEncryptionTest, AbsorbScalarInPlace) {
for (const auto& params :
rlwe::testing::ContextParameters<TypeParam>::Value()) {
ASSERT_OK_AND_ASSIGN(auto context,
rlwe::RlweContext<TypeParam>::Create(params));
unsigned int seed = 0;
for (unsigned int i = 0; i < kTestingRounds; i++) {
ASSERT_OK_AND_ASSIGN(auto key, this->SampleKey(context.get()));
// Create the initial plaintexts.
std::vector<typename TypeParam::Int> plaintext =
rlwe::testing::SamplePlaintext<TypeParam>(context->GetN(),
context->GetT());
ASSERT_OK_AND_ASSIGN(auto m_plaintext,
rlwe::testing::ConvertToMontgomery<TypeParam>(
plaintext, context->GetModulusParams()));
rlwe::Polynomial<TypeParam> plaintext_ntt =
rlwe::Polynomial<TypeParam>::ConvertToNtt(
m_plaintext, context->GetNttParams(),
context->GetModulusParams());
ASSERT_OK_AND_ASSIGN(TypeParam to_absorb,
TypeParam::ImportInt(rand_r(&seed) % context->GetT(),
context->GetModulusParams()));
// Create our expected value.
ASSERT_OK_AND_ASSIGN(
rlwe::Polynomial<TypeParam> expected_ntt,
plaintext_ntt.Mul(to_absorb, context->GetModulusParams()));
std::vector<typename TypeParam::Int> expected =
rlwe::RemoveError<TypeParam>(
expected_ntt.InverseNtt(context->GetNttParams(),
context->GetModulusParams()),
context->GetModulus(), context->GetT(),
context->GetModulusParams());
// Encrypt, absorb, and decrypt.
ASSERT_OK_AND_ASSIGN(auto ciphertext,
this->Encrypt(key, plaintext, context.get()));
ASSERT_OK(ciphertext.AbsorbInPlace(to_absorb));
ASSERT_OK_AND_ASSIGN(std::vector<typename TypeParam::Int> decrypted,
rlwe::Decrypt<TypeParam>(key, ciphertext));
EXPECT_EQ(expected, decrypted);
// Expect the error to grow multiplicatively.
EXPECT_EQ(ciphertext.Error(), context->GetErrorParams()->B_encryption() *
static_cast<double>(to_absorb.ExportInt(
context->GetModulusParams())));
}
}
}
// Check that we cannot multiply with an empty ciphertext.
TYPED_TEST(SymmetricRlweEncryptionTest, EmptyCipherMultiplication) {
for (const auto& params :
rlwe::testing::ContextParameters<TypeParam>::Value()) {
ASSERT_OK_AND_ASSIGN(auto context,
rlwe::RlweContext<TypeParam>::Create(params));
ASSERT_OK_AND_ASSIGN(auto key, this->SampleKey(context.get()));
// Create a plaintext
std::vector<typename TypeParam::Int> plaintext =
rlwe::testing::SamplePlaintext<TypeParam>(context->GetN(),
context->GetT());
// Encrypt, multiply
ASSERT_OK_AND_ASSIGN(auto ciphertext1,
this->Encrypt(key, plaintext, context.get()));
// empty cipher
std::vector<rlwe::Polynomial<TypeParam>> c;
rlwe::SymmetricRlweCiphertext<TypeParam> ciphertext2(
c, 1, 0, context->GetModulusParams(), context->GetErrorParams());
EXPECT_THAT(
ciphertext1 * ciphertext2,
StatusIs(::absl::StatusCode::kInvalidArgument,
HasSubstr("Cannot multiply using an empty ciphertext.")));
EXPECT_THAT(
ciphertext2 * ciphertext1,
StatusIs(::absl::StatusCode::kInvalidArgument,
HasSubstr("Cannot multiply using an empty ciphertext.")));
c.push_back(rlwe::Polynomial<TypeParam>());
rlwe::SymmetricRlweCiphertext<TypeParam> ciphertext3(
c, 1, 0, context->GetModulusParams(), context->GetErrorParams());
EXPECT_THAT(ciphertext1 * ciphertext3,
StatusIs(::absl::StatusCode::kInvalidArgument,
HasSubstr("Cannot multiply using an empty polynomial "
"in the ciphertext.")));
EXPECT_THAT(ciphertext3 * ciphertext1,
StatusIs(::absl::StatusCode::kInvalidArgument,
HasSubstr("Cannot multiply using an empty polynomial "
"in the ciphertext.")));
}
}
// Check that the scheme is multiplicatively homomorphic.
TYPED_TEST(SymmetricRlweEncryptionTest, MultiplicativelyHomomorphic) {
if (sizeof(TypeParam) > 2) { // No multiplicative homomorphism possible when
// TypeParam = Uint16
for (const auto& params :
rlwe::testing::ContextParameters<TypeParam>::Value()) {
ASSERT_OK_AND_ASSIGN(auto context,
rlwe::RlweContext<TypeParam>::Create(params));
for (int i = 0; i < kTestingRounds; i++) {
ASSERT_OK_AND_ASSIGN(auto key, this->SampleKey(context.get()));
// Create the initial plaintexts.
std::vector<typename TypeParam::Int> plaintext1 =
rlwe::testing::SamplePlaintext<TypeParam>(context->GetN(),
context->GetT());
ASSERT_OK_AND_ASSIGN(auto mp1,
rlwe::testing::ConvertToMontgomery<TypeParam>(
plaintext1, context->GetModulusParams()));
rlwe::Polynomial<TypeParam> plaintext1_ntt =
rlwe::Polynomial<TypeParam>::ConvertToNtt(
mp1, context->GetNttParams(), context->GetModulusParams());
std::vector<typename TypeParam::Int> plaintext2 =
rlwe::testing::SamplePlaintext<TypeParam>(context->GetN(),
context->GetT());
ASSERT_OK_AND_ASSIGN(auto mp2,
rlwe::testing::ConvertToMontgomery<TypeParam>(
plaintext2, context->GetModulusParams()));
rlwe::Polynomial<TypeParam> plaintext2_ntt =
rlwe::Polynomial<TypeParam>::ConvertToNtt(
mp2, context->GetNttParams(), context->GetModulusParams());
// Encrypt, multiply, and decrypt.
ASSERT_OK_AND_ASSIGN(auto ciphertext1,
this->Encrypt(key, plaintext1, context.get()));
ASSERT_OK_AND_ASSIGN(auto ciphertext2,
this->Encrypt(key, plaintext2, context.get()));
ASSERT_OK_AND_ASSIGN(auto product, ciphertext1* ciphertext2);
ASSERT_OK_AND_ASSIGN(std::vector<typename TypeParam::Int> decrypted,
rlwe::Decrypt<TypeParam>(key, product));
// Create the polynomial we expect.
ASSERT_OK_AND_ASSIGN(
rlwe::Polynomial<TypeParam> expected_ntt,
plaintext1_ntt.Mul(plaintext2_ntt, context->GetModulusParams()));
std::vector<typename TypeParam::Int> expected =
rlwe::RemoveError<TypeParam>(
expected_ntt.InverseNtt(context->GetNttParams(),
context->GetModulusParams()),
context->GetModulus(), context->GetT(),
context->GetModulusParams());
EXPECT_EQ(expected, decrypted);
// Expect that the error grows multiplicatively.
EXPECT_EQ(product.Error(), ciphertext1.Error() * ciphertext2.Error());
}
}
}
}
// Check that many homomorphic additions can be performed.
TYPED_TEST(SymmetricRlweEncryptionTest, ManyHomomorphicAdds) {
for (const auto& params :
rlwe::testing::ContextParameters<TypeParam>::Value()) {
ASSERT_OK_AND_ASSIGN(auto context,
rlwe::RlweContext<TypeParam>::Create(params));
// Sample a starting plaintext and ciphertext and create aggregators;
std::vector<typename TypeParam::Int> plaintext =
rlwe::testing::SamplePlaintext<TypeParam>(context->GetN(),
context->GetT());
std::vector<typename TypeParam::Int> plaintext_sum = plaintext;
ASSERT_OK_AND_ASSIGN(auto key, this->SampleKey(context.get()));
ASSERT_OK_AND_ASSIGN(auto ciphertext_sum,
this->Encrypt(key, plaintext, context.get()));
// Sample a fresh plaintext.
plaintext = rlwe::testing::SamplePlaintext<TypeParam>(context->GetN(),
context->GetT());
ASSERT_OK_AND_ASSIGN(auto ciphertext,
this->Encrypt(key, plaintext, context.get()));
int num_adds = 50;
// Perform 50 homomorphic ciphertext additions with the fresh ciphertext.
for (int j = 0; j < num_adds; j++) {
// Add the new plaintext to the old plaintext.
for (unsigned int k = 0; k < context->GetN(); k++) {
plaintext_sum[k] += plaintext[k];
plaintext_sum[k] %= context->GetT();
}
// Add the new ciphertext to the old ciphertext.
ASSERT_OK_AND_ASSIGN(ciphertext_sum, ciphertext_sum + ciphertext);
}
ASSERT_OK_AND_ASSIGN(std::vector<typename TypeParam::Int> decrypted,
rlwe::Decrypt<TypeParam>(key, ciphertext_sum));
// Ensure the values are the same.
EXPECT_EQ(plaintext_sum, decrypted);
// Expect that the ciphertext sum's error grows by the additively by the
// ciphertext's error.
EXPECT_GT(ciphertext_sum.Error(), num_adds * ciphertext.Error());
}
}
// Check that ciphertext deserialization cannot handle more than
// rlwe::kMaxNumCoeffs coefficients.
TYPED_TEST(SymmetricRlweEncryptionTest,
ExceedMaxNumCoeffDeserializeCiphertext) {
for (const auto& params :
rlwe::testing::ContextParameters<TypeParam>::Value()) {
ASSERT_OK_AND_ASSIGN(auto context,
rlwe::RlweContext<TypeParam>::Create(params));
int num_coeffs = rlwe::kMaxNumCoeffs + 1;
std::vector<rlwe::Polynomial<TypeParam>> c;
for (int i = 0; i < num_coeffs; i++) {
c.push_back(rlwe::Polynomial<TypeParam>(1, context->GetModulusParams()));
}
rlwe::SymmetricRlweCiphertext<TypeParam> ciphertext(
c, 1, 0, context->GetModulusParams(), context->GetErrorParams());
// Serialize and deserialize.
ASSERT_OK_AND_ASSIGN(rlwe::SerializedSymmetricRlweCiphertext serialized,
ciphertext.Serialize());
EXPECT_THAT(
rlwe::SymmetricRlweCiphertext<TypeParam>::Deserialize(
serialized, context->GetModulusParams(), context->GetErrorParams()),
StatusIs(::absl::StatusCode::kInvalidArgument,
HasSubstr(absl::StrCat(
"Number of coefficients, ", serialized.c_size(),
", cannot be more than ", rlwe::kMaxNumCoeffs, "."))));
}
}
// Check that ciphertext serialization works.
TYPED_TEST(SymmetricRlweEncryptionTest, SerializeCiphertext) {
for (const auto& params :
rlwe::testing::ContextParameters<TypeParam>::Value()) {
ASSERT_OK_AND_ASSIGN(auto context,
rlwe::RlweContext<TypeParam>::Create(params));
for (int i = 0; i < kTestingRounds; i++) {
ASSERT_OK_AND_ASSIGN(auto key, this->SampleKey(context.get()));
std::vector<typename TypeParam::Int> plaintext =
rlwe::testing::SamplePlaintext<TypeParam>(context->GetN(),
context->GetT());
ASSERT_OK_AND_ASSIGN(auto ciphertext,
this->Encrypt(key, plaintext, context.get()));
// Serialize and deserialize.
ASSERT_OK_AND_ASSIGN(rlwe::SerializedSymmetricRlweCiphertext serialized,
ciphertext.Serialize());
ASSERT_OK_AND_ASSIGN(
auto deserialized,
rlwe::SymmetricRlweCiphertext<TypeParam>::Deserialize(
serialized, context->GetModulusParams(),
context->GetErrorParams()));
// Decrypt and check equality.
ASSERT_OK_AND_ASSIGN(
std::vector<typename TypeParam::Int> deserialized_plaintext,
rlwe::Decrypt<TypeParam>(key, deserialized));
EXPECT_EQ(plaintext, deserialized_plaintext);
// Check that the error stays the same.
EXPECT_EQ(deserialized.Error(), ciphertext.Error());
}
}
}
// Check that key serialization works.
TYPED_TEST(SymmetricRlweEncryptionTest, SerializeKey) {
for (const auto& params :
rlwe::testing::ContextParameters<TypeParam>::Value()) {
ASSERT_OK_AND_ASSIGN(auto context,
rlwe::RlweContext<TypeParam>::Create(params));
for (int i = 0; i < kTestingRounds; i++) {
ASSERT_OK_AND_ASSIGN(auto original_key, this->SampleKey(context.get()));
std::vector<typename TypeParam::Int> plaintext =
rlwe::testing::SamplePlaintext<TypeParam>(context->GetN(),
context->GetT());
// Serialize key, deserialize, and ensure the deserialized key is
// interoperable with the original key.
ASSERT_OK_AND_ASSIGN(rlwe::SerializedNttPolynomial serialized,
original_key.Serialize());
ASSERT_OK_AND_ASSIGN(
auto deserialized_key,
rlwe::SymmetricRlweKey<TypeParam>::Deserialize(
context->GetVariance(), context->GetLogT(), serialized,
context->GetModulusParams(), context->GetNttParams()));
// Test that a ciphertext encrypted with the original key decrypts under
// the deserialized key.
ASSERT_OK_AND_ASSIGN(
auto ekey1, this->Encrypt(original_key, plaintext, context.get()));
ASSERT_OK_AND_ASSIGN(auto dkey1,
rlwe::Decrypt<TypeParam>(deserialized_key, ekey1));
EXPECT_EQ(dkey1, plaintext);
// Test that a ciphertext encrypted with the deserialized key decrypts
// under the original key.
ASSERT_OK_AND_ASSIGN(auto ekey2, this->Encrypt(deserialized_key,
plaintext, context.get()));
ASSERT_OK_AND_ASSIGN(auto dkey2,
rlwe::Decrypt<TypeParam>(original_key, ekey2));
EXPECT_EQ(dkey2, plaintext);
}
}
}
// Try an ill-formed key modulus switching
TYPED_TEST(SymmetricRlweEncryptionTest, FailingKeyModulusReduction) {
for (const auto& params :
rlwe::testing::ContextParameters<TypeParam>::Value()) {
ASSERT_OK_AND_ASSIGN(auto context,
rlwe::RlweContext<TypeParam>::Create(params));
// p is the original modulus and q is the new modulus we want to switch to
// For modulus switching, p % t must be equal to q % t, where t is the
// plaintext modulus, and both need to be congruent to 1 mod 2n.
typename TypeParam::Int p = context->GetModulus();
typename TypeParam::Int q = p - (context->GetN() << 1);
EXPECT_NE(q % context->GetT(), p % context->GetT());
ASSERT_OK_AND_ASSIGN(auto context_q, rlwe::RlweContext<TypeParam>::Create(
{.modulus = q,
.log_n = params.log_n,
.log_t = params.log_t,
.variance = params.variance}));
ASSERT_OK_AND_ASSIGN(auto key_p, this->SampleKey(context.get()));
auto status = key_p.template SwitchModulus<TypeParam>(
context_q->GetModulusParams(), context_q->GetNttParams());
EXPECT_THAT(status, StatusIs(::absl::StatusCode::kInvalidArgument,
HasSubstr("p % t != q % t")));
}
}
// Try an ill-formed ciphertext modulus switching
TYPED_TEST(SymmetricRlweEncryptionTest, FailingCiphertextModulusReduction) {
for (const auto& params :
rlwe::testing::ContextParameters<TypeParam>::Value()) {
ASSERT_OK_AND_ASSIGN(auto context,
rlwe::RlweContext<TypeParam>::Create(params));
// p is the original modulus and q is the new modulus we want to switch to
// For modulus switching, p % t must be equal to q % t, where t is the
// plaintext modulus, and both need to be congruent to 1 mod 2n.
typename TypeParam::Int p = context->GetModulus();
typename TypeParam::Int q = p - (context->GetN() << 1);
EXPECT_NE(q % context->GetT(), p % context->GetT());
ASSERT_OK_AND_ASSIGN(auto context_q, rlwe::RlweContext<TypeParam>::Create(
{.modulus = q,
.log_n = params.log_n,
.log_t = params.log_t,
.variance = params.variance}));
ASSERT_OK_AND_ASSIGN(auto key_p, this->SampleKey(context.get()));
// sample ciphertext modulo p.
auto plaintext = rlwe::testing::SamplePlaintext<TypeParam>(context->GetN(),
context->GetT());
ASSERT_OK_AND_ASSIGN(auto ciphertext,
this->Encrypt(key_p, plaintext, context.get()));
auto status = ciphertext.template SwitchModulus<TypeParam>(
context->GetNttParams(), context_q->GetModulusParams(),
context_q->GetNttParams(), context_q->GetErrorParams(),
context->GetT());
EXPECT_THAT(status, StatusIs(::absl::StatusCode::kInvalidArgument,
HasSubstr("p % t != q % t")));
}
}
// Test modulus switching.
TYPED_TEST(SymmetricRlweEncryptionTest, ModulusReduction) {
for (const auto& params :
rlwe::testing::ContextParametersModulusSwitching<TypeParam>::Value()) {
auto params1 = std::get<0>(params), params2 = std::get<1>(params);
ASSERT_OK_AND_ASSIGN(auto context1,
rlwe::RlweContext<TypeParam>::Create(params1));
ASSERT_OK_AND_ASSIGN(auto context2,
rlwe::RlweContext<TypeParam>::Create(params2));
for (int i = 0; i < kTestingRounds; i++) {
// Create a key.
ASSERT_OK_AND_ASSIGN(auto key, this->SampleKey(context1.get()));
ASSERT_OK_AND_ASSIGN(
auto key_switched,
key.template SwitchModulus<TypeParam>(context2->GetModulusParams(),
context2->GetNttParams()));
// Create a plaintext.
std::vector<typename TypeParam::Int> plaintext =
rlwe::testing::SamplePlaintext<TypeParam>(context1->GetN(),
context1->GetT());
ASSERT_OK_AND_ASSIGN(auto ciphertext,
this->Encrypt(key, plaintext, context1.get()));
// Switch moduli.
ASSERT_OK_AND_ASSIGN(
auto ciphertext_switched,
ciphertext.template SwitchModulus<TypeParam>(
context1->GetNttParams(), context2->GetModulusParams(),
context2->GetNttParams(), context2->GetErrorParams(),
context2->GetT()));
// Decrypt in the smaller modulus.
ASSERT_OK_AND_ASSIGN(
auto decrypted,
rlwe::Decrypt<TypeParam>(key_switched, ciphertext_switched));
EXPECT_EQ(plaintext, decrypted);
}
}
}
// Check that modulus switching reduces the error.
TYPED_TEST(SymmetricRlweEncryptionTest, ModulusSwitchingReducesLargeError) {
for (const auto& params :
rlwe::testing::ContextParametersModulusSwitching<TypeParam>::Value()) {
auto params1 = std::get<0>(params), params2 = std::get<1>(params);
ASSERT_OK_AND_ASSIGN(auto context1,
rlwe::RlweContext<TypeParam>::Create(params1));
ASSERT_OK_AND_ASSIGN(auto context2,
rlwe::RlweContext<TypeParam>::Create(params2));
for (int i = 0; i < kTestingRounds; i++) {
// Create a key.
ASSERT_OK_AND_ASSIGN(auto key, this->SampleKey(context1.get()));
ASSERT_OK_AND_ASSIGN(
auto key_switched,
key.template SwitchModulus<TypeParam>(context2->GetModulusParams(),
context2->GetNttParams()));
// Create a plaintext.
std::vector<typename TypeParam::Int> plaintext =
rlwe::testing::SamplePlaintext<TypeParam>(context1->GetN(),
context1->GetT());
ASSERT_OK_AND_ASSIGN(auto ciphertext,
this->Encrypt(key, plaintext, context1.get()));
// Square the ciphertext
ASSERT_OK_AND_ASSIGN(auto squared, ciphertext* ciphertext);
// Switch moduli.
ASSERT_OK_AND_ASSIGN(
auto squared_switched,
squared.template SwitchModulus<TypeParam>(
context1->GetNttParams(), context2->GetModulusParams(),
context2->GetNttParams(), context2->GetErrorParams(),
context2->GetT()));
// Decrypt
ASSERT_OK_AND_ASSIGN(auto squared_decrypted,
rlwe::Decrypt<TypeParam>(key, squared));
ASSERT_OK_AND_ASSIGN(
auto squared_switched_decrypted,
rlwe::Decrypt<TypeParam>(key_switched, squared_switched));
EXPECT_EQ(squared_decrypted, squared_switched_decrypted);
// Expect that the error reduces after a modulus switch when the error is
// large.
EXPECT_LT(squared_switched.Error(), squared.Error());
// But that the error doesn't reduce when the error is small.
EXPECT_GT(squared_switched.Error(), ciphertext.Error());
}
}
}
// Check that we cannot perform operations between ciphertexts encrypted under
// different powers of s.
TYPED_TEST(SymmetricRlweEncryptionTest, OperationsFailOnMismatchedPowersOfS) {
for (const auto& params :
rlwe::testing::ContextParameters<TypeParam>::Value()) {
ASSERT_OK_AND_ASSIGN(auto context,
rlwe::RlweContext<TypeParam>::Create(params));
ASSERT_OK_AND_ASSIGN(auto key, this->SampleKey(context.get()));
std::vector<typename TypeParam::Int> plaintext1 =
rlwe::testing::SamplePlaintext<TypeParam>(context->GetN(),
context->GetT());
ASSERT_OK_AND_ASSIGN(auto m1, rlwe::testing::ConvertToMontgomery<TypeParam>(
plaintext1, context->GetModulusParams()));
auto plaintext1_ntt = rlwe::Polynomial<TypeParam>::ConvertToNtt(
m1, context->GetNttParams(), context->GetModulusParams());
std::vector<typename TypeParam::Int> plaintext2 =
rlwe::testing::SamplePlaintext<TypeParam>(context->GetN(),
context->GetT());
auto ciphertext1 = rlwe::SymmetricRlweCiphertext<TypeParam>(
{plaintext1_ntt}, 1, context->GetErrorParams()->B_encryption(),
context->GetModulusParams(), context->GetErrorParams());
auto ciphertext2 = rlwe::SymmetricRlweCiphertext<TypeParam>(
{plaintext1_ntt}, 2, context->GetErrorParams()->B_encryption(),
context->GetModulusParams(), context->GetErrorParams());
EXPECT_THAT(ciphertext1 + ciphertext2,
StatusIs(::absl::StatusCode::kInvalidArgument,
HasSubstr("must be encrypted with the same key")));
EXPECT_THAT(ciphertext1 * ciphertext2,
StatusIs(::absl::StatusCode::kInvalidArgument,
HasSubstr("must be encrypted with the same key")));
}
}
// Verifies that the power of S changes as expected in adds / mults.
TYPED_TEST(SymmetricRlweEncryptionTest, AddsAndMultPreservePowerOfS) {
for (const auto& params :
rlwe::testing::ContextParameters<TypeParam>::Value()) {
ASSERT_OK_AND_ASSIGN(auto context,
rlwe::RlweContext<TypeParam>::Create(params));
ASSERT_OK_AND_ASSIGN(auto key, this->SampleKey(context.get()));
std::vector<typename TypeParam::Int> plaintext1 =
rlwe::testing::SamplePlaintext<TypeParam>(context->GetN(),
context->GetT());
ASSERT_OK_AND_ASSIGN(auto m1, rlwe::testing::ConvertToMontgomery<TypeParam>(
plaintext1, context->GetModulusParams()));
auto plaintext1_ntt = rlwe::Polynomial<TypeParam>::ConvertToNtt(
m1, context->GetNttParams(), context->GetModulusParams());
std::vector<typename TypeParam::Int> plaintext2 =
rlwe::testing::SamplePlaintext<TypeParam>(context->GetN(),
context->GetT());
auto ciphertext1 = rlwe::SymmetricRlweCiphertext<TypeParam>(
{plaintext1_ntt}, 2, context->GetErrorParams()->B_encryption(),
context->GetModulusParams(), context->GetErrorParams());
auto ciphertext2 = rlwe::SymmetricRlweCiphertext<TypeParam>(
{plaintext1_ntt}, 2, context->GetErrorParams()->B_encryption(),
context->GetModulusParams(), context->GetErrorParams());
EXPECT_EQ(ciphertext1.PowerOfS(), 2);
EXPECT_EQ(ciphertext2.PowerOfS(), 2);
ASSERT_OK_AND_ASSIGN(auto sum, ciphertext1 + ciphertext2);
EXPECT_EQ(sum.PowerOfS(), 2);
ASSERT_OK_AND_ASSIGN(auto prod, ciphertext1* ciphertext2);
EXPECT_EQ(prod.PowerOfS(), 2);
}
}
// Check that substitutions of the form 2^k + 1 work.
TYPED_TEST(SymmetricRlweEncryptionTest, Substitutes) {
for (const auto& params :
rlwe::testing::ContextParameters<TypeParam>::Value()) {
ASSERT_OK_AND_ASSIGN(auto context,
rlwe::RlweContext<TypeParam>::Create(params));
for (int k = 1; k < context->GetLogN(); k++) {
int substitution_power = (1 << k) + 1;
ASSERT_OK_AND_ASSIGN(auto key, this->SampleKey(context.get()));
auto plaintext = rlwe::testing::SamplePlaintext<TypeParam>(
context->GetN(), context->GetT());
// Create the expected polynomial output by substituting the plaintext.
ASSERT_OK_AND_ASSIGN(auto m_plaintext,
rlwe::testing::ConvertToMontgomery<TypeParam>(
plaintext, context->GetModulusParams()));
auto plaintext_ntt = rlwe::Polynomial<TypeParam>::ConvertToNtt(
m_plaintext, context->GetNttParams(), context->GetModulusParams());
ASSERT_OK_AND_ASSIGN(
auto expected_ntt,
plaintext_ntt.Substitute(substitution_power, context->GetNttParams(),
context->GetModulusParams()));
std::vector<typename TypeParam::Int> expected =
rlwe::RemoveError<TypeParam>(
expected_ntt.InverseNtt(context->GetNttParams(),
context->GetModulusParams()),
context->GetModulus(), context->GetT(),
context->GetModulusParams());
// Encrypt and substitute the ciphertext. Decrypt with a substituted key.
ASSERT_OK_AND_ASSIGN(auto ciphertext,
this->Encrypt(key, plaintext, context.get()));
ASSERT_OK_AND_ASSIGN(
auto substituted,
ciphertext.Substitute(substitution_power, context->GetNttParams()));
ASSERT_OK_AND_ASSIGN(auto key_sub, key.Substitute(substitution_power));
ASSERT_OK_AND_ASSIGN(std::vector<typename TypeParam::Int> decrypted,
rlwe::Decrypt<TypeParam>(key_sub, substituted));
EXPECT_EQ(decrypted, expected);
EXPECT_EQ(substituted.PowerOfS(), substitution_power);
EXPECT_EQ(substituted.Error(), ciphertext.Error());
}
}
}
// Check that substitution of 2 does not work.
TYPED_TEST(SymmetricRlweEncryptionTest, SubstitutionFailsOnEvenPower) {
for (const auto& params :
rlwe::testing::ContextParameters<TypeParam>::Value()) {
ASSERT_OK_AND_ASSIGN(auto context,
rlwe::RlweContext<TypeParam>::Create(params));
ASSERT_OK_AND_ASSIGN(auto key, this->SampleKey(context.get()));
auto plaintext = rlwe::testing::SamplePlaintext<TypeParam>(context->GetN(),
context->GetT());
ASSERT_OK_AND_ASSIGN(auto enc,
this->Encrypt(key, plaintext, context.get()));
EXPECT_THAT(
enc.Substitute(2, context->GetNttParams()),
StatusIs(::absl::StatusCode::kInvalidArgument,
HasSubstr("power must be a non-negative odd integer")));
}
}
// Check that the power of s updates after several substitutions.
TYPED_TEST(SymmetricRlweEncryptionTest, PowerOfSUpdatedAfterRepeatedSubs) {
for (const auto& params :
rlwe::testing::ContextParameters<TypeParam>::Value()) {
ASSERT_OK_AND_ASSIGN(auto context,
rlwe::RlweContext<TypeParam>::Create(params));
int substitution_power = 5;
ASSERT_OK_AND_ASSIGN(auto key, this->SampleKey(context.get()));
auto plaintext = rlwe::testing::SamplePlaintext<TypeParam>(context->GetN(),
context->GetT());
// Encrypt and substitute the ciphertext. Decrypt with a substituted key.
ASSERT_OK_AND_ASSIGN(auto ciphertext1,
this->Encrypt(key, plaintext, context.get()));
ASSERT_OK_AND_ASSIGN(
auto ciphertext2,
ciphertext1.Substitute(substitution_power, context->GetNttParams()));
ASSERT_OK_AND_ASSIGN(
auto ciphertext3,
ciphertext2.Substitute(substitution_power, context->GetNttParams()));
EXPECT_EQ(ciphertext3.PowerOfS(),
(substitution_power * substitution_power) % (2 * key.Len()));
}
}
// Check that operations can only be performed when powers of s match.
TYPED_TEST(SymmetricRlweEncryptionTest, PowersOfSMustMatchOnOperations) {
for (const auto& params :
rlwe::testing::ContextParameters<TypeParam>::Value()) {
ASSERT_OK_AND_ASSIGN(auto context,
rlwe::RlweContext<TypeParam>::Create(params));
int substitution_power = 5;
ASSERT_OK_AND_ASSIGN(auto key, this->SampleKey(context.get()));
std::vector<typename TypeParam::Int> plaintext1 =
rlwe::testing::SamplePlaintext<TypeParam>(context->GetN(),
context->GetT());
std::vector<typename TypeParam::Int> plaintext2 =
rlwe::testing::SamplePlaintext<TypeParam>(context->GetN(),
context->GetT());
ASSERT_OK_AND_ASSIGN(auto ciphertext1,
this->Encrypt(key, plaintext1, context.get()));
ASSERT_OK_AND_ASSIGN(auto ciphertext2,
this->Encrypt(key, plaintext2, context.get()));
ASSERT_OK_AND_ASSIGN(
auto ciphertext2_sub,
ciphertext2.Substitute(substitution_power, context->GetNttParams()));
EXPECT_THAT(ciphertext1 + ciphertext2_sub,
StatusIs(::absl::StatusCode::kInvalidArgument,
HasSubstr("must be encrypted with the same key")));
EXPECT_THAT(ciphertext1 * ciphertext2_sub,
StatusIs(::absl::StatusCode::kInvalidArgument,
HasSubstr("must be encrypted with the same key")));
}
}
// Check that the null key has value 0.
TYPED_TEST(SymmetricRlweEncryptionTest, NullKeyHasValueZero) {
for (const auto& params :
rlwe::testing::ContextParameters<TypeParam>::Value()) {
ASSERT_OK_AND_ASSIGN(auto context,
rlwe::RlweContext<TypeParam>::Create(params));
rlwe::Polynomial<TypeParam> zero(context->GetN(),
context->GetModulusParams());
ASSERT_OK_AND_ASSIGN(
auto null_key,
rlwe::SymmetricRlweKey<TypeParam>::NullKey(
context->GetLogN(), context->GetVariance(), context->GetLogT(),
context->GetModulusParams(), context->GetNttParams()));
EXPECT_THAT(zero, Eq(null_key.Key()));
}
}
// Check the addition and subtraction of keys.
TYPED_TEST(SymmetricRlweEncryptionTest, AddAndSubKeys) {
for (const auto& params :
rlwe::testing::ContextParameters<TypeParam>::Value()) {
ASSERT_OK_AND_ASSIGN(auto context,
rlwe::RlweContext<TypeParam>::Create(params));
ASSERT_OK_AND_ASSIGN(auto key_1, this->SampleKey(context.get()));
ASSERT_OK_AND_ASSIGN(auto key_2, this->SampleKey(context.get()));
ASSERT_OK_AND_ASSIGN(auto key_3, key_1.Add(key_2));
ASSERT_OK_AND_ASSIGN(auto key_4, key_1.Sub(key_2));
ASSERT_OK_AND_ASSIGN(
rlwe::Polynomial<TypeParam> poly_3,
key_1.Key().Add(key_2.Key(), context->GetModulusParams()));
ASSERT_OK_AND_ASSIGN(
rlwe::Polynomial<TypeParam> poly_4,
key_1.Key().Sub(key_2.Key(), context->GetModulusParams()));
EXPECT_THAT(key_3.Key(), Eq(poly_3));
EXPECT_THAT(key_4.Key(), Eq(poly_4));
}
}
// Check that decryption works with added and subtracted keys.
TYPED_TEST(SymmetricRlweEncryptionTest, EncryptAndDecryptWithAddAndSubKeys) {
for (const auto& params :
rlwe::testing::ContextParameters<TypeParam>::Value()) {
ASSERT_OK_AND_ASSIGN(auto context,
rlwe::RlweContext<TypeParam>::Create(params));
ASSERT_OK_AND_ASSIGN(auto key_1, this->SampleKey(context.get()));
ASSERT_OK_AND_ASSIGN(auto key_2, this->SampleKey(context.get()));
ASSERT_OK_AND_ASSIGN(auto add_keys, key_1.Add(key_2));
ASSERT_OK_AND_ASSIGN(auto sub_keys, key_1.Sub(key_2));
std::vector<typename TypeParam::Int> plaintext =
rlwe::testing::SamplePlaintext<TypeParam>(context->GetN(),
context->GetT());
ASSERT_OK_AND_ASSIGN(auto add_ciphertext,
this->Encrypt(add_keys, plaintext, context.get()));
ASSERT_OK_AND_ASSIGN(auto sub_ciphertext,
this->Encrypt(sub_keys, plaintext, context.get()));
ASSERT_OK_AND_ASSIGN(auto decrypted_add_ciphertext,
rlwe::Decrypt(add_keys, add_ciphertext));
ASSERT_OK_AND_ASSIGN(auto decrypted_sub_ciphertext,
rlwe::Decrypt(sub_keys, sub_ciphertext));
EXPECT_EQ(plaintext, decrypted_add_ciphertext);
EXPECT_EQ(plaintext, decrypted_sub_ciphertext);
}
}
// Check that the scheme is key homomorphic.
TYPED_TEST(SymmetricRlweEncryptionTest, IsKeyHomomorphic) {
for (const auto& params :
rlwe::testing::ContextParameters<TypeParam>::Value()) {
ASSERT_OK_AND_ASSIGN(auto context,
rlwe::RlweContext<TypeParam>::Create(params));
ASSERT_OK_AND_ASSIGN(auto prng_seed,
rlwe::SingleThreadPrng::GenerateSeed());
ASSERT_OK_AND_ASSIGN(auto prng, rlwe::SingleThreadPrng::Create(prng_seed));
// Generate the keys.
ASSERT_OK_AND_ASSIGN(auto key_1, this->SampleKey(context.get()));
ASSERT_OK_AND_ASSIGN(auto key_2, this->SampleKey(context.get()));
// Generate the plaintexts.
std::vector<typename TypeParam::Int> plaintext_1 =
rlwe::testing::SamplePlaintext<TypeParam>(context->GetN(),
context->GetT());
std::vector<typename TypeParam::Int> plaintext_2 =
rlwe::testing::SamplePlaintext<TypeParam>(context->GetN(),
context->GetT());
ASSERT_OK_AND_ASSIGN(auto plaintext_mont_1,
rlwe::testing::ConvertToMontgomery<TypeParam>(
plaintext_1, context->GetModulusParams()));
ASSERT_OK_AND_ASSIGN(auto plaintext_mont_2,
rlwe::testing::ConvertToMontgomery<TypeParam>(
plaintext_2, context->GetModulusParams()));
auto poly_1 = rlwe::Polynomial<TypeParam>::ConvertToNtt(
plaintext_mont_1, context->GetNttParams(), context->GetModulusParams());
auto poly_2 = rlwe::Polynomial<TypeParam>::ConvertToNtt(
plaintext_mont_2, context->GetNttParams(), context->GetModulusParams());
// Compute the expected plaintexts.
std::vector<typename TypeParam::Int> add_plaintext = plaintext_1;
std::vector<typename TypeParam::Int> sub_plaintext = plaintext_2;
std::transform(
plaintext_1.begin(), plaintext_1.end(), plaintext_2.begin(),
add_plaintext.begin(),
[&context = context](typename TypeParam::Int u,
typename TypeParam::Int v) ->
typename TypeParam::Int { return (u + v) % context->GetT(); });
std::transform(plaintext_1.begin(), plaintext_1.end(), plaintext_2.begin(),
sub_plaintext.begin(),
[&context = context](typename TypeParam::Int u,
typename TypeParam::Int v) ->
typename TypeParam::Int {
return (context->GetT() + u - v) % context->GetT();
});
// Sample the "a" to be used in both ciphertexts.
ASSERT_OK_AND_ASSIGN(auto a,
rlwe::SamplePolynomialFromPrng<TypeParam>(
key_1.Len(), prng.get(), key_1.ModulusParams()));
// Encrypt with the same a and different keys
ASSERT_OK_AND_ASSIGN(auto poly_ciphertext_1,
rlwe::internal::Encrypt(key_1, poly_1, a, prng.get()));
ASSERT_OK_AND_ASSIGN(auto poly_ciphertext_2,
rlwe::internal::Encrypt(key_2, poly_2, a, prng.get()));
// Add and Substract the ciphertexts
ASSERT_OK_AND_ASSIGN(
auto add_poly_ciphertext,
poly_ciphertext_1.Add(poly_ciphertext_2, context->GetModulusParams()));
ASSERT_OK_AND_ASSIGN(
auto sub_poly_ciphertext,
poly_ciphertext_1.Sub(poly_ciphertext_2, context->GetModulusParams()));
// The resulting ciphertexts should be decryptable unded the added (resp.
// substracted) keys.
ASSERT_OK_AND_ASSIGN(auto add_keys, key_1.Add(key_2));
ASSERT_OK_AND_ASSIGN(auto sub_keys, key_1.Sub(key_2));
ASSERT_OK_AND_ASSIGN(
auto decrypted_add_ciphertext,
rlwe::Decrypt(
add_keys,
rlwe::SymmetricRlweCiphertext<TypeParam>(
{add_poly_ciphertext, a.Negate(context->GetModulusParams())}, 1,
context->GetErrorParams()->B_encryption(),
context->GetModulusParams(), context->GetErrorParams())));
ASSERT_OK_AND_ASSIGN(
auto decrypted_sub_ciphertext,
rlwe::Decrypt(
sub_keys,
rlwe::SymmetricRlweCiphertext<TypeParam>(
{sub_poly_ciphertext, a.Negate(context->GetModulusParams())}, 1,
context->GetErrorParams()->B_encryption(),
context->GetModulusParams(), context->GetErrorParams())));
EXPECT_EQ(add_plaintext, decrypted_add_ciphertext);
EXPECT_EQ(sub_plaintext, decrypted_sub_ciphertext);
}
}
// Check that incompatible key cannot be added or subtracted.
TYPED_TEST(SymmetricRlweEncryptionTest, CannotAddOrSubIncompatibleKeys) {
for (const auto& params :
rlwe::testing::ContextParameters<TypeParam>::Value()) {
ASSERT_OK_AND_ASSIGN(auto context,
rlwe::RlweContext<TypeParam>::Create(params));
ASSERT_OK_AND_ASSIGN(auto context_different_variance,
rlwe::RlweContext<TypeParam>::Create(
{.modulus = params.modulus,
.log_n = params.log_n,
.log_t = params.log_t,
.variance = params.variance + 1}));
ASSERT_OK_AND_ASSIGN(
auto context_different_log_t,
rlwe::RlweContext<TypeParam>::Create({.modulus = params.modulus,
.log_n = params.log_n,
.log_t = params.log_t + 1,
.variance = params.variance}));
ASSERT_OK_AND_ASSIGN(auto key_1, this->SampleKey(context.get()));
ASSERT_OK_AND_ASSIGN(auto key_2,
this->SampleKey(context_different_variance.get()));
ASSERT_OK_AND_ASSIGN(auto key_3,
this->SampleKey(context_different_log_t.get()));
EXPECT_THAT(
key_1.Add(key_2),
StatusIs(::absl::StatusCode::kInvalidArgument,
HasSubstr("is different than the variance of this key")));
EXPECT_THAT(
key_1.Sub(key_2),
StatusIs(::absl::StatusCode::kInvalidArgument,
HasSubstr("is different than the variance of this key")));
EXPECT_THAT(key_1.Add(key_3),
StatusIs(::absl::StatusCode::kInvalidArgument,
HasSubstr("is different than the log_t of this key")));
EXPECT_THAT(key_1.Sub(key_3),
StatusIs(::absl::StatusCode::kInvalidArgument,
HasSubstr("is different than the log_t of this key")));
}
}
} // namespace