diff options
| author | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-05 22:46:52 +0000 |
|---|---|---|
| committer | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-05 22:46:52 +0000 |
| commit | ceae3d076b5ad11ee84729ae43f14e9312896646 (patch) | |
| tree | 7e9132aa871fdc01cb95268d2f5ea3bb11853ddf /scripts/verify-release.sh | |
| parent | be63e83d79e7e00e6b8ca889a70e6a19886aa1d8 (diff) | |
| parent | 66e0b9158d050c46b1ff2509faeee3f4573cac1c (diff) | |
Bring main onto the line this project actually ships
main had drifted to a history with no ancestor in common with the tree we
build and deploy. It sat at be63e83 (2026-08-09) with 28 commits; the shipped
line, deploy/soak-2026-08-21, had 383. The two shared nothing -- git
merge-base returns no result at all -- so anyone cloning this repository
landed on a default branch that was neither current nor related to the
running node.
The 28 commits on main were audited before this merge: every one is already
present in the tree by content, including the two that are hardest to see
because they were rewritten afterwards -- the payout reorg-safety work
(PayoutBlockStillOnChain, since evolved to re-check on every retry rather
than once at maturity) and the nostr relay certificate verification
(SSL_set1_host, now with a Windows root-store path). Nothing on main is lost
by this merge.
Recorded as a merge rather than a force-push: main only ever moves forward,
its old history stays reachable as the first parent, and this is revertible.
The tree is taken wholesale from the merged branch, so main's content is now
exactly what was built and tested.
What this brings onto main:
- compactblock: BIP152-style compact block relay ported from upstream 1.2.23,
primitives and unit test only, with the RAND_bytes failure path closed and
the reconstruction allocation bounded.
- upstream/main 17741d9: isolated testnet parameters and genesis, optional
2 MB large pages for RandomX. Mainnet genesis verified byte-identical
through upstream's BuildGenesisBlock() refactor.
- deploy: every image built on bookworm so binaries run on Debian 12, and the
container build now honours the RandomX and libsecp256k1 pins it had been
silently ignoring since before 1.2.15.
Gates at this tree: 15/15 selftests, compactblock's 17 checks, consensus
constants unchanged against e4f898c apart from the pre-existing
MAX_BLOCK_SIZE_GEN, mainnet genesis intact, money-log check ok.
Diffstat (limited to 'scripts/verify-release.sh')
| -rwxr-xr-x[-rw-r--r--] | scripts/verify-release.sh | 108 |
1 files changed, 58 insertions, 50 deletions
diff --git a/scripts/verify-release.sh b/scripts/verify-release.sh index c651e78..4fd4558 100644..100755 --- a/scripts/verify-release.sh +++ b/scripts/verify-release.sh | |||
| @@ -1,23 +1,50 @@ | |||
| 1 | #!/usr/bin/env bash | 1 | #!/usr/bin/env bash |
| 2 | set -euo pipefail | 2 | set -euo pipefail |
| 3 | 3 | ||
| 4 | repo="bitflash-coin/bitflash" | 4 | # Verifies a Bitflash release: the checksum file's signature against a pinned |
| 5 | tag="${1:-latest}" | 5 | # key, then the assets against the checksums. |
| 6 | # | ||
| 7 | # The signing key is embedded below, not fetched. That is the whole point of a | ||
| 8 | # pinned key: the trust anchor ships with this script (in git), so it does not | ||
| 9 | # depend on the same server that serves the release. A server that is | ||
| 10 | # compromised can swap the binaries and the checksums, but it cannot forge a | ||
| 11 | # signature by this key. | ||
| 12 | |||
| 13 | host="https://releases.bitflash.network" | ||
| 6 | require_signature=0 | 14 | require_signature=0 |
| 7 | 15 | ||
| 16 | # The Bitflash release signing key. Verification requires this exact key -- | ||
| 17 | # gpg's own "good signature" is not enough, because a good signature by *some* | ||
| 18 | # key proves nothing. See issue #138. | ||
| 19 | RELEASE_KEY_FP="910A2B4CCA879E81FB4B2AEA1D4A53D3B78AA4B8" | ||
| 20 | read -r -d '' RELEASE_KEY <<'KEY' || true | ||
| 21 | -----BEGIN PGP PUBLIC KEY BLOCK----- | ||
| 22 | |||
| 23 | mDMEanSOmRYJKwYBBAHaRw8BAQdAXj24i2BYUcAmOpoXwjE4ZTynHsifCZ+niwQK | ||
| 24 | HqO0nMa0UEJpdGZsYXNoIFJlbGVhc2VzIChiaXRmbGFzaC5uZXR3b3JrIHJlbGVh | ||
| 25 | c2Ugc2lnbmluZykgPHJlbGVhc2VzQGJpdGZsYXNoLm5ldHdvcms+iJAEExYKADgW | ||
| 26 | IQSRCitMyoeegftLKuodSlPTt4qkuAUCanSOmQIbAwULCQgHAgYVCgkICwIEFgID | ||
| 27 | AQIeAQIXgAAKCRAdSlPTt4qkuIi9AP96X3OcstiCTTgKOr16Vhds3LyuZ19q3Apq | ||
| 28 | 4KAcNuOUMAD+NqgJ1CqkLGPXfZLCtYBCRHXNYWxMMEk+ViLcRtdLMg4= | ||
| 29 | =mxic | ||
| 30 | -----END PGP PUBLIC KEY BLOCK----- | ||
| 31 | KEY | ||
| 32 | |||
| 8 | usage() { | 33 | usage() { |
| 9 | cat <<'EOF' | 34 | cat <<'EOF' |
| 10 | Usage: scripts/verify-release.sh [tag|latest] [--require-signature] | 35 | Usage: scripts/verify-release.sh [tag|latest] [--require-signature] |
| 11 | 36 | ||
| 12 | Downloads the release checksum file and every asset named in it, then verifies: | 37 | Downloads the release checksum file and every asset named in it, then verifies: |
| 13 | 1. SHA256SUMS.asc, if present, against SHA256SUMS with gpg. | 38 | 1. SHA256SUMS.asc against SHA256SUMS, requiring the pinned Bitflash release |
| 39 | key (fingerprint 910A 2B4C CA87 9E81 FB4B 2AEA 1D4A 53D3 B78A A4B8). | ||
| 14 | 2. SHA256SUMS against the downloaded assets. | 40 | 2. SHA256SUMS against the downloaded assets. |
| 15 | 41 | ||
| 16 | Use --require-signature for release audits. Without it, older releases that | 42 | Releases from before signing was introduced ship only SHA256SUMS; without |
| 17 | ship only SHA256SUMS are checked for integrity and reported as unsigned. | 43 | --require-signature those are checked for integrity and reported as unsigned. |
| 18 | EOF | 44 | EOF |
| 19 | } | 45 | } |
| 20 | 46 | ||
| 47 | tag="${1:-latest}" | ||
| 21 | for arg in "${@:2}"; do | 48 | for arg in "${@:2}"; do |
| 22 | case "$arg" in | 49 | case "$arg" in |
| 23 | --require-signature) require_signature=1 ;; | 50 | --require-signature) require_signature=1 ;; |
| @@ -25,19 +52,9 @@ for arg in "${@:2}"; do | |||
| 25 | *) echo "unknown option: $arg" >&2; usage >&2; exit 2 ;; | 52 | *) echo "unknown option: $arg" >&2; usage >&2; exit 2 ;; |
| 26 | esac | 53 | esac |
| 27 | done | 54 | done |
| 55 | if [ "$tag" = "-h" ] || [ "$tag" = "--help" ]; then usage; exit 0; fi | ||
| 28 | 56 | ||
| 29 | if [ "$tag" = "-h" ] || [ "$tag" = "--help" ]; then | 57 | need() { command -v "$1" >/dev/null 2>&1 || { echo "missing required command: $1" >&2; exit 1; }; } |
| 30 | usage | ||
| 31 | exit 0 | ||
| 32 | fi | ||
| 33 | |||
| 34 | need() { | ||
| 35 | if ! command -v "$1" >/dev/null 2>&1; then | ||
| 36 | echo "missing required command: $1" >&2 | ||
| 37 | exit 1 | ||
| 38 | fi | ||
| 39 | } | ||
| 40 | |||
| 41 | need sed | 58 | need sed |
| 42 | need awk | 59 | need awk |
| 43 | 60 | ||
| @@ -46,39 +63,25 @@ if command -v sha256sum >/dev/null 2>&1; then | |||
| 46 | elif command -v shasum >/dev/null 2>&1; then | 63 | elif command -v shasum >/dev/null 2>&1; then |
| 47 | sha_check=(shasum -a 256 -c SHA256SUMS) | 64 | sha_check=(shasum -a 256 -c SHA256SUMS) |
| 48 | else | 65 | else |
| 49 | echo "missing required command: sha256sum or shasum" >&2 | 66 | echo "missing required command: sha256sum or shasum" >&2; exit 1 |
| 50 | exit 1 | ||
| 51 | fi | 67 | fi |
| 52 | 68 | ||
| 53 | download() { | 69 | download() { |
| 54 | local url="$1" | 70 | if command -v curl >/dev/null 2>&1; then curl -fsSL "$1" -o "$2" |
| 55 | local out="$2" | 71 | elif command -v wget >/dev/null 2>&1; then wget -q "$1" -O "$2" |
| 56 | if command -v curl >/dev/null 2>&1; then | 72 | else echo "missing required command: curl or wget" >&2; exit 1; fi |
| 57 | curl -fsSL "$url" -o "$out" | ||
| 58 | elif command -v wget >/dev/null 2>&1; then | ||
| 59 | wget -q "$url" -O "$out" | ||
| 60 | else | ||
| 61 | echo "missing required command: curl or wget" >&2 | ||
| 62 | exit 1 | ||
| 63 | fi | ||
| 64 | } | 73 | } |
| 65 | 74 | ||
| 66 | if [ "$tag" = "latest" ]; then | 75 | if [ "$tag" = "latest" ]; then |
| 67 | tag="$(download "https://api.github.com/repos/$repo/releases/latest" - | | 76 | tag="$(download "$host/latest.txt" - 2>/dev/null | tr -d '[:space:]' || true)" |
| 68 | sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | | 77 | [ -n "$tag" ] || { echo "could not resolve the latest release tag from $host/latest.txt" >&2; exit 1; } |
| 69 | head -1)" | ||
| 70 | if [ -z "$tag" ]; then | ||
| 71 | echo "could not resolve latest release tag" >&2 | ||
| 72 | exit 1 | ||
| 73 | fi | ||
| 74 | fi | 78 | fi |
| 75 | 79 | ||
| 76 | workdir="$(mktemp -d "${TMPDIR:-/tmp}/bitflash-release-verify.XXXXXX")" | 80 | workdir="$(mktemp -d "${TMPDIR:-/tmp}/bitflash-release-verify.XXXXXX")" |
| 77 | cleanup() { rm -rf "$workdir"; } | 81 | cleanup() { rm -rf "$workdir"; } |
| 78 | trap cleanup EXIT | 82 | trap cleanup EXIT |
| 79 | 83 | ||
| 80 | base_url="https://github.com/$repo/releases/download/$tag" | 84 | base_url="$host/$tag" |
| 81 | |||
| 82 | echo "Verifying Bitflash $tag" | 85 | echo "Verifying Bitflash $tag" |
| 83 | echo "Working directory: $workdir" | 86 | echo "Working directory: $workdir" |
| 84 | cd "$workdir" | 87 | cd "$workdir" |
| @@ -86,27 +89,32 @@ cd "$workdir" | |||
| 86 | download "$base_url/SHA256SUMS" SHA256SUMS | 89 | download "$base_url/SHA256SUMS" SHA256SUMS |
| 87 | 90 | ||
| 88 | if download "$base_url/SHA256SUMS.asc" SHA256SUMS.asc 2>/dev/null; then | 91 | if download "$base_url/SHA256SUMS.asc" SHA256SUMS.asc 2>/dev/null; then |
| 89 | if ! command -v gpg >/dev/null 2>&1; then | 92 | need gpg |
| 90 | echo "SHA256SUMS.asc exists, but gpg is not installed" >&2 | 93 | # Verify in a throwaway keyring holding ONLY the pinned key, and require that |
| 94 | # the signature validates against it with the pinned fingerprint. This is the | ||
| 95 | # difference between "signed" and "signed by Bitflash". | ||
| 96 | keyring="$workdir/keyring" | ||
| 97 | mkdir -p "$keyring"; chmod 700 "$keyring" | ||
| 98 | printf '%s\n' "$RELEASE_KEY" | GNUPGHOME="$keyring" gpg --batch --quiet --import 2>/dev/null | ||
| 99 | status="$(GNUPGHOME="$keyring" gpg --batch --status-fd=1 --verify SHA256SUMS.asc SHA256SUMS 2>/dev/null || true)" | ||
| 100 | if printf '%s' "$status" | grep -q "VALIDSIG.*$RELEASE_KEY_FP"; then | ||
| 101 | echo "Signature OK -- signed by the pinned Bitflash release key" | ||
| 102 | else | ||
| 103 | echo "SIGNATURE REJECTED: not a valid signature by the pinned release key" >&2 | ||
| 104 | echo " expected key: $RELEASE_KEY_FP" >&2 | ||
| 91 | exit 1 | 105 | exit 1 |
| 92 | fi | 106 | fi |
| 93 | gpg --verify SHA256SUMS.asc SHA256SUMS | ||
| 94 | echo "Signature OK" | ||
| 95 | else | 107 | else |
| 96 | if [ "$require_signature" -eq 1 ]; then | 108 | if [ "$require_signature" -eq 1 ]; then |
| 97 | echo "release does not publish SHA256SUMS.asc" >&2 | 109 | echo "release does not publish SHA256SUMS.asc" >&2; exit 1 |
| 98 | exit 1 | ||
| 99 | fi | 110 | fi |
| 100 | echo "WARNING: no SHA256SUMS.asc found; checking hashes only" | 111 | echo "WARNING: no SHA256SUMS.asc found; checking hashes only (unsigned release)" |
| 101 | fi | 112 | fi |
| 102 | 113 | ||
| 103 | awk '{print $2}' SHA256SUMS | while IFS= read -r file; do | 114 | awk '{print $2}' SHA256SUMS | while IFS= read -r file; do |
| 104 | file="${file#\*}" | 115 | file="${file#\*}"; file="${file#./}" |
| 105 | file="${file#./}" | ||
| 106 | [ -z "$file" ] && continue | 116 | [ -z "$file" ] && continue |
| 107 | case "$file" in | 117 | case "$file" in */*|..*) echo "refusing unexpected checksum path: $file" >&2; exit 1 ;; esac |
| 108 | */*|..*) echo "refusing unexpected checksum path: $file" >&2; exit 1 ;; | ||
| 109 | esac | ||
| 110 | echo "Downloading $file" | 118 | echo "Downloading $file" |
| 111 | download "$base_url/$file" "$file" | 119 | download "$base_url/$file" "$file" |
| 112 | done | 120 | done |