DEV Community

Nkatha Kaburu
Nkatha Kaburu

Posted on

Bitcoin Core Guix Attestation Guide

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Build the Docker image (takes ~15 minutes, downloads Bitcoin Core source):

docker build --pull --no-cache -t alpine_guix - < imagefile
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

If the container already exists from a previous run:

docker start -ai alpine_guix_build
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Add to ~/.bashrc so it persists:

echo 'source /var/guix/profiles/per-user/root/current-guix/etc/profile' >> ~/.bashrc
Enter fullscreen mode Exit fullscreen mode

Start the Guix daemon with an extended timeout (important for slow connections):

guix-daemon --build-users-group=guixbuild --timeout=21600 &
Enter fullscreen mode Exit fullscreen mode

Step 3: Checkout the Release Tag

cd /bitcoin
git fetch origin
git checkout v31.0   # replace with the version you're building
Enter fullscreen mode Exit fullscreen mode

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:

  1. Download Xcode-26.1.1-17B100-extracted-SDK-with-libcxx-headers.tar (check contrib/macdeploy/README.md for current version and hash)
  2. Verify the SHA256 hash against the one in the README
  3. 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/
Enter fullscreen mode Exit fullscreen mode

Step 5: Start the Build

Use tmux to protect against terminal disconnection:

tmux new -s bitcoin-build
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

If your machine sleeps or terminal closes, reattach:

docker start -ai alpine_guix_build
tmux attach -t bitcoin-build
Enter fullscreen mode Exit fullscreen mode

If the build fails with "build directories already exist":

./contrib/guix/guix-clean
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Step 7: Clone guix.sigs Inside the Container

cd /
git clone https://github.com/YOUR_USERNAME/guix.sigs.git
Enter fullscreen mode Exit fullscreen mode

Step 8: Import Your GPG Key

GPG isn't in the container by default:

apk add gnupg
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Step 9: Generate the Attestation

cd /bitcoin
GUIX_SIGS_REPO=/guix.sigs SIGNER=YOUR_FINGERPRINT=your_github_username ./contrib/guix/guix-attest
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

What Happens After You Open the PR

  1. github-actions bot posts a hash matrix showing your hashes vs all other builders
  2. = your hash matches everyone else ✅
  3. X = missing hash (you didn't build that platform)
  4. Any other symbol = mismatch (investigate!)
  5. 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
Enter fullscreen mode Exit fullscreen mode

Subsequent Releases

For future releases (v31.1, v32.0 etc.), the process is faster:

  1. Your builder key is already in the repo — no need to add it again
  2. The Docker container has all Guix cache — no re-downloading toolchains
  3. 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Resources


Written based on attesting Bitcoin Core v31.0 on Fedora Linux 43, April 2026. PR: bitcoin-core/guix.sigs#2400

Top comments (0)