From 377aab8e5a8da2ea20383b4dde59094cc42d3407 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C5=91rinc?= Date: Sat, 5 Jul 2025 13:39:15 +0200 Subject: [PATCH] refactor: move `util::Xor` to `Obfuscation().Xor` This is meant to focus the usages to narrow the scope of the obfuscation optimization. `Obfuscation::Xor` is mostly a move. Co-authored-by: maflcko <6399679+maflcko@users.noreply.github.com> --- src/bench/obfuscation.cpp | 3 +-- src/streams.cpp | 5 +++-- src/streams.h | 24 ++---------------------- src/test/streams_tests.cpp | 4 ++-- src/util/obfuscation.h | 18 ++++++++++++++++++ 5 files changed, 26 insertions(+), 28 deletions(-) diff --git a/src/bench/obfuscation.cpp b/src/bench/obfuscation.cpp index 27a254f8037..2e9f9a453af 100644 --- a/src/bench/obfuscation.cpp +++ b/src/bench/obfuscation.cpp @@ -4,7 +4,6 @@ #include #include -#include #include #include @@ -18,7 +17,7 @@ static void ObfuscationBench(benchmark::Bench& bench) size_t offset{0}; bench.batch(data.size()).unit("byte").run([&] { - util::Xor(data, key, offset++); // mutated differently each time + Obfuscation().Xor(data, key, offset++); // mutated differently each time ankerl::nanobench::doNotOptimizeAway(data); }); } diff --git a/src/streams.cpp b/src/streams.cpp index ed80d11dcee..b33a1288879 100644 --- a/src/streams.cpp +++ b/src/streams.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include @@ -24,7 +25,7 @@ std::size_t AutoFile::detail_fread(std::span dst) 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"); - util::Xor(dst.subspan(0, ret), m_obfuscation, *m_position); + Obfuscation().Xor(dst.subspan(0, ret), m_obfuscation, *m_position); } if (m_position.has_value()) *m_position += ret; return ret; @@ -103,7 +104,7 @@ 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_position) throw std::ios_base::failure("AutoFile::write_buffer: obfuscation position unknown"); - util::Xor(src, m_obfuscation, *m_position); // obfuscate in-place + Obfuscation().Xor(src, m_obfuscation, *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 ac6d5a0de16..f2b455742bd 100644 --- a/src/streams.h +++ b/src/streams.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -27,27 +28,6 @@ #include #include -namespace util { -inline void Xor(std::span write, std::span key, size_t key_offset = 0) -{ - if (key.size() == 0) { - return; - } - key_offset %= key.size(); - - for (size_t i = 0, j = key_offset; i != write.size(); i++) { - write[i] ^= 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 - // way instead of doing a %, which would effectively be a division - // for each byte Xor'd -- much slower than need be. - if (j == key.size()) - j = 0; - } -} -} // namespace util - /* Minimal stream for overwriting and/or appending to an existing byte vector * * The referenced vector will grow as necessary @@ -279,7 +259,7 @@ public: */ void Xor(const std::vector& key) { - util::Xor(MakeWritableByteSpan(*this), MakeByteSpan(key)); + Obfuscation().Xor(MakeWritableByteSpan(*this), MakeByteSpan(key)); } /** Compute total memory usage of this object (own memory + any dynamic memory). */ diff --git a/src/test/streams_tests.cpp b/src/test/streams_tests.cpp index 5da1ff99154..346497b3c31 100644 --- a/src/test/streams_tests.cpp +++ b/src/test/streams_tests.cpp @@ -24,7 +24,7 @@ BOOST_AUTO_TEST_CASE(xor_roundtrip_random_chunks) auto apply_random_xor_chunks{[&](std::span target, std::span 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); + Obfuscation().Xor(target.subspan(offset, chunk_size), obfuscation, offset); offset += chunk_size; } }}; @@ -67,7 +67,7 @@ BOOST_AUTO_TEST_CASE(xor_bytes_reference) std::vector actual{expected}; expected_xor(std::span{expected}.subspan(write_offset), key_bytes, key_offset); - util::Xor(std::span{actual}.subspan(write_offset), key_bytes, key_offset); + Obfuscation().Xor(std::span{actual}.subspan(write_offset), key_bytes, key_offset); BOOST_CHECK_EQUAL_COLLECTIONS(expected.begin(), expected.end(), actual.begin(), actual.end()); } diff --git a/src/util/obfuscation.h b/src/util/obfuscation.h index 628dacfc9d5..c39d7bf80a5 100644 --- a/src/util/obfuscation.h +++ b/src/util/obfuscation.h @@ -6,11 +6,29 @@ #define BITCOIN_UTIL_OBFUSCATION_H #include +#include class Obfuscation { public: static constexpr size_t KEY_SIZE{sizeof(uint64_t)}; + + void Xor(std::span write, std::span key, size_t key_offset = 0) + { + assert(key.size() == KEY_SIZE); + key_offset %= KEY_SIZE; + + for (size_t i = 0, j = key_offset; i != write.size(); i++) { + write[i] ^= 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 + // way instead of doing a %, which would effectively be a division + // for each byte Xor'd -- much slower than need be. + if (j == KEY_SIZE) + j = 0; + } + } }; #endif // BITCOIN_UTIL_OBFUSCATION_H