blob: 7296971644a51b22997da33f540c8c3730a3147c [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors
//
// 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
//
// http://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 "algorithms/forculus/forculus_analyzer.h"
#include <gflags/gflags.h>
#include <glog/logging.h>
#include "algorithms/forculus/forculus_encrypter.h"
#include "encoder/client_secret.h"
#include "third_party/googletest/googletest/include/gtest/gtest.h"
namespace cobalt {
namespace forculus {
using encoder::ClientSecret;
static const uint32_t kThreshold = 20;
namespace {
// Encrypts the plaintext using Forculus encryption with the given day_index
// and EpochType and threshold = kThreshold and default values for the other
// parameters. A fresh ClientSecret will be generated each time this function
// is invoked. Returns a ForculusObservation containing the ciphertext.
ForculusObservation Encrypt(uint32_t day_index, const EpochType& epoch_type,
const std::string& plaintext) {
// Make a config with the given threshold
ForculusConfig config;
config.set_threshold(kThreshold);
config.set_epoch_type(epoch_type);
// Construct an Encrypter.
ForculusEncrypter encrypter(config, 0, 0, 0, "",
ClientSecret::GenerateNewSecret());
// Invoke Encrypt() and check the status.
ForculusObservation obs;
EXPECT_EQ(ForculusEncrypter::kOK,
encrypter.Encrypt(plaintext, day_index,
&obs));
return obs;
}
// Creates and adds observations to |forculus_analyzer| based on the parameters.
void AddObservations(ForculusAnalyzer* forculus_analyzer, uint32_t day_index,
const EpochType& epoch_type, const std::string& plaintext, int num_clients,
int num_copies_per_client) {
// Simulate num_clients different clients.
for (int i = 0; i < num_clients; i++) {
auto observation = Encrypt(day_index, epoch_type, plaintext);
// Each client adds the same observation |num_copies_per_client| times.
for (int i = 0; i < num_copies_per_client; i++) {
EXPECT_TRUE(forculus_analyzer->AddObservation(day_index, observation));
}
}
}
} // namespace
// Tests the use of a ForculusAnalyzer when used properly with no
// errors. We simulate multiple clients encrypting multpile plaintexts
// on multiple days. Each client can encrypt the same plaintext multiple
// times on the same day.
TEST(ForculusAnalyzerTest, NoErrors) {
ForculusConfig forculus_config;
forculus_config.set_threshold(kThreshold);
ForculusAnalyzer forculus_analyzer(forculus_config);
const std::string plaintext1("The woods are lovely, dark and deep,");
const std::string plaintext2("But I have promises to keep,");
const std::string plaintext3("And miles to go before I sleep,");
const std::string plaintext4("And miles to go before I sleep.");
// 20 * 5 observations of plaintext1 on day 0. (This means 20 different
// clients each encrypting plaintext1 5 times on day 0.)
AddObservations(&forculus_analyzer, 0, DAY, plaintext1, kThreshold, 5);
// 20 * 5 observations of plaintext1 on day 1.
AddObservations(&forculus_analyzer, 1, DAY, plaintext1, kThreshold, 5);
// 21 * 6 observations of plaintext2 on day 0.
AddObservations(&forculus_analyzer, 0, DAY, plaintext2, kThreshold + 1, 6);
// 19 * 6 observations of plaintext2 on day 1. These will not be decrypted.
AddObservations(&forculus_analyzer, 1, DAY, plaintext2, kThreshold - 1, 6);
// 19 * 7 observations of plaintext3 on day 0. These will not be decrypted.
AddObservations(&forculus_analyzer, 0, DAY, plaintext3, kThreshold - 1, 7);
// 19 * 7 observations of plaintext3 on day 1. These will not be decrypted.
AddObservations(&forculus_analyzer, 1, DAY, plaintext3, kThreshold - 1, 7);
// 22 * 8 observations of plaintext4 on day 3.
AddObservations(&forculus_analyzer, 3, DAY, plaintext4, kThreshold + 2, 8);
EXPECT_EQ(0u, forculus_analyzer.observation_errors());
static const size_t kExpectedNumObservations =
kThreshold * 5 + kThreshold * 5 +
(kThreshold + 1) * 6 + (kThreshold - 1) * 6 +
(kThreshold - 1) * 7 + (kThreshold - 1) * 7 +
(kThreshold + 2) * 8;
EXPECT_EQ(kExpectedNumObservations, forculus_analyzer.num_observations());
auto results = forculus_analyzer.TakeResults();
// We should have decrypted plaintexts 1, 2 and 4.
EXPECT_EQ(3u, results.size());
// Check plaintext1
EXPECT_EQ(kThreshold * 5 + kThreshold * 5, results[plaintext1]->total_count);
EXPECT_EQ(2u, results[plaintext1]->num_epochs);
// Check plaintext2
EXPECT_EQ((kThreshold + 1) * 6, results[plaintext2]->total_count);
EXPECT_EQ(1u, results[plaintext2]->num_epochs);
// Check plaintext4
EXPECT_EQ((kThreshold + 2) * 8, results[plaintext4]->total_count);
EXPECT_EQ(1u, results[plaintext4]->num_epochs);
// Plaintext3 should not be decrypted.
EXPECT_EQ(nullptr, results[plaintext3]);
}
// We test Forculus encryption and analysis using DAY, WEEK and MONTH epochs.
TEST(ForculusAnalyzerTest, TestEpochTypes) {
ForculusConfig forculus_config;
forculus_config.set_threshold(kThreshold);
std::unique_ptr<ForculusAnalyzer> forculus_analyzer(
new ForculusAnalyzer(forculus_config));
const std::string plaintext("Some text");
// First we test with a DAY epoch, the default.
// Add 10 observations on day 0,
AddObservations(forculus_analyzer.get(), 0, DAY, plaintext, kThreshold - 10,
1);
// and 10 observations on day 1.
AddObservations(forculus_analyzer.get(), 1, DAY, plaintext, 10, 1);
// Since day 0 and day 1 are different epochs we should not have decrypted the
// plaintext.
auto results = forculus_analyzer->TakeResults();
EXPECT_EQ(0u, results.size());
// Next we test with a WEEK epoch
forculus_config.set_epoch_type(WEEK);
forculus_analyzer.reset(new ForculusAnalyzer(forculus_config));
// Add 10 observations on day 0,
AddObservations(forculus_analyzer.get(), 0, WEEK, plaintext, kThreshold - 10,
1);
// and 10 observations on day 1.
AddObservations(forculus_analyzer.get(), 1, WEEK, plaintext, 10, 1);
// Since day 0 and day 1 are in the same epoch we should have decrypted the
// plaintext.
results = forculus_analyzer->TakeResults();
EXPECT_EQ(1u, results.size());
// Next we test with a WEEK epoch but two days in different weeks.
forculus_analyzer.reset(new ForculusAnalyzer(forculus_config));
// Add 10 observations on day 0,
AddObservations(forculus_analyzer.get(), 0, WEEK, plaintext, kThreshold - 10,
1);
// and 10 observations on day 7.
AddObservations(forculus_analyzer.get(), 7, WEEK, plaintext, 10, 1);
// Since day 0 and day 7 are different epochs we should not have decrypted the
// plaintext.
results = forculus_analyzer->TakeResults();
EXPECT_EQ(0u, results.size());
// Next we test with a MONTH epoch
forculus_config.set_epoch_type(MONTH);
forculus_analyzer.reset(new ForculusAnalyzer(forculus_config));
// Add 10 observations on day 0,
AddObservations(forculus_analyzer.get(), 0, MONTH, plaintext, kThreshold - 10,
1);
// and 10 observations on day 7.
AddObservations(forculus_analyzer.get(), 7, MONTH, plaintext, 10, 1);
// Since day 0 and day 7 are in the same epoch we should have decrypted the
// plaintext.
results = forculus_analyzer->TakeResults();
EXPECT_EQ(1u, results.size());
// Finally we test with a MONTH epoch but two days in different months.
forculus_config.set_epoch_type(MONTH);
forculus_analyzer.reset(new ForculusAnalyzer(forculus_config));
// Add 10 observations on day 0,
AddObservations(forculus_analyzer.get(), 0, MONTH, plaintext, kThreshold - 10,
1);
// and 10 observations on day 31.
AddObservations(forculus_analyzer.get(), 31, MONTH, plaintext, 10, 1);
// Since day 0 and day 31 are in different epochs we should not have decrypted
// the plaintext.
results = forculus_analyzer->TakeResults();
EXPECT_EQ(0u, results.size());
}
// Tests the use of a ForculusAnalyzer when fed observations with errors.
TEST(ForculusAnalyzerTest, WithErrors) {
ForculusConfig forculus_config;
forculus_config.set_threshold(3);
ForculusAnalyzer forculus_analyzer(forculus_config);
ForculusObservation obs;
obs.set_ciphertext("1 ciphertext fake");
obs.set_point_x("1 x fake");
obs.set_point_y("1 y fake");
// Add an observation.
EXPECT_TRUE(forculus_analyzer.AddObservation(0, obs));
EXPECT_EQ(1u, forculus_analyzer.num_observations());
EXPECT_EQ(0u, forculus_analyzer.observation_errors());
// Now add another observation with the same ciphertext and x-value
// but a different y-value. This causes an error.
obs.set_point_y("2 y fake");
EXPECT_FALSE(forculus_analyzer.AddObservation(0, obs));
EXPECT_EQ(1u, forculus_analyzer.num_observations());
EXPECT_EQ(1u, forculus_analyzer.observation_errors());
// The whole ciphertext is considered corupt now so even changing to
// a differnt x value still yields an error.
obs.set_point_x("2 x fake");
EXPECT_FALSE(forculus_analyzer.AddObservation(0, obs));
EXPECT_EQ(1u, forculus_analyzer.num_observations());
EXPECT_EQ(2u, forculus_analyzer.observation_errors());
// Adding an observation for a different epoch succeeds.
obs.set_point_x("1 x fake");
obs.set_point_y("1 y fake");
EXPECT_TRUE(forculus_analyzer.AddObservation(1, obs));
EXPECT_EQ(2u, forculus_analyzer.num_observations());
EXPECT_EQ(2u, forculus_analyzer.observation_errors());
// Adding a second obseration for epoch 1 also succeeds.
obs.set_point_x("2 x fake");
obs.set_point_y("2 y fake");
EXPECT_TRUE(forculus_analyzer.AddObservation(1, obs));
EXPECT_EQ(3u, forculus_analyzer.num_observations());
EXPECT_EQ(2u, forculus_analyzer.observation_errors());
// Adding a third obseration for epoch 1 invokes a decryption which fails.
obs.set_point_x("3 x fake");
obs.set_point_y("3 y fake");
EXPECT_FALSE(forculus_analyzer.AddObservation(1, obs));
EXPECT_EQ(3u, forculus_analyzer.num_observations());
EXPECT_EQ(3u, forculus_analyzer.observation_errors());
// Adding a fourth obseration for epoch 1 also fails.
obs.set_point_x("4 x fake");
obs.set_point_y("4 y fake");
EXPECT_FALSE(forculus_analyzer.AddObservation(1, obs));
EXPECT_EQ(3u, forculus_analyzer.num_observations());
EXPECT_EQ(4u, forculus_analyzer.observation_errors());
// There should be no results.
auto results = forculus_analyzer.TakeResults();
EXPECT_EQ(0u, results.size());
}
} // namespace forculus
} // namespace cobalt
int main(int argc, char **argv) {
google::ParseCommandLineFlags(&argc, &argv, true);
google::InitGoogleLogging(argv[0]);
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}