Matching Hashes: Reproducing the Guix-built Bitcoin Core release binary with Nix

Wednesday, May 27, 2026

Earlier this week, I produced a Nix-built bitcoind binary for Bitcoin Core v31.0 whose hash exactly matches the official Guix-built x86_64-pc-linux-gnu release binary. The result came out of a three year old side project, with a difficult goal: Can a binary built with Nix be made bit-for-bit identical to one produced by Bitcoin Core’s Guix reproducible build system?

Surprisingly, the answer is yes:

# on a x86_64 host with Nix installed:
 $ git clone https://github.com/0xB10C/bitcoind-gunix.git &&\
   cd bitcoind-gunix &&\
   git checkout v31.0-match &&\
   time nix build
 $ tmpdir=$(mktemp -d) &&\
   curl -fsSL https://bitcoincore.org/bin/bitcoin-core-31.0/bitcoin-31.0-x86_64-linux-gnu.tar.gz |\
   tar -xz -C "$tmpdir" --strip-components=2 bitcoin-31.0/bin/bitcoind
 $ sha256sum "$tmpdir/bitcoind" result/bin/bitcoind

 dae69848ae9aaadcc3aa697d1c92b1283273a59c8d87b220b29ddc2813e25eb6 /tmp/tmp.D1kB5yIfR9/bitcoind
 dae69848ae9aaadcc3aa697d1c92b1283273a59c8d87b220b29ddc2813e25eb6 result/bin/bitcoind

At first glance, this might sounds like this should be straightforward: Nix and Guix both compile the same Bitcoin Core source code with GCC, so why wouldn’t the resulting binaries match? In practice, reproducible builds are extremely fragile. Tiny differences in compiler behavior, filesystem paths, timestamps, linker metadata, dependency versions, or environment variables are enough to end up with a different binary, having a different final hash. And while Nix and Guix are philosophically similar, they are independently constructed toolchains with different package graphs, patches, and bootstrap chains. Bitcoin Core uses Guix so release binaries can be independently verified by other developers and users.

The matching hash shows that the Bitcoin Core build process is deterministic enough that an independently built toolchain can end up producing the exact same binary. The reproducibility doesn’t seem to be coming from Guix as a special environment alone, but from the build itself. In that sense, it shifts reproducible builds from something that only holds inside the carefully controlled Guix system to something you can actually check across two (carefully controlled) systems. In the future, Guix could be just one participant in a supply-chain model where trust comes from independent builds converging on the same artifact.

When starting this project, I didn’t really set out to have a fully matching binary. This seemed to be way too much work in an area I’m far from an expert in. A first goal was to build all dependencies from source instead of using the versions packaged by Nix. Then, the next goal was to get to a similar stripped binary size, but I eventually lost interest and worked on other things. In 2025, josiebake investigated a bit more, but wasn’t able to make much progress either.

Reading about LLM tools now frequently solving math problems, I figured I’d give Claude Code a VM on a powerful host (for fast compilation) with Nix installed, a checkout of the bitcoind-gunix repository, allowed most tool usage, and give it a /goal produce a Nix-built binary with a hash matching the Bitcoin Core v31.0 release binary for x86_64-pc-linux-gnu. Along the way, I told it to write down its progress in a CLAUDE.md file and make frequent, atomic commits. To help it, I ended up supplying a checkout of the Bitcoin Core repository at the v31.0 tag, later a v31.0 Guix build log, a directory with the Guix depends build artifacts, and a checkout of the Guix repository at the commit used by Bitcoin Core. After burning through roughly 1.5 weeks’ worth of Claude Pro usage (I set up a /loop 2h continue with what you were doing. If you are stuck, try solving a different problem first command so it would continue once my usage reset), while I spent the weekend camping, it came back to me with a Nix-built binary that matches the bitcoind Guix v31.0 release binary for x86_64-pc-linux-gnu. I used a mix of Claude Sonnet 4.6 and Opus 4.7.

Claude ended up producing around 80 commits, which I haven’t reviewed. Looking at the end result, it’s obvious that it worked around some problems by hardcoding replacements for a few bytes in e.g. glibc ELF notes and replaced a debug-section CRC32 in the bitcoind binary. At some point it said: “This is a multi-day refactor - I’m not going to do it” and started working around it. Getting rid of these workarounds would probably require more LLM tokens, or money, than I don’t plan to spend. If someone is interested in moving this forward, this could be an interesting place to spend spare LLM tokens on. There are also other binaries, like bitcoin-cli, that would need to match as well. Then, as a much bigger step, there’s cross-compiling for macOS and Windows. Personally, I’m not planning to continue working on this, since I have other projects requiring my attention.

By the way, you can’t actually run the binary on NixOS.

$ ./result/bin/bitcoind 
 Could not start dynamically linked executable: ./result/bin/bitcoind
 NixOS cannot run dynamically linked executables intended for generic
 linux environments out of the box. For more information, see:
 https://nix.dev/permalink/stub-ld


My open-source work is currently funded by an OpenSats LTS grant. You can learn more about my funding and how to support my work on my funding page.

Creative Commons License Text and images on this page are licensed under the Creative Commons Attribution-ShareAlike 4.0 International License

Previous

Image for BIP-54 demo stream: Slow block validation on Signet

April 8, 2026

BIP-54 demo stream: Slow block validation on Signet

In April 2026, Antoine Poinsot and Anthony Towns publicly demonstrated a few slow-to-validate blocks on the Signet test network. These blocks are …