git / moonbite-network / docs/REPRODUCIBLE-BUILDS.md · raw
# Reproducible and attested releasesMoonBite is a proof-of-work coin maintained by a pseudonymous operator. Usersare used to two vendor identity schemes, Microsoft Authenticode and Applenotarization, and both require a verified legal identity, so MoonBite binariescannot carry them today. Windows SmartScreen and macOS Gatekeeper will warn.Release trust is built instead on evidence that anyone can check on their ownmachine, without trusting the download page, the operator, or a singlecertificate authority.This document explains the four checks, how to perform them, how to rebuild arelease from source and compare hashes, and the roadmap for vendor signing.## The four checks| Check | What it proves | What it does not prove ||---|---|---|| **SHA256SUMS.txt** | The file you hold is byte-identical to the one the manifest names. Detects corruption and a swapped binary when the manifest comes from a different channel. | Nothing about who produced the manifest: an attacker who replaces the asset can replace the co-located manifest in the same motion. || **Sigstore attestation** (`gh attestation verify`) | The asset with this exact digest was produced by the named GitHub Actions workflow in the named public repository at the named commit; signed with GitHub's OIDC identity for that run and recorded in the public Sigstore transparency log. An attacker who swaps the asset cannot forge this without control of the repository's Actions. | That the source at that commit is honest, that GitHub is honest, or that the runner image was clean. || **minisign signature** over `SHA256SUMS.txt` | The manifest was signed by whoever holds the MoonBite release private key (pinned public key in `deploy/moonbite-release.pub`). Independent of GitHub. | Currently nothing: the public key is still a `REPLACE_ME` placeholder until the ceremony in `deploy/RELEASE-SIGNING.md` is done. The verifiers skip this check and say so. || **Reproducibility** | The same source and toolchain yield the same bytes. Every release run builds the Linux core target and both Windows bundles twice on separate runner VMs and fails if the hashes differ; you can rebuild yourself and compare. This is the only check that speaks to the *source* being what shipped. | That the source is free of bugs or malice; that is what public review is for. |Together: the manifest gives integrity, the attestation gives build provenance,minisign (once live) gives operator authenticity independent of GitHub, andreproducibility lets a third party replace trust with a rebuild.## Verifying a downloadDownload the artifact and `SHA256SUMS.txt` (and `SHA256SUMS.txt.minisig` ifpresent) from the same release, then run one of:```bash# Linux / macOS / Git Bashscripts/release/verify-release.sh moonbite-miner-windows-x86_64.zip``````powershell# Windows PowerShell.\scripts\release\Verify-Release.ps1 .\moonbite-miner-windows-x86_64.zip```Each prints `PASS`, `FAIL` or `SKIP` per check and a final verdict. `SKIP`means the check could not run (tool missing, placeholder key, no `.minisig`),not that it passed; the verdict line states what was and was not proven.Manually, the same checks are:```bashsha256sum -c SHA256SUMS.txt --ignore-missinggh attestation verify moonbite-miner-windows-x86_64.zip --owner moonbitecoinminisign -Vm SHA256SUMS.txt -p deploy/moonbite-release.pub # when a .minisig exists````gh attestation verify` needs the GitHub CLI 2.49 or later and a `gh authlogin`; it talks only to GitHub's attestation API and the Sigstore log.## Where the artifacts come fromTwo workflows, one per repository, both SHA-pinned on every third-party action:- **moonbite-core `.github/workflows/release.yml`** builds `moonbited` and `moonbite-cli` for Linux, Windows (mingw cross) and both macOS architectures with the `depends` system and a RandomX rebuilt from its pinned commit. The Linux target is built twice; a `reproducibility` job compares them. The release job attests every asset and `SHA256SUMS.txt`, signs the manifest with minisign when the `MINISIGN_SECRET_KEY` secret exists, and publishes.- **moonbite-network `.github/workflows/release-bundles.yml`** downloads the Windows node binaries from the core release of the same version, verifies them (sha256, attestation, minisign when live), and packs `moonbite-miner-windows-x86_64.zip` (node, CLI, `mine.ps1`, `Mine-MoonBite.bat`, README) and `moonbite-wallet-windows-x86_64.zip` (desktop wallet) with `scripts/release/make-bundle.py`. Two passes on separate runners are compared, then the release job attests, optionally minisigns, and uploads.### Determinism measuresCore (C++):- `SOURCE_DATE_EPOCH` is set from the commit timestamp (`git log -1 --pretty=%ct`).- `-ffile-prefix-map=<checkout>=/build/moonbite` on both the node and RandomX, so no runner path reaches `__FILE__`, assertions or debug info.- `strip -D` and `ranlib -D` (deterministic archives: zero uid/gid/mtime).- `-frandom-seed` is deliberately not used: GCC requires a distinct seed per translation unit, and the default seed is already derived from the input.- Archives: `tar --sort=name --mtime=@EPOCH --owner=0 --group=0 --numeric-owner | gzip -n`; Windows zip from a sorted file list with `zip -X`; everything under `TZ=UTC LC_ALL=C`.Bundles (Python, `scripts/release/make-bundle.py`):- Members written in sorted order, no directory entries.- Every entry stamped `1980-01-01 00:00:00` (the zip epoch; matches what `deploy/miner/build-windows-bundle.py` has always used).- `external_attr` fixed to 0755 for executables and 0644 otherwise, so the build host's umask never leaks in.- stdlib DEFLATE level 9, no extra fields, no comments.## Rebuilding locally and comparing### Bundles (quick, pure Python)```bashgit checkout vX.Y.Zmkdir core-dl && cd core-dlgh release download vX.Y.Z --repo moonbitecoin/moonbite-core \ --pattern moonbite-miner-windows-x86_64.zip --pattern 'SHA256SUMS.txt*'sha256sum -c SHA256SUMS.txt --ignore-missingpython -I -c 'import zipfile;zipfile.ZipFile("moonbite-miner-windows-x86_64.zip").extractall("x")'cd ..python -I scripts/release/make-bundle.py --sort \ --out dist/moonbite-miner-windows-x86_64.zip --root moonbite-miner \ core-dl/x/moonbite-miner/moonbited.exe core-dl/x/moonbite-miner/moonbite-cli.exe \ release/miner/mine.ps1 release/miner/Mine-MoonBite.bat release/miner/README.mdsha256sum dist/moonbite-miner-windows-x86_64.zip # compare with the release's SHA256SUMS.txt```Any Python 3.9+ on any OS gives the same bytes.### Core (Docker, matches the CI image)```bashgit clone https://moonbite.org/git/moonbite-core && cd moonbite-core && git checkout vX.Y.Zdocker run --rm -it -v "$PWD:/src" -w /src ubuntu:22.04 bashapt-get update && apt-get install -y build-essential libtool autotools-dev automake \ pkg-config bsdmainutils curl git ca-certificates cmake python3 zipexport TZ=UTC LC_ALL=C SOURCE_DATE_EPOCH="$(git log -1 --pretty=%ct)" \ GITHUB_WORKSPACE=/src BUILD_PREFIX_MAP_TO=/build/moonbite# Run the "Build RandomX", "Build depends", "Configure and build node" and# "Assemble bundle" steps from .github/workflows/release.yml verbatim.sha256sum moonbite-miner-linux-x86_64.tar.gz```A different compiler version will legitimately give a different hash; theclaim is "same inputs, same output". If the hash differs on the same image,extract both archives and hash each member to localise the divergence, thenopen an issue with the two member lists.### Core (Guix, the gold standard)`contrib/guix/` in moonbite-core (inherited from Litecoin and Bitcoin Core)builds the whole toolchain from pinned sources, removing the GitHub runner imagefrom the trusted base. It is the path independent builders should use and thelong-term target for all hosts; it is not run in CI yet because a full build ofevery host takes many hours and needs a persistent Guix store. The `guix` job inthe core workflow is a disabled stub marking where it plugs in. See`doc/reproducible-builds.md` in moonbite-core.## Roadmap for vendor identitiesPseudonymity blocks Authenticode and notarization today. Options, from least tomost identity required:1. **SignPath Foundation**: free code signing for open-source projects under SignPath's own certificate. The project is vetted, not the individual, and signing happens in their pipeline from a public CI build, which fits a reproducible, attested build well.2. **Azure Trusted Signing**: needs an organisation with a verifiable identity and a three-year history for some tiers; feasible once a legal entity exists.3. **Forming an entity** (foundation or company) to hold the Authenticode certificate and an Apple Developer account on the project's behalf. This is the only route to macOS notarization.Until one of these is in place, the verification steps above are thesubstitute. Do not disable Gatekeeper or SmartScreen globally; verify the file,then allow that single binary.## Open items- Complete the minisign key ceremony (`deploy/RELEASE-SIGNING.md`), replace the placeholder in `deploy/moonbite-release.pub`, and decide whether a CI-held signing key (`MINISIGN_SECRET_KEY` secret) is acceptable or whether manifests are signed offline and attached by hand. The workflows handle both: with no secret they publish unsigned and say so.- Stand up a Guix builder and enable the stub job.- Apply to SignPath Foundation once the repository history is clean and public.