diff --git a/src/bench/obfuscation.cpp b/src/bench/obfuscation.cpp index 2e9f9a453af..178be56a5d5 100644 --- a/src/bench/obfuscation.cpp +++ b/src/bench/obfuscation.cpp @@ -13,11 +13,11 @@ static void ObfuscationBench(benchmark::Bench& bench) { FastRandomContext frc{/*fDeterministic=*/true}; auto data{frc.randbytes(1024)}; - const auto key{frc.randbytes()}; + const Obfuscation obfuscation{frc.randbytes()}; size_t offset{0}; bench.batch(data.size()).unit("byte").run([&] { - Obfuscation().Xor(data, key, offset++); // mutated differently each time + obfuscation(data, offset++); // mutated differently each time ankerl::nanobench::doNotOptimizeAway(data); }); } diff --git a/src/dbwrapper.cpp b/src/dbwrapper.cpp index f92c9b4cf62..b13699572c4 100644 --- a/src/dbwrapper.cpp +++ b/src/dbwrapper.cpp @@ -174,7 +174,7 @@ void CDBBatch::Clear() void CDBBatch::WriteImpl(std::span key, DataStream& ssValue) { leveldb::Slice slKey(CharCast(key.data()), key.size()); - ssValue.Xor(dbwrapper_private::GetObfuscation(parent)); + dbwrapper_private::GetObfuscation(parent)(ssValue); leveldb::Slice slValue(CharCast(ssValue.data()), ssValue.size()); m_impl_batch->batch.Put(slKey, slValue); } @@ -249,15 +249,14 @@ CDBWrapper::CDBWrapper(const DBParams& params) LogPrintf("Finished database compaction of %s\n", fs::PathToString(params.path)); } - m_obfuscation = std::vector(Obfuscation::KEY_SIZE, '\000'); // Needed for unobfuscated Read()/Write() below + assert(!m_obfuscation); // Needed for unobfuscated Read()/Write() below if (!Read(OBFUSCATION_KEY_KEY, m_obfuscation) && params.obfuscate && IsEmpty()) { // Generate, write and read back the new obfuscation key, making sure we don't obfuscate the key itself Write(OBFUSCATION_KEY_KEY, FastRandomContext{}.randbytes(Obfuscation::KEY_SIZE)); Read(OBFUSCATION_KEY_KEY, m_obfuscation); - LogInfo("Wrote new obfuscation key for %s: %s", fs::PathToString(params.path), HexStr(m_obfuscation)); + LogInfo("Wrote new obfuscation key for %s: %s", fs::PathToString(params.path), m_obfuscation.HexKey()); } - LogInfo("Using obfuscation key for %s: %s", fs::PathToString(params.path), HexStr(m_obfuscation)); - + LogInfo("Using obfuscation key for %s: %s", fs::PathToString(params.path), m_obfuscation.HexKey()); } CDBWrapper::~CDBWrapper() @@ -385,7 +384,7 @@ void CDBIterator::Next() { m_impl_iter->iter->Next(); } namespace dbwrapper_private { -const std::vector& GetObfuscation(const CDBWrapper &w) +const Obfuscation& GetObfuscation(const CDBWrapper& w) { return w.m_obfuscation; } diff --git a/src/dbwrapper.h b/src/dbwrapper.h index 0935320ecac..c5d49404861 100644 --- a/src/dbwrapper.h +++ b/src/dbwrapper.h @@ -18,7 +18,6 @@ #include #include #include -#include static const size_t DBWRAPPER_PREALLOC_KEY_SIZE = 64; static const size_t DBWRAPPER_PREALLOC_VALUE_SIZE = 1024; @@ -63,8 +62,7 @@ namespace dbwrapper_private { * Database obfuscation should be considered an implementation detail of the * specific database. */ -const std::vector& GetObfuscation(const CDBWrapper &w); - +const Obfuscation& GetObfuscation(const CDBWrapper&); }; // namespace dbwrapper_private bool DestroyDB(const std::string& path_str); @@ -166,7 +164,7 @@ public: template bool GetValue(V& value) { try { DataStream ssValue{GetValueImpl()}; - ssValue.Xor(dbwrapper_private::GetObfuscation(parent)); + dbwrapper_private::GetObfuscation(parent)(ssValue); ssValue >> value; } catch (const std::exception&) { return false; @@ -179,7 +177,7 @@ struct LevelDBContext; class CDBWrapper { - friend const std::vector& dbwrapper_private::GetObfuscation(const CDBWrapper &w); + friend const Obfuscation& dbwrapper_private::GetObfuscation(const CDBWrapper&); private: //! holds all leveldb-specific fields of this class std::unique_ptr m_db_context; @@ -187,8 +185,8 @@ private: //! the name of this database std::string m_name; - //! a key used for optional XOR-obfuscation of the database - std::vector m_obfuscation; + //! optional XOR-obfuscation of the database + Obfuscation m_obfuscation; //! obfuscation key storage key, null-prefixed to avoid collisions inline static const std::string OBFUSCATION_KEY_KEY{"\000obfuscate_key", 14}; // explicit size to avoid truncation at leading \0 @@ -223,7 +221,7 @@ public: } try { DataStream ssValue{MakeByteSpan(*strValue)}; - ssValue.Xor(m_obfuscation); + m_obfuscation(ssValue); ssValue >> value; } catch (const std::exception&) { return false; diff --git a/src/node/blockstorage.cpp b/src/node/blockstorage.cpp index 53a2fd14c38..4f6a4977bdb 100644 --- a/src/node/blockstorage.cpp +++ b/src/node/blockstorage.cpp @@ -1174,7 +1174,7 @@ static auto InitBlocksdirXorKey(const BlockManager::Options& opts) }; } LogInfo("Using obfuscation key for blocksdir *.dat files (%s): '%s'\n", fs::PathToString(opts.blocks_dir), HexStr(obfuscation)); - return std::vector{obfuscation.begin(), obfuscation.end()}; + return Obfuscation{obfuscation}; } BlockManager::BlockManager(const util::SignalInterrupt& interrupt, Options opts) diff --git a/src/node/blockstorage.h b/src/node/blockstorage.h index 5c3a5f024b8..cee0eb61ed6 100644 --- a/src/node/blockstorage.h +++ b/src/node/blockstorage.h @@ -235,7 +235,7 @@ private: const bool m_prune_mode; - const std::vector m_obfuscation; + const Obfuscation m_obfuscation; /** Dirty block index entries. */ std::set m_dirty_blockindex; diff --git a/src/node/mempool_persist.cpp b/src/node/mempool_persist.cpp index eac8d386e74..5b0f80be6c8 100644 --- a/src/node/mempool_persist.cpp +++ b/src/node/mempool_persist.cpp @@ -64,7 +64,7 @@ bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, Chainstate& active if (version == MEMPOOL_DUMP_VERSION_NO_XOR_KEY) { file.SetObfuscation({}); } else if (version == MEMPOOL_DUMP_VERSION) { - std::vector obfuscation(Obfuscation::KEY_SIZE); + Obfuscation obfuscation; file >> obfuscation; file.SetObfuscation(obfuscation); } else { @@ -183,8 +183,7 @@ bool DumpMempool(const CTxMemPool& pool, const fs::path& dump_path, FopenFn mock file << version; if (!pool.m_opts.persist_v1_dat) { - std::vector obfuscation(Obfuscation::KEY_SIZE); - FastRandomContext{}.fillrand(obfuscation); + const Obfuscation obfuscation{FastRandomContext{}.randbytes()}; file << obfuscation; file.SetObfuscation(obfuscation); } else { diff --git a/src/streams.cpp b/src/streams.cpp index b33a1288879..0364c2134f4 100644 --- a/src/streams.cpp +++ b/src/streams.cpp @@ -10,8 +10,7 @@ #include -AutoFile::AutoFile(std::FILE* file, std::vector obfuscation) - : m_file{file}, m_obfuscation{std::move(obfuscation)} +AutoFile::AutoFile(std::FILE* file, const Obfuscation& obfuscation) : m_file{file}, m_obfuscation{obfuscation} { if (!IsNull()) { auto pos{std::ftell(m_file)}; @@ -22,12 +21,12 @@ AutoFile::AutoFile(std::FILE* file, std::vector obfuscation) std::size_t AutoFile::detail_fread(std::span dst) { if (!m_file) throw std::ios_base::failure("AutoFile::read: file handle is nullptr"); - size_t ret = std::fread(dst.data(), 1, dst.size(), m_file); - if (!m_obfuscation.empty()) { - if (!m_position.has_value()) throw std::ios_base::failure("AutoFile::read: position unknown"); - Obfuscation().Xor(dst.subspan(0, ret), m_obfuscation, *m_position); + const size_t ret = std::fread(dst.data(), 1, dst.size(), m_file); + if (m_obfuscation) { + if (!m_position) throw std::ios_base::failure("AutoFile::read: position unknown"); + m_obfuscation(dst.subspan(0, ret), *m_position); } - if (m_position.has_value()) *m_position += ret; + if (m_position) *m_position += ret; return ret; } @@ -82,7 +81,7 @@ void AutoFile::ignore(size_t nSize) void AutoFile::write(std::span src) { if (!m_file) throw std::ios_base::failure("AutoFile::write: file handle is nullptr"); - if (m_obfuscation.empty()) { + if (!m_obfuscation) { if (std::fwrite(src.data(), 1, src.size(), m_file) != src.size()) { throw std::ios_base::failure("AutoFile::write: write failed"); } @@ -102,9 +101,9 @@ void AutoFile::write(std::span src) void AutoFile::write_buffer(std::span src) { if (!m_file) throw std::ios_base::failure("AutoFile::write_buffer: file handle is nullptr"); - if (m_obfuscation.size()) { + if (m_obfuscation) { if (!m_position) throw std::ios_base::failure("AutoFile::write_buffer: obfuscation position unknown"); - Obfuscation().Xor(src, m_obfuscation, *m_position); // obfuscate in-place + m_obfuscation(src, *m_position); // obfuscate in-place } if (std::fwrite(src.data(), 1, src.size(), m_file) != src.size()) { throw std::ios_base::failure("AutoFile::write_buffer: write failed"); diff --git a/src/streams.h b/src/streams.h index f2b455742bd..36af8dd6159 100644 --- a/src/streams.h +++ b/src/streams.h @@ -25,7 +25,6 @@ #include #include #include -#include #include /* Minimal stream for overwriting and/or appending to an existing byte vector @@ -245,23 +244,13 @@ public: return (*this); } - template + template DataStream& operator>>(T&& obj) { ::Unserialize(*this, obj); return (*this); } - /** - * XOR the contents of this stream with a certain key. - * - * @param[in] key The key used to XOR the data in this stream. - */ - void Xor(const std::vector& key) - { - Obfuscation().Xor(MakeWritableByteSpan(*this), MakeByteSpan(key)); - } - /** Compute total memory usage of this object (own memory + any dynamic memory). */ size_t GetMemoryUsage() const noexcept; }; @@ -382,12 +371,12 @@ class AutoFile { protected: std::FILE* m_file; - std::vector m_obfuscation; + Obfuscation m_obfuscation; std::optional m_position; bool m_was_written{false}; public: - explicit AutoFile(std::FILE* file, std::vector obfuscation={}); + explicit AutoFile(std::FILE* file, const Obfuscation& obfuscation = {}); ~AutoFile() { @@ -435,7 +424,7 @@ public: bool IsNull() const { return m_file == nullptr; } /** Continue with a different XOR key */ - void SetObfuscation(std::vector obfuscation) { m_obfuscation = obfuscation; } + void SetObfuscation(const Obfuscation& obfuscation) { m_obfuscation = obfuscation; } /** Implementation detail, only used internally. */ std::size_t detail_fread(std::span dst); diff --git a/src/test/dbwrapper_tests.cpp b/src/test/dbwrapper_tests.cpp index 9c723351d6b..cd0f347b66e 100644 --- a/src/test/dbwrapper_tests.cpp +++ b/src/test/dbwrapper_tests.cpp @@ -9,21 +9,12 @@ #include #include +#include #include using util::ToString; -// Test if a string consists entirely of null characters -static bool is_null_key(const std::vector& key) { - bool isnull = true; - - for (unsigned int i = 0; i < key.size(); i++) - isnull &= (key[i] == '\x00'); - - return isnull; -} - BOOST_FIXTURE_TEST_SUITE(dbwrapper_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(dbwrapper) @@ -33,7 +24,7 @@ BOOST_AUTO_TEST_CASE(dbwrapper) constexpr size_t CACHE_SIZE{1_MiB}; const fs::path path{m_args.GetDataDirBase() / "dbwrapper"}; - std::vector obfuscation_key{}; + Obfuscation obfuscation; std::vector> key_values{}; // Write values @@ -42,8 +33,8 @@ BOOST_AUTO_TEST_CASE(dbwrapper) BOOST_CHECK_EQUAL(obfuscate, !dbw.IsEmpty()); // Ensure that we're doing real obfuscation when obfuscate=true - obfuscation_key = dbwrapper_private::GetObfuscation(dbw); - BOOST_CHECK_EQUAL(obfuscate, !is_null_key(obfuscation_key)); + obfuscation = dbwrapper_private::GetObfuscation(dbw); + BOOST_CHECK_EQUAL(obfuscate, dbwrapper_private::GetObfuscation(dbw)); for (uint8_t k{0}; k < 10; ++k) { uint8_t key{k}; @@ -56,7 +47,7 @@ BOOST_AUTO_TEST_CASE(dbwrapper) // Verify that the obfuscation key is never obfuscated { CDBWrapper dbw{{.path = path, .cache_bytes = CACHE_SIZE, .obfuscate = false}}; - BOOST_CHECK(obfuscation_key == dbwrapper_private::GetObfuscation(dbw)); + BOOST_CHECK_EQUAL(obfuscation, dbwrapper_private::GetObfuscation(dbw)); } // Read back the values @@ -64,8 +55,8 @@ BOOST_AUTO_TEST_CASE(dbwrapper) CDBWrapper dbw{{.path = path, .cache_bytes = CACHE_SIZE, .obfuscate = obfuscate}}; // Ensure obfuscation is read back correctly - BOOST_CHECK(obfuscation_key == dbwrapper_private::GetObfuscation(dbw)); - BOOST_CHECK_EQUAL(obfuscate, !is_null_key(obfuscation_key)); + BOOST_CHECK_EQUAL(obfuscation, dbwrapper_private::GetObfuscation(dbw)); + BOOST_CHECK_EQUAL(obfuscate, dbwrapper_private::GetObfuscation(dbw)); // Verify all written values for (const auto& [key, expected_value] : key_values) { @@ -89,7 +80,7 @@ BOOST_AUTO_TEST_CASE(dbwrapper_basic_data) bool res_bool; // Ensure that we're doing real obfuscation when obfuscate=true - BOOST_CHECK_EQUAL(obfuscate, !is_null_key(dbwrapper_private::GetObfuscation(dbw))); + BOOST_CHECK_EQUAL(obfuscate, dbwrapper_private::GetObfuscation(dbw)); //Simulate block raw data - "b + block hash" std::string key_block = "b" + m_rng.rand256().ToString(); @@ -264,7 +255,7 @@ BOOST_AUTO_TEST_CASE(existing_data_no_obfuscate) BOOST_CHECK_EQUAL(res2.ToString(), in.ToString()); BOOST_CHECK(!odbw.IsEmpty()); - BOOST_CHECK(is_null_key(dbwrapper_private::GetObfuscation(odbw))); // The key should be an empty string + BOOST_CHECK(!dbwrapper_private::GetObfuscation(odbw)); // The key should be an empty string uint256 in2 = m_rng.rand256(); uint256 res3; @@ -301,7 +292,7 @@ BOOST_AUTO_TEST_CASE(existing_data_reindex) // Check that the key/val we wrote with unobfuscated wrapper doesn't exist uint256 res2; BOOST_CHECK(!odbw.Read(key, res2)); - BOOST_CHECK(!is_null_key(dbwrapper_private::GetObfuscation(odbw))); + BOOST_CHECK(dbwrapper_private::GetObfuscation(odbw)); uint256 in2 = m_rng.rand256(); uint256 res3; diff --git a/src/test/fuzz/autofile.cpp b/src/test/fuzz/autofile.cpp index 2cebba227f6..0de855668d0 100644 --- a/src/test/fuzz/autofile.cpp +++ b/src/test/fuzz/autofile.cpp @@ -22,7 +22,7 @@ FUZZ_TARGET(autofile) const auto key_bytes{ConsumeFixedLengthByteVector(fuzzed_data_provider, Obfuscation::KEY_SIZE)}; AutoFile auto_file{ fuzzed_file_provider.open(), - key_bytes, + Obfuscation{std::span{key_bytes}.first()}, }; LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 100) { diff --git a/src/test/fuzz/buffered_file.cpp b/src/test/fuzz/buffered_file.cpp index c61910e55a7..75ef8f313ae 100644 --- a/src/test/fuzz/buffered_file.cpp +++ b/src/test/fuzz/buffered_file.cpp @@ -24,7 +24,7 @@ FUZZ_TARGET(buffered_file) const auto key_bytes{ConsumeFixedLengthByteVector(fuzzed_data_provider, Obfuscation::KEY_SIZE)}; AutoFile fuzzed_file{ fuzzed_file_provider.open(), - key_bytes, + Obfuscation{std::span{key_bytes}.first()}, }; try { auto n_buf_size = fuzzed_data_provider.ConsumeIntegralInRange(0, 4096); diff --git a/src/test/streams_tests.cpp b/src/test/streams_tests.cpp index 346497b3c31..ce496df5a9f 100644 --- a/src/test/streams_tests.cpp +++ b/src/test/streams_tests.cpp @@ -21,10 +21,10 @@ 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 target, std::span obfuscation) { + auto apply_random_xor_chunks{[&](std::span target, const Obfuscation& obfuscation) { for (size_t offset{0}; offset < target.size();) { const size_t chunk_size{1 + m_rng.randrange(target.size() - offset)}; - Obfuscation().Xor(target.subspan(offset, chunk_size), obfuscation, offset); + obfuscation(target.subspan(offset, chunk_size), offset); offset += chunk_size; } }}; @@ -35,13 +35,14 @@ BOOST_AUTO_TEST_CASE(xor_roundtrip_random_chunks) std::vector roundtrip{original}; const auto key_bytes{m_rng.randbool() ? m_rng.randbytes() : std::array{}}; - apply_random_xor_chunks(roundtrip, key_bytes); + const Obfuscation obfuscation{key_bytes}; + apply_random_xor_chunks(roundtrip, obfuscation); const bool key_all_zeros{std::ranges::all_of( std::span{key_bytes}.first(std::min(write_size, Obfuscation::KEY_SIZE)), [](auto b) { return b == std::byte{0}; })}; BOOST_CHECK(key_all_zeros ? original == roundtrip : original != roundtrip); - apply_random_xor_chunks(roundtrip, key_bytes); + apply_random_xor_chunks(roundtrip, obfuscation); BOOST_CHECK(original == roundtrip); } } @@ -62,24 +63,58 @@ BOOST_AUTO_TEST_CASE(xor_bytes_reference) const size_t write_offset{std::min(write_size, m_rng.randrange(Obfuscation::KEY_SIZE * 2))}; // Write unaligned data const auto key_bytes{m_rng.randbool() ? m_rng.randbytes() : std::array{}}; - const std::vector obfuscation{key_bytes.begin(), key_bytes.end()}; + const Obfuscation obfuscation{key_bytes}; std::vector expected{m_rng.randbytes(write_size)}; std::vector actual{expected}; expected_xor(std::span{expected}.subspan(write_offset), key_bytes, key_offset); - Obfuscation().Xor(std::span{actual}.subspan(write_offset), key_bytes, key_offset); + obfuscation(std::span{actual}.subspan(write_offset), key_offset); BOOST_CHECK_EQUAL_COLLECTIONS(expected.begin(), expected.end(), actual.begin(), actual.end()); } } +BOOST_AUTO_TEST_CASE(obfuscation_hexkey) +{ + const auto key_bytes{m_rng.randbytes()}; + + const Obfuscation obfuscation{key_bytes}; + BOOST_CHECK_EQUAL(obfuscation.HexKey(), HexStr(key_bytes)); +} + +BOOST_AUTO_TEST_CASE(obfuscation_serialize) +{ + const Obfuscation original{m_rng.randbytes()}; + + // Serialization + DataStream ds; + ds << original; + + BOOST_CHECK_EQUAL(ds.size(), 1 + Obfuscation::KEY_SIZE); // serialized as a vector + + // Deserialization + Obfuscation recovered{}; + ds >> recovered; + + BOOST_CHECK_EQUAL(recovered.HexKey(), original.HexKey()); +} + +BOOST_AUTO_TEST_CASE(obfuscation_empty) +{ + const Obfuscation null_obf{}; + BOOST_CHECK(!null_obf); + + const Obfuscation non_null_obf{"ff00ff00ff00ff00"_hex}; + BOOST_CHECK(non_null_obf); +} + 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 test1{1, 2, 3}; const std::vector test2{4, 5}; - const auto obfuscation{"ff00ff00ff00ff00"_hex_v}; + const Obfuscation obfuscation{"ff00ff00ff00ff00"_hex}; { // Check errors for missing file @@ -284,23 +319,23 @@ BOOST_AUTO_TEST_CASE(streams_serializedata_xor) // Degenerate case { DataStream ds{}; - ds.Xor("0000000000000000"_hex_v_u8); + Obfuscation{}(ds); BOOST_CHECK_EQUAL(""s, ds.str()); } { - const auto obfuscation{"ffffffffffffffff"_hex_v_u8}; + const Obfuscation obfuscation{"ffffffffffffffff"_hex}; DataStream ds{"0ff0"_hex}; - ds.Xor(obfuscation); + obfuscation(ds); BOOST_CHECK_EQUAL("\xf0\x0f"s, ds.str()); } { - const auto obfuscation{"ff0fff0fff0fff0f"_hex_v_u8}; + const Obfuscation obfuscation{"ff0fff0fff0fff0f"_hex}; DataStream ds{"f00f"_hex}; - ds.Xor(obfuscation); + obfuscation(ds); BOOST_CHECK_EQUAL("\x0f\x00"s, ds.str()); } } @@ -613,7 +648,7 @@ BOOST_AUTO_TEST_CASE(buffered_reader_matches_autofile_random_content) const FlatFilePos pos{0, 0}; const FlatFileSeq test_file{m_args.GetDataDirBase(), "buffered_file_test_random", node::BLOCKFILE_CHUNK_SIZE}; - const auto obfuscation{m_rng.randbytes(Obfuscation::KEY_SIZE)}; + const Obfuscation obfuscation{m_rng.randbytes()}; // Write out the file with random content { @@ -668,7 +703,7 @@ BOOST_AUTO_TEST_CASE(buffered_writer_matches_autofile_random_content) const FlatFileSeq test_buffered{m_args.GetDataDirBase(), "buffered_write_test", node::BLOCKFILE_CHUNK_SIZE}; const FlatFileSeq test_direct{m_args.GetDataDirBase(), "direct_write_test", node::BLOCKFILE_CHUNK_SIZE}; - const auto obfuscation{m_rng.randbytes(Obfuscation::KEY_SIZE)}; + const Obfuscation obfuscation{m_rng.randbytes()}; { DataBuffer test_data{m_rng.randbytes(file_size)}; diff --git a/src/util/obfuscation.h b/src/util/obfuscation.h index c39d7bf80a5..23bf805aa2a 100644 --- a/src/util/obfuscation.h +++ b/src/util/obfuscation.h @@ -7,19 +7,32 @@ #include #include +#include +#include + +#include class Obfuscation { public: - static constexpr size_t KEY_SIZE{sizeof(uint64_t)}; + using KeyType = uint64_t; + static constexpr size_t KEY_SIZE{sizeof(KeyType)}; - void Xor(std::span write, std::span key, size_t key_offset = 0) + Obfuscation() : m_key{KEY_SIZE, std::byte{0}} {} + explicit Obfuscation(std::span key_bytes) { - assert(key.size() == KEY_SIZE); + m_key = {key_bytes.begin(), key_bytes.end()}; + } + + operator bool() const { return ToKey() != 0; } + + void operator()(std::span write, size_t key_offset = 0) const + { + assert(m_key.size() == KEY_SIZE); key_offset %= KEY_SIZE; for (size_t i = 0, j = key_offset; i != write.size(); i++) { - write[i] ^= key[j++]; + write[i] ^= m_key[j++]; // This potentially acts on very many bytes of data, so it's // important that we calculate `j`, i.e. the `key` index in this @@ -29,6 +42,34 @@ public: j = 0; } } + + template + void Serialize(Stream& s) const + { + s << m_key; + } + + template + void Unserialize(Stream& s) + { + s >> m_key; + if (m_key.size() != KEY_SIZE) throw std::ios_base::failure(strprintf("Obfuscation key size should be exactly %s bytes long", KEY_SIZE)); + } + + std::string HexKey() const + { + return HexStr(m_key); + } + +private: + std::vector m_key; + + KeyType ToKey() const + { + KeyType key{}; + std::memcpy(&key, m_key.data(), KEY_SIZE); + return key; + } }; #endif // BITCOIN_UTIL_OBFUSCATION_H