From 1839347459759fb3b23cc747ec4368b724fee008 Mon Sep 17 00:00:00 2001 From: Nsable-sh Date: Tue, 11 Aug 2026 14:13:26 -0300 Subject: tools: harden chain audit scripts --- tests/scripts/test_bitflash_tools.py | 107 +++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 tests/scripts/test_bitflash_tools.py (limited to 'tests/scripts') diff --git a/tests/scripts/test_bitflash_tools.py b/tests/scripts/test_bitflash_tools.py new file mode 100644 index 0000000..ff569e1 --- /dev/null +++ b/tests/scripts/test_bitflash_tools.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +"""Regression tests for deterministic Bitflash Python tooling.""" + +import json +import os +import struct +import subprocess +import sys +import tempfile +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[2] +SCRIPTS = ROOT / "scripts" +sys.path.insert(0, str(SCRIPTS)) + +import bitflash_chain as chain # noqa: E402 + + +def run(args, **kwargs): + env = dict(os.environ) + env["PYTHONIOENCODING"] = "utf-8" + return subprocess.run( + [sys.executable] + [str(a) for a in args], + cwd=str(ROOT), + env=env, + check=True, + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + **kwargs + ) + + +def write_block_file(path, blocks): + with Path(path).open("wb") as f: + f.write(b"noise") + f.write(chain.MAGIC) + f.write(struct.pack(" Date: Tue, 11 Aug 2026 15:11:40 -0300 Subject: tests: add deterministic chain tool fixtures --- tests/fixtures/chain-tools/explorer-blocks.json | 1 + tests/fixtures/chain-tools/fair-launch-report.json | 1 + .../chain-tools/utxo-proof-verification.json | 1 + tests/fixtures/chain-tools/utxo-proof.json | 1 + tests/fixtures/chain-tools/utxo-report.json | 1 + tests/scripts/test_bitflash_tools.py | 178 ++++++++++++++++++++- 6 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/chain-tools/explorer-blocks.json create mode 100644 tests/fixtures/chain-tools/fair-launch-report.json create mode 100644 tests/fixtures/chain-tools/utxo-proof-verification.json create mode 100644 tests/fixtures/chain-tools/utxo-proof.json create mode 100644 tests/fixtures/chain-tools/utxo-report.json (limited to 'tests/scripts') diff --git a/tests/fixtures/chain-tools/explorer-blocks.json b/tests/fixtures/chain-tools/explorer-blocks.json new file mode 100644 index 0000000..795d6e4 --- /dev/null +++ b/tests/fixtures/chain-tools/explorer-blocks.json @@ -0,0 +1 @@ +{"blocks":[{"cbAddr":"BDnXDAAvEameyQk12FrtvBiPBp9MsfRmUA","cbValue":5000000000,"hash":"7c0e819cec25ebe29452049f1b35479526ba679bc2c1b00407417109384dea76","height":2,"nTx":2,"size":331,"time":1753315440},{"cbAddr":"B61KYnjpfXHN2w2e1nR869HneK4P1Gz4va","cbValue":5000000000,"hash":"17bdb455d41592adbff4aa97b7fca2091fef69600e67306baca829ab5a588b30","height":1,"nTx":2,"size":331,"time":1753315320},{"cbAddr":"BDU4zj6apurZ7YWNxsndaTt75PMgzvwbN4","cbValue":5000000000,"hash":"5bd7cb255d814e48cebcdfb72da4dc87b34bd774227f8ceb546c5640f4bdc169","height":0,"nTx":1,"size":278,"time":1753315200}],"count":3,"network":"bitflash","tipHeight":2} diff --git a/tests/fixtures/chain-tools/fair-launch-report.json b/tests/fixtures/chain-tools/fair-launch-report.json new file mode 100644 index 0000000..d35f30b --- /dev/null +++ b/tests/fixtures/chain-tools/fair-launch-report.json @@ -0,0 +1 @@ +{"attestation_payload_sha256":"bf192bdbbb84c69d15bb68cf133abd474d0e1021600febc68e9f1a9de844b2af","chain":{"coin":100000000,"message_start_hex":"bf205cfd","name":"Bitflash","ticker":"BTF"},"checks":{"computed_merkle_matches_header":true,"genesis_has_single_transaction":true,"genesis_hash_matches_source":true,"genesis_merkle_matches_source":true,"genesis_supply_is_50_btf":true,"genesis_transaction_is_coinbase":true},"coinbase_outputs":[{"block_hash":"5bd7cb255d814e48cebcdfb72da4dc87b34bd774227f8ceb546c5640f4bdc169","coinbase_txid":"1a45b4482532abb29b10e234d3f13132230525a339ecea91658ffa675a5b1325","height":0,"outputs":[{"script_pub_key_hex":"4104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac","value_btf":"50.00000000","value_satoshis":5000000000}],"value_btf":"50.00000000","value_satoshis":5000000000},{"block_hash":"17bdb455d41592adbff4aa97b7fca2091fef69600e67306baca829ab5a588b30","coinbase_txid":"d6967c16013f1da136183f962aefd2f15e59b6836043f8ef49aadb92f38dca05","height":1,"outputs":[{"script_pub_key_hex":"76a914111111111111111111111111111111111111111188ac","value_btf":"50.00000000","value_satoshis":5000000000}],"value_btf":"50.00000000","value_satoshis":5000000000},{"block_hash":"7c0e819cec25ebe29452049f1b35479526ba679bc2c1b00407417109384dea76","coinbase_txid":"d55af2c80e8b70a9ae8f362a8aac5448e7925267c7f737d1fa0d227fd472a657","height":2,"outputs":[{"script_pub_key_hex":"76a914666666666666666666666666666666666666666688ac","value_btf":"50.00000000","value_satoshis":5000000000}],"value_btf":"50.00000000","value_satoshis":5000000000}],"deterministic":true,"external_timestamping":{"instructions":["Save the canonical JSON report.","Run: ots stamp bitflash-fair-launch-report.json","Later verify with: ots verify bitflash-fair-launch-report.json.ots"],"recommended":"OpenTimestamps","status":"not_embedded"},"genesis":{"bits":"0x1f0fffff","coinbase_txid":"1a45b4482532abb29b10e234d3f13132230525a339ecea91658ffa675a5b1325","computed_merkle_root":"1a45b4482532abb29b10e234d3f13132230525a339ecea91658ffa675a5b1325","hash":"5bd7cb255d814e48cebcdfb72da4dc87b34bd774227f8ceb546c5640f4bdc169","merkle_root":"1a45b4482532abb29b10e234d3f13132230525a339ecea91658ffa675a5b1325","nonce":3141,"outputs":[{"script_pub_key_hex":"4104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac","value_btf":"50.00000000","value_satoshis":5000000000}],"time":1753315200,"time_utc":"2025-07-24T00:00:00Z","tx_count":1},"scanned":{"block_count":3,"first_height":0,"last_height":2,"non_coinbase_transactions":2,"total_coinbase_btf":"150.00000000","total_coinbase_satoshis":15000000000},"schema":"bitflash-fair-launch-report-1","source_constants":{"genesis_bits":"0x1f0fffff","genesis_hash":"5bd7cb255d814e48cebcdfb72da4dc87b34bd774227f8ceb546c5640f4bdc169","genesis_merkle_root":"1a45b4482532abb29b10e234d3f13132230525a339ecea91658ffa675a5b1325","genesis_message":"Bitflash 24/Jul/2026 Fair launch: one CPU one vote, no premine","genesis_nonce":3141,"genesis_time":1753315200,"genesis_time_utc":"2025-07-24T00:00:00Z"},"source_mode":"local-block-files","verdict":"FAIR_LAUNCH_BASELINE_VERIFIED"} diff --git a/tests/fixtures/chain-tools/utxo-proof-verification.json b/tests/fixtures/chain-tools/utxo-proof-verification.json new file mode 100644 index 0000000..a40c673 --- /dev/null +++ b/tests/fixtures/chain-tools/utxo-proof-verification.json @@ -0,0 +1 @@ +{"checks":{"leaf_hash_matches_target":true,"root_matches":true,"schema_is_supported":true},"commitment":{"computed_root":"6e49b47ffccb178b62402235343ae7c55ec4a03effe19debb8c057e8ffc5b3c0","expected_root":"6e49b47ffccb178b62402235343ae7c55ec4a03effe19debb8c057e8ffc5b3c0","leaf_hash":"da964462812e40c918d6349baa57aee1dc838416eca44ccd31b782f0d1542d7b","path_length":3},"deterministic":true,"failures":[],"schema":"bitflash-utxo-proof-verification-1","target":{"created_height":2,"outpoint":"218609b2969dc0b6ae1c378ae255bbed863902987df4c943200e3c291b0e4c2a:1","value_btf":"17.50000000","value_satoshis":1750000000},"verdict":"UTXO_PROOF_VERIFIED"} diff --git a/tests/fixtures/chain-tools/utxo-proof.json b/tests/fixtures/chain-tools/utxo-proof.json new file mode 100644 index 0000000..154c45b --- /dev/null +++ b/tests/fixtures/chain-tools/utxo-proof.json @@ -0,0 +1 @@ +{"chain":{"coin":100000000,"message_start_hex":"bf205cfd","name":"Bitflash","ticker":"BTF"},"commitment":{"algorithm":"sorted-outpoint-merkle-sha256","leaf_count":5,"leaf_domain":"BTFUTXO1","node_domain":"BTFNODE1","root":"6e49b47ffccb178b62402235343ae7c55ec4a03effe19debb8c057e8ffc5b3c0"},"deterministic":true,"proof":[{"duplicated":false,"hash":"7a4f4f13aaf91f8b6b32d1fb22feaa3d0398782504915e9c156e60fdeca1255d","level":0,"side":"left"},{"duplicated":false,"hash":"5f91d5ba472b7d6ab96e9ce99d689896dad3c264d3abbce4c77f257687608943","level":1,"side":"right"},{"duplicated":false,"hash":"f041ba6a82fbc6759f0632f412dd8aab971ac1317358d6f204af3e57da4d12de","level":2,"side":"right"}],"scanned":{"first_height":0,"last_height":2,"main_chain_block_count":3,"raw_block_count":3,"side_blocks_ignored":0,"tip_hash":"7c0e819cec25ebe29452049f1b35479526ba679bc2c1b00407417109384dea76","tip_time":1753315440,"tip_time_utc":"2025-07-24T00:04:00Z"},"schema":"bitflash-utxo-inclusion-proof-1","target":{"leaf_hash":"da964462812e40c918d6349baa57aee1dc838416eca44ccd31b782f0d1542d7b","leaf_index":1,"outpoint":"218609b2969dc0b6ae1c378ae255bbed863902987df4c943200e3c291b0e4c2a:1","txid":"218609b2969dc0b6ae1c378ae255bbed863902987df4c943200e3c291b0e4c2a","utxo":{"coinbase":false,"created_block_hash":"7c0e819cec25ebe29452049f1b35479526ba679bc2c1b00407417109384dea76","created_height":2,"created_txid":"218609b2969dc0b6ae1c378ae255bbed863902987df4c943200e3c291b0e4c2a","script_pub_key_hex":"76a914555555555555555555555555555555555555555588ac","txid":"218609b2969dc0b6ae1c378ae255bbed863902987df4c943200e3c291b0e4c2a","value_btf":"17.50000000","value_satoshis":1750000000,"vout":1},"vout":1},"verdict":"UTXO_INCLUSION_PROOF_CREATED","warnings":{"legacy_duplicate_output_overwrites":0}} diff --git a/tests/fixtures/chain-tools/utxo-report.json b/tests/fixtures/chain-tools/utxo-report.json new file mode 100644 index 0000000..c6e98ee --- /dev/null +++ b/tests/fixtures/chain-tools/utxo-report.json @@ -0,0 +1 @@ +{"chain":{"coin":100000000,"message_start_hex":"bf205cfd","name":"Bitflash","ticker":"BTF"},"checks":{"all_scanned_merkle_roots_match":true,"genesis_hash_matches_source":true,"genesis_merkle_matches_source":true,"no_missing_spends":true},"commitment":{"algorithm":"sorted-outpoint-merkle-sha256","leaf_domain":"BTFUTXO1","node_domain":"BTFNODE1","root":"6e49b47ffccb178b62402235343ae7c55ec4a03effe19debb8c057e8ffc5b3c0"},"deterministic":true,"scanned":{"first_height":0,"last_height":2,"main_chain_block_count":3,"raw_block_count":3,"side_blocks_ignored":0,"tip_hash":"7c0e819cec25ebe29452049f1b35479526ba679bc2c1b00407417109384dea76","tip_time":1753315440,"tip_time_utc":"2025-07-24T00:04:00Z"},"schema":"bitflash-utxo-set-commitment-1","stats":{"coinbase_minus_unspent_btf":"1.50000000","coinbase_minus_unspent_satoshis":150000000,"duplicate_outputs":[],"missing_spends":[],"normal_transactions":2,"spent_outputs":8000000000,"total_coinbase_btf":"150.00000000","total_coinbase_satoshis":15000000000,"total_fees_btf":"1.50000000","total_fees_satoshis":150000000,"total_unspent_btf":"148.50000000","total_unspent_satoshis":14850000000,"utxo_count":5},"utxos":{"218609b2969dc0b6ae1c378ae255bbed863902987df4c943200e3c291b0e4c2a:0":{"coinbase":false,"created_block_hash":"7c0e819cec25ebe29452049f1b35479526ba679bc2c1b00407417109384dea76","created_height":2,"created_txid":"218609b2969dc0b6ae1c378ae255bbed863902987df4c943200e3c291b0e4c2a","script_pub_key_hex":"76a914444444444444444444444444444444444444444488ac","txid":"218609b2969dc0b6ae1c378ae255bbed863902987df4c943200e3c291b0e4c2a","value_btf":"12.00000000","value_satoshis":1200000000,"vout":0},"218609b2969dc0b6ae1c378ae255bbed863902987df4c943200e3c291b0e4c2a:1":{"coinbase":false,"created_block_hash":"7c0e819cec25ebe29452049f1b35479526ba679bc2c1b00407417109384dea76","created_height":2,"created_txid":"218609b2969dc0b6ae1c378ae255bbed863902987df4c943200e3c291b0e4c2a","script_pub_key_hex":"76a914555555555555555555555555555555555555555588ac","txid":"218609b2969dc0b6ae1c378ae255bbed863902987df4c943200e3c291b0e4c2a","value_btf":"17.50000000","value_satoshis":1750000000,"vout":1},"24bddfded9e22c2f98d9369e9ac00a4eccc8dcf05a2d1b9e12d94fd9f62ababc:1":{"coinbase":false,"created_block_hash":"17bdb455d41592adbff4aa97b7fca2091fef69600e67306baca829ab5a588b30","created_height":1,"created_txid":"24bddfded9e22c2f98d9369e9ac00a4eccc8dcf05a2d1b9e12d94fd9f62ababc","script_pub_key_hex":"76a914333333333333333333333333333333333333333388ac","txid":"24bddfded9e22c2f98d9369e9ac00a4eccc8dcf05a2d1b9e12d94fd9f62ababc","value_btf":"19.00000000","value_satoshis":1900000000,"vout":1},"d55af2c80e8b70a9ae8f362a8aac5448e7925267c7f737d1fa0d227fd472a657:0":{"coinbase":true,"created_block_hash":"7c0e819cec25ebe29452049f1b35479526ba679bc2c1b00407417109384dea76","created_height":2,"created_txid":"d55af2c80e8b70a9ae8f362a8aac5448e7925267c7f737d1fa0d227fd472a657","script_pub_key_hex":"76a914666666666666666666666666666666666666666688ac","txid":"d55af2c80e8b70a9ae8f362a8aac5448e7925267c7f737d1fa0d227fd472a657","value_btf":"50.00000000","value_satoshis":5000000000,"vout":0},"d6967c16013f1da136183f962aefd2f15e59b6836043f8ef49aadb92f38dca05:0":{"coinbase":true,"created_block_hash":"17bdb455d41592adbff4aa97b7fca2091fef69600e67306baca829ab5a588b30","created_height":1,"created_txid":"d6967c16013f1da136183f962aefd2f15e59b6836043f8ef49aadb92f38dca05","script_pub_key_hex":"76a914111111111111111111111111111111111111111188ac","txid":"d6967c16013f1da136183f962aefd2f15e59b6836043f8ef49aadb92f38dca05","value_btf":"50.00000000","value_satoshis":5000000000,"vout":0}},"verdict":"UTXO_COMMITMENT_VERIFIED","warnings":{"legacy_duplicate_output_overwrites":0}} diff --git a/tests/scripts/test_bitflash_tools.py b/tests/scripts/test_bitflash_tools.py index ff569e1..4b18d1e 100644 --- a/tests/scripts/test_bitflash_tools.py +++ b/tests/scripts/test_bitflash_tools.py @@ -12,6 +12,7 @@ from pathlib import Path ROOT = Path(__file__).resolve().parents[2] SCRIPTS = ROOT / "scripts" +FIXTURES = ROOT / "tests" / "fixtures" / "chain-tools" sys.path.insert(0, str(SCRIPTS)) import bitflash_chain as chain # noqa: E402 @@ -45,12 +46,130 @@ def write_block_file(path, blocks): f.write(payload) +def p2pkh_script(byte): + return "76a914%s88ac" % (bytes([byte]) * 20).hex() + + +def make_tx(vin, vout): + tx = { + "version": 1, + "vin": vin, + "vout": vout, + "lock_time": 0, + } + tx["txid"] = chain.hash_hex(chain.ser_tx(tx)) + return tx + + +def make_coinbase(label, script_byte, value=50 * chain.COIN): + return make_tx( + [{ + "prevout_hash": "0" * 64, + "prevout_n": 0xFFFFFFFF, + "script_sig_hex": label.encode("ascii").hex(), + "sequence": 0xFFFFFFFF, + }], + [{ + "value_satoshis": value, + "value_btf": chain.format_money(value), + "script_pub_key_hex": p2pkh_script(script_byte), + }], + ) + + +def make_spend(prev_txid, prev_n, outputs, label): + return make_tx( + [{ + "prevout_hash": prev_txid, + "prevout_n": prev_n, + "script_sig_hex": label.encode("ascii").hex(), + "sequence": 0xFFFFFFFF, + }], + [{ + "value_satoshis": value, + "value_btf": chain.format_money(value), + "script_pub_key_hex": p2pkh_script(script_byte), + } for value, script_byte in outputs], + ) + + +def make_block(prev_hash, txs, ntime, nonce): + merkle = chain.merkle_root([tx["txid"] for tx in txs]) + header = ( + struct.pack(" 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 'tests/scripts') 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