blob: 2383b3e2c8fe52c6a196f7fd4f7a88075db73e66 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/lib/crypto_util/random.h"
#include <bitset>
#include <cmath>
#include <memory>
#include <utility>
#include <vector>
#include <gtest/gtest.h>
#include "src/lib/crypto_util/random_test_utils.h"
namespace cobalt::crypto {
// Returns the number of bits of the size bytes in x that are set to one.
int NumBitsSet(byte* x, std::size_t size) {
int count = 0;
for (std::size_t i = 0; i < size; i++) {
std::bitset<8> bit_set(x[i]);
count += bit_set.count();
}
return count;
}
// Performs the experiment of invoking RandomBits(p, 10) 100 times and
// returns the average number of bits set per byte.
// Uses DeterministicRandom in order to ensure reproducibility.
float AverageNumBitsSet(float p) {
std::unique_ptr<Random> rand(new DeterministicRandom());
int cumulative_num_bits_set = 0;
std::size_t size = 10;
auto random_bits = std::make_unique<byte[]>(size);
for (int i = 0; i < 100; i++) {
rand->RandomBits(p, random_bits.get(), size);
cumulative_num_bits_set += NumBitsSet(random_bits.get(), size);
}
return static_cast<float>(cumulative_num_bits_set) / 1000.0f;
}
// Invokes RandomBits(p, 10) 1000 times, collects the sum of the values for
// each of the 8 bits per byte separately, and then performs Pearson's
// chi-squared test on each bit separately to check for goodness of fit to a
// binomial distribution with parameter p. Fails if chi-squared >= 5.024 which
// corresponds to rejecting the null hypothesis with confidence 0.975.
// Uses DeterministicRandom in order to ensure reproducibility.
void DoChiSquaredTest(float p) {
// Do the experiment and collect the counts.
std::unique_ptr<Random> rand(new DeterministicRandom());
std::vector<unsigned int> counts(8, 0);
std::size_t size = 10;
static const int kNumTrials = 1000;
auto random_bits = std::make_unique<byte[]>(size);
for (int i = 0; i < kNumTrials; i++) {
rand->RandomBits(p, random_bits.get(), size);
for (std::size_t b = 0; b < size; b++) {
std::bitset<8> bit_set(random_bits[b]);
for (size_t j = 0; j < 8; j++) {
counts[j] += bit_set[j];
}
}
}
// Check the errors.
const double expected_1 = static_cast<double>(kNumTrials * size) * p;
const double expected_0 = static_cast<double>(kNumTrials * size) - expected_1;
for (size_t j = 0; j < 8; j++) {
// Difference between actual 1 count and expected 1 count.
double delta_1 = static_cast<double>(counts[j]) - expected_1;
// Difference between actual 0 count and expected 0 count.
double delta_0 = static_cast<double>(kNumTrials * size - counts[j]) - expected_0;
// Compute and check the Chi-Squared value.
double chi_squared = delta_1 * delta_1 / expected_1 + delta_0 * delta_0 / expected_0;
// The number 5.024 below has the property that
// P(X < 5.024) = 0.975 where X ~ chi^2(1)
EXPECT_TRUE(chi_squared < 5.024);
}
}
// Tests the function RandomBits()
TEST(RandomTest, TestRandomBitsExtremeValues) {
std::unique_ptr<Random> rand(new Random());
std::size_t size = 100;
// When p = 0 none of the bits should be set.
auto buffer = std::make_unique<byte[]>(size);
rand->RandomBits(0.0, buffer.get(), size);
for (std::size_t i = 0; i < size; i++) {
EXPECT_EQ(0, buffer[i]);
}
// When p = 1 all of the bits should be set.
rand->RandomBits(1.0, buffer.get(), size);
for (std::size_t i = 0; i < size; i++) {
EXPECT_EQ(0xFF, buffer[i]);
}
}
TEST(RandomTest, TestRandomBitsStatisticalTests) {
// We use DeterministicRandom so that the test is not flaky.
// When p = i/8 then on average i of the bits should be set.
for (int i = 1; i <= 7; i++) {
auto p = static_cast<float>(i / 8.0);
EXPECT_NEAR(i, AverageNumBitsSet(p), 2 * p);
}
// Pick some other values for p and both test the average number of bits
// set and also perform a Chi-squared test.
static const float ps[] = {0.01f, 0.1f, 0.2f, 0.25f, 0.3f, 0.4f, 0.5f,
0.6f, 0.7f, 0.75f, 0.8f, 0.9f, 0.95f, 0.99f};
for (auto p : ps) {
EXPECT_NEAR(8.0 * p, AverageNumBitsSet(p), 2 * p);
DoChiSquaredTest(p);
}
}
} // namespace cobalt::crypto