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