mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-04-29 14:59:39 -04:00
test: compare util::Xor with randomized inputs against simple impl
Since production code only uses keys of length 8, we're not testing with other values anymore
This commit is contained in:
parent
971952588d
commit
b9c54ccd8c
2 changed files with 124 additions and 36 deletions
|
@ -30,18 +30,51 @@ BOOST_AUTO_TEST_CASE(dbwrapper)
|
|||
{
|
||||
// Perform tests both obfuscated and non-obfuscated.
|
||||
for (const bool obfuscate : {false, true}) {
|
||||
fs::path ph = m_args.GetDataDirBase() / (obfuscate ? "dbwrapper_obfuscate_true" : "dbwrapper_obfuscate_false");
|
||||
CDBWrapper dbw({.path = ph, .cache_bytes = 1 << 20, .memory_only = true, .wipe_data = false, .obfuscate = obfuscate});
|
||||
uint8_t key{'k'};
|
||||
uint256 in = m_rng.rand256();
|
||||
uint256 res;
|
||||
constexpr size_t CACHE_SIZE{1_MiB};
|
||||
const fs::path path{m_args.GetDataDirBase() / "dbwrapper"};
|
||||
|
||||
// Ensure that we're doing real obfuscation when obfuscate=true
|
||||
BOOST_CHECK(obfuscate != is_null_key(dbwrapper_private::GetObfuscateKey(dbw)));
|
||||
std::vector<uint8_t> obfuscation_key{};
|
||||
std::vector<std::pair<uint8_t, uint256>> key_values{};
|
||||
|
||||
BOOST_CHECK(dbw.Write(key, in));
|
||||
BOOST_CHECK(dbw.Read(key, res));
|
||||
BOOST_CHECK_EQUAL(res.ToString(), in.ToString());
|
||||
// Write values
|
||||
{
|
||||
CDBWrapper dbw{{.path = path, .cache_bytes = CACHE_SIZE, .wipe_data = true, .obfuscate = obfuscate}};
|
||||
BOOST_CHECK_EQUAL(obfuscate, !dbw.IsEmpty());
|
||||
|
||||
// Ensure that we're doing real obfuscation when obfuscate=true
|
||||
BOOST_CHECK(obfuscate != is_null_key(dbwrapper_private::GetObfuscateKey(dbw)));
|
||||
|
||||
obfuscation_key = dbwrapper_private::GetObfuscateKey(dbw);
|
||||
|
||||
for (uint8_t k{0}; k < 10; ++k) {
|
||||
uint8_t key{k};
|
||||
uint256 value{m_rng.rand256()};
|
||||
BOOST_CHECK(dbw.Write(key, value));
|
||||
key_values.emplace_back(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that the obfuscation key is never obfuscated
|
||||
{
|
||||
CDBWrapper dbw{{.path = path, .cache_bytes = CACHE_SIZE, .obfuscate = false}};
|
||||
BOOST_CHECK(obfuscation_key == dbwrapper_private::GetObfuscateKey(dbw));
|
||||
}
|
||||
|
||||
// Read back the values
|
||||
{
|
||||
CDBWrapper dbw{{.path = path, .cache_bytes = CACHE_SIZE, .obfuscate = obfuscate}};
|
||||
|
||||
// Ensure obfuscation is read back correctly
|
||||
BOOST_CHECK(obfuscate != is_null_key(dbwrapper_private::GetObfuscateKey(dbw)));
|
||||
BOOST_CHECK(obfuscation_key == dbwrapper_private::GetObfuscateKey(dbw));
|
||||
|
||||
// Verify all written values
|
||||
for (const auto& [key, expected_value] : key_values) {
|
||||
uint256 read_value{};
|
||||
BOOST_CHECK(dbw.Read(key, read_value));
|
||||
BOOST_CHECK_EQUAL(read_value.ToString(), expected_value.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,19 +13,82 @@
|
|||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
using namespace std::string_literals;
|
||||
using namespace util::hex_literals;
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(streams_tests, BasicTestingSetup)
|
||||
|
||||
// Test that obfuscation can be properly reverted even with random chunk sizes.
|
||||
BOOST_AUTO_TEST_CASE(xor_roundtrip_random_chunks)
|
||||
{
|
||||
auto apply_random_xor_chunks{[&](std::span<std::byte> target, const std::span<std::byte> obfuscation) {
|
||||
for (size_t offset{0}; offset < target.size();) {
|
||||
const size_t chunk_size{1 + m_rng.randrange(target.size() - offset)};
|
||||
util::Xor(target.subspan(offset, chunk_size), obfuscation, offset);
|
||||
offset += chunk_size;
|
||||
}
|
||||
}};
|
||||
|
||||
for (size_t test{0}; test < 100; ++test) {
|
||||
const size_t write_size{1 + m_rng.randrange(100U)};
|
||||
const std::vector original{m_rng.randbytes<std::byte>(write_size)};
|
||||
std::vector roundtrip{original};
|
||||
|
||||
auto key_bytes{m_rng.randbool() ? std::vector(sizeof(uint64_t), std::byte{0}) : m_rng.randbytes<std::byte>(sizeof(uint64_t))};
|
||||
uint64_t obfuscation;
|
||||
std::memcpy(&obfuscation, key_bytes.data(), sizeof(obfuscation));
|
||||
apply_random_xor_chunks(roundtrip, key_bytes);
|
||||
|
||||
// Verify intermediate state differs from original unless the key is all zeros
|
||||
const bool all_zeros{(obfuscation == 0) || std::ranges::all_of(
|
||||
std::span{key_bytes}.first(std::min(write_size, key_bytes.size())), [](auto b) { return b == std::byte{0}; })};
|
||||
BOOST_CHECK_EQUAL(original != roundtrip, !all_zeros);
|
||||
|
||||
apply_random_xor_chunks(roundtrip, key_bytes);
|
||||
BOOST_CHECK(original == roundtrip);
|
||||
}
|
||||
}
|
||||
|
||||
// Compares optimized obfuscation against a trivial byte-by-byte reference implementation
|
||||
// with random offsets to ensure proper handling of key wrapping.
|
||||
BOOST_AUTO_TEST_CASE(xor_bytes_reference)
|
||||
{
|
||||
auto expected_xor{[](std::span<std::byte> target, const std::span<const std::byte> obfuscation, size_t key_offset) {
|
||||
for (auto& b : target) {
|
||||
b ^= obfuscation[key_offset++ % obfuscation.size()];
|
||||
}
|
||||
}};
|
||||
|
||||
for (size_t test{0}; test < 100; ++test) {
|
||||
const size_t write_size{1 + m_rng.randrange(100U)};
|
||||
const size_t key_offset{m_rng.randrange(3 * 8U)}; // Should wrap around
|
||||
|
||||
std::vector key_bytes{m_rng.randbytes<std::byte>(sizeof(uint64_t))};
|
||||
uint64_t obfuscation;
|
||||
std::memcpy(&obfuscation, key_bytes.data(), sizeof(obfuscation));
|
||||
|
||||
std::vector expected{m_rng.randbytes<std::byte>(write_size)};
|
||||
std::vector actual{expected};
|
||||
|
||||
expected_xor(expected, key_bytes, key_offset);
|
||||
util::Xor(actual, key_bytes, key_offset);
|
||||
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(expected.begin(), expected.end(), actual.begin(), actual.end());
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(xor_file)
|
||||
{
|
||||
fs::path xor_path{m_args.GetDataDirBase() / "test_xor.bin"};
|
||||
auto raw_file{[&](const auto& mode) { return fsbridge::fopen(xor_path, mode); }};
|
||||
const std::vector<uint8_t> test1{1, 2, 3};
|
||||
const std::vector<uint8_t> test2{4, 5};
|
||||
const std::vector<std::byte> xor_pat{std::byte{0xff}, std::byte{0x00}};
|
||||
auto key_bytes{"ff00ff00ff00ff00"_hex_v};
|
||||
uint64_t xor_key;
|
||||
std::memcpy(&xor_key, key_bytes.data(), sizeof(xor_key));
|
||||
|
||||
{
|
||||
// Check errors for missing file
|
||||
AutoFile xor_file{raw_file("rb"), xor_pat};
|
||||
AutoFile xor_file{raw_file("rb"), key_bytes};
|
||||
BOOST_CHECK_EXCEPTION(xor_file << std::byte{}, std::ios_base::failure, HasReason{"AutoFile::write: file handle is nullpt"});
|
||||
BOOST_CHECK_EXCEPTION(xor_file >> std::byte{}, std::ios_base::failure, HasReason{"AutoFile::read: file handle is nullpt"});
|
||||
BOOST_CHECK_EXCEPTION(xor_file.ignore(1), std::ios_base::failure, HasReason{"AutoFile::ignore: file handle is nullpt"});
|
||||
|
@ -37,7 +100,7 @@ BOOST_AUTO_TEST_CASE(xor_file)
|
|||
#else
|
||||
const char* mode = "wbx";
|
||||
#endif
|
||||
AutoFile xor_file{raw_file(mode), xor_pat};
|
||||
AutoFile xor_file{raw_file(mode), key_bytes};
|
||||
xor_file << test1 << test2;
|
||||
}
|
||||
{
|
||||
|
@ -50,7 +113,7 @@ BOOST_AUTO_TEST_CASE(xor_file)
|
|||
BOOST_CHECK_EXCEPTION(non_xor_file.ignore(1), std::ios_base::failure, HasReason{"AutoFile::ignore: end of file"});
|
||||
}
|
||||
{
|
||||
AutoFile xor_file{raw_file("rb"), xor_pat};
|
||||
AutoFile xor_file{raw_file("rb"), key_bytes};
|
||||
std::vector<std::byte> read1, read2;
|
||||
xor_file >> read1 >> read2;
|
||||
BOOST_CHECK_EQUAL(HexStr(read1), HexStr(test1));
|
||||
|
@ -59,7 +122,7 @@ BOOST_AUTO_TEST_CASE(xor_file)
|
|||
BOOST_CHECK_EXCEPTION(xor_file >> std::byte{}, std::ios_base::failure, HasReason{"AutoFile::read: end of file"});
|
||||
}
|
||||
{
|
||||
AutoFile xor_file{raw_file("rb"), xor_pat};
|
||||
AutoFile xor_file{raw_file("rb"), key_bytes};
|
||||
std::vector<std::byte> read2;
|
||||
// Check that ignore works
|
||||
xor_file.ignore(4);
|
||||
|
@ -75,7 +138,7 @@ BOOST_AUTO_TEST_CASE(streams_vector_writer)
|
|||
{
|
||||
unsigned char a(1);
|
||||
unsigned char b(2);
|
||||
unsigned char bytes[] = { 3, 4, 5, 6 };
|
||||
unsigned char bytes[] = {3, 4, 5, 6};
|
||||
std::vector<unsigned char> vch;
|
||||
|
||||
// Each test runs twice. Serializing a second time at the same starting
|
||||
|
@ -222,34 +285,26 @@ BOOST_AUTO_TEST_CASE(bitstream_reader_writer)
|
|||
|
||||
BOOST_AUTO_TEST_CASE(streams_serializedata_xor)
|
||||
{
|
||||
std::vector<std::byte> in;
|
||||
|
||||
// Degenerate case
|
||||
{
|
||||
DataStream ds{in};
|
||||
ds.Xor({0x00, 0x00});
|
||||
DataStream ds{};
|
||||
ds.Xor("0000000000000000"_hex_v_u8);
|
||||
BOOST_CHECK_EQUAL(""s, ds.str());
|
||||
}
|
||||
|
||||
in.push_back(std::byte{0x0f});
|
||||
in.push_back(std::byte{0xf0});
|
||||
|
||||
// Single character key
|
||||
{
|
||||
DataStream ds{in};
|
||||
ds.Xor({0xff});
|
||||
const auto key_bytes{"ffffffffffffffff"_hex_v_u8};
|
||||
|
||||
DataStream ds{"0ff0"_hex};
|
||||
ds.Xor(key_bytes);
|
||||
BOOST_CHECK_EQUAL("\xf0\x0f"s, ds.str());
|
||||
}
|
||||
|
||||
// Multi character key
|
||||
|
||||
in.clear();
|
||||
in.push_back(std::byte{0xf0});
|
||||
in.push_back(std::byte{0x0f});
|
||||
|
||||
{
|
||||
DataStream ds{in};
|
||||
ds.Xor({0xff, 0x0f});
|
||||
const auto key_bytes{"ff0fff0fff0fff0f"_hex_v_u8};
|
||||
|
||||
DataStream ds{"f00f"_hex};
|
||||
ds.Xor(key_bytes);
|
||||
BOOST_CHECK_EQUAL("\x0f\x00"s, ds.str());
|
||||
}
|
||||
}
|
||||
|
@ -272,7 +327,7 @@ BOOST_AUTO_TEST_CASE(streams_buffered_file)
|
|||
BOOST_CHECK(false);
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_CHECK(strstr(e.what(),
|
||||
"Rewind limit must be less than buffer size") != nullptr);
|
||||
"Rewind limit must be less than buffer size") != nullptr);
|
||||
}
|
||||
|
||||
// The buffer is 25 bytes, allow rewinding 10 bytes.
|
||||
|
@ -361,7 +416,7 @@ BOOST_AUTO_TEST_CASE(streams_buffered_file)
|
|||
BOOST_CHECK(false);
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_CHECK(strstr(e.what(),
|
||||
"BufferedFile::Fill: end of file") != nullptr);
|
||||
"BufferedFile::Fill: end of file") != nullptr);
|
||||
}
|
||||
// Attempting to read beyond the end sets the EOF indicator.
|
||||
BOOST_CHECK(bf.eof());
|
||||
|
|
Loading…
Add table
Reference in a new issue