Claude
Skills
Sign in
Back

reproducible-builds

Included with Lifetime
$97 forever

Reproducible builds — bit-for-bit identical artifacts from the same source, independently verifiable. Covers the reproducible-builds.org methodology, Bitcoin Core's Guix-based reproducible builds (the gold standard for cryptocurrency software), Nix Flakes for deterministic environments, source-date-epoch (SOURCE_DATE_EPOCH), build flag normalization (file ordering, locale, paths), .reproducible-builds.org diff tooling (diffoscope), and how to apply this to Rust + Gradle + mobile builds. USE WHEN: user mentions "reproducible builds", "deterministic builds", "bit-for-bit", "Guix builds", "diffoscope", "SOURCE_DATE_EPOCH", "Bitcoin Core build", "Nix flake build", "verify binary identical", "supply-chain attestation" DO NOT USE FOR: Artifact signing - use `security/sigstore-cosign` DO NOT USE FOR: Cross-compile mechanics - use `build-tools/rust-cross-compile` DO NOT USE FOR: General Gradle - use `build-tools/gradle-kmp`

Backend & APIs

What this skill does

# Reproducible Builds

> **Deep Knowledge**: Use `mcp__documentation__fetch_docs` with technology: `reproducible-builds`.

## What & Why

A **reproducible build** produces bit-for-bit identical binaries from the same source code, independent of who runs the build, when, or where. Essential for:

- **Trust**: anyone can verify that a published binary actually matches the source
- **Supply-chain security**: detect compromised build infrastructure
- **Wallet/crypto software**: users verify binaries match audited source — no hidden changes
- **Regulatory compliance**: provable provenance

Bitcoin Core has used **Gitian** (deprecated) and now **Guix** for reproducible builds since 2013. BHODL-style wallet apps should follow.

## Sources of Non-Determinism

| Source | Example | Fix |
|---|---|---|
| Timestamps in archives | `tar` records `mtime` | `SOURCE_DATE_EPOCH` env |
| Random IDs | UUIDs in metadata | Pin via build script |
| Build paths | `/home/alice` vs `/build` | Strip via `--remap-path-prefix` (Rust) / `-fdebug-prefix-map` (gcc/clang) |
| Locale-dependent sort | `LC_ALL` differences | `LC_ALL=C` |
| Parallel compilation order | Output depends on race | Sort outputs deterministically |
| File ordering in archives | `glob` filesystem order | Sort before adding |
| Compiler versions | gcc 13 vs 14 produces different code | Pin compiler version |
| ABI / linker | dynamic vs static, link order | Pin linker, use static when possible |
| Filesystem encoding | UTF-8 NFC vs NFD | Normalize source paths |
| Username, hostname embedded | Build hostname in binary | Patch out via build flags |
| Kernel/OS version (rare) | Some compilers branch on `uname` | Containerize the build |

## SOURCE_DATE_EPOCH

The **standard env var** for fixing build timestamps. Most modern build tools honor it.

```bash
export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)        # last commit time
# or fixed value
export SOURCE_DATE_EPOCH=1700000000

# Most tools auto-honor:
tar --mtime=@$SOURCE_DATE_EPOCH ...
zip ...                                                     # zip 3.0+
gcc -frecord-gcc-switches ...                              # debug info uses SOURCE_DATE_EPOCH
sphinx-build ...                                            # docs

# Build with normalized timestamps
make
```

For Rust, use `--remap-path-prefix`:

```toml
# Cargo.toml
[profile.release]
opt-level = 3
strip = true
panic = "abort"

[build]
rustflags = ["--remap-path-prefix", "/home/builder/src=src"]
```

## Bitcoin Core Approach (Guix)

Bitcoin Core uses **Guix** — a functional package manager + build system that captures every dependency, compiler, and config bit. Builders independently produce identical binaries; signed attestations posted to `bitcoin-core/guix.sigs`.

```bash
# Inside Bitcoin Core repo
./contrib/guix/guix-build

# Output (signed by builder):
guix-build-<commit>/output/<host>/bitcoin-<version>-<host>.tar.gz
guix-build-<commit>/output/<host>/SHA256SUMS.part

# Each builder runs same command, all produce identical SHA256SUMS
# Multiple builders sign attestation file → published as multi-sig proof
```

**Why Guix specifically**:
- Hermetic builds (no host system leakage)
- Bit-perfect dependency pinning via content-addressed store
- Cross-compile from one host to many targets
- Reproducible across Linux distros, including Arch/Debian/NixOS

**Trade-offs**:
- Steep learning curve
- Big initial download (Guix store ~5-10 GB)
- Mostly Linux-focused (macOS/Windows targets cross-compiled from Linux host)

## Nix Flakes (Modern Alternative)

For non-Guix users, **Nix Flakes** offers similar guarantees with broader ecosystem:

```nix
# flake.nix
{
    description = "BHODL reproducible build";
    inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
    inputs.flake-utils.url = "github:numtide/flake-utils";
    inputs.rust-overlay.url = "github:oxalica/rust-overlay";

    outputs = { self, nixpkgs, flake-utils, rust-overlay }:
        flake-utils.lib.eachDefaultSystem (system:
            let
                pkgs = import nixpkgs {
                    inherit system;
                    overlays = [ rust-overlay.overlays.default ];
                };
                rust = pkgs.rust-bin.stable."1.85.0".default;
            in {
                packages.default = pkgs.rustPlatform.buildRustPackage {
                    pname = "bhodl-ffi";
                    version = "1.0.0";
                    src = ./.;
                    cargoLock.lockFile = ./Cargo.lock;
                    nativeBuildInputs = [ rust ];
                    SOURCE_DATE_EPOCH = "1735689600";   # fixed
                };
            }
        );
}
```

```bash
nix build
# Output deterministic: result/bin/bhodl-ffi
sha256sum result/bin/bhodl-ffi
```

`flake.lock` pins all transitive dependencies by content hash. Two users running same `nix build` get bit-identical output.

## Rust Reproducibility

```toml
# .cargo/config.toml
[build]
rustflags = [
    "--remap-path-prefix", "/home/builder=src",
    "--remap-path-prefix", "/Users/builder=src",
]

[profile.release]
strip = true                                    # remove non-deterministic debug paths
panic = "abort"                                 # less variability than unwind
codegen-units = 1                               # deterministic codegen order

[net]
git-fetch-with-cli = false
```

```bash
# Pin Rust toolchain
echo "1.85.0" > rust-toolchain.toml
# Or full TOML:
cat > rust-toolchain.toml <<EOF
[toolchain]
channel = "1.85.0"
components = ["rustfmt", "clippy"]
targets = ["aarch64-apple-ios", "aarch64-linux-android"]
profile = "minimal"
EOF
```

Cargo.lock **must be committed** for libraries that need reproducible builds (it's optional for libraries by default; required for binaries).

## Verifying Reproducibility — diffoscope

When two builds produce different binaries, find why:

```bash
# Install
brew install diffoscope        # or apt
pip install diffoscope          # has many optional formats

# Compare
diffoscope build1/output build2/output
diffoscope build1/binary build2/binary --html report.html
```

Outputs hierarchical diff: archive members, binary sections, debug info, recursive into nested archives.

For mobile APK comparison:

```bash
diffoscope app-1.apk app-2.apk --html-dir report/
```

Common findings: `META-INF` order, `classes.dex` opt order (D8 nondeterminism), `.so` build paths, asset compression metadata.

## Android APK Reproducibility

APK is a `.zip` with embedded DEX, native libs, resources. Sources of nondeterminism:

| Source | Fix |
|---|---|
| Build timestamp in `AndroidManifest.xml` | None — accept or strip post-build |
| `META-INF/*.RSA`/`*.SF` order | `apksigner` v3+ deterministic |
| `classes.dex` D8 codegen | Use `--release` mode + pinned D8 version |
| Resource compression metadata | Use `-Z store` for `aapt2` if needed |
| File ordering in zip | `apksigner` rewrites in fixed order |
| ProGuard/R8 minification | Set `-printseeds`, `-printusage`, `-printmapping` for diff visibility |

Modern AGP (8+) with R8 produces increasingly deterministic output. Pin AGP version, JDK version, NDK version.

```kotlin
// app/build.gradle.kts
android {
    compileSdk = 35
    ndkVersion = "27.1.12297006"               // pin
    defaultConfig {
        vectorDrawables.useSupportLibrary = true
    }
    packaging {
        resources.excludes += setOf(
            "META-INF/MANIFEST.MF",
            "META-INF/build-data.properties",
        )
    }
    signingConfigs {
        create("release") {
            storeFile = file("release.keystore")
            storePassword = System.getenv("KEYSTORE_PASS")
            keyAlias = "bhodl"
            keyPassword = System.getenv("KEY_PASS")
        }
    }
}
```

Users verify by re-running build, comparing SHA-256 of unsigned APK (`app-release-unsigned.apk`).

## iOS / Xcode Reproducibility

Hardest target. Xcode's build process embeds many timestamps and host-specific paths.

| Source | Fix |
|---|---|
| `Info.

Related in Backend & APIs