# Reproducible and attested releases MoonBite is a proof-of-work coin maintained by a pseudonymous operator. Users are used to two vendor identity schemes, Microsoft Authenticode and Apple notarization, and both require a verified legal identity, so MoonBite binaries cannot carry them today. Windows SmartScreen and macOS Gatekeeper will warn. Release trust is built instead on evidence that anyone can check on their own machine, without trusting the download page, the operator, or a single certificate authority. This document explains the four checks, how to perform them, how to rebuild a release 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, and reproducibility lets a third party replace trust with a rebuild. ## Verifying a download Download the artifact and `SHA256SUMS.txt` (and `SHA256SUMS.txt.minisig` if present) from the same release, then run one of: ```bash # Linux / macOS / Git Bash scripts/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: ```bash sha256sum -c SHA256SUMS.txt --ignore-missing gh attestation verify moonbite-miner-windows-x86_64.zip --owner moonbitecoin minisign -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 auth login`; it talks only to GitHub's attestation API and the Sigstore log. ## Where the artifacts come from Two 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 measures Core (C++): - `SOURCE_DATE_EPOCH` is set from the commit timestamp (`git log -1 --pretty=%ct`). - `-ffile-prefix-map==/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) ```bash git checkout vX.Y.Z mkdir core-dl && cd core-dl gh release download vX.Y.Z --repo moonbitecoin/moonbite-core \ --pattern moonbite-miner-windows-x86_64.zip --pattern 'SHA256SUMS.txt*' sha256sum -c SHA256SUMS.txt --ignore-missing python -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.md sha256sum 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) ```bash git clone https://moonbite.org/git/moonbite-core && cd moonbite-core && git checkout vX.Y.Z docker run --rm -it -v "$PWD:/src" -w /src ubuntu:22.04 bash apt-get update && apt-get install -y build-essential libtool autotools-dev automake \ pkg-config bsdmainutils curl git ca-certificates cmake python3 zip export 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; the claim is "same inputs, same output". If the hash differs on the same image, extract both archives and hash each member to localise the divergence, then open 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 image from the trusted base. It is the path independent builders should use and the long-term target for all hosts; it is not run in CI yet because a full build of every host takes many hours and needs a persistent Guix store. The `guix` job in the core workflow is a disabled stub marking where it plugs in. See `doc/reproducible-builds.md` in moonbite-core. ## Roadmap for vendor identities Pseudonymity blocks Authenticode and notarization today. Options, from least to most 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 the substitute. 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.