Skip to content

Commit e7114fc

Browse files
l0rinchodlinatorryanofsky
committed
optimization: migrate fixed-size obfuscation from std::vector<std::byte> to uint64_t
All former `std::vector<std::byte>` keys were replaced with `uint64_t` (we still serialize them as vectors but convert immediately to `uint64_t` on load). This is why some tests still generate vector keys and convert them to `uint64_t` later instead of generating them directly. In `Obfuscation::Unserialize` we can safely throw an `std::ios_base::failure` since during mempool fuzzing `mempool_persist.cpp#L141` catches and ignored these errors. > C++ compiler .......................... GNU 14.2.0 | ns/byte | byte/s | err% | ins/byte | cyc/byte | IPC | bra/byte | miss% | total | benchmark |--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:---------- | 0.04 | 28,365,698,819.44 | 0.0% | 0.34 | 0.13 | 2.714 | 0.07 | 0.0% | 5.33 | `ObfuscationBench` > C++ compiler .......................... Clang 20.1.7 | ns/byte | byte/s | err% | ins/byte | cyc/byte | IPC | bra/byte | miss% | total | benchmark |--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:---------- | 0.08 | 13,012,464,203.00 | 0.0% | 0.65 | 0.28 | 2.338 | 0.13 | 0.8% | 5.50 | `ObfuscationBench` Co-authored-by: Hodlinator <[email protected]> Co-authored-by: Ryan Ofsky <[email protected]>
1 parent 478d40a commit e7114fc

File tree

1 file changed

+45
-23
lines changed

1 file changed

+45
-23
lines changed

src/util/obfuscation.h

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
#include <tinyformat.h>
1111
#include <util/strencodings.h>
1212

13+
#include <array>
14+
#include <bit>
15+
#include <climits>
1316
#include <ios>
1417

1518
class Obfuscation
@@ -18,58 +21,77 @@ class Obfuscation
1821
using KeyType = uint64_t;
1922
static constexpr size_t KEY_SIZE{sizeof(KeyType)};
2023

21-
Obfuscation() : m_key{KEY_SIZE, std::byte{0}} {}
24+
Obfuscation() { SetRotations(0); }
2225
explicit Obfuscation(std::span<const std::byte, KEY_SIZE> key_bytes)
2326
{
24-
m_key = {key_bytes.begin(), key_bytes.end()};
27+
SetRotations(ToKey(key_bytes));
2528
}
2629

27-
operator bool() const { return ToKey() != 0; }
30+
operator bool() const { return m_rotations[0] != 0; }
2831

29-
void operator()(std::span<std::byte> write, size_t key_offset = 0) const
32+
void operator()(std::span<std::byte> target, size_t key_offset = 0) const
3033
{
31-
assert(m_key.size() == KEY_SIZE);
32-
key_offset %= KEY_SIZE;
33-
34-
for (size_t i = 0, j = key_offset; i != write.size(); i++) {
35-
write[i] ^= m_key[j++];
36-
37-
// This potentially acts on very many bytes of data, so it's
38-
// important that we calculate `j`, i.e. the `key` index in this
39-
// way instead of doing a %, which would effectively be a division
40-
// for each byte Xor'd -- much slower than need be.
41-
if (j == KEY_SIZE)
42-
j = 0;
34+
if (!*this) return;
35+
36+
const KeyType rot_key{m_rotations[key_offset % KEY_SIZE]}; // Continue obfuscation from where we left off
37+
for (; target.size() >= KEY_SIZE; target = target.subspan(KEY_SIZE)) {
38+
XorWord(target.first<KEY_SIZE>(), rot_key);
4339
}
40+
XorWord(target, rot_key);
4441
}
4542

4643
template <typename Stream>
4744
void Serialize(Stream& s) const
4845
{
49-
s << m_key;
46+
// Use vector serialization for convenient compact size prefix.
47+
std::vector<std::byte> bytes{KEY_SIZE};
48+
std::memcpy(bytes.data(), &m_rotations[0], KEY_SIZE);
49+
s << bytes;
5050
}
5151

5252
template <typename Stream>
5353
void Unserialize(Stream& s)
5454
{
55-
s >> m_key;
56-
if (m_key.size() != KEY_SIZE) throw std::ios_base::failure(strprintf("Obfuscation key size should be exactly %s bytes long", KEY_SIZE));
55+
std::vector<std::byte> bytes{KEY_SIZE};
56+
s >> bytes;
57+
if (bytes.size() != KEY_SIZE) throw std::ios_base::failure(strprintf("Obfuscation key size should be exactly %s bytes long", KEY_SIZE));
58+
SetRotations(ToKey(std::span<std::byte, KEY_SIZE>(bytes)));
5759
}
5860

5961
std::string HexKey() const
6062
{
61-
return HexStr(m_key);
63+
return HexStr(std::bit_cast<std::array<uint8_t, KEY_SIZE>>(m_rotations[0]));
6264
}
6365

6466
private:
65-
std::vector<std::byte> m_key;
67+
// Cached key rotations for different offsets.
68+
std::array<KeyType, KEY_SIZE> m_rotations;
69+
70+
void SetRotations(KeyType key)
71+
{
72+
for (size_t i{0}; i < KEY_SIZE; ++i) {
73+
int key_rotation_bits{int(CHAR_BIT * i)};
74+
if constexpr (std::endian::native == std::endian::big) key_rotation_bits *= -1;
75+
m_rotations[i] = std::rotr(key, key_rotation_bits);
76+
}
77+
}
6678

67-
KeyType ToKey() const
79+
static KeyType ToKey(std::span<const std::byte, KEY_SIZE> key_span)
6880
{
6981
KeyType key{};
70-
std::memcpy(&key, m_key.data(), KEY_SIZE);
82+
std::memcpy(&key, key_span.data(), KEY_SIZE);
7183
return key;
7284
}
85+
86+
static void XorWord(std::span<std::byte> target, KeyType key)
87+
{
88+
assert(target.size() <= KEY_SIZE);
89+
if (target.empty()) return;
90+
KeyType raw{};
91+
std::memcpy(&raw, target.data(), target.size());
92+
raw ^= key;
93+
std::memcpy(target.data(), &raw, target.size());
94+
}
7395
};
7496

7597
#endif // BITCOIN_UTIL_OBFUSCATION_H

0 commit comments

Comments
 (0)