From f6c5b813075cae4228d241a3567e70b210504749 Mon Sep 17 00:00:00 2001 From: mtg0001 <106211915+mtg0001@users.noreply.github.com> Date: Tue, 4 Aug 2026 10:18:01 -0300 Subject: Add release verification tooling (#108) Co-authored-by: Gilcimar Duarte --- docs/release-verification.md | 62 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 docs/release-verification.md (limited to 'docs/release-verification.md') diff --git a/docs/release-verification.md b/docs/release-verification.md new file mode 100644 index 0000000..ecacd5f --- /dev/null +++ b/docs/release-verification.md @@ -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. -- cgit v1.2.3 From c3f6b68f907b03865d2c417ee39fe9526787196f Mon Sep 17 00:00:00 2001 From: Bitflash Date: Thu, 6 Aug 2026 10:46:26 -0300 Subject: release: sign SHA256SUMS with a pinned key, verify against it verify-release.sh embeds the Bitflash release signing key and now requires that the checksum signature validate against that exact key, not merely against some key in the caller's keyring (#138). It also fetches releases from releases.bitflash.network instead of the retired GitHub host, resolving 'latest' from latest.txt. Release signing key fingerprint: 910A 2B4C CA87 9E81 FB4B 2AEA 1D4A 53D3 B78A A4B8 --- docs/release-verification.md | 78 ++++++++++++++++++++----------- scripts/verify-release.sh | 108 +++++++++++++++++++++++-------------------- 2 files changed, 108 insertions(+), 78 deletions(-) (limited to 'docs/release-verification.md') diff --git a/docs/release-verification.md b/docs/release-verification.md index ecacd5f..32de74f 100644 --- a/docs/release-verification.md +++ b/docs/release-verification.md @@ -3,60 +3,82 @@ 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. +signed by the Bitflash 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. +1. `gpg --verify SHA256SUMS.asc SHA256SUMS` — who signed the checksums. +2. `sha256sum -c SHA256SUMS` — the binaries against those checksums. -## User Check +But the first check is only worth something if you require a **specific** key. +A good signature by *some* key proves nothing; anyone can make one. -From a shell with `curl` or `wget` and `sha256sum`: +## The release signing key -```bash -scripts/verify-release.sh latest +```text +fingerprint 910A 2B4C CA87 9E81 FB4B 2AEA 1D4A 53D3 B78A A4B8 +uid Bitflash Releases (bitflash.network release signing) + ``` -For a specific release: +The public key is published at +`https://releases.bitflash.network/bitflash-release-key.asc` and embedded in +`scripts/verify-release.sh`, so the tool's trust anchor lives in this +repository rather than on the server it verifies. + +## User check + +From a shell with `curl` or `wget`, `sha256sum` and `gpg`: ```bash -scripts/verify-release.sh v1.2.13 +scripts/verify-release.sh latest ``` -For release audits, require the signature: +For a specific release: ```bash -scripts/verify-release.sh v1.2.13 --require-signature +scripts/verify-release.sh v1.2.15 ``` -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 +The script downloads the release, imports the pinned key into a throwaway +keyring, and **refuses anything that is not a valid signature by the key above** +before checking the hashes. Releases from before signing was introduced ship +only `SHA256SUMS`; without `--require-signature` those are checked for integrity +and reported as unsigned. -After building release assets in the repository root: +By hand, if you would rather not run the script: ```bash -scripts/make-release-checksums.sh --sign --local-user RELEASE_KEY_ID +curl -O https://releases.bitflash.network/bitflash-release-key.asc +gpg --import bitflash-release-key.asc +gpg --verify SHA256SUMS.asc SHA256SUMS # must name the fingerprint above +sha256sum -c SHA256SUMS ``` -Upload all built assets plus: +## Maintainer flow -```text -SHA256SUMS -SHA256SUMS.asc +The signing key lives on a dedicated location, not in the build tree. After +building the release assets in the repository root, write and sign the +checksums: + +```bash +sha256sum Bitflash-*.zip Bitflash-*.AppImage bitflash-node-* > SHA256SUMS +gpg --armor --detach-sign --output SHA256SUMS.asc SHA256SUMS ``` -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. +Publish, alongside the assets, `SHA256SUMS` and `SHA256SUMS.asc`, and update +`latest.txt` on the releases host to the new tag. Keep using the same key; if it +is ever rotated, announce the new fingerprint clearly and update the embedded +key in `verify-release.sh`. -## Why This Matters +## 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. +replacing both a binary and the checksum file. Signing `SHA256SUMS`, and +requiring a pinned key, means an attacker must also hold the release signing key +to make the replacement verify. Serving from bitflash.network makes this the +guarantee that matters: without a pinned key, "trust the site" is the only +thing standing behind a download. This is not reproducible builds yet. It is the smaller, immediate step that -makes every release asset auditable by users before they run it. +makes every release asset auditable before anyone runs it. diff --git a/scripts/verify-release.sh b/scripts/verify-release.sh index 23e71d1..4fd4558 100755 --- a/scripts/verify-release.sh +++ b/scripts/verify-release.sh @@ -1,23 +1,50 @@ #!/usr/bin/env bash set -euo pipefail -repo="Bitflash-sh/bitflash" -tag="${1:-latest}" +# Verifies a Bitflash release: the checksum file's signature against a pinned +# key, then the assets against the checksums. +# +# The signing key is embedded below, not fetched. That is the whole point of a +# pinned key: the trust anchor ships with this script (in git), so it does not +# depend on the same server that serves the release. A server that is +# compromised can swap the binaries and the checksums, but it cannot forge a +# signature by this key. + +host="https://releases.bitflash.network" require_signature=0 +# The Bitflash release signing key. Verification requires this exact key -- +# gpg's own "good signature" is not enough, because a good signature by *some* +# key proves nothing. See issue #138. +RELEASE_KEY_FP="910A2B4CCA879E81FB4B2AEA1D4A53D3B78AA4B8" +read -r -d '' RELEASE_KEY <<'KEY' || true +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mDMEanSOmRYJKwYBBAHaRw8BAQdAXj24i2BYUcAmOpoXwjE4ZTynHsifCZ+niwQK +HqO0nMa0UEJpdGZsYXNoIFJlbGVhc2VzIChiaXRmbGFzaC5uZXR3b3JrIHJlbGVh +c2Ugc2lnbmluZykgPHJlbGVhc2VzQGJpdGZsYXNoLm5ldHdvcms+iJAEExYKADgW +IQSRCitMyoeegftLKuodSlPTt4qkuAUCanSOmQIbAwULCQgHAgYVCgkICwIEFgID +AQIeAQIXgAAKCRAdSlPTt4qkuIi9AP96X3OcstiCTTgKOr16Vhds3LyuZ19q3Apq +4KAcNuOUMAD+NqgJ1CqkLGPXfZLCtYBCRHXNYWxMMEk+ViLcRtdLMg4= +=mxic +-----END PGP PUBLIC KEY BLOCK----- +KEY + 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. + 1. SHA256SUMS.asc against SHA256SUMS, requiring the pinned Bitflash release + key (fingerprint 910A 2B4C CA87 9E81 FB4B 2AEA 1D4A 53D3 B78A A4B8). 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. +Releases from before signing was introduced ship only SHA256SUMS; without +--require-signature those are checked for integrity and reported as unsigned. EOF } +tag="${1:-latest}" for arg in "${@:2}"; do case "$arg" in --require-signature) require_signature=1 ;; @@ -25,19 +52,9 @@ for arg in "${@:2}"; do *) echo "unknown option: $arg" >&2; usage >&2; exit 2 ;; esac done +if [ "$tag" = "-h" ] || [ "$tag" = "--help" ]; then usage; exit 0; fi -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() { command -v "$1" >/dev/null 2>&1 || { echo "missing required command: $1" >&2; exit 1; }; } need sed need awk @@ -46,39 +63,25 @@ if command -v sha256sum >/dev/null 2>&1; then 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 + 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 command -v curl >/dev/null 2>&1; then curl -fsSL "$1" -o "$2" + elif command -v wget >/dev/null 2>&1; then wget -q "$1" -O "$2" + 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 + tag="$(download "$host/latest.txt" - 2>/dev/null | tr -d '[:space:]' || true)" + [ -n "$tag" ] || { echo "could not resolve the latest release tag from $host/latest.txt" >&2; exit 1; } 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" - +base_url="$host/$tag" echo "Verifying Bitflash $tag" echo "Working directory: $workdir" cd "$workdir" @@ -86,27 +89,32 @@ 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 + need gpg + # Verify in a throwaway keyring holding ONLY the pinned key, and require that + # the signature validates against it with the pinned fingerprint. This is the + # difference between "signed" and "signed by Bitflash". + keyring="$workdir/keyring" + mkdir -p "$keyring"; chmod 700 "$keyring" + printf '%s\n' "$RELEASE_KEY" | GNUPGHOME="$keyring" gpg --batch --quiet --import 2>/dev/null + status="$(GNUPGHOME="$keyring" gpg --batch --status-fd=1 --verify SHA256SUMS.asc SHA256SUMS 2>/dev/null || true)" + if printf '%s' "$status" | grep -q "VALIDSIG.*$RELEASE_KEY_FP"; then + echo "Signature OK -- signed by the pinned Bitflash release key" + else + echo "SIGNATURE REJECTED: not a valid signature by the pinned release key" >&2 + echo " expected key: $RELEASE_KEY_FP" >&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 + echo "release does not publish SHA256SUMS.asc" >&2; exit 1 fi - echo "WARNING: no SHA256SUMS.asc found; checking hashes only" + echo "WARNING: no SHA256SUMS.asc found; checking hashes only (unsigned release)" fi awk '{print $2}' SHA256SUMS | while IFS= read -r file; do - file="${file#\*}" - file="${file#./}" + file="${file#\*}"; file="${file#./}" [ -z "$file" ] && continue - case "$file" in - */*|..*) echo "refusing unexpected checksum path: $file" >&2; exit 1 ;; - esac + case "$file" in */*|..*) echo "refusing unexpected checksum path: $file" >&2; exit 1 ;; esac echo "Downloading $file" download "$base_url/$file" "$file" done -- cgit v1.2.3 From b0ed2ce585d2a6fd7c6d8d4db7422734aedccb58 Mon Sep 17 00:00:00 2001 From: Nsable-sh Date: Wed, 12 Aug 2026 11:58:03 -0300 Subject: site: document public deployment contract --- README.md | 17 +++++ docs/mining-calculator.md | 22 ++++++ docs/pool-directory.md | 18 +++++ docs/release-verification.md | 10 +++ docs/site.md | 115 +++++++++++++++++++++++++++++++ docs/tor.md | 29 ++++++++ scripts/build-explorer.py | 128 +++++++++++++++++++++++------------ tests/scripts/test_bitflash_tools.py | 7 +- 8 files changed, 300 insertions(+), 46 deletions(-) create mode 100644 docs/site.md (limited to 'docs/release-verification.md') diff --git a/README.md b/README.md index ef4f39b..e671302 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,8 @@ Worth being precise, because the difference matters if you are relying on it. This is unlinkability between peers, not anonymity against a network observer. For Tor routing, start the node with `-tor`; see [Tor mode](docs/tor.md). +For a plain SOCKS5 proxy without Tor-specific defaults, use +`-socks=HOST:PORT`. --- @@ -74,6 +76,14 @@ scripts/verify-release.sh latest See [release verification](docs/release-verification.md) for the full release audit flow and the maintainer signing step. +Public mirrors should make the latest checksum files available both under the +versioned release directory and at the release root: + +```text +https://releases.bitflash.network/v1.2.18/SHA256SUMS +https://releases.bitflash.network/SHA256SUMS # alias to latest +``` + Bitflash also ships a deterministic UTXO-set commitment tool: ```bash @@ -113,6 +123,12 @@ python3 scripts/build-explorer.py --datadir ~/.bitflash ./explorer-out python3 scripts/build-explorer.py ~/.bitflash/blk0001.dat ~/.bitflash/blk0002.dat ./explorer-out ``` +The generated explorer is static and self-contained: `index.html`, +`style.css`, `explorer.js`, `blocks.json`, `block/*.json`, and `logo.png`. It is +safe to host behind a strict policy such as `default-src 'none'; img-src 'self'; +style-src 'self'; script-src 'self'; connect-src 'self'; base-uri 'none'; +form-action 'none'`. + **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 @@ -265,6 +281,7 @@ default both are created in the data directory; use `-poolstatusfile=PATH` and `-poolroundsfile=PATH` to write them somewhere a dashboard or web sync can read. The files are informational only: nodes still discover pools through Nostr and `.btf`, not through any web domain. +See [public pool directory](docs/pool-directory.md) for the website/API shape. **Participant** — mine to someone else's pool. Enter or select the pool's `.btf` address and enable Start Mining. diff --git a/docs/mining-calculator.md b/docs/mining-calculator.md index 352eea1..e6cb61b 100644 --- a/docs/mining-calculator.md +++ b/docs/mining-calculator.md @@ -84,3 +84,25 @@ The script is intentionally standalone so the same math can later back a website widget or status dashboard. Until a public price exists, a website calculator should keep price as a manual field and label fiat profit as speculative. + +## Website Fields + +A public calculator should ask for: + +- miner hashrate and unit; +- power draw in watts; +- electricity price per kWh; +- pool fee percent; +- optional BTF price; +- either current Bitflash difficulty or current network hashrate. + +The output should separate: + +- estimated BTF per day; +- estimated gross fiat revenue, if a manual price was provided; +- electricity cost; +- net profit/loss; +- the difficulty/hashrate assumptions used. + +Never present the fiat number as a market quote unless it comes from a real +public market. Before that exists, label it as a manual scenario. diff --git a/docs/pool-directory.md b/docs/pool-directory.md index 76269b9..ff0640a 100644 --- a/docs/pool-directory.md +++ b/docs/pool-directory.md @@ -26,6 +26,10 @@ The generated output contains: - `index.html`: human-readable ranking page. - `pools.json`: machine-readable API for `api.bitflash.network/pools.json`. +The website is a convenience layer, not a consensus or discovery dependency. +Nodes discover pools through Nostr descriptors and `.btf`; a missing website +must not stop mining or pool operation. + When a source is an HTTP(S) `pool_status.json`, the directory links to a `pool_rounds.json` proof file next to it. That file records the block height, block hash, coinbase txid, coinbase value, pool fee, total shares, per-address @@ -41,3 +45,17 @@ Ranking order is intentionally simple: An entry is considered online when the source says the pool is running and its status file is no more than 120 seconds old. + +## Recommended Hostnames + +Use: + +```text +pool.bitflash.network human-readable pool directory +api.bitflash.network/pools.json +status.bitflash.network relay/node health, not pool ranking +docs.bitflash.network operator setup guide +``` + +The GUI may use the API as a shortcut for humans, but it should keep the +existing decentralized pool discovery path as the source of truth. diff --git a/docs/release-verification.md b/docs/release-verification.md index 32de74f..408bab9 100644 --- a/docs/release-verification.md +++ b/docs/release-verification.md @@ -34,6 +34,16 @@ From a shell with `curl` or `wget`, `sha256sum` and `gpg`: scripts/verify-release.sh latest ``` +The public release host should expose latest-file aliases for convenience: + +```text +https://releases.bitflash.network/SHA256SUMS +https://releases.bitflash.network/SHA256SUMS.asc +``` + +Those aliases must point at the current versioned directory. They do not replace +signature verification; they only make the common manual path shorter. + For a specific release: ```bash diff --git a/docs/site.md b/docs/site.md new file mode 100644 index 0000000..522da10 --- /dev/null +++ b/docs/site.md @@ -0,0 +1,115 @@ +# Public Site Contract + +The public Bitflash sites are generated from this repository. If a deployed +page and the source disagree, the deployed page is stale. + +## Canonical Navigation + +Every public page should use the same top navigation: + +| label | target | +|---|---| +| home | `https://bitflash.network/` | +| code | `https://git.bitflash.network/bitflash/bitflash` | +| docs | `https://docs.bitflash.network/` | +| downloads | `https://releases.bitflash.network/` | +| explorer | `https://explorer.bitflash.network/` | +| status | `https://status.bitflash.network/` | + +The `code` link intentionally points at the repository, not the Forgejo +instance root. A visitor choosing "code" wants the source tree. + +## Documentation Pages To Publish + +The docs index should expose these source files: + +| page | source | why it matters | +|---|---|---| +| Manual | `README.md` | run, mine, recover, operate | +| Key derivation and address format | `docs/derivation.md` | independent wallet recovery | +| Release verification | `docs/release-verification.md` | pinned signing-key flow | +| Dependencies | `docs/dependencies.md` | pinned secp256k1/RandomX supply chain | +| Rendezvous and .btf addressing | `docs/rendezvous.md` | network design and caveats | +| Tor mode | `docs/tor.md` | `-tor`, `-socks`, onion relay operation | +| Fair launch verifier | `docs/fair-launch.md` | no-premine launch audit | +| UTXO set commitment | `docs/utxo-commitment.md` | deterministic supply/root audit | +| UTXO inclusion proofs | `docs/utxo-proofs.md` | proof for one unspent output | +| Mining calculator | `docs/mining-calculator.md` | local WhatToMine-style estimates | +| Public pool directory | `docs/pool-directory.md` | `pool_status.json`, `pools.json`, ranking | + +## Release Mirror Aliases + +Each release remains under its versioned directory: + +```text +https://releases.bitflash.network/v1.2.18/SHA256SUMS +https://releases.bitflash.network/v1.2.18/SHA256SUMS.asc +``` + +The release root should also alias the latest files: + +```text +https://releases.bitflash.network/SHA256SUMS +https://releases.bitflash.network/SHA256SUMS.asc +https://releases.bitflash.network/Bitflash-latest-windows.zip +https://releases.bitflash.network/Bitflash-latest-x86_64.AppImage +https://releases.bitflash.network/bitflash-node-latest-x86_64 +``` + +The aliases are convenience only. Verification must still require the pinned +release key documented in `docs/release-verification.md`. + +## Static Explorer + +`scripts/build-explorer.py` writes a static explorer with no backend: + +```text +index.html +style.css +explorer.js +logo.png +blocks.json +block/.json +``` + +Serve it with a strict content policy: + +```text +default-src 'none'; +img-src 'self'; +style-src 'self'; +script-src 'self'; +connect-src 'self'; +base-uri 'none'; +form-action 'none'; +frame-ancestors 'none'; +``` + +The viewer must never use `innerHTML` for chain data. A malicious transaction +must be rendered as text, not interpreted as markup. + +## Cache Policy + +Recommended defaults: + +| path | cache | +|---|---| +| HTML pages | `public, max-age=0, must-revalidate` | +| `status.json` | `no-store` | +| `blocks.json`, `block/*.json` | `public, max-age=0, must-revalidate` | +| release assets | `public, max-age=14400, must-revalidate` | +| `SHA256SUMS`, signatures, keys | `public, max-age=0, must-revalidate` | + +## Security Headers + +Recommended for all static pages: + +```text +Strict-Transport-Security: max-age=31536000; includeSubDomains +X-Content-Type-Options: nosniff +Referrer-Policy: no-referrer +Permissions-Policy: interest-cohort=(), geolocation=(), camera=(), microphone=() +``` + +Forgejo has its own application headers and should not blindly inherit the +static-site CSP. diff --git a/docs/tor.md b/docs/tor.md index 087c822..69b29f1 100644 --- a/docs/tor.md +++ b/docs/tor.md @@ -25,6 +25,17 @@ bitflash -tor=127.0.0.1:9150 bitflash -tor=[::1]:9050 ``` +For a SOCKS5 proxy that is not Tor, use `-socks` directly: + +```bash +bitflash -socks=127.0.0.1:9050 +bitflash -socks=[::1]:9050 +``` + +If both `-tor` and `-socks` are present, Tor mode wins and the node prints a +warning. That keeps a command line with `-tor` from silently becoming a custom +proxy route. + When Tor mode is enabled: - outbound Nostr relay dials use Tor; @@ -34,6 +45,24 @@ When Tor mode is enabled: - direct `.onion` dials are refused unless a SOCKS5/Tor proxy is enabled; - plain HTTP external-IP probes are skipped. +Tor mode affects outbound discovery and relay dials. It does not encrypt +`wallet.dat`, hide mining rewards on-chain, or change the `.btf` identity. A +relay still sees the TCP client that connected to it; with Tor mode that client +is the Tor exit or onion circuit endpoint instead of the user's direct IP. + +## What to test + +Use the public status page only as a rough health check. To prove the local node +is using Tor/SOCKS, test from the node machine: + +```bash +bitflash -tor -debug +``` + +Then confirm in `debug.log` that Nostr and rendezvous dials report the proxy +path. A DNS leak test should show no local resolver lookup for relay hostnames: +the SOCKS5 request uses the domain-name form and asks the proxy to resolve it. + ## Onion rendezvous relay An operator can expose a Bitflash rendezvous relay as a Tor hidden service. The diff --git a/scripts/build-explorer.py b/scripts/build-explorer.py index 97a7f4e..dbb8e17 100755 --- a/scripts/build-explorer.py +++ b/scripts/build-explorer.py @@ -4,6 +4,7 @@ import argparse import hashlib import json +import shutil import sys from pathlib import Path @@ -148,6 +149,11 @@ def build_explorer(raw_blocks, out_dir): encoding="ascii", ) (out / "index.html").write_text(INDEX_HTML, encoding="utf-8") + (out / "style.css").write_text(STYLE_CSS, encoding="utf-8") + (out / "explorer.js").write_text(EXPLORER_JS, encoding="utf-8") + logo = Path(__file__).resolve().parents[1] / "docs" / "logo.png" + if logo.exists(): + shutil.copyfile(logo, out / "logo.png") return len(blocks), len(blocks) - 1 @@ -167,43 +173,26 @@ INDEX_HTML = r""" Bitflash Block Explorer - + + + + -
-

Bitflash Block Explorer

- - - +
+ Bitflash + +
+

Block explorer

Read-only view of the main chain: blocks, their transactions, and the coinbase payout. There is no mempool and no per-address history here.

@@ -213,9 +202,63 @@ INDEX_HTML = r"""

- + - - - """ diff --git a/tests/scripts/test_bitflash_tools.py b/tests/scripts/test_bitflash_tools.py index 4b18d1e..3b4c846 100644 --- a/tests/scripts/test_bitflash_tools.py +++ b/tests/scripts/test_bitflash_tools.py @@ -159,8 +159,11 @@ def compare_golden(name, actual, update): path.parent.mkdir(parents=True, exist_ok=True) path.write_bytes(actual) return - expected = path.read_bytes() - if actual != expected: + # The tools promise canonical LF-terminated JSON. On Windows checkouts a + # fixture may still appear as CRLF in the working tree; normalize only line + # endings so content, field order and compact serialization still drift-test. + expected = path.read_bytes().replace(b"\r\n", b"\n") + if actual.replace(b"\r\n", b"\n") != expected: raise AssertionError("golden mismatch: %s" % name) -- cgit v1.2.3 From 44fbf6895bd3acd11f552514e746a2e3876cad2d Mon Sep 17 00:00:00 2001 From: Nsable-sh Date: Thu, 13 Aug 2026 14:29:14 -0300 Subject: release: package Windows builds with managed Tor --- Makefile | 6 +- README.md | 13 ++++ docs/release-verification.md | 6 ++ docs/tor.md | 16 +++++ scripts/make-release-checksums.sh | 2 + scripts/package-windows-tor.sh | 132 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 174 insertions(+), 1 deletion(-) create mode 100755 scripts/package-windows-tor.sh (limited to 'docs/release-verification.md') diff --git a/Makefile b/Makefile index b4d0cf7..d1277a4 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,7 @@ # # make linux build Bitflash-*-x86_64.AppImage # make windows build Bitflash-*-windows.zip (from MSYS2 UCRT64) +# make windows-tor build Bitflash-*-windows-with-tor.zip # make tests build and run standalone unit tests # make checksums create SHA256SUMS for release assets # make verify-release TAG=v1.2.13 @@ -180,6 +181,9 @@ windows: deps-windows @zip -r Bitflash-$(VERSION)-windows.zip Bitflash-$(VERSION)-windows/ @echo "Built: Bitflash-$(VERSION)-windows.zip" +windows-tor: windows + ./scripts/package-windows-tor.sh + # ---- Clean ---------------------------------------------------------------- clean: @@ -206,6 +210,6 @@ sign-checksums: verify-release: ./scripts/verify-release.sh $(if $(TAG),$(TAG),latest) -.PHONY: linux windows clean appimage \ +.PHONY: linux windows windows-tor clean appimage \ tests fuzz-net-message-smoke fuzz-script-smoke checksums sign-checksums verify-release \ deps-linux deps-windows deps-apt deps-secp256k1 deps-randomx diff --git a/README.md b/README.md index a7ce2b2..c30f171 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,8 @@ configuration — it connects automatically and starts syncing. **Linux:** make the `.AppImage` executable and run it. **Windows:** extract the `-windows.zip` and run `Bitflash.exe`. +If you want managed Tor without a separate Tor install, use the +`-windows-with-tor.zip` package and start Bitflash with `-managedtor`. Every release ships a `SHA256SUMS` covering the assets. Verifying takes a second and is worth doing: @@ -452,6 +454,17 @@ make windows ``` Installs deps via pacman, produces `Bitflash-*-windows.zip`. +To build the Windows package that also carries the Tor Expert Bundle: + +```bash +make windows-tor +``` + +That produces `Bitflash-*-windows-with-tor.zip`. The release script downloads +the Tor Expert Bundle from the official Tor archive, verifies its pinned SHA256, +and places `tor/tor.exe` beside `Bitflash.exe` so `-managedtor` works without a +separate Tor install. + --- ## At a glance diff --git a/docs/release-verification.md b/docs/release-verification.md index 408bab9..04d18a3 100644 --- a/docs/release-verification.md +++ b/docs/release-verification.md @@ -81,6 +81,12 @@ Publish, alongside the assets, `SHA256SUMS` and `SHA256SUMS.asc`, and update is ever rotated, announce the new fingerprint clearly and update the embedded key in `verify-release.sh`. +For Windows releases with managed Tor built in, run `make windows-tor`. It +creates `Bitflash-*-windows-with-tor.zip` by downloading the Tor Expert Bundle, +checking the pinned SHA256 in `scripts/package-windows-tor.sh`, and placing +`tor/tor.exe` next to `Bitflash.exe`. Include that zip in `SHA256SUMS` like any +other release asset. + ## Why this matters `SHA256SUMS` alone protects against a broken download, but not against someone diff --git a/docs/tor.md b/docs/tor.md index 1c0f589..be6a145 100644 --- a/docs/tor.md +++ b/docs/tor.md @@ -53,6 +53,22 @@ Without a value, Bitflash looks for Tor in this order: 2. `tor.exe` or `tor` beside the Bitflash binary; 3. `tor.exe` or `tor` on `PATH`. +Windows releases can also be built as `Bitflash-*-windows-with-tor.zip`. That +package includes the official Tor Expert Bundle under `tor/`, so the first path +above exists immediately after extraction and `bitflash -managedtor` needs no +separate Tor install. + +The bundled-Tor package is created by: + +```bash +make windows-tor +``` + +The packaging script verifies a pinned SHA256 for the downloaded Tor Expert +Bundle before copying it into the release directory. The Tor binary is bundled; +the runtime data directory, hidden-service key, and control cookie are still +created under the user's Bitflash data directory. + To point at a specific Tor executable: ```bash diff --git a/scripts/make-release-checksums.sh b/scripts/make-release-checksums.sh index 5b29e2d..9c4f45a 100755 --- a/scripts/make-release-checksums.sh +++ b/scripts/make-release-checksums.sh @@ -10,6 +10,7 @@ Usage: scripts/make-release-checksums.sh [--sign] [--local-user KEYID] Creates SHA256SUMS for release assets in the current directory: Bitflash-*-windows.zip + Bitflash-*-windows-with-tor.zip Bitflash-*-x86_64.AppImage bitflash-node-*-x86_64 @@ -44,6 +45,7 @@ fi assets=() for pattern in \ "Bitflash-*-windows.zip" \ + "Bitflash-*-windows-with-tor.zip" \ "Bitflash-*-x86_64.AppImage" \ "bitflash-node-*-x86_64" do diff --git a/scripts/package-windows-tor.sh b/scripts/package-windows-tor.sh new file mode 100755 index 0000000..084fd1a --- /dev/null +++ b/scripts/package-windows-tor.sh @@ -0,0 +1,132 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +VERSION="${VERSION:-$(sed -n 's/^VERSION[[:space:]]*:= *//p' "$ROOT/Makefile" | head -1)}" + +TOR_VERSION="${TOR_VERSION:-15.0.19}" +TOR_ARCHIVE_NAME="tor-expert-bundle-windows-x86_64-${TOR_VERSION}.tar.gz" +TOR_URL="${TOR_URL:-https://archive.torproject.org/tor-package-archive/torbrowser/${TOR_VERSION}/${TOR_ARCHIVE_NAME}}" +TOR_SHA256="${TOR_SHA256:-6AC067402C7B4A3DC37887ED3754B3914B67FDC220C966190683E9CCF91ABF0F}" + +BUNDLE_DIR="${BUNDLE_DIR:-$ROOT/Bitflash-${VERSION}-windows}" +OUT_ZIP="${OUT_ZIP:-$ROOT/Bitflash-${VERSION}-windows-with-tor.zip}" +CACHE_DIR="${CACHE_DIR:-$ROOT/.cache}" +ARCHIVE="${TOR_ARCHIVE:-$CACHE_DIR/$TOR_ARCHIVE_NAME}" + +usage() { + cat </dev/null 2>&1 || { + echo "missing required command: $1" >&2 + exit 1 + } +} + +need sha256sum +need tar +need zip + +if [ ! -d "$BUNDLE_DIR" ]; then + echo "missing Windows release directory: $BUNDLE_DIR" >&2 + echo "run 'make windows' first, or set BUNDLE_DIR" >&2 + exit 1 +fi +if [ ! -f "$BUNDLE_DIR/Bitflash.exe" ]; then + echo "missing Bitflash.exe in $BUNDLE_DIR" >&2 + exit 1 +fi +BUNDLE_PARENT="$(cd "$(dirname "$BUNDLE_DIR")" && pwd)" +BUNDLE_NAME="$(basename "$BUNDLE_DIR")" +OUT_PARENT="$(mkdir -p "$(dirname "$OUT_ZIP")" && cd "$(dirname "$OUT_ZIP")" && pwd)" +OUT_NAME="$(basename "$OUT_ZIP")" +OUT_ZIP_ABS="$OUT_PARENT/$OUT_NAME" + +mkdir -p "$CACHE_DIR" +if [ ! -f "$ARCHIVE" ]; then + if command -v curl >/dev/null 2>&1; then + curl -L --fail --output "$ARCHIVE" "$TOR_URL" + elif command -v wget >/dev/null 2>&1; then + wget -O "$ARCHIVE" "$TOR_URL" + else + echo "missing required command: curl or wget" >&2 + exit 1 + fi +fi + +got="$(sha256sum "$ARCHIVE" | awk '{print tolower($1)}')" +want="$(printf '%s' "$TOR_SHA256" | tr 'A-F' 'a-f')" +if [ "$got" != "$want" ]; then + echo "Tor archive SHA256 mismatch" >&2 + echo " expected: $want" >&2 + echo " got: $got" >&2 + exit 1 +fi + +tmp="$(mktemp -d "${TMPDIR:-/tmp}/bitflash-tor-package.XXXXXX")" +trap 'rm -rf "$tmp"' EXIT + +mkdir -p "$tmp/extract" +tar -xzf "$ARCHIVE" -C "$tmp/extract" + +if [ ! -f "$tmp/extract/tor/tor.exe" ]; then + echo "Tor archive did not contain tor/tor.exe" >&2 + exit 1 +fi + +rm -rf "$BUNDLE_DIR/tor" +mkdir -p "$BUNDLE_DIR/tor" +cp -R "$tmp/extract/tor/." "$BUNDLE_DIR/tor/" +if [ -d "$tmp/extract/docs" ]; then + mkdir -p "$BUNDLE_DIR/tor/docs" + cp -R "$tmp/extract/docs/." "$BUNDLE_DIR/tor/docs/" +fi +if [ -d "$tmp/extract/data" ]; then + mkdir -p "$BUNDLE_DIR/tor/data-defaults" + cp -R "$tmp/extract/data/." "$BUNDLE_DIR/tor/data-defaults/" +fi + +cat > "$BUNDLE_DIR/TOR-EXPERT-BUNDLE.txt" < Date: Thu, 13 Aug 2026 14:35:49 -0300 Subject: release: verify bundled Tor signatures --- README.md | 6 ++-- docs/release-verification.md | 8 +++-- docs/tor.md | 9 ++++-- scripts/package-windows-tor.sh | 71 +++++++++++++++++++++++++++++++++++++----- 4 files changed, 78 insertions(+), 16 deletions(-) (limited to 'docs/release-verification.md') diff --git a/README.md b/README.md index c30f171..bb6a4d4 100644 --- a/README.md +++ b/README.md @@ -462,8 +462,10 @@ make windows-tor That produces `Bitflash-*-windows-with-tor.zip`. The release script downloads the Tor Expert Bundle from the official Tor archive, verifies its pinned SHA256, -and places `tor/tor.exe` beside `Bitflash.exe` so `-managedtor` works without a -separate Tor install. +optionally verifies the Tor Project GPG signature when `gpg` is available, and +places `tor/tor.exe` beside `Bitflash.exe` so `-managedtor` works without a +separate Tor install. For release builds, use `TOR_VERIFY_GPG=required make +windows-tor` to require the extra signature check. --- diff --git a/docs/release-verification.md b/docs/release-verification.md index 04d18a3..b8eaf5e 100644 --- a/docs/release-verification.md +++ b/docs/release-verification.md @@ -83,9 +83,11 @@ key in `verify-release.sh`. For Windows releases with managed Tor built in, run `make windows-tor`. It creates `Bitflash-*-windows-with-tor.zip` by downloading the Tor Expert Bundle, -checking the pinned SHA256 in `scripts/package-windows-tor.sh`, and placing -`tor/tor.exe` next to `Bitflash.exe`. Include that zip in `SHA256SUMS` like any -other release asset. +checking the pinned SHA256 in `scripts/package-windows-tor.sh`, optionally +verifying the Tor Project `.asc` signature, and placing `tor/tor.exe` next to +`Bitflash.exe`. For release builds, run `TOR_VERIFY_GPG=required make +windows-tor` so the signature check is mandatory. Include that zip in +`SHA256SUMS` like any other release asset. ## Why this matters diff --git a/docs/tor.md b/docs/tor.md index be6a145..a408704 100644 --- a/docs/tor.md +++ b/docs/tor.md @@ -65,9 +65,12 @@ make windows-tor ``` The packaging script verifies a pinned SHA256 for the downloaded Tor Expert -Bundle before copying it into the release directory. The Tor binary is bundled; -the runtime data directory, hidden-service key, and control cookie are still -created under the user's Bitflash data directory. +Bundle before copying it into the release directory. When `gpg` is available it +also verifies the matching `.asc` signature against the pinned Tor Browser +Developers signing-key fingerprint. Release builds should use +`TOR_VERIFY_GPG=required make windows-tor` to make that second check mandatory. +The Tor binary is bundled; the runtime data directory, hidden-service key, and +control cookie are still created under the user's Bitflash data directory. To point at a specific Tor executable: diff --git a/scripts/package-windows-tor.sh b/scripts/package-windows-tor.sh index 084fd1a..c8cca3b 100755 --- a/scripts/package-windows-tor.sh +++ b/scripts/package-windows-tor.sh @@ -8,11 +8,15 @@ TOR_VERSION="${TOR_VERSION:-15.0.19}" TOR_ARCHIVE_NAME="tor-expert-bundle-windows-x86_64-${TOR_VERSION}.tar.gz" TOR_URL="${TOR_URL:-https://archive.torproject.org/tor-package-archive/torbrowser/${TOR_VERSION}/${TOR_ARCHIVE_NAME}}" TOR_SHA256="${TOR_SHA256:-6AC067402C7B4A3DC37887ED3754B3914B67FDC220C966190683E9CCF91ABF0F}" +TOR_ASC_URL="${TOR_ASC_URL:-${TOR_URL}.asc}" +TOR_SIGNING_KEY_FINGERPRINT="${TOR_SIGNING_KEY_FINGERPRINT:-EF6E286DDA85EA2A4BA7DE684E2C6E8793298290}" +TOR_VERIFY_GPG="${TOR_VERIFY_GPG:-auto}" BUNDLE_DIR="${BUNDLE_DIR:-$ROOT/Bitflash-${VERSION}-windows}" OUT_ZIP="${OUT_ZIP:-$ROOT/Bitflash-${VERSION}-windows-with-tor.zip}" CACHE_DIR="${CACHE_DIR:-$ROOT/.cache}" ARCHIVE="${TOR_ARCHIVE:-$CACHE_DIR/$TOR_ARCHIVE_NAME}" +ASC_FILE="${TOR_ASC:-$ARCHIVE.asc}" usage() { cat </dev/null 2>&1; then + curl -L --fail --output "$out" "$url" + elif command -v wget >/dev/null 2>&1; then + wget -O "$out" "$url" + else + echo "missing required command: curl or wget" >&2 + exit 1 + fi +} + if [ ! -d "$BUNDLE_DIR" ]; then echo "missing Windows release directory: $BUNDLE_DIR" >&2 echo "run 'make windows' first, or set BUNDLE_DIR" >&2 @@ -65,14 +85,7 @@ OUT_ZIP_ABS="$OUT_PARENT/$OUT_NAME" mkdir -p "$CACHE_DIR" if [ ! -f "$ARCHIVE" ]; then - if command -v curl >/dev/null 2>&1; then - curl -L --fail --output "$ARCHIVE" "$TOR_URL" - elif command -v wget >/dev/null 2>&1; then - wget -O "$ARCHIVE" "$TOR_URL" - else - echo "missing required command: curl or wget" >&2 - exit 1 - fi + download "$TOR_URL" "$ARCHIVE" fi got="$(sha256sum "$ARCHIVE" | awk '{print tolower($1)}')" @@ -87,6 +100,48 @@ fi tmp="$(mktemp -d "${TMPDIR:-/tmp}/bitflash-tor-package.XXXXXX")" trap 'rm -rf "$tmp"' EXIT +case "$TOR_VERIFY_GPG" in + auto|required|off) ;; + *) echo "TOR_VERIFY_GPG must be auto, required, or off" >&2; exit 2 ;; +esac + +if [ "$TOR_VERIFY_GPG" != "off" ]; then + if command -v gpg >/dev/null 2>&1; then + if [ ! -f "$ASC_FILE" ]; then + download "$TOR_ASC_URL" "$ASC_FILE" + fi + gnupg_home="$tmp/gnupg" + mkdir -p "$gnupg_home" + chmod 0700 "$gnupg_home" + gpg --homedir "$gnupg_home" --batch --auto-key-locate nodefault,wkd,keyserver \ + --locate-keys torbrowser@torproject.org >/dev/null 2>&1 || { + if [ "$TOR_VERIFY_GPG" = "required" ]; then + echo "could not import Tor Browser Developers signing key" >&2 + exit 1 + fi + echo "WARNING: skipped Tor GPG signature verification; signing key import failed" >&2 + } + if gpg --homedir "$gnupg_home" --batch --with-colons --fingerprint "$TOR_SIGNING_KEY_FINGERPRINT" \ + 2>/dev/null | grep -q "^fpr:::::::::${TOR_SIGNING_KEY_FINGERPRINT}:"; then + gpg --homedir "$gnupg_home" --batch --verify "$ASC_FILE" "$ARCHIVE" >/dev/null 2>&1 || { + echo "Tor archive GPG signature verification failed" >&2 + exit 1 + } + echo "Tor archive GPG signature verified against ${TOR_SIGNING_KEY_FINGERPRINT}" + elif [ "$TOR_VERIFY_GPG" = "required" ]; then + echo "Tor signing key fingerprint mismatch" >&2 + exit 1 + else + echo "WARNING: skipped Tor GPG signature verification; signing key fingerprint was not available" >&2 + fi + elif [ "$TOR_VERIFY_GPG" = "required" ]; then + echo "missing required command for TOR_VERIFY_GPG=required: gpg" >&2 + exit 1 + else + echo "WARNING: gpg not found; relying on pinned SHA256 only" >&2 + fi +fi + mkdir -p "$tmp/extract" tar -xzf "$ARCHIVE" -C "$tmp/extract" -- cgit v1.2.3