# Release signing & checksum integrity Fixes audit findings **B2** (stale/mismatched published checksums) and **B3/B5** (installers run unverified binaries as root). The download channel is threat-model asset #3 — a compromise here is everyone's keys at once — so binary trust must be **authenticity** (a signature you pin), not just a checksum sitting next to the file. This repo now ships the *machinery*. Two things only a human can do remain, and the channel is not trustworthy until both are done: 1. **Generate and hold the signing key** (offline, passphrase-protected). 2. **Decide the canonical binary set** and stamp its real hashes. --- ## What is already wired | Piece | File | Behaviour | |---|---|---| | Checksum manifest is generated from real artifacts | `deploy/miner/build-windows-bundle.py` | writes `SHA256SUMS.txt` (zip + members); prints the `minisign -Sm` next step; `--stage` copies zip **+ sums + .minisig** together and **refuses to stage unsigned** once a key is configured | | Drift guard | `deploy/verify-release-checksums.py` | re-hashes everything referenced by `release/SHA256SUMS.txt` and `website/downloads/README.txt`; fails on mismatch or two same-named builds that disagree | | CI gate | `.github/workflows/ci.yml` | runs the drift guard on every push/PR (**green** as of 2026-10-03: the stale beta `.exe` rows were removed and the committed manifest is Linux-only) | | Publish signs the manifest | `deploy/publish-node-binaries.sh` | step [6/6] runs `minisign -Sm` when `moonbite-release.key` is present, emits `SHA256SUMS.txt.minisig`, prints the `gh release`/`setup-node.sh` commands with the signature asset and `MOONBITE_SUMS_URL` wired in; fails loud (never silently unsigned) when no key | | Node installer verification | `deploy/setup-node.sh` | fetches to a staging dir, `mb_verify`s against the signed manifest, installs only on pass; refuses if a key is set but `MOONBITE_SUMS_URL` is unset | | Seed installer verification | `deploy/setup-seed2.sh` | verifies the tarball **before** extracting/running as root; key pinned inline (`RELEASE_PUBKEY`) because it is `curl\|bash`'d | | Pinned public key | `deploy/moonbite-release.pub` | placeholder; installers run sha256-only + loud warning until replaced | Fail-closed rule: once a real key is present, a **missing or bad signature aborts the install**. It never silently downgrades. **Fail-closed by DEFAULT (2026-10-03).** Even with no key yet, the network installers (`setup-node.sh`, `setup-seed2.sh`) now **refuse** to install a fetched binary that has no verification at all, rather than warning and installing it. The only way to install a fully-unverified network binary is an explicit, loud operator override, `MOONBITE_ALLOW_UNVERIFIED=1`, intended solely for a build you produced yourself; local-path installs (you hand the script the binary) stay trusted. So the silent "curl a binary, run it as root" backdoor is shut for everyone today; completing the key ceremony upgrades the remaining sha256-only path (manifest reachable, no key) to full signature verification. --- ## 1. Key ceremony (do once, on an offline machine) ```bash # Offline box. minisign: https://jedisct1.github.io/minisign/ minisign -G -p moonbite-release.pub -s moonbite-release.key ``` - `moonbite-release.key` — **secret**. Keep it offline, passphrase-protected. **Never commit it.** (`*.key` is git-ignored; confirm.) - `moonbite-release.pub` — the public key line (starts `RW…`). Publish it widely so users pin it independently of any one origin: 1. paste it into `deploy/moonbite-release.pub`, replacing `REPLACE_ME…`; 2. paste the **same** line into `deploy/setup-seed2.sh` at the `RELEASE_PUBKEY=` marker (grep `RELEASE_PUBKEY`); 3. add it to the site (`website/`, the download page) and the top-level `README`. Rotated/compromised key? Publish a new pubkey in all the above and re-sign; revoke by announcement (minisign has no revocation). --- ## 2. Decide the canonical binaries (audit B2 — partly done 2026-10-03) Already done (2026-10-03 hardening): - The **stale pre-relaunch (Sep 3) Windows `.exe`** were removed from `release/bin` and dropped from `release/SHA256SUMS.txt`, so the drift guard passes (incl. `--strict`) and no `.exe` is committed with a hash that no longer matches. - `website/downloads/README.txt` no longer pastes a drift-prone hash — it tells users to verify against the `SHA256SUMS.txt` shipped with each download / on the GitHub release (and its `.minisig`, once signing is live). - `release/dist/` and `release/miner/*.exe` are git-ignored so a second divergent binary can't be committed next to `release/bin` again. Still your call (needs the build machine; cannot be done from this checkout): - The **Linux** binaries still in `release/bin` (`moonbited`, `moonbite-cli`, …) are also **Sep 3 beta**. Rebuild the relaunch binaries from a tagged `moonbite-core` commit (reproducible, not the dev machine), refresh `release/bin` + its manifest rows, or stop committing binaries entirely and rely on the publish-time manifests. - Produce the **canonical Windows build** (`release/miner/*.exe` is the current dev-built, unsigned staging set that `build-windows-bundle.py` packages). Confirm it is the real relaunch build, then sign and ship it via the runbook below. --- ## 3. Build, sign, stamp, stage (every release) ```bash # 1. Put the canonical binaries in release/miner/ (windows) and the linux build dir. # 2. Build the bundle + machine-readable manifest: python deploy/miner/build-windows-bundle.py # writes release/dist/{zip,SHA256SUMS.txt} # 3. Sign the manifest with the offline key: minisign -Sm release/dist/SHA256SUMS.txt # -> SHA256SUMS.txt.minisig # (do the same for the linux SHA256SUMS-linux.txt) # 4. Update the human-facing references to the REAL hashes: # - release/SHA256SUMS.txt (canonical set only; drop the stale beta rows) # - website/downloads/README.txt (the "verify what you downloaded" block) # - release/README.md (fix the genesis line if it still says 3d053c59) # 5. Prove they match before publishing: python deploy/verify-release-checksums.py --strict # must exit 0 # 6. Stage (copies zip + sums + .minisig into the served download dir): python deploy/miner/build-windows-bundle.py --stage # Publish SHA256SUMS-linux.txt(+.minisig) to website/downloads/ so # setup-seed2.sh's SUMS_URL resolves. ``` Then, and only then, commit the canonical binaries / references. Until step 5 is green, CI stays red and nothing should be advertised as verifiable. > The pending untracked `release/miner/moonbited.exe` and `moonbite-cli.exe` should > not be committed or shipped until they are the confirmed canonical build with > signed sums (this runbook). That was flagged in the audit. --- ## 4. Verify as a user (what the site should tell people) ```bash minisign -Vm SHA256SUMS.txt -P RW… # authenticity (paste the published pubkey) sha256sum -c SHA256SUMS.txt # integrity of each file ``` Replace the current "compare this hash" prose with "verify this signature" — a bare checksum next to the binary proves nothing against an origin swap.