obfuscation.h
1 // Copyright (c) 2025-present The Bitcoin Core developers 2 // Distributed under the MIT software license, see the accompanying 3 // file COPYING or http://www.opensource.org/licenses/mit-license.php. 4 5 #ifndef BITCOIN_UTIL_OBFUSCATION_H 6 #define BITCOIN_UTIL_OBFUSCATION_H 7 8 #include <cstdint> 9 #include <span.h> 10 #include <tinyformat.h> 11 #include <util/strencodings.h> 12 13 #include <array> 14 #include <bit> 15 #include <climits> 16 #include <ios> 17 #include <memory> 18 19 class Obfuscation 20 { 21 public: 22 using KeyType = uint64_t; 23 static constexpr size_t KEY_SIZE{sizeof(KeyType)}; 24 25 Obfuscation() { SetRotations(0); } 26 explicit Obfuscation(std::span<const std::byte, KEY_SIZE> key_bytes) 27 { 28 SetRotations(ToKey(key_bytes)); 29 } 30 31 operator bool() const { return m_rotations[0] != 0; } 32 33 void operator()(std::span<std::byte> target, size_t key_offset = 0) const 34 { 35 if (!*this) return; 36 37 KeyType rot_key{m_rotations[key_offset % KEY_SIZE]}; // Continue obfuscation from where we left off 38 if (target.size() > KEY_SIZE) { 39 // Obfuscate until KEY_SIZE alignment boundary 40 if (const auto misalign{reinterpret_cast<uintptr_t>(target.data()) % KEY_SIZE}) { 41 const size_t alignment{KEY_SIZE - misalign}; 42 XorWord(target.first(alignment), rot_key); 43 44 target = {std::assume_aligned<KEY_SIZE>(target.data() + alignment), target.size() - alignment}; 45 rot_key = m_rotations[(key_offset + alignment) % KEY_SIZE]; 46 } 47 // Aligned obfuscation in 8*KEY_SIZE chunks 48 for (constexpr auto unroll{8}; target.size() >= KEY_SIZE * unroll; target = target.subspan(KEY_SIZE * unroll)) { 49 for (size_t i{0}; i < unroll; ++i) { 50 XorWord(target.subspan(i * KEY_SIZE, KEY_SIZE), rot_key); 51 } 52 } 53 // Aligned obfuscation in KEY_SIZE chunks 54 for (; target.size() >= KEY_SIZE; target = target.subspan(KEY_SIZE)) { 55 XorWord(target.first<KEY_SIZE>(), rot_key); 56 } 57 } 58 XorWord(target, rot_key); 59 } 60 61 template <typename Stream> 62 void Serialize(Stream& s) const 63 { 64 // Use vector serialization for convenient compact size prefix. 65 std::vector<std::byte> bytes{KEY_SIZE}; 66 std::memcpy(bytes.data(), &m_rotations[0], KEY_SIZE); 67 s << bytes; 68 } 69 70 template <typename Stream> 71 void Unserialize(Stream& s) 72 { 73 std::vector<std::byte> bytes{KEY_SIZE}; 74 s >> bytes; 75 if (bytes.size() != KEY_SIZE) throw std::ios_base::failure(strprintf("Obfuscation key size should be exactly %s bytes long", KEY_SIZE)); 76 SetRotations(ToKey(std::span<std::byte, KEY_SIZE>(bytes))); 77 } 78 79 std::string HexKey() const 80 { 81 return HexStr(std::as_bytes(std::span{&m_rotations[0], 1})); 82 } 83 84 private: 85 // Cached key rotations for different offsets. 86 std::array<KeyType, KEY_SIZE> m_rotations; 87 88 void SetRotations(KeyType key) 89 { 90 for (size_t i{0}; i < KEY_SIZE; ++i) { 91 int key_rotation_bits{int(CHAR_BIT * i)}; 92 if constexpr (std::endian::native == std::endian::big) key_rotation_bits *= -1; 93 m_rotations[i] = std::rotr(key, key_rotation_bits); 94 } 95 } 96 97 static KeyType ToKey(std::span<const std::byte, KEY_SIZE> key_span) 98 { 99 KeyType key{}; 100 std::memcpy(&key, key_span.data(), KEY_SIZE); 101 return key; 102 } 103 104 static void XorWord(std::span<std::byte> target, KeyType key) 105 { 106 assert(target.size() <= KEY_SIZE); 107 if (target.empty()) return; 108 KeyType raw{}; 109 std::memcpy(&raw, target.data(), target.size()); 110 raw ^= key; 111 std::memcpy(target.data(), &raw, target.size()); 112 } 113 }; 114 115 #endif // BITCOIN_UTIL_OBFUSCATION_H