A practical guide to independently reproducing Bitcoin Core release binaries and submitting attestations to bitcoin-core/guix.sigs. Written from the experience of attesting v31.0 on Fedora Linux 43.
What is Guix Attestation?**
Bitcoin Core uses Guix — a reproducible build system — to produce release binaries. Multiple independent builders compile Bitcoin Core from the same source tag and compare their SHA256 hashes. If hashes match across builders, it proves the official release binaries weren't tampered with.
The two-stage process:
| Stage | What happens | Who does it |
|---|---|---|
noncodesigned |
Compile from source, hash outputs | Anyone with Linux/macOS |
all (codesigned) |
Attach Windows/macOS code signatures | Builders with Apple/Microsoft signing certs |
As a community builder, you contribute noncodesigned attestations. This is what most builders do.
Requirements
Hardware:
- 8GB RAM minimum (16GB recommended — the build is memory-intensive)
- 50GB+ free disk space for a full multi-platform build
- x86_64 Linux machine
Software:
- Docker (tested with Docker 29.4.0)
- GPG (usually pre-installed)
- Git
- A GitHub account
Accounts/Keys:
- GitHub account
- GPG key (generated during setup)
- GitHub Personal Access Token (for pushing)
One-Time Setup
1. Generate a GPG Key
gpg --full-generate-key
At each prompt:
- Key type:
1(RSA and RSA) - Key size:
4096 - Expiry:
0(never expires) - Real name: your name or handle
- Email: your email
- Comment: leave blank
- Passphrase: choose a strong one — you'll need it every time you sign
Note your fingerprint:
gpg --list-secret-keys --keyid-format LONG
The fingerprint is the long string under pub, e.g. AC5DDD2ED0068633D2223CFFD5DBABC9625AB5BE.
2. Add Your GPG Key to GitHub
Export and add to GitHub so your commits show "Verified":
gpg --export --armor your@email.com
Copy the output and add it at: GitHub → Settings → SSH and GPG keys → New GPG key
Configure git to sign commits:
git config --global user.signingkey YOUR_FINGERPRINT
git config --global commit.gpgsign true
3. Fork and Clone guix.sigs
Fork https://github.com/bitcoin-core/guix.sigs to your account, then:
git clone https://github.com/YOUR_USERNAME/guix.sigs.git
cd guix.sigs
git remote add upstream https://github.com/bitcoin-core/guix.sigs.git
4. Set Up the Docker Build Environment
Use fanquake's core-review (https://github.com/fanquake/core-review) Docker image which has Guix pre-installed:
git clone https://github.com/fanquake/core-review.git
cd core-review/guix
Build the Docker image (takes ~15 minutes, downloads Bitcoin Core source):
docker build --pull --no-cache -t alpine_guix - < imagefile
Building and Attesting a Release
Step 1: Start the Container
Always start with DNS specified and --privileged (required for Guix sandboxing):
docker run -it --privileged --dns 8.8.8.8 --dns 8.8.4.4 --name alpine_guix_build alpine_guix bash
If the container already exists from a previous run:
docker start -ai alpine_guix_build
Step 2: Inside the Container — Initial Setup
Source the Guix profile (required every session, or add to ~/.bashrc):
source /var/guix/profiles/per-user/root/current-guix/etc/profile
Add to ~/.bashrc so it persists:
echo 'source /var/guix/profiles/per-user/root/current-guix/etc/profile' >> ~/.bashrc
Start the Guix daemon with an extended timeout (important for slow connections):
guix-daemon --build-users-group=guixbuild --timeout=21600 &
Step 3: Checkout the Release Tag
cd /bitcoin
git fetch origin
git checkout v31.0 # replace with the version you're building
Step 4: (Optional) macOS SDK
Building macOS targets requires the Xcode SDK. Without it, your attestation will have X (missing) for darwin entries.
To get the SDK:
- Download
Xcode-26.1.1-17B100-extracted-SDK-with-libcxx-headers.tar(checkcontrib/macdeploy/README.mdfor current version and hash) - Verify the SHA256 hash against the one in the README
- Copy into the container and extract:
# On your host — copy into container
docker cp ~/Downloads/Xcode-26.1.1-17B100-extracted-SDK-with-libcxx-headers.tar alpine_guix_build:/bitcoin/depends/SDKs/
# Inside container — verify and extract
sha256sum /bitcoin/depends/SDKs/Xcode-26.1.1-17B100-extracted-SDK-with-libcxx-headers.tar
# Verify it matches README before extracting
tar xf /bitcoin/depends/SDKs/Xcode-26.1.1-17B100-extracted-SDK-with-libcxx-headers.tar -C /bitcoin/depends/SDKs/
Step 5: Start the Build
Use tmux to protect against terminal disconnection:
tmux new -s bitcoin-build
Set environment variables and run:
export SIGNER="your_github_username"
export VERSION="31.0" # without the v prefix
export JOBS=2 # lower if machine has <16GB RAM
export HOSTS="x86_64-linux-gnu aarch64-linux-gnu arm-linux-gnueabihf riscv64-linux-gnu powerpc64-linux-gnu x86_64-w64-mingw32"
# Add darwin targets if you have the SDK:
# export HOSTS="x86_64-linux-gnu aarch64-linux-gnu arm-linux-gnueabihf riscv64-linux-gnu powerpc64-linux-gnu x86_64-w64-mingw32 x86_64-apple-darwin arm64-apple-darwin"
./contrib/guix/guix-build
If your machine sleeps or terminal closes, reattach:
docker start -ai alpine_guix_build
tmux attach -t bitcoin-build
If the build fails with "build directories already exist":
./contrib/guix/guix-clean
Expected build time: 4-8 hours for all Linux + Windows platforms on an 8-core machine.
Step 6: Verify Build Output
When the build finishes you'll see -- Installing: lines. Verify output exists:
ls /bitcoin/guix-build-31.0/output/
# Should show: aarch64-linux-gnu arm-linux-gnueabihf dist-archive powerpc64-linux-gnu riscv64-linux-gnu x86_64-linux-gnu x86_64-w64-mingw32
# (plus x86_64-apple-darwin and arm64-apple-darwin if you built darwin)
Step 7: Clone guix.sigs Inside the Container
cd /
git clone https://github.com/YOUR_USERNAME/guix.sigs.git
Step 8: Import Your GPG Key
GPG isn't in the container by default:
apk add gnupg
Export your key from host and copy in:
# On your HOST terminal:
gpg --export-secret-keys --armor your@email.com > /tmp/mykey.gpg
docker cp /tmp/mykey.gpg alpine_guix_build:/tmp/mykey.gpg
rm /tmp/mykey.gpg # clean up immediately
# Inside container:
gpg --import /tmp/mykey.gpg
rm /tmp/mykey.gpg
gpg --list-secret-keys # verify it imported
Step 9: Generate the Attestation
cd /bitcoin
GUIX_SIGS_REPO=/guix.sigs SIGNER=YOUR_FINGERPRINT=your_github_username ./contrib/guix/guix-attest
You'll be prompted for your GPG passphrase. This creates:
/guix.sigs/31.0/your_username/noncodesigned.SHA256SUMS/guix.sigs/31.0/your_username/noncodesigned.SHA256SUMS.asc
Verify the files look correct:
cat /guix.sigs/31.0/your_username/noncodesigned.SHA256SUMS
# Should show hashes for all platforms you built
Step 10: Commit and Push
Configure git inside the container:
cd /guix.sigs
git config user.email "your@email.com"
git config user.name "your_github_username"
Create a branch and commit (include your builder key on first attestation):
git checkout -b add-your_username-31.0-attestation
git add 31.0/your_username/ builder-keys/your_username.gpg
git commit -m "Add attestations by your_username for 31.0 noncodesigned"
git push origin add-your_username-31.0-attestation
Note: On your first attestation, also add your GPG public key:
# On your host: gpg --export --armor your@email.com > ~/guix.sigs/builder-keys/your_username.gpg # Then copy into container or commit from host
Step 11: Open a Pull Request
Go to https://github.com/bitcoin-core/guix.sigs and open a PR from your fork.
PR title: Add attestations by your_username for 31.0 noncodesigned
PR description:
Noncodesigned attestations for v31.0 built on [Your OS].
GPG fingerprint: YOUR_FINGERPRINT
What Happens After You Open the PR
- github-actions bot posts a hash matrix showing your hashes vs all other builders
-
█= your hash matches everyone else ✅ -
X= missing hash (you didn't build that platform) - Any other symbol = mismatch (investigate!)
- A maintainer approves and merges
Single Commit Requirement
Maintainers require a single non-merge commit. If you have multiple commits:
# On your host
cd ~/guix.sigs
git fetch origin
git checkout add-your_username-31.0-attestation
git reset --soft HEAD~N # N = number of commits to squash
git commit -m "Add attestations by your_username for 31.0 noncodesigned"
git push origin add-your_username-31.0-attestation --force
Subsequent Releases
For future releases (v31.1, v32.0 etc.), the process is faster:
- Your builder key is already in the repo — no need to add it again
- The Docker container has all Guix cache — no re-downloading toolchains
- Just clean, checkout new tag, and build:
docker start -ai alpine_guix_build
# Inside container:
source /var/guix/profiles/per-user/root/current-guix/etc/profile
guix-daemon --build-users-group=guixbuild --timeout=21600 &
cd /bitcoin
git fetch origin
git checkout v31.1
./contrib/guix/guix-clean
export SIGNER="your_username"
export VERSION="31.1"
export JOBS=2
export HOSTS="x86_64-linux-gnu aarch64-linux-gnu arm-linux-gnueabihf riscv64-linux-gnu powerpc64-linux-gnu x86_64-w64-mingw32 x86_64-apple-darwin arm64-apple-darwin"
./contrib/guix/guix-build
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
guix-daemon: command not found |
Profile not sourced | source /var/guix/profiles/per-user/root/current-guix/etc/profile |
clone: Operation not permitted |
Docker missing --privileged
|
Restart container with --privileged flag |
Temporary failure in name resolution |
No DNS in container | Restart with --dns 8.8.8.8 --dns 8.8.4.4
|
SSL error: syscall failure |
Flaky network to codeberg.org | Re-run ./contrib/guix/guix-build — it retries |
substitution timed out after 3600 seconds |
Slow internet | Use --timeout=21600 flag on guix-daemon |
Build directories already exist |
Interrupted previous build | Run ./contrib/guix/guix-clean
|
macOS SDK does not exist |
Missing Xcode SDK | Download SDK, verify hash, extract to depends/SDKs/
|
OOM kill / signal 9 |
Not enough RAM | Set export JOBS=2 to limit parallelism |
noncodesigned.SHA256SUMS already exists |
Previous partial attestation | rm /guix.sigs/VERSION/username/noncodesigned.SHA256SUMS{,.asc} |
Key Commands Reference
# Start container (first time)
docker run -it --privileged --dns 8.8.8.8 --dns 8.8.4.4 --name alpine_guix_build alpine_guix bash
# Resume container
docker start -ai alpine_guix_build
# Save container state (before recreating)
docker commit alpine_guix_build alpine_guix_cached
# Stop container
docker stop alpine_guix_build
# Source Guix profile
source /var/guix/profiles/per-user/root/current-guix/etc/profile
# Start Guix daemon
guix-daemon --build-users-group=guixbuild --timeout=21600 &
# Run build
export SIGNER="username" VERSION="31.0" JOBS=2
export HOSTS="x86_64-linux-gnu aarch64-linux-gnu arm-linux-gnueabihf riscv64-linux-gnu powerpc64-linux-gnu x86_64-w64-mingw32 x86_64-apple-darwin arm64-apple-darwin"
./contrib/guix/guix-build
# Generate attestation
GUIX_SIGS_REPO=/guix.sigs SIGNER=FINGERPRINT=username ./contrib/guix/guix-attest
# Clean interrupted build
./contrib/guix/guix-clean
Resources
- guix.sigs repository
- Bitcoin Core release process
- fanquake/core-review — Docker images for building
- macOS SDK extraction
- Guix installation
Written based on attesting Bitcoin Core v31.0 on Fedora Linux 43, April 2026. PR: bitcoin-core/guix.sigs#2400
Top comments (0)