git / moonbite-network / scripts/release/verify-release.sh · raw
#!/usr/bin/env bash# Verify a downloaded MoonBite release artifact.## scripts/release/verify-release.sh <artifact> [SHA256SUMS.txt] [--pub deploy/moonbite-release.pub] [--owner moonbitecoin]## Checks, in order:# 1. sha256 - the artifact's hash appears in SHA256SUMS.txt under its own name (REQUIRED)# 2. minisign - SHA256SUMS.txt.minisig verifies against the pinned public key (when minisign is installed and the key is not the placeholder)# 3. sigstore - `gh attestation verify <artifact> --owner <owner>` passes (when gh is installed)## Exit 0 only when every check that could run passed. A check that could not run# (tool missing, placeholder key, no .minisig) is reported as SKIP and does not# fail the result, but the summary says what was and was not proven.# Works in bash on Linux, macOS and Git Bash. No emoji; plain PASS/FAIL/SKIP.set -euo pipefailusage() { sed -n '2,15p' "$0" | sed 's/^# \{0,1\}//'; exit 2; }ARTIFACT=""; SUMS=""; PUB=""; OWNER="moonbitecoin"while [ $# -gt 0 ]; do case "$1" in --pub) PUB="$2"; shift 2 ;; --owner) OWNER="$2"; shift 2 ;; -h|--help) usage ;; -*) echo "unknown option: $1"; usage ;; *) if [ -z "$ARTIFACT" ]; then ARTIFACT="$1"; elif [ -z "$SUMS" ]; then SUMS="$1"; else usage; fi; shift ;; esacdone[ -n "$ARTIFACT" ] || usage[ -f "$ARTIFACT" ] || { echo "FAIL artifact not found: $ARTIFACT"; exit 1; }DIR="$(cd "$(dirname "$ARTIFACT")" && pwd)"NAME="$(basename "$ARTIFACT")"[ -n "$SUMS" ] || SUMS="$DIR/SHA256SUMS.txt"if [ -z "$PUB" ]; then HERE="$(cd "$(dirname "$0")" && pwd)" for c in "$DIR/moonbite-release.pub" "$HERE/../../deploy/moonbite-release.pub"; do [ -f "$c" ] && { PUB="$c"; break; } donefipass=0; fail=0; skip=0ok() { echo "PASS $*"; pass=$((pass+1)); }bad() { echo "FAIL $*"; fail=$((fail+1)); }skp() { echo "SKIP $*"; skip=$((skip+1)); }sha256_of() { if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | cut -d' ' -f1 elif command -v shasum >/dev/null 2>&1; then shasum -a 256 "$1" | cut -d' ' -f1 else openssl dgst -sha256 "$1" | awk '{print $NF}'; fi}echo "artifact : $ARTIFACT"echo "manifest : $SUMS"echo "pubkey : ${PUB:-<none found>}"echo# 1. sha256 ------------------------------------------------------------------if [ ! -f "$SUMS" ]; then bad "sha256: manifest not found ($SUMS). Download SHA256SUMS.txt from the same release."else ACTUAL="$(sha256_of "$ARTIFACT" | tr 'A-F' 'a-f')" # "<hex> name" or "<hex> *name"; match on basename EXPECTED="$(grep -Ei "^[0-9a-f]{64}[[:space:]]+\*?(.*/)?${NAME//./\\.}[[:space:]]*$" "$SUMS" | head -1 | awk '{print tolower($1)}' || true)" if [ -z "$EXPECTED" ]; then bad "sha256: '$NAME' is not listed in $SUMS" elif [ "$EXPECTED" = "$ACTUAL" ]; then ok "sha256: $ACTUAL matches manifest" else bad "sha256: MISMATCH manifest=$EXPECTED actual=$ACTUAL -- do not run this file" fifi# 2. minisign ------------------------------------------------------------------KEYLINE=""if [ -n "$PUB" ] && [ -f "$PUB" ]; then KEYLINE="$(grep -v '^[[:space:]]*#' "$PUB" | grep -v '^[[:space:]]*$' | grep -v '^untrusted comment' | head -1 || true)"fiif ! command -v minisign >/dev/null 2>&1; then skp "minisign: tool not installed (https://jedisct1.github.io/minisign/)"elif [ -z "$KEYLINE" ] || [ "${KEYLINE#REPLACE_ME}" != "$KEYLINE" ]; then skp "minisign: release public key is still a placeholder (key ceremony pending, deploy/RELEASE-SIGNING.md)"elif [ ! -f "$SUMS.minisig" ]; then skp "minisign: no $(basename "$SUMS").minisig next to the manifest"elif minisign -Vm "$SUMS" -P "$KEYLINE" >/dev/null 2>&1; then ok "minisign: $(basename "$SUMS").minisig verifies against pinned key"else bad "minisign: signature on $(basename "$SUMS") does NOT verify -- manifest may be forged"fi# 3. Sigstore attestation ------------------------------------------------------if ! command -v gh >/dev/null 2>&1; then skp "sigstore: GitHub CLI (gh) not installed; cannot check build provenance"else if OUT="$(gh attestation verify "$ARTIFACT" --owner "$OWNER" 2>&1)"; then ok "sigstore: attestation verified for owner '$OWNER' (built by GitHub Actions from the public repo)" echo "$OUT" | grep -Ei 'repo|workflow|ref' | sed 's/^/ /' || true else case "$OUT" in *"not logged"*|*"auth login"*|*"authentication"*|*"Bad credentials"*|*"HTTP 401"*) skp "sigstore: gh is not authenticated or its token is invalid (run 'gh auth login'); attestation not checked" ;; *) bad "sigstore: no valid attestation for this file under owner '$OWNER'" echo "$OUT" | tail -5 | sed 's/^/ /' ;; esac fifi# Summary ----------------------------------------------------------------------echoecho "result: $pass passed, $fail failed, $skip skipped"if [ "$fail" -gt 0 ]; then echo "VERDICT: FAIL - do not use this artifact." exit 1fiif [ "$pass" -eq 0 ]; then echo "VERDICT: NOTHING PROVEN - no check could run." exit 1fiif [ "$skip" -gt 0 ]; then echo "VERDICT: PASS (with $skip check(s) skipped - integrity shown; see SKIP lines for what was not proven)"else echo "VERDICT: PASS - integrity, authenticity and build provenance all verified."fiexit 0