Skip to content

[IBD] multi-byte block obfuscation#31144

Merged
achow101 merged 12 commits into
bitcoin:masterfrom
l0rinc:l0rinc/optimize-xor
Jul 19, 2025
Merged

[IBD] multi-byte block obfuscation#31144
achow101 merged 12 commits into
bitcoin:masterfrom
l0rinc:l0rinc/optimize-xor

Conversation

@l0rinc

@l0rinc l0rinc commented Oct 24, 2024

Copy link
Copy Markdown
Contributor

This change is part of [IBD] - Tracking PR for speeding up Initial Block Download

Summary

Current block obfuscations are done byte-by-byte, this PR batches them to 64 bit primitives to speed up obfuscating bigger memory batches.
This is especially relevant now that #31551 was merged, having bigger obfuscatable chunks.

Since this obfuscation is optional, the speedup measured here depends on whether it's a random value or completely turned off (i.e. XOR-ing with 0).

Changes in testing, benchmarking and implementation

  • Added new tests comparing randomized inputs against a trivial implementation and performing roundtrip checks with random chunks.
  • Migrated std::vector<std::byte>(8) keys to plain uint64_t;
  • Process unaligned bytes separately and unroll body to 64 bytes.

Assembly

Memory alignment is enforced by a small peel-loop (std::memcpy is optimized out on tested platform), with an std::assume_aligned<8> check, see the Godbolt listing at https://godbolt.org/z/59EMv7h6Y for details

Details
Target & Compiler Stride (per hot-loop iter) Main operation(s) in loop Effective XORs / iter
Clang x86-64 (trunk) 64 bytes 4 × movdqu → pxor → store 8 × 64-bit
GCC x86-64 (trunk) 64 bytes 4 × movdqu/pxor sequence, enabled by 8-way unroll 8 × 64-bit
GCC RV32 (trunk) 8 bytes copy 8 B to temp → 2 × 32-bit XOR → copy back 1 × 64-bit (as 2 × 32-bit)
GCC s390x (big-endian 14.2) 64 bytes 8 × XC (mem-mem 8-B XOR) with key cached on stack 8 × 64-bit

Endianness

The only endianness issue was with bit rotation, intended to realign the key if obfuscation halted before full key consumption.
Elsewhere, memory is read, processed, and written back in the same endianness, preserving byte order.
Since CI lacks a big-endian machine, testing was done locally via Docker.

Details
brew install podman pigz
softwareupdate --install-rosetta
podman machine init
podman machine start
docker run --platform linux/s390x -it ubuntu:latest /bin/bash
  apt update && apt install -y git build-essential cmake ccache pkg-config libevent-dev libboost-dev libssl-dev libsqlite3-dev python3 && \
  cd /mnt && git clone --depth=1 https://github.com/bitcoin/bitcoin.git && cd bitcoin && git remote add l0rinc https://github.com/l0rinc/bitcoin.git && git fetch --all && git checkout l0rinc/optimize-xor && \
  cmake -B build && cmake --build build --target test_bitcoin -j$(nproc) && \
  ./build/bin/test_bitcoin --run_test=streams_tests

Measurements (micro benchmarks and full IBDs)

cmake -B build -DBUILD_BENCH=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=gcc/clang -DCMAKE_CXX_COMPILER=g++/clang++ &&
cmake --build build -j$(nproc) &&
build/bin/bench_bitcoin -filter='ObfuscationBench' -min-time=5000

GNU 14.2.0

Before:

ns/byte byte/s err% ins/byte cyc/byte IPC bra/byte miss% total benchmark
0.84 1,184,138,235.64 0.0% 9.01 3.03 2.971 1.00 0.1% 5.50 ObfuscationBench

After (first optimizing commit):

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

and (second optimizing commit):

ns/byte byte/s err% ins/byte cyc/byte IPC bra/byte miss% total benchmark
0.03 32,464,658,919.11 0.0% 0.50 0.11 4.474 0.08 0.0% 5.29 ObfuscationBench
Clang 20.1.7

Before:

ns/byte byte/s err% ins/byte cyc/byte IPC bra/byte miss% total benchmark
0.89 1,124,087,330.23 0.1% 6.52 3.20 2.041 0.50 0.2% 5.50 ObfuscationBench

After (first optimizing commit):

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

and (second optimizing commit):

ns/byte byte/s err% ins/byte cyc/byte IPC bra/byte miss% total benchmark
0.02 41,231,547,045.17 0.0% 0.30 0.09 3.463 0.02 0.0% 5.47 ObfuscationBench

i.e. 27.4x faster obfuscation with GCC, 36.7x faster with Clang

For other benchmark speedups see https://corecheck.dev/bitcoin/bitcoin/pulls/31144


Running an IBD until 888888 blocks reveals a 4% speedup.

Details

SSD:

COMMITS="8324a00bd4a6a5291c841f2d01162d8a014ddb02 5ddfd31b4158a89b0007cfb2be970c03d9278525"; \
STOP_HEIGHT=888888; DBCACHE=1000; \
CC=gcc; CXX=g++; \
BASE_DIR="/mnt/my_storage"; DATA_DIR="$BASE_DIR/BitcoinData"; LOG_DIR="$BASE_DIR/logs"; \
(for c in $COMMITS; do git fetch origin $c -q && git log -1 --pretty=format:'%h %s' $c || exit 1; done) && \
hyperfine \
  --sort 'command' \
  --runs 1 \
  --export-json "$BASE_DIR/ibd-${COMMITS// /-}-$STOP_HEIGHT-$DBCACHE-$CC.json" \
  --parameter-list COMMIT ${COMMITS// /,} \
  --prepare "killall bitcoind; rm -rf $DATA_DIR/*; git checkout {COMMIT}; git clean -fxd; git reset --hard; \
    cmake -B build -DCMAKE_BUILD_TYPE=Release -DENABLE_WALLET=OFF && \
    cmake --build build -j$(nproc) --target bitcoind && \
    ./build/bin/bitcoind -datadir=$DATA_DIR -stopatheight=1 -printtoconsole=0; sleep 100" \
  --cleanup "cp $DATA_DIR/debug.log $LOG_DIR/debug-{COMMIT}-$(date +%s).log" \
  "COMPILER=$CC ./build/bin/bitcoind -datadir=$DATA_DIR -stopatheight=$STOP_HEIGHT -dbcache=$DBCACHE -blocksonly -printtoconsole=0"

8324a00bd4 test: Compare util::Xor with randomized inputs against simple impl
5ddfd31b41 optimization: Xor 64 bits together instead of byte-by-byte

Benchmark 1: COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=888888 -dbcache=1000 -blocksonly -printtoconsole=0 (COMMIT = 8324a00bd4a6a5291c841f2d01162d8a014ddb02)
  Time (abs ≡):        25033.413 s               [User: 33953.984 s, System: 2613.604 s]

Benchmark 2: COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=888888 -dbcache=1000 -blocksonly -printtoconsole=0 (COMMIT = 5ddfd31b4158a89b0007cfb2be970c03d9278525)
  Time (abs ≡):        24110.710 s               [User: 33389.536 s, System: 2660.292 s]

Relative speed comparison
        1.04          COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=888888 -dbcache=1000 -blocksonly -printtoconsole=0 (COMMIT = 8324a00bd4a6a5291c841f2d01162d8a014ddb02)
        1.00          COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=888888 -dbcache=1000 -blocksonly -printtoconsole=0 (COMMIT = 5ddfd31b4158a89b0007cfb2be970c03d9278525)

HDD:

COMMITS="71eb6eaa740ad0b28737e90e59b89a8e951d90d9 46854038e7984b599d25640de26d4680e62caba7"; \
STOP_HEIGHT=888888; DBCACHE=4500; \
CC=gcc; CXX=g++; \
BASE_DIR="/mnt/my_storage"; DATA_DIR="$BASE_DIR/BitcoinData"; LOG_DIR="$BASE_DIR/logs"; \
(for c in $COMMITS; do git fetch origin $c -q && git log -1 --pretty=format:'%h %s' $c || exit 1; done) && \
hyperfine \
  --sort 'command' \
  --runs 2 \
  --export-json "$BASE_DIR/ibd-${COMMITS// /-}-$STOP_HEIGHT-$DBCACHE-$CC.json" \
  --parameter-list COMMIT ${COMMITS// /,} \
  --prepare "killall bitcoind; rm -rf $DATA_DIR/*; git checkout {COMMIT}; git clean -fxd; git reset --hard; \
    cmake -B build -DCMAKE_BUILD_TYPE=Release -DENABLE_WALLET=OFF && cmake --build build -j$(nproc) --target bitcoind && \
    ./build/bin/bitcoind -datadir=$DATA_DIR -stopatheight=1 -printtoconsole=0; sleep 100" \
  --cleanup "cp $DATA_DIR/debug.log $LOG_DIR/debug-{COMMIT}-$(date +%s).log" \
  "COMPILER=$CC ./build/bin/bitcoind -datadir=$DATA_DIR -stopatheight=$STOP_HEIGHT -dbcache=$DBCACHE -blocksonly -printtoconsole=0"

71eb6eaa74 test: compare util::Xor with randomized inputs against simple impl
46854038e7 optimization: migrate fixed-size obfuscation from std::vector<std::byte> to uint64_t

Benchmark 1: COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=888888 -dbcache=4500 -blocksonly -printtoconsole=0 (COMMIT = 71eb6eaa740ad0b28737e90e59b89a8e951d90d9)
  Time (mean ± σ):     37676.293 s ± 83.100 s    [User: 36900.535 s, System: 2220.382 s]
  Range (minmax):   37617.533 s37735.053 s    2 runs

Benchmark 2: COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=888888 -dbcache=4500 -blocksonly -printtoconsole=0 (COMMIT = 46854038e7984b599d25640de26d4680e62caba7)
  Time (mean ± σ):     36181.287 s ± 195.248 s    [User: 34962.822 s, System: 1988.614 s]
  Range (minmax):   36043.226 s36319.349 s    2 runs

Relative speed comparison
        1.04 ±  0.01  COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=888888 -dbcache=4500 -blocksonly -printtoconsole=0 (COMMIT = 71eb6eaa740ad0b28737e90e59b89a8e951d90d9)
        1.00          COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=888888 -dbcache=4500 -blocksonly -printtoconsole=0 (COMMIT = 46854038e7984b599d25640de26d4680e62caba7)

@DrahtBot

DrahtBot commented Oct 24, 2024

Copy link
Copy Markdown
Contributor

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Code Coverage & Benchmarks

For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/31144.

Reviews

See the guideline for information on the review process.

Type Reviewers
ACK maflcko, ryanofsky, achow101
Stale ACK hodlinator, yuvicc

If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #32967 (log: [refactor] Use info level for init logs by maflcko)
  • #31860 (init: Take lock on blocks directory in BlockManager ctor by TheCharlatan)
  • #29641 (scripted-diff: Use LogInfo over LogPrintf [WIP, NOMERGE, DRAFT] by maflcko)
  • #28792 (Embed default ASMap as binary dump header file by fjahr)
  • #19463 (Prune locks by luke-jr)

If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

Comment thread src/streams.h Outdated
Comment thread src/streams.h Outdated
@maflcko

maflcko commented Oct 24, 2024

Copy link
Copy Markdown
Member

I think your example may be a bit skewed? It shows how much time is spent when deserializing a CScript from a block file. However, block files contain full blocks, where many (most?) of the writes are single bytes (or 4 bytes), see #30833 (comment). Thus, it would be useful to know what the overall end-to-end performance difference is. Also taking into account the utxo db.

If you want the micro-benchmark to be representative, I'd presume you'd have to mimic the histogram of the sizes of writes. Just picking one (1024, or 1004), which is never hit in reality, and then optimizing for that may be misleading.

@l0rinc l0rinc force-pushed the l0rinc/optimize-xor branch 2 times, most recently from 23f4809 to 6ae466b Compare October 24, 2024 09:59
@l0rinc

l0rinc commented Oct 24, 2024

Copy link
Copy Markdown
Contributor Author

where many (most?) of the writes are single bytes (or 4 bytes)

Thanks, I've extended your previous benchmarks with both Autofile serialization and very small vectors.
I will also run a reindex of 400k blocks before and after to see if the effect is measurable or not.

Comment thread src/streams.h Outdated
Comment thread src/streams.h Outdated
@l0rinc l0rinc force-pushed the l0rinc/optimize-xor branch 2 times, most recently from 353915b to a3dc138 Compare October 24, 2024 12:20
@l0rinc l0rinc changed the title optimization: pack util::Xor into 64 bit chunks instead of doing it byte-by-byte optimization: pack util::Xor into 64/32 bit chunks instead of doing it byte-by-byte Oct 24, 2024
@l0rinc l0rinc marked this pull request as ready for review October 24, 2024 14:22
@maflcko

maflcko commented Oct 24, 2024

Copy link
Copy Markdown
Member

It would be good to explain the jpegs in the description, or even remove them. They will be excluded from the merge commit and aren't shown, unless GitHub happens to be reachable and online. Are they saying that IBD was 4% faster? Also, I think they were created with the UB version of this pull, so may be outdated either way?

I did a quick check on my laptop and it seems the XorSmall (1+4 bytes) is slower with this pull. The Xor (modified to check 40 bytes) was twice as fast. Overall, I'd expect it to be slower on my machine, due to the histogram of real data showing more small byte writes than long ones, IIRC.

I can try to bench on another machine later, to see if it makes a difference.

Can you clarify what type of machine you tested this on?

@l0rinc

l0rinc commented Oct 24, 2024

Copy link
Copy Markdown
Contributor Author

Are they saying that IBD was 4% faster?

That's what I'm measuring currently, but I don't expect more than 2% difference here.

Also, I think they were created with the UB version of this pull, so may be outdated either way?

Benchmarks indicated that the 64 bit compiled result was basically the same.

Overall, I'd expect it to be slower on my machine, due to the histogram of real data showing more small byte writes than long ones, IIRC.

I'll investigate, thanks.

Details

Posting the perf here for reference:
image
Reindexing until 300k blocks reveals that XOR usage was reduced:
image

@hodlinator hodlinator left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept ACK a3dc138798e2f2c7aa1c9b37633c16c1b523a251

Operating on CPU words rather than individual bytes. 👍

Not entirely clear to me from #31144 (comment) whether the optimizer is able to use SIMD. Guess picking through the binary of a GUIX-build would give a definitive answer.

The verbosity of std::memcpy hurts readability but alignment issues are real.

Comment thread src/test/streams_tests.cpp Outdated
Comment thread src/test/streams_tests.cpp Outdated
Comment thread src/test/streams_tests.cpp Outdated
@l0rinc l0rinc marked this pull request as draft October 25, 2024 18:47
@l0rinc l0rinc force-pushed the l0rinc/optimize-xor branch 3 times, most recently from 95e38ac to cd61e8b Compare October 26, 2024 21:58
@DrahtBot

Copy link
Copy Markdown
Contributor

🚧 At least one of the CI tasks failed.
Debug: https://github.com/bitcoin/bitcoin/runs/32109998785

Hints

Try to run the tests locally, according to the documentation. However, a CI failure may still
happen due to a number of reasons, for example:

  • Possibly due to a silent merge conflict (the changes in this pull request being
    incompatible with the current code in the target branch). If so, make sure to rebase on the latest
    commit of the target branch.

  • A sanitizer issue, which can only be found by compiling with the sanitizer and running the
    affected test.

  • An intermittent issue.

Leave a comment here, if you need help tracking down a confusing failure.

@l0rinc l0rinc force-pushed the l0rinc/optimize-xor branch from cd61e8b to d72968f Compare October 26, 2024 22:17
@l0rinc l0rinc changed the title optimization: pack util::Xor into 64/32 bit chunks instead of doing it byte-by-byte optimization: change XOR obfuscation key from std::vector<std::byte>(8) to uint64_t Oct 26, 2024
@l0rinc l0rinc force-pushed the l0rinc/optimize-xor branch from d72968f to 555f8c8 Compare October 27, 2024 15:17
l0rinc

This comment was marked as duplicate.

@DrahtBot

Copy link
Copy Markdown
Contributor

🚧 At least one of the CI tasks failed.
Task lint: https://github.com/bitcoin/bitcoin/runs/46055133794
LLM reason (✨ experimental): The CI failure is due to a missing include guard in src/util/obfuscation.h detected by the include guard linter.

Hints

Try to run the tests locally, according to the documentation. However, a CI failure may still
happen due to a number of reasons, for example:

  • Possibly due to a silent merge conflict (the changes in this pull request being
    incompatible with the current code in the target branch). If so, make sure to rebase on the latest
    commit of the target branch.

  • A sanitizer issue, which can only be found by compiling with the sanitizer and running the
    affected test.

  • An intermittent issue.

Leave a comment here, if you need help tracking down a confusing failure.

@DrahtBot DrahtBot mentioned this pull request Jul 16, 2025

@maflcko maflcko left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm ack 7b93fa394acaee7a715fae9a2b968d33c5adc174 📕

Show signature

Signature:

untrusted comment: signature from minisign secret key on empty file; verify via: minisign -Vm "${path_to_any_empty_file}" -P RWTRmVTMeKV5noAMqVlsMugDDCyyTSbA3Re5AkUrhvLVln0tSaFWglOw -x "${path_to_this_whole_four_line_signature_blob}"
RUTRmVTMeKV5npGrKx1nqXCw5zeVHdtdYURB/KlyA/LMFgpNCs+SkW9a8N95d+U4AP1RJMi+krxU1A3Yux4bpwZNLvVBKy0wLgM=
trusted comment: lgtm ack 7b93fa394acaee7a715fae9a2b968d33c5adc174 📕
oZkgF1tzWxCxE4D9fRIXf9lAc9P9X7sxHugu5QW4rogkU1EhQ/XW6zHnGVGtAXhrw82ClIvoDbDWH5ksSwyiDg==

Comment thread src/dbwrapper.cpp Outdated
Comment thread src/util/obfuscation.h Outdated
Comment on lines 75 to 82

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit in 43189b8: Is exposing this implementation detail really needed? This is adding more lines to real code than there are test-only lines that need it. There are two tests:

  • One that checks the ToKey round-trip. However, this is already checked by the existing tests and the serialize test can instead do the comparison on a DataStream, if needed: Check that a std::vector (de)serialization (roundtrip) is equal to an obfuscation (de)serialization (roundtrip).
  • One that checks a serialize round-trip. However, this would be covered by the above suggested test as well.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's redundant, but I wanted to make absolutely sure the internals are also correct.
If other reviewers also comfortable deleting this, I don't mind.

@maflcko maflcko Jul 16, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It only checks m_rotations[0] against a hardcoded memcpy, it doesn't check ToKey in the top commit, so I don't think the unit test adds any new checks. Otherwise, it would be good to know what exact bug this could possibly catch that the other test isn't catching.

In fact the unit test seems a bit confusing, because it constructs the obfuscation from an uint64_t, whereas production code only uses byte spans.

If you really want to directly test Serialize uses m_rotations[0] and Unserialize or the constructor use ToKey, so I am thinking using those public functions is a better way to test the internals than to introduce new and duplicate test-only function.

edit: Also putting a type named generally "KeyType" in the global namespace for this test-only feature seems a bit confusing. If you really want to test the internal state, my preference would be to mark the private field protected and then in the unit test code expose the protected field in a derived class. (But my first preference would still be the above 😅 )

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me know what you think of the result now.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

re: #31144 (comment)

Looks like the code changes in the latest push, but here was the test I was going to suggest that relied on public methods only and I think covered everything.

diff

--- a/src/test/streams_tests.cpp
+++ b/src/test/streams_tests.cpp
@@ -74,32 +74,24 @@ BOOST_AUTO_TEST_CASE(xor_bytes_reference)
     }
 }
 
-BOOST_AUTO_TEST_CASE(obfuscation_constructors)
-{
-    using KeyType = uint64_t;
-    constexpr KeyType test_key{0x0123456789ABCDEF};
-
-    std::array<std::byte, Obfuscation::KEY_SIZE> key_bytes{};
-    std::memcpy(key_bytes.data(), &test_key, Obfuscation::KEY_SIZE);
-    const Obfuscation obfuscation{key_bytes};
-    BOOST_CHECK_EQUAL(obfuscation_private::Key(obfuscation), test_key);
-}
-
 BOOST_AUTO_TEST_CASE(obfuscation_serialize)
 {
-    const Obfuscation original{m_rng.randbytes<Obfuscation::KEY_SIZE>()};
+    Obfuscation obfuscation{};
 
-    // Serialize
-    DataStream ds;
-    ds << original;
+    // Test loading a key.
+    std::vector<std::byte> key_in{m_rng.randbytes<std::byte>(Obfuscation::KEY_SIZE)};
+    DataStream ds_in;
+    ds_in << key_in;
+    ds_in >> obfuscation;
 
-    BOOST_CHECK_EQUAL(ds.size(), 1 + Obfuscation::KEY_SIZE); // serialized as a vector
+    // Test saving the key.
+    std::vector<std::byte> key_out;
+    DataStream ds_out;
+    ds_out << obfuscation;
+    ds_out >> key_out;
 
-    // Deserialize
-    Obfuscation recovered{};
-    ds >> recovered;
-
-    BOOST_CHECK_EQUAL(obfuscation_private::Key(recovered), obfuscation_private::Key(original));
+    // Make sure saved key is the same.
+    BOOST_CHECK_EQUAL_COLLECTIONS(key_in.begin(), key_in.end(), key_out.begin(), key_out.end());
 }
 
 BOOST_AUTO_TEST_CASE(obfuscation_empty)
--- a/src/util/obfuscation.h
+++ b/src/util/obfuscation.h
@@ -15,17 +15,10 @@
 #include <ios>
 #include <memory>
 
-using KeyType = uint64_t;
-
-class Obfuscation;
-
-namespace obfuscation_private {
-KeyType Key(const Obfuscation& obfuscation);
-}
-
 class Obfuscation
 {
 public:
+    using KeyType = uint64_t; //!< Internal key representation.
     static constexpr size_t KEY_SIZE{sizeof(KeyType)};
 
     Obfuscation() { SetRotations(0); }
@@ -111,13 +104,7 @@ private:
         raw ^= key;
         std::memcpy(target.data(), &raw, target.size());
     }
-
-    friend KeyType obfuscation_private::Key(const Obfuscation& obfuscation);
 };
 
-inline KeyType obfuscation_private::Key(const Obfuscation& obfuscation)
-{
-    return obfuscation.m_rotations[0];
-}
 
 #endif // BITCOIN_UTIL_OBFUSCATION_H

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can still do that, but given that we need to be able to display the contents of the obfuscation anyway (unless we go back to reading/writing vectors), I think the current way is probably simpler. Please let me know if you disagree.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

re: #31144 (comment)

I think the current way is probably simpler. Please let me know if you disagree.

I think current way is ok, but serialization test in the diff has some benefits over the current one. Diff suggested:

BOOST_AUTO_TEST_CASE(obfuscation_serialize)
{
    Obfuscation obfuscation{};

    // Test loading a key.
    std::vector<std::byte> key_in{m_rng.randbytes<std::byte>(Obfuscation::KEY_SIZE)};
    DataStream ds_in;
    ds_in << key_in;
    ds_in >> obfuscation;

    // Test saving the key.
    std::vector<std::byte> key_out;
    DataStream ds_out;
    ds_out << obfuscation;
    ds_out >> key_out;

    // Make sure saved key is the same.
    BOOST_CHECK_EQUAL_COLLECTIONS(key_in.begin(), key_in.end(), key_out.begin(), key_out.end());
}

IMO this was better because it actually checks output of serialization is as expected, not just that it round-trips and is a certain length. Also think it's better to not use HexStr/HexKey to check for equality. But feel free to keep the current test.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added it to the test suite: 2dea045 (#33039)

Comment thread src/dbwrapper.cpp Outdated
Comment thread src/util/obfuscation.h Outdated
Comment thread src/util/obfuscation.h Outdated
l0rinc and others added 2 commits July 16, 2025 13:19
Co-authored-by: Hodlinator <172445034+hodlinator@users.noreply.github.com>
Since 31 byte xor-keys are not used in the codebase, using the common size (8 bytes) makes the benchmarks more realistic.

Co-authored-by: maflcko <6399679+maflcko@users.noreply.github.com>

@l0rinc l0rinc left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the hints, the latest version simplifies reading the obfuscation key by using the new Obfuscation object's serialization paths + reading back the new key directly after generating a new one.

The obfuscation_constructors test was also changes to validate the new HexKey conversion which we need since we're logging the generated obfuscations keys so we need to peek into the object.

It also avoids a possible Obfuscation copy used in GetObfuscation and removed the obfuscation_private::Key friend accessor - which was ugly and we don't need it now that HexKey was exposed.

Comment thread src/dbwrapper.cpp Outdated
Comment thread src/util/obfuscation.h Outdated
Comment thread src/dbwrapper.cpp Outdated
Comment thread src/util/obfuscation.h Outdated
Comment on lines 75 to 82

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me know what you think of the result now.

@ryanofsky ryanofsky left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review 7b93fa394acaee7a715fae9a2b968d33c5adc174. Still in progress reviewing but wanted to post comments I had so far. There was a new push since these were written so please ignore anything that no longer applies.

Comment thread src/test/streams_tests.cpp Outdated
Comment thread src/test/streams_tests.cpp Outdated
Comment thread src/dbwrapper.cpp Outdated
Comment thread src/node/mempool_persist.cpp Outdated
Comment thread src/dbwrapper.cpp Outdated
Comment thread src/util/obfuscation.h Outdated
Comment on lines 75 to 82

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

re: #31144 (comment)

Looks like the code changes in the latest push, but here was the test I was going to suggest that relied on public methods only and I think covered everything.

diff

--- a/src/test/streams_tests.cpp
+++ b/src/test/streams_tests.cpp
@@ -74,32 +74,24 @@ BOOST_AUTO_TEST_CASE(xor_bytes_reference)
     }
 }
 
-BOOST_AUTO_TEST_CASE(obfuscation_constructors)
-{
-    using KeyType = uint64_t;
-    constexpr KeyType test_key{0x0123456789ABCDEF};
-
-    std::array<std::byte, Obfuscation::KEY_SIZE> key_bytes{};
-    std::memcpy(key_bytes.data(), &test_key, Obfuscation::KEY_SIZE);
-    const Obfuscation obfuscation{key_bytes};
-    BOOST_CHECK_EQUAL(obfuscation_private::Key(obfuscation), test_key);
-}
-
 BOOST_AUTO_TEST_CASE(obfuscation_serialize)
 {
-    const Obfuscation original{m_rng.randbytes<Obfuscation::KEY_SIZE>()};
+    Obfuscation obfuscation{};
 
-    // Serialize
-    DataStream ds;
-    ds << original;
+    // Test loading a key.
+    std::vector<std::byte> key_in{m_rng.randbytes<std::byte>(Obfuscation::KEY_SIZE)};
+    DataStream ds_in;
+    ds_in << key_in;
+    ds_in >> obfuscation;
 
-    BOOST_CHECK_EQUAL(ds.size(), 1 + Obfuscation::KEY_SIZE); // serialized as a vector
+    // Test saving the key.
+    std::vector<std::byte> key_out;
+    DataStream ds_out;
+    ds_out << obfuscation;
+    ds_out >> key_out;
 
-    // Deserialize
-    Obfuscation recovered{};
-    ds >> recovered;
-
-    BOOST_CHECK_EQUAL(obfuscation_private::Key(recovered), obfuscation_private::Key(original));
+    // Make sure saved key is the same.
+    BOOST_CHECK_EQUAL_COLLECTIONS(key_in.begin(), key_in.end(), key_out.begin(), key_out.end());
 }
 
 BOOST_AUTO_TEST_CASE(obfuscation_empty)
--- a/src/util/obfuscation.h
+++ b/src/util/obfuscation.h
@@ -15,17 +15,10 @@
 #include <ios>
 #include <memory>
 
-using KeyType = uint64_t;
-
-class Obfuscation;
-
-namespace obfuscation_private {
-KeyType Key(const Obfuscation& obfuscation);
-}
-
 class Obfuscation
 {
 public:
+    using KeyType = uint64_t; //!< Internal key representation.
     static constexpr size_t KEY_SIZE{sizeof(KeyType)};
 
     Obfuscation() { SetRotations(0); }
@@ -111,13 +104,7 @@ private:
         raw ^= key;
         std::memcpy(target.data(), &raw, target.size());
     }
-
-    friend KeyType obfuscation_private::Key(const Obfuscation& obfuscation);
 };
 
-inline KeyType obfuscation_private::Key(const Obfuscation& obfuscation)
-{
-    return obfuscation.m_rotations[0];
-}
 
 #endif // BITCOIN_UTIL_OBFUSCATION_H

l0rinc and others added 10 commits July 16, 2025 14:18
The two tests are doing different things - `xor_roundtrip_random_chunks` does black-box style property-based testing to validate that certain invariants hold - that deobfuscating an obfuscation results in the original message (higher level, it doesn't have to know about the implementation details).

The `xor_bytes_reference` test makes sure the optimized xor implementation behaves in every imaginable scenario exactly as the simplest possible obfuscation - with random chunks, random alignment, random data, random key.

Since we're touching the file, other related small refactors were also applied:
* `nullpt` typo fixed;
* manual byte-by-byte xor key creations were replaced with `_hex` factories;
* since we're only using 64 bit keys in production, smaller keys were changed to reflect real-world usage;

Co-authored-by: Hodlinator <172445034+hodlinator@users.noreply.github.com>
A previous PR already solved the tiny byte-array-xors during serialization, so it makes sense to keep focusing on the performance of bigger continuous chunks.

This also renames the file from `xor` to `obfuscation` to enable scripted diff name unification later.

> C++ compiler .......................... GNU 14.2.0

|             ns/byte |              byte/s |    err% |        ins/byte |        cyc/byte |    IPC |       bra/byte |   miss% |     total | benchmark
|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------
|                0.84 |    1,184,138,235.64 |    0.0% |            9.01 |            3.03 |  2.971 |           1.00 |    0.1% |      5.50 | `ObfuscationBench`

> C++ compiler .......................... Clang 20.1.7

|             ns/byte |              byte/s |    err% |        ins/byte |        cyc/byte |    IPC |       bra/byte |   miss% |     total | benchmark
|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------
|                0.89 |    1,124,087,330.23 |    0.1% |            6.52 |            3.20 |  2.041 |           0.50 |    0.2% |      5.50 | `ObfuscationBench`
Mechanical refactor of the low-level "xor" wording to signal the intent instead of the implementation used.
The renames are ordered by heaviest-hitting substitutions first, and were constructed such that after each replacement the code is still compilable.

-BEGIN VERIFY SCRIPT-
sed -i \
  -e 's/\bGetObfuscateKey\b/GetObfuscation/g' \
  -e 's/\bxor_key\b/obfuscation/g' \
  -e 's/\bxor_pat\b/obfuscation/g' \
  -e 's/\bm_xor_key\b/m_obfuscation/g' \
  -e 's/\bm_xor\b/m_obfuscation/g' \
  -e 's/\bobfuscate_key\b/m_obfuscation/g' \
  -e 's/\bOBFUSCATE_KEY_KEY\b/OBFUSCATION_KEY_KEY/g' \
  -e 's/\bSetXor(/SetObfuscation(/g' \
  -e 's/\bdata_xor\b/obfuscation/g' \
  -e 's/\bCreateObfuscateKey\b/CreateObfuscation/g' \
  -e 's/\bobfuscate key\b/obfuscation key/g' \
  $(git ls-files '*.cpp' '*.h')
-END VERIFY SCRIPT-
Since `FastRandomContext` delegates to `GetRandBytes` anyway, we can simplify new key generation to a Write/Read combo, unifying the flow of enabling obfuscation via `Read`.

The comments were also adjusted to clarify that the `m_obfuscation` field affects the behavior of `Read` and `Write` methods.

These changes are meant to simplify the diffs for the riskier optimization commits later.
These changes are meant to simplify the diffs for the riskier optimization commits later.
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>
…yte>` 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 <172445034+hodlinator@users.noreply.github.com>
Co-authored-by: Ryan Ofsky <ryan@ofsky.org>
Benchmarks indicated that obfuscating multiple bytes already gives an order of magnitude speed-up, but:
* GCC still emitted scalar code;
* Clang’s auto-vectorized loop ran on the slow unaligned-load path.

Fix contains:
* peeling the misaligned head enabled the hot loop starting at an 8-byte address;
* `std::assume_aligned<8>` tells the optimizer the promise holds - required to keep Apple Clang happy;
* manually unrolling the body to 64 bytes enabled GCC to auto-vectorize.

Note that `target.size() > KEY_SIZE` condition is just an optimization, the aligned and unaligned loops work without it as well - it's why the alignment calculation still contains `std::min`.

>  C++ compiler .......................... GNU 14.2.0

|             ns/byte |              byte/s |    err% |        ins/byte |        cyc/byte |    IPC |       bra/byte |   miss% |     total | benchmark
|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------
|                0.03 |   32,464,658,919.11 |    0.0% |            0.50 |            0.11 |  4.474 |           0.08 |    0.0% |      5.29 | `ObfuscationBench`

> C++ compiler .......................... Clang 20.1.7

|             ns/byte |              byte/s |    err% |        ins/byte |        cyc/byte |    IPC |       bra/byte |   miss% |     total | benchmark
|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------
|                0.02 |   41,231,547,045.17 |    0.0% |            0.30 |            0.09 |  3.463 |           0.02 |    0.0% |      5.47 | `ObfuscationBench`

Co-authored-by: Hodlinator <172445034+hodlinator@users.noreply.github.com>

@l0rinc l0rinc left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot Russ, I've split out a test commit, moved a rename to the scripted diff, clarified test refactors and testing strategies in commit messages.

Comment thread src/test/streams_tests.cpp Outdated
Comment thread src/dbwrapper.cpp Outdated
Comment thread src/node/mempool_persist.cpp Outdated
Comment thread src/dbwrapper.cpp Outdated
Comment thread src/util/obfuscation.h Outdated
Comment on lines 75 to 82

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can still do that, but given that we need to be able to display the contents of the obfuscation anyway (unless we go back to reading/writing vectors), I think the current way is probably simpler. Please let me know if you disagree.

Comment thread src/test/streams_tests.cpp Outdated

@maflcko maflcko left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure the code is correct (see comment), but I've looked at it:

review ACK 248b6a2 🎻

Show signature

Signature:

untrusted comment: signature from minisign secret key on empty file; verify via: minisign -Vm "${path_to_any_empty_file}" -P RWTRmVTMeKV5noAMqVlsMugDDCyyTSbA3Re5AkUrhvLVln0tSaFWglOw -x "${path_to_this_whole_four_line_signature_blob}"
RUTRmVTMeKV5npGrKx1nqXCw5zeVHdtdYURB/KlyA/LMFgpNCs+SkW9a8N95d+U4AP1RJMi+krxU1A3Yux4bpwZNLvVBKy0wLgM=
trusted comment: review ACK 248b6a27c351690d3596711cc36b8102977adeab 🎻
y+Vf1FMMs+Mh/qNuFt7m5rGXcMVrJvCxgLk26KJE7ErP9Qbcwd7mBpBp7xxVtWzxWIsB66iJvW59yuotufVoBg==

Comment thread src/dbwrapper.cpp
Comment thread src/util/obfuscation.h
std::string HexKey() const
{
return HexStr(m_key);
return HexStr(std::bit_cast<std::array<uint8_t, KEY_SIZE>>(m_rotations[0]));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit in e7114fc: Not that it matters, but you can also use a span to avoid the copy:

-        return HexStr(std::bit_cast<std::array<uint8_t, KEY_SIZE>>(m_rotations[0]));
+        return HexStr(std::as_bytes(std::span{&m_rotations[0], 1})));

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I know we're using these in serialization.h, but it seems to me the current option might be easier to optimize, see https://godbolt.org/z/fh8o9xdvz

It's possible my reproducer is not representative, I also don't mind switching if others prefer it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

re: #31144 (comment)

as_bytes also seems a little clearer to me and might suggest a variation HexStr(std::as_bytes(std::span{m_rotations}.first(1)));, but no strong opinion. I'd be interested to understand godbolt example too, not sure if there is a simple explantion of why bit_cast might help?

@l0rinc l0rinc Jul 18, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, not sure it matters, but can take @maflcko's suggestion if I have to push again.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in a separate PR, thanks: 298bf95 (#33039)

@ryanofsky ryanofsky left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review ACK 248b6a2. Looks good! Thanks for adapting this and considering all the suggestions. I did leave more comments below but non are important and this looks good as-is

Comment thread src/test/streams_tests.cpp Outdated
Comment thread src/dbwrapper.cpp Outdated
m_obfuscation = std::vector<unsigned char>(Obfuscation::KEY_SIZE, '\000');

bool key_exists = Read(OBFUSCATE_KEY_KEY, obfuscate_key);
bool key_exists = Read(OBFUSCATION_KEY_KEY, m_obfuscation);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In commit "scripted-diff: unify xor-vs-obfuscation nomenclature" (0b8bec8)

Given latest changes where the obfuscation object is what's serialized instead of the key (e.g. Read(OBFUSCATION_KEY_KEY, m_obfuscation)), I think it probably makes sense to rename OBFUSCATE_KEY_KEY to OBFUSCATION_KEY

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did that before, but @hodlinator pointed out that the previous version made more sense since it's storing obfuscation key's database key: #31144 (comment).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

re: #31144 (comment)

Did that before, but @hodlinator pointed out that the previous version made more sense since it's storing obfuscation key's database key: #31144 (comment).

Yes, but that was when the row contained was read as a key rather than an Obfuscation object. IMO, if the row contains a serialized Obfuscation object the key should be called OBFUSCATION_KEY, and if contains a serialized obfuscation key OBFUSCATION_KEY_KEY is a reasonable name. Prior to the most recent push the Obfuscation class had serialization methods but they were not used here, so now that they are used I think the OBFUSCATION_KEY name probably makes more sense.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, done in a separate PR: e5b1b7c (#33039)

Comment thread src/node/mempool_persist.cpp Outdated
Comment thread src/dbwrapper.cpp Outdated
Comment thread src/dbwrapper.cpp Outdated
Comment thread src/dbwrapper.cpp
Comment thread src/util/obfuscation.h
std::string HexKey() const
{
return HexStr(m_key);
return HexStr(std::bit_cast<std::array<uint8_t, KEY_SIZE>>(m_rotations[0]));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

re: #31144 (comment)

as_bytes also seems a little clearer to me and might suggest a variation HexStr(std::as_bytes(std::span{m_rotations}.first(1)));, but no strong opinion. I'd be interested to understand godbolt example too, not sure if there is a simple explantion of why bit_cast might help?

Comment thread src/util/obfuscation.h Outdated
Comment thread src/util/obfuscation.h
Comment thread src/util/obfuscation.h

@l0rinc l0rinc left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review.
Since the PR is in review for almost a year now, and since the remaining comments seem like they can potentially be done in follow-ups as well, I'd prefer getting this over the finish line soon.

Comment thread src/util/obfuscation.h
std::string HexKey() const
{
return HexStr(m_key);
return HexStr(std::bit_cast<std::array<uint8_t, KEY_SIZE>>(m_rotations[0]));

@l0rinc l0rinc Jul 18, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, not sure it matters, but can take @maflcko's suggestion if I have to push again.

Comment thread src/dbwrapper.cpp
Comment thread src/dbwrapper.cpp Outdated
m_obfuscation = std::vector<unsigned char>(Obfuscation::KEY_SIZE, '\000');

bool key_exists = Read(OBFUSCATE_KEY_KEY, obfuscate_key);
bool key_exists = Read(OBFUSCATION_KEY_KEY, m_obfuscation);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did that before, but @hodlinator pointed out that the previous version made more sense since it's storing obfuscation key's database key: #31144 (comment).

Comment thread src/util/obfuscation.h Outdated
Comment thread src/util/obfuscation.h
Comment thread src/util/obfuscation.h
Comment thread src/test/streams_tests.cpp Outdated
Comment thread src/node/mempool_persist.cpp Outdated
@achow101

Copy link
Copy Markdown
Member

ACK 248b6a2

@l0rinc

l0rinc commented Jul 19, 2025

Copy link
Copy Markdown
Contributor Author

Thanks a lot @maflcko, @ryanofsky, @hodlinator and @achow101 for all the reviews and reproducers and suggestions!

@l0rinc l0rinc left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a follow-up with the remaining nits, see: #33039

Comment thread src/test/streams_tests.cpp Outdated
Comment thread src/util/obfuscation.h
std::string HexKey() const
{
return HexStr(m_key);
return HexStr(std::bit_cast<std::array<uint8_t, KEY_SIZE>>(m_rotations[0]));

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in a separate PR, thanks: 298bf95 (#33039)

Comment thread src/dbwrapper.cpp Outdated
m_obfuscation = std::vector<unsigned char>(Obfuscation::KEY_SIZE, '\000');

bool key_exists = Read(OBFUSCATE_KEY_KEY, obfuscate_key);
bool key_exists = Read(OBFUSCATION_KEY_KEY, m_obfuscation);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, done in a separate PR: e5b1b7c (#33039)

Comment thread src/util/obfuscation.h Outdated
Comment on lines 75 to 82

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added it to the test suite: 2dea045 (#33039)

Comment thread src/dbwrapper.cpp
Comment thread src/util/obfuscation.h Outdated
Comment thread src/util/obfuscation.h
Comment thread src/util/obfuscation.h
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.