Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Bitflash-*-windows.zip
Bitflash-*.AppImage
bitflash-node-*-x86_64
SHA256SUMS
SHA256SUMS.asc

# Runtime data / logs
debug.log
Expand Down
14 changes: 13 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
# make linux build Bitflash-*-x86_64.AppImage
# make windows build Bitflash-*-windows.zip (from MSYS2 UCRT64)
# make tests build and run standalone unit tests
# make checksums create SHA256SUMS for release assets
# make verify-release TAG=v1.2.13
# make clean remove build artifacts

ROOT := $(shell pwd)
Expand Down Expand Up @@ -160,5 +162,15 @@ clean:
tests:
$(MAKE) -C src -f Makefile tests

checksums:
./scripts/make-release-checksums.sh

sign-checksums:
./scripts/make-release-checksums.sh --sign $(if $(KEY),--local-user $(KEY),)

verify-release:
./scripts/verify-release.sh $(if $(TAG),$(TAG),latest)

.PHONY: linux windows clean appimage \
tests deps-linux deps-windows deps-apt deps-secp256k1 deps-randomx
tests checksums sign-checksums verify-release \
deps-linux deps-windows deps-apt deps-secp256k1 deps-randomx
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,30 @@ configuration — it connects automatically and starts syncing.

**Windows:** extract the `-windows.zip` and run `Bitflash.exe`.

Every release ships a `SHA256SUMS` covering both assets. Verifying takes a second
Every release ships a `SHA256SUMS` covering the assets. Verifying takes a second
and is worth doing:

```bash
sha256sum -c SHA256SUMS
```

For newer signed releases, verify the checksum file itself first:

```bash
gpg --verify SHA256SUMS.asc SHA256SUMS
sha256sum -c SHA256SUMS
```

The helper below downloads the release assets, verifies `SHA256SUMS.asc` when it
is present, then checks the hashes:

```bash
scripts/verify-release.sh latest
```

See [release verification](docs/release-verification.md) for the full release
audit flow and the maintainer signing step.

**Keep your node current.** Consensus rules have changed since the first
releases — 1.2.1 fixed a bug that let anyone spend anyone's coins, and 1.2.2
added a per-block signature-operation cap. A node on an older build will accept
Expand Down
62 changes: 62 additions & 0 deletions docs/release-verification.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Release Verification

Bitflash releases publish binaries and a `SHA256SUMS` file. The checksum file
proves that the file you downloaded matches the file the release page names.
A detached signature, `SHA256SUMS.asc`, proves that the checksum file itself was
signed by a trusted release key.

That gives users two separate checks:

1. `gpg --verify SHA256SUMS.asc SHA256SUMS` checks who signed the checksums.
2. `sha256sum -c SHA256SUMS` checks the binaries against those checksums.

## User Check

From a shell with `curl` or `wget` and `sha256sum`:

```bash
scripts/verify-release.sh latest
```

For a specific release:

```bash
scripts/verify-release.sh v1.2.13
```

For release audits, require the signature:

```bash
scripts/verify-release.sh v1.2.13 --require-signature
```

Older releases may not have `SHA256SUMS.asc`. In that case the script warns and
still checks file integrity. New release audits should use `--require-signature`.

## Maintainer Flow

After building release assets in the repository root:

```bash
scripts/make-release-checksums.sh --sign --local-user RELEASE_KEY_ID
```

Upload all built assets plus:

```text
SHA256SUMS
SHA256SUMS.asc
```

Keep the private signing key offline or on a dedicated release machine. Publish
the public key fingerprint in the release notes and keep using the same key for
future releases unless there is a clearly announced rotation.

## Why This Matters

`SHA256SUMS` alone protects against a broken download, but not against someone
replacing both a binary and the checksum file. Signing `SHA256SUMS` means an
attacker must also have the release signing key to make the replacement verify.

This is not reproducible builds yet. It is the smaller, immediate step that
makes every release asset auditable by users before they run it.
78 changes: 78 additions & 0 deletions scripts/make-release-checksums.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#!/usr/bin/env bash
set -euo pipefail

sign=0
key=""

usage() {
cat <<'EOF'
Usage: scripts/make-release-checksums.sh [--sign] [--local-user KEYID]

Creates SHA256SUMS for release assets in the current directory:
Bitflash-*-windows.zip
Bitflash-*-x86_64.AppImage
bitflash-node-*-x86_64

With --sign, also creates SHA256SUMS.asc as a detached ASCII-armored GPG
signature. Upload both files with the release assets.
EOF
}

while [ "$#" -gt 0 ]; do
case "$1" in
--sign) sign=1 ;;
--local-user)
[ "$#" -ge 2 ] || { echo "--local-user needs a key id" >&2; exit 2; }
key="$2"
shift
;;
-h|--help) usage; exit 0 ;;
*) echo "unknown option: $1" >&2; usage >&2; exit 2 ;;
esac
shift
done

if command -v sha256sum >/dev/null 2>&1; then
sha_cmd=(sha256sum)
elif command -v shasum >/dev/null 2>&1; then
sha_cmd=(shasum -a 256)
else
echo "missing required command: sha256sum or shasum" >&2
exit 1
fi

assets=()
for pattern in \
"Bitflash-*-windows.zip" \
"Bitflash-*-x86_64.AppImage" \
"bitflash-node-*-x86_64"
do
for file in $pattern; do
[ -e "$file" ] || continue
assets+=("$file")
done
done

if [ "${#assets[@]}" -eq 0 ]; then
echo "no release assets found in $(pwd)" >&2
exit 1
fi

printf '%s\n' "${assets[@]}" | LC_ALL=C sort | while IFS= read -r file; do
"${sha_cmd[@]}" "$file"
done > SHA256SUMS

echo "Wrote SHA256SUMS"

if [ "$sign" -eq 1 ]; then
if ! command -v gpg >/dev/null 2>&1; then
echo "missing required command: gpg" >&2
exit 1
fi
gpg_args=(--armor --detach-sign --output SHA256SUMS.asc)
if [ -n "$key" ]; then
gpg_args+=(--local-user "$key")
fi
gpg "${gpg_args[@]}" SHA256SUMS
echo "Wrote SHA256SUMS.asc"
fi
115 changes: 115 additions & 0 deletions scripts/verify-release.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#!/usr/bin/env bash
set -euo pipefail

repo="Bitflash-sh/bitflash"
tag="${1:-latest}"
require_signature=0

usage() {
cat <<'EOF'
Usage: scripts/verify-release.sh [tag|latest] [--require-signature]

Downloads the release checksum file and every asset named in it, then verifies:
1. SHA256SUMS.asc, if present, against SHA256SUMS with gpg.
2. SHA256SUMS against the downloaded assets.

Use --require-signature for release audits. Without it, older releases that
ship only SHA256SUMS are checked for integrity and reported as unsigned.
EOF
}

for arg in "${@:2}"; do
case "$arg" in
--require-signature) require_signature=1 ;;
-h|--help) usage; exit 0 ;;
*) echo "unknown option: $arg" >&2; usage >&2; exit 2 ;;
esac
done

if [ "$tag" = "-h" ] || [ "$tag" = "--help" ]; then
usage
exit 0
fi

need() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "missing required command: $1" >&2
exit 1
fi
}

need sed
need awk

if command -v sha256sum >/dev/null 2>&1; then
sha_check=(sha256sum -c SHA256SUMS)
elif command -v shasum >/dev/null 2>&1; then
sha_check=(shasum -a 256 -c SHA256SUMS)
else
echo "missing required command: sha256sum or shasum" >&2
exit 1
fi

download() {
local url="$1"
local out="$2"
if command -v curl >/dev/null 2>&1; then
curl -fsSL "$url" -o "$out"
elif command -v wget >/dev/null 2>&1; then
wget -q "$url" -O "$out"
else
echo "missing required command: curl or wget" >&2
exit 1
fi
}

if [ "$tag" = "latest" ]; then
tag="$(download "https://api.github.com/repos/$repo/releases/latest" - |
sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' |
head -1)"
if [ -z "$tag" ]; then
echo "could not resolve latest release tag" >&2
exit 1
fi
fi

workdir="$(mktemp -d "${TMPDIR:-/tmp}/bitflash-release-verify.XXXXXX")"
cleanup() { rm -rf "$workdir"; }
trap cleanup EXIT

base_url="https://github.com/$repo/releases/download/$tag"

echo "Verifying Bitflash $tag"
echo "Working directory: $workdir"
cd "$workdir"

download "$base_url/SHA256SUMS" SHA256SUMS

if download "$base_url/SHA256SUMS.asc" SHA256SUMS.asc 2>/dev/null; then
if ! command -v gpg >/dev/null 2>&1; then
echo "SHA256SUMS.asc exists, but gpg is not installed" >&2
exit 1
fi
gpg --verify SHA256SUMS.asc SHA256SUMS
echo "Signature OK"
else
if [ "$require_signature" -eq 1 ]; then
echo "release does not publish SHA256SUMS.asc" >&2
exit 1
fi
echo "WARNING: no SHA256SUMS.asc found; checking hashes only"
fi

awk '{print $2}' SHA256SUMS | while IFS= read -r file; do
file="${file#\*}"
file="${file#./}"
[ -z "$file" ] && continue
case "$file" in
*/*|..*) echo "refusing unexpected checksum path: $file" >&2; exit 1 ;;
esac
echo "Downloading $file"
download "$base_url/$file" "$file"
done

"${sha_check[@]}"
echo "Release assets OK"