diff options
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/dependencies.md | 18 | ||||
| -rw-r--r-- | docs/fair-launch.md | 70 | ||||
| -rw-r--r-- | docs/mining-calculator.md | 86 | ||||
| -rw-r--r-- | docs/pool-directory.md | 43 | ||||
| -rw-r--r-- | docs/release-verification.md | 78 | ||||
| -rw-r--r-- | docs/rendezvous.md | 288 | ||||
| -rw-r--r-- | docs/social.png | bin | 208957 -> 162267 bytes | |||
| -rw-r--r-- | docs/tor.md | 84 | ||||
| -rw-r--r-- | docs/utxo-commitment.md | 125 | ||||
| -rw-r--r-- | docs/utxo-proofs.md | 61 |
10 files changed, 825 insertions, 28 deletions
diff --git a/docs/dependencies.md b/docs/dependencies.md new file mode 100644 index 0000000..9fd45b6 --- /dev/null +++ b/docs/dependencies.md | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | # Build Dependencies | ||
| 2 | |||
| 3 | Bitflash links consensus-adjacent cryptographic and proof-of-work code from | ||
| 4 | external repositories. Fresh builds must not follow upstream `master` silently: | ||
| 5 | a changed upstream branch can break reproducible builds or change behavior in | ||
| 6 | code that every node relies on. | ||
| 7 | |||
| 8 | The top-level `Makefile` pins source dependencies by full commit hash: | ||
| 9 | |||
| 10 | | dependency | repository | pinned commit | | ||
| 11 | |---|---|---| | ||
| 12 | | libsecp256k1 | https://github.com/bitcoin-core/secp256k1 | `7fecac74aed8e1fd9078380d67dd04663705c989` | | ||
| 13 | | RandomX | https://github.com/tevador/RandomX | `1e9d4b2df63fa6edf46b06789486e23c0fcaa55a` | | ||
| 14 | |||
| 15 | When updating either dependency, rebuild from a clean dependency directory and | ||
| 16 | run the full selftest suite before opening the PR. The PR should explain why the | ||
| 17 | dependency changed and whether the update can affect consensus, addresses, | ||
| 18 | signatures, proof-of-work validation, or mining. | ||
diff --git a/docs/fair-launch.md b/docs/fair-launch.md new file mode 100644 index 0000000..9603910 --- /dev/null +++ b/docs/fair-launch.md | |||
| @@ -0,0 +1,70 @@ | |||
| 1 | # Bitflash fair-launch verifier | ||
| 2 | |||
| 3 | Bitflash ships a deterministic fair-launch audit script: | ||
| 4 | |||
| 5 | ```bash | ||
| 6 | python3 scripts/verify-fair-launch.py --max-blocks 1000 --out bitflash-fair-launch-report.json --json | ||
| 7 | ``` | ||
| 8 | |||
| 9 | The verifier reads `blk*.dat` directly from the local Bitflash data directory. | ||
| 10 | It does not use Berkeley DB, does not call the node binary, and does not need | ||
| 11 | network access. It uses the same shared Python chain parser as the UTXO | ||
| 12 | commitment and proof tools, so block serialization fixes land in one place. | ||
| 13 | |||
| 14 | The report proves the launch baseline that can be checked from the chain: | ||
| 15 | |||
| 16 | - the genesis block hash matches the source constant; | ||
| 17 | - the genesis merkle root matches the source constant; | ||
| 18 | - the genesis transaction is a single coinbase transaction; | ||
| 19 | - the genesis output total is exactly `50.00000000 BTF`; | ||
| 20 | - the scanned block range and mined coinbase total are reproducible. | ||
| 21 | |||
| 22 | For machines that do not have a synced data directory yet, the source constants | ||
| 23 | can be checked without disk blocks: | ||
| 24 | |||
| 25 | ```bash | ||
| 26 | python3 scripts/verify-fair-launch.py --reconstruct-genesis | ||
| 27 | ``` | ||
| 28 | |||
| 29 | ## Deterministic output | ||
| 30 | |||
| 31 | Use `--json` or `--out` for canonical JSON. The JSON is written with sorted | ||
| 32 | keys, fixed separators, ASCII escaping, and one trailing newline. It deliberately | ||
| 33 | does not include local paths or wall-clock generation time, so two auditors | ||
| 34 | scanning the same block range produce the same bytes. | ||
| 35 | |||
| 36 | That makes the report shareable: | ||
| 37 | |||
| 38 | ```bash | ||
| 39 | python3 scripts/verify-fair-launch.py --max-blocks 1000 --out report.json --json | ||
| 40 | sha256sum report.json | ||
| 41 | ``` | ||
| 42 | |||
| 43 | If another auditor runs the same command against the same chain prefix, the | ||
| 44 | `sha256sum` should match. | ||
| 45 | |||
| 46 | ## OpenTimestamps | ||
| 47 | |||
| 48 | OpenTimestamps is optional and external. It anchors the report in Bitcoin | ||
| 49 | without adding any bridge or dependency to Bitflash consensus. | ||
| 50 | |||
| 51 | After generating a canonical JSON report: | ||
| 52 | |||
| 53 | ```bash | ||
| 54 | ots stamp report.json | ||
| 55 | ots verify report.json.ots | ||
| 56 | ``` | ||
| 57 | |||
| 58 | The `.ots` file proves that the exact report bytes existed no later than the | ||
| 59 | Bitcoin block that confirms the timestamp. | ||
| 60 | |||
| 61 | ## What this does not claim | ||
| 62 | |||
| 63 | This is not a retroactive genesis commitment. The Bitflash genesis block already | ||
| 64 | exists, so adding a new on-chain commitment to genesis would create a different | ||
| 65 | chain. This verifier instead audits the launch state that is already on-chain | ||
| 66 | and makes the result reproducible and timestampable. | ||
| 67 | |||
| 68 | It also does not prove real-world miner identity. It proves the genesis state, | ||
| 69 | the absence of non-coinbase premine in genesis, and the deterministic coinbase | ||
| 70 | totals over the scanned prefix. | ||
diff --git a/docs/mining-calculator.md b/docs/mining-calculator.md new file mode 100644 index 0000000..352eea1 --- /dev/null +++ b/docs/mining-calculator.md | |||
| @@ -0,0 +1,86 @@ | |||
| 1 | # Bitflash mining calculator | ||
| 2 | |||
| 3 | `scripts/bitflash-mining-calculator.py` is a local WhatToMine-style calculator | ||
| 4 | for Bitflash. | ||
| 5 | |||
| 6 | It does not fetch market prices, does not talk to exchanges, and does not | ||
| 7 | change consensus. Price is a manual input until Bitflash has a reliable public | ||
| 8 | market. | ||
| 9 | |||
| 10 | ## Examples | ||
| 11 | |||
| 12 | Estimate from network hashrate: | ||
| 13 | |||
| 14 | ```bash | ||
| 15 | python3 scripts/bitflash-mining-calculator.py \ | ||
| 16 | --hashrate 21 --hashrate-unit kh/s \ | ||
| 17 | --network-hashrate 1.56 --network-hashrate-unit mh/s \ | ||
| 18 | --watts 140 --kwh-cost 0.10 \ | ||
| 19 | --price 0.001 --pool-fee 1 | ||
| 20 | ``` | ||
| 21 | |||
| 22 | Estimate from Bitflash difficulty: | ||
| 23 | |||
| 24 | ```bash | ||
| 25 | python3 scripts/bitflash-mining-calculator.py \ | ||
| 26 | --hashrate 21 --hashrate-unit kh/s \ | ||
| 27 | --difficulty 45600 \ | ||
| 28 | --watts 140 --kwh-cost 0.10 | ||
| 29 | ``` | ||
| 30 | |||
| 31 | Machine-readable output: | ||
| 32 | |||
| 33 | ```bash | ||
| 34 | python3 scripts/bitflash-mining-calculator.py \ | ||
| 35 | --hashrate 21 --hashrate-unit kh/s \ | ||
| 36 | --network-hashrate 1.56 --network-hashrate-unit mh/s \ | ||
| 37 | --json | ||
| 38 | ``` | ||
| 39 | |||
| 40 | ## Bitflash Difficulty Scale | ||
| 41 | |||
| 42 | Bitflash does not use Bitcoin's SHA256 difficulty-1 constant for pool shares. | ||
| 43 | The chain's easiest RandomX target is `bnProofOfWorkLimit = ~uint256(0) >> 12`, | ||
| 44 | so difficulty 1 expects about `4096` hashes. | ||
| 45 | |||
| 46 | The calculator uses: | ||
| 47 | |||
| 48 | ```text | ||
| 49 | network_hashrate = difficulty * 4096 / block_time_seconds | ||
| 50 | ``` | ||
| 51 | |||
| 52 | At the default two-minute block target: | ||
| 53 | |||
| 54 | ```text | ||
| 55 | network_hashrate = difficulty * 4096 / 120 | ||
| 56 | ``` | ||
| 57 | |||
| 58 | If you already know network hashrate, pass `--network-hashrate` directly and | ||
| 59 | the script derives the equivalent Bitflash difficulty for display. | ||
| 60 | |||
| 61 | ## Reward Estimate | ||
| 62 | |||
| 63 | The expected daily reward is: | ||
| 64 | |||
| 65 | ```text | ||
| 66 | blocks_per_day = 86400 / block_time_seconds | ||
| 67 | miner_share = miner_hashrate / network_hashrate | ||
| 68 | gross_btf_per_day = blocks_per_day * block_reward * miner_share | ||
| 69 | net_btf_per_day = gross_btf_per_day * (1 - pool_fee_percent / 100) | ||
| 70 | ``` | ||
| 71 | |||
| 72 | Electricity is: | ||
| 73 | |||
| 74 | ```text | ||
| 75 | electricity_per_day = watts / 1000 * 24 * kwh_cost | ||
| 76 | ``` | ||
| 77 | |||
| 78 | This is an expectation, not a promise. Solo mining variance can be very large, | ||
| 79 | especially when the miner has a small share of the network. | ||
| 80 | |||
| 81 | ## Future Website Use | ||
| 82 | |||
| 83 | The script is intentionally standalone so the same math can later back a | ||
| 84 | website widget or status dashboard. Until a public price exists, a website | ||
| 85 | calculator should keep price as a manual field and label fiat profit as | ||
| 86 | speculative. | ||
diff --git a/docs/pool-directory.md b/docs/pool-directory.md new file mode 100644 index 0000000..76269b9 --- /dev/null +++ b/docs/pool-directory.md | |||
| @@ -0,0 +1,43 @@ | |||
| 1 | # Public Pool Directory | ||
| 2 | |||
| 3 | `pool.bitflash.network` should be a public directory, not a network dependency. | ||
| 4 | Nodes discover pools through Nostr and `.btf`; the website only helps humans | ||
| 5 | compare operators. | ||
| 6 | |||
| 7 | Operators can publish the `pool_status.json` and `pool_rounds.json` files | ||
| 8 | produced by: | ||
| 9 | |||
| 10 | ```bash | ||
| 11 | bitflash -nogui -gen -operator \ | ||
| 12 | -poolstatusfile=/var/www/pool_status.json \ | ||
| 13 | -poolroundsfile=/var/www/pool_rounds.json | ||
| 14 | ``` | ||
| 15 | |||
| 16 | Build a static directory from one or more status files: | ||
| 17 | |||
| 18 | ```bash | ||
| 19 | python3 scripts/build-pool-index.py --out-dir public/pool \ | ||
| 20 | https://operator-one.example/pool_status.json \ | ||
| 21 | https://operator-two.example/pool_status.json | ||
| 22 | ``` | ||
| 23 | |||
| 24 | The generated output contains: | ||
| 25 | |||
| 26 | - `index.html`: human-readable ranking page. | ||
| 27 | - `pools.json`: machine-readable API for `api.bitflash.network/pools.json`. | ||
| 28 | |||
| 29 | When a source is an HTTP(S) `pool_status.json`, the directory links to a | ||
| 30 | `pool_rounds.json` proof file next to it. That file records the block height, | ||
| 31 | block hash, coinbase txid, coinbase value, pool fee, total shares, per-address | ||
| 32 | share counts, computed payouts, and payout txids once paid. | ||
| 33 | |||
| 34 | Ranking order is intentionally simple: | ||
| 35 | |||
| 36 | 1. online pools first; | ||
| 37 | 2. higher reported hashrate; | ||
| 38 | 3. more authorized miners; | ||
| 39 | 4. lower fee; | ||
| 40 | 5. name as a stable tie-breaker. | ||
| 41 | |||
| 42 | An entry is considered online when the source says the pool is running and its | ||
| 43 | status file is no more than 120 seconds old. | ||
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 @@ | |||
| 3 | Bitflash releases publish binaries and a `SHA256SUMS` file. The checksum file | 3 | Bitflash releases publish binaries and a `SHA256SUMS` file. The checksum file |
| 4 | proves that the file you downloaded matches the file the release page names. | 4 | proves that the file you downloaded matches the file the release page names. |
| 5 | A detached signature, `SHA256SUMS.asc`, proves that the checksum file itself was | 5 | A detached signature, `SHA256SUMS.asc`, proves that the checksum file itself was |
| 6 | signed by a trusted release key. | 6 | signed by the Bitflash release key. |
| 7 | 7 | ||
| 8 | That gives users two separate checks: | 8 | That gives users two separate checks: |
| 9 | 9 | ||
| 10 | 1. `gpg --verify SHA256SUMS.asc SHA256SUMS` checks who signed the checksums. | 10 | 1. `gpg --verify SHA256SUMS.asc SHA256SUMS` — who signed the checksums. |
| 11 | 2. `sha256sum -c SHA256SUMS` checks the binaries against those checksums. | 11 | 2. `sha256sum -c SHA256SUMS` — the binaries against those checksums. |
| 12 | 12 | ||
| 13 | ## User Check | 13 | But the first check is only worth something if you require a **specific** key. |
| 14 | A good signature by *some* key proves nothing; anyone can make one. | ||
| 14 | 15 | ||
| 15 | From a shell with `curl` or `wget` and `sha256sum`: | 16 | ## The release signing key |
| 16 | 17 | ||
| 17 | ```bash | 18 | ```text |
| 18 | scripts/verify-release.sh latest | 19 | fingerprint 910A 2B4C CA87 9E81 FB4B 2AEA 1D4A 53D3 B78A A4B8 |
| 20 | uid Bitflash Releases (bitflash.network release signing) | ||
| 21 | <releases@bitflash.network> | ||
| 19 | ``` | 22 | ``` |
| 20 | 23 | ||
| 21 | For a specific release: | 24 | The public key is published at |
| 25 | `https://releases.bitflash.network/bitflash-release-key.asc` and embedded in | ||
| 26 | `scripts/verify-release.sh`, so the tool's trust anchor lives in this | ||
| 27 | repository rather than on the server it verifies. | ||
| 28 | |||
| 29 | ## User check | ||
| 30 | |||
| 31 | From a shell with `curl` or `wget`, `sha256sum` and `gpg`: | ||
| 22 | 32 | ||
| 23 | ```bash | 33 | ```bash |
| 24 | scripts/verify-release.sh v1.2.13 | 34 | scripts/verify-release.sh latest |
| 25 | ``` | 35 | ``` |
| 26 | 36 | ||
| 27 | For release audits, require the signature: | 37 | For a specific release: |
| 28 | 38 | ||
| 29 | ```bash | 39 | ```bash |
| 30 | scripts/verify-release.sh v1.2.13 --require-signature | 40 | scripts/verify-release.sh v1.2.15 |
| 31 | ``` | 41 | ``` |
| 32 | 42 | ||
| 33 | Older releases may not have `SHA256SUMS.asc`. In that case the script warns and | 43 | The script downloads the release, imports the pinned key into a throwaway |
| 34 | still checks file integrity. New release audits should use `--require-signature`. | 44 | keyring, and **refuses anything that is not a valid signature by the key above** |
| 35 | 45 | before checking the hashes. Releases from before signing was introduced ship | |
| 36 | ## Maintainer Flow | 46 | only `SHA256SUMS`; without `--require-signature` those are checked for integrity |
| 47 | and reported as unsigned. | ||
| 37 | 48 | ||
| 38 | After building release assets in the repository root: | 49 | By hand, if you would rather not run the script: |
| 39 | 50 | ||
| 40 | ```bash | 51 | ```bash |
| 41 | scripts/make-release-checksums.sh --sign --local-user RELEASE_KEY_ID | 52 | curl -O https://releases.bitflash.network/bitflash-release-key.asc |
| 53 | gpg --import bitflash-release-key.asc | ||
| 54 | gpg --verify SHA256SUMS.asc SHA256SUMS # must name the fingerprint above | ||
| 55 | sha256sum -c SHA256SUMS | ||
| 42 | ``` | 56 | ``` |
| 43 | 57 | ||
| 44 | Upload all built assets plus: | 58 | ## Maintainer flow |
| 45 | 59 | ||
| 46 | ```text | 60 | The signing key lives on a dedicated location, not in the build tree. After |
| 47 | SHA256SUMS | 61 | building the release assets in the repository root, write and sign the |
| 48 | SHA256SUMS.asc | 62 | checksums: |
| 63 | |||
| 64 | ```bash | ||
| 65 | sha256sum Bitflash-*.zip Bitflash-*.AppImage bitflash-node-* > SHA256SUMS | ||
| 66 | gpg --armor --detach-sign --output SHA256SUMS.asc SHA256SUMS | ||
| 49 | ``` | 67 | ``` |
| 50 | 68 | ||
| 51 | Keep the private signing key offline or on a dedicated release machine. Publish | 69 | Publish, alongside the assets, `SHA256SUMS` and `SHA256SUMS.asc`, and update |
| 52 | the public key fingerprint in the release notes and keep using the same key for | 70 | `latest.txt` on the releases host to the new tag. Keep using the same key; if it |
| 53 | future releases unless there is a clearly announced rotation. | 71 | is ever rotated, announce the new fingerprint clearly and update the embedded |
| 72 | key in `verify-release.sh`. | ||
| 54 | 73 | ||
| 55 | ## Why This Matters | 74 | ## Why this matters |
| 56 | 75 | ||
| 57 | `SHA256SUMS` alone protects against a broken download, but not against someone | 76 | `SHA256SUMS` alone protects against a broken download, but not against someone |
| 58 | replacing both a binary and the checksum file. Signing `SHA256SUMS` means an | 77 | replacing both a binary and the checksum file. Signing `SHA256SUMS`, and |
| 59 | attacker must also have the release signing key to make the replacement verify. | 78 | requiring a pinned key, means an attacker must also hold the release signing key |
| 79 | to make the replacement verify. Serving from bitflash.network makes this the | ||
| 80 | guarantee that matters: without a pinned key, "trust the site" is the only | ||
| 81 | thing standing behind a download. | ||
| 60 | 82 | ||
| 61 | This is not reproducible builds yet. It is the smaller, immediate step that | 83 | This is not reproducible builds yet. It is the smaller, immediate step that |
| 62 | makes every release asset auditable by users before they run it. | 84 | makes every release asset auditable before anyone runs it. |
diff --git a/docs/rendezvous.md b/docs/rendezvous.md new file mode 100644 index 0000000..ac8e598 --- /dev/null +++ b/docs/rendezvous.md | |||
| @@ -0,0 +1,288 @@ | |||
| 1 | # Bitflash `.btf` Rendezvous Protocol | ||
| 2 | |||
| 3 | This is the specification of how Bitflash nodes reach each other, and how miners | ||
| 4 | reach pools, without revealing an IP address and without any central directory. | ||
| 5 | It documents the protocol as implemented in `src/btfaddr.cpp`, `src/btfchan.cpp`, | ||
| 6 | `src/btfrv.cpp`, `src/btftunnel.cpp` and `src/nostr.cpp`. | ||
| 7 | |||
| 8 | The design is ported from the Itzal hidden service design. A `.btf` address names | ||
| 9 | a public key, a node proves ownership of that key by signing a service | ||
| 10 | descriptor, other nodes find the descriptor over Nostr, and the two sides then | ||
| 11 | meet through an untrusted relay over an end to end encrypted tunnel. The relay | ||
| 12 | carries ciphertext only and never learns the plaintext. | ||
| 13 | |||
| 14 | ## 1. Overview | ||
| 15 | |||
| 16 | A connection to a `.btf` service has three layers: | ||
| 17 | |||
| 18 | 1. **Discovery (Nostr).** The service publishes a signed descriptor as a Nostr | ||
| 19 | event. A caller fetches it by the service public key and verifies the | ||
| 20 | signature. The descriptor says which rendezvous relay the service is waiting | ||
| 21 | at and which encryption key to use. | ||
| 22 | 2. **Rendezvous (TCP).** Caller and service both connect out to the same relay | ||
| 23 | and are paired by public key. Neither needs an inbound port or a public IP. | ||
| 24 | After pairing the relay is a blind byte pipe. | ||
| 25 | 3. **Encrypted channel (X25519 + XChaCha20-Poly1305).** Over the paired pipe the | ||
| 26 | caller and service run an ephemeral to static handshake and then exchange | ||
| 27 | authenticated frames. The relay sees only ciphertext. | ||
| 28 | |||
| 29 | The caller ends up holding an ordinary local socket that behaves like a direct | ||
| 30 | TCP connection to the peer. The node treats it exactly like any other peer | ||
| 31 | socket. | ||
| 32 | |||
| 33 | ## 2. Identities and keys | ||
| 34 | |||
| 35 | A node holds two independent keypairs. | ||
| 36 | |||
| 37 | - **Signing / identity key**: a secp256k1 x-only key (BIP340 Schnorr). This is | ||
| 38 | the `.btf` identity and also the node's Nostr identity. Persisted in | ||
| 39 | `nostr.key`. | ||
| 40 | - **Encryption key**: an X25519 (Curve25519) keypair. Persisted in | ||
| 41 | `btf_enc.key`. Its public half is published in the descriptor as `enc` and is | ||
| 42 | used for the tunnel key agreement. | ||
| 43 | |||
| 44 | The identity key names the service and authenticates the descriptor. The | ||
| 45 | encryption key secures the tunnel. They are separate so the long term identity | ||
| 46 | never performs Diffie-Hellman and the encryption key can be rotated in a new | ||
| 47 | signed descriptor. | ||
| 48 | |||
| 49 | ## 3. The `.btf` address | ||
| 50 | |||
| 51 | An address is the identity public key plus a short checksum, Base32 encoded, with | ||
| 52 | the suffix `.btf`. | ||
| 53 | |||
| 54 | ``` | ||
| 55 | label = Base32( pubkey[32] || checksum[2] ) | ||
| 56 | address = label + ".btf" | ||
| 57 | ``` | ||
| 58 | |||
| 59 | - `pubkey` is the 32 byte secp256k1 x-only public key. | ||
| 60 | - `checksum` is the first two bytes of `SHA256(".btf-checksum-v1" || pubkey)`. | ||
| 61 | - Base32 uses the lowercase alphabet `abcdefghijklmnopqrstuvwxyz234567`, no | ||
| 62 | padding. The 34 byte payload encodes to 55 characters. | ||
| 63 | - Parsing is case insensitive, trims surrounding whitespace, and strips a | ||
| 64 | trailing dot. A wrong checksum is rejected. | ||
| 65 | |||
| 66 | The address is self certifying: it *is* the public key, so no registry has to be | ||
| 67 | trusted to map a name to a key. | ||
| 68 | |||
| 69 | ## 4. Discovery over Nostr | ||
| 70 | |||
| 71 | Nodes publish and read three kinds of parameterized replaceable Nostr events. | ||
| 72 | Parameterized replaceable means the newest event for a given author and `d` tag | ||
| 73 | supersedes the older one, so a descriptor is always the latest state. | ||
| 74 | |||
| 75 | | Purpose | kind | `d` tag | | ||
| 76 | |---|---|---| | ||
| 77 | | Service descriptor | 38501 | `btf-descriptor-2` | | ||
| 78 | | Rendezvous relay announcement | 38502 | `btf-relay-2` | | ||
| 79 | | Pool announcement | 38503 | `btf-pool-2` | | ||
| 80 | |||
| 81 | The Nostr event is signed with the node's identity key, so the event author | ||
| 82 | equals the `.btf` public key. That binds the descriptor to the address at the | ||
| 83 | Nostr layer, and the descriptor is *also* signed inside its own content (see | ||
| 84 | below), so a relay operator cannot forge or swap a descriptor for an address it | ||
| 85 | does not control. | ||
| 86 | |||
| 87 | **Resolving an address.** To reach `X.btf`, a caller parses the address to the 32 | ||
| 88 | byte key and sends a Nostr `REQ` filtered by `authors = [X]`, `kinds = [38501]`, | ||
| 89 | `#d = ["btf-descriptor-2"]`, `limit = 1`. It verifies the returned descriptor and | ||
| 90 | uses its `meeting_node` and `enc`. | ||
| 91 | |||
| 92 | **Relay auto discovery.** A relay operator runs a rendezvous relay and publishes a | ||
| 93 | kind 38502 event carrying `host:port`. Every node that reads it adds the relay to | ||
| 94 | the set it can register at and dial through. No seed list edit or maintainer | ||
| 95 | approval is needed. A small curated seed list is still shipped for bootstrap: | ||
| 96 | |||
| 97 | ``` | ||
| 98 | 92.246.128.180:8434 | ||
| 99 | 31.44.4.249:8434 | ||
| 100 | 90.156.222.107:8434 | ||
| 101 | ``` | ||
| 102 | |||
| 103 | The default rendezvous port is `8434`. | ||
| 104 | |||
| 105 | ## 5. The service descriptor | ||
| 106 | |||
| 107 | The descriptor is a JSON object, signed with the identity key over a domain | ||
| 108 | separated digest of its own fields. | ||
| 109 | |||
| 110 | ```json | ||
| 111 | { | ||
| 112 | "v": 2, | ||
| 113 | "pubkey": "<hex 32-byte x-only identity key>", | ||
| 114 | "enc": "<hex 32-byte X25519 public key>", | ||
| 115 | "meeting_node": "host:port", | ||
| 116 | "created": <unix seconds>, | ||
| 117 | "sig": "<hex 64-byte BIP340 Schnorr signature>" | ||
| 118 | } | ||
| 119 | ``` | ||
| 120 | |||
| 121 | The signed digest is: | ||
| 122 | |||
| 123 | ``` | ||
| 124 | digest = SHA256( ".btf-descriptor-v2" | ||
| 125 | || pubkey[32] | ||
| 126 | || enc (raw bytes of the string) | ||
| 127 | || meeting_node (raw bytes of the string) | ||
| 128 | || created (8 bytes, big endian) ) | ||
| 129 | ``` | ||
| 130 | |||
| 131 | The signature is a deterministic BIP340 Schnorr signature (no auxiliary | ||
| 132 | randomness) over `digest` by the identity key. | ||
| 133 | |||
| 134 | **Verification** (untrusted input, must never throw): parse the JSON, require all | ||
| 135 | fields with the right types, confirm `pubkey` equals the address being resolved, | ||
| 136 | recompute the digest, and verify the Schnorr signature. Any failure rejects the | ||
| 137 | descriptor. | ||
| 138 | |||
| 139 | `created` lets a reader prefer the freshest descriptor when more than one is | ||
| 140 | seen. A node only publishes a descriptor once it has actually registered at a | ||
| 141 | `meeting_node`, so it never advertises a relay it cannot be reached at. | ||
| 142 | |||
| 143 | ## 6. Rendezvous transport | ||
| 144 | |||
| 145 | The relay is a small TCP server. Every connection opens with a fixed 33 byte | ||
| 146 | header: | ||
| 147 | |||
| 148 | ``` | ||
| 149 | byte 0 role: 'S' (service) or 'C' (client) | ||
| 150 | bytes 1..32 target public key (the .btf identity, 32 bytes) | ||
| 151 | ``` | ||
| 152 | |||
| 153 | **Service registration (`S`).** The service connects out to the relay and sends | ||
| 154 | `'S' || own_pubkey`. The relay stores the connection in a table keyed by the | ||
| 155 | public key and leaves it open, waiting. There is one slot per key: a new | ||
| 156 | registration for the same key replaces and closes the old one. The service does | ||
| 157 | not block waiting to be paired at registration time; it is considered registered | ||
| 158 | the moment the relay reads the header, which is what lets it publish a descriptor | ||
| 159 | pointing at this relay. | ||
| 160 | |||
| 161 | **Client dial (`C`).** The caller connects out to the same relay and sends | ||
| 162 | `'C' || target_pubkey`. The relay looks up the waiting service: | ||
| 163 | |||
| 164 | - If present, it removes it from the table, writes a single `0x01` byte to both | ||
| 165 | sockets, and then forwards raw bytes in both directions until either side | ||
| 166 | closes. From this point the relay is a blind pipe. | ||
| 167 | - If absent, it writes `0x00` to the caller and closes. | ||
| 168 | |||
| 169 | Both sides connect *outbound* to the relay, so neither needs a public IP or an | ||
| 170 | open inbound port. The relay never sees the identity of the caller (the caller | ||
| 171 | sends no key of its own, only the target it wants). | ||
| 172 | |||
| 173 | **Liveness.** A registration socket is idle by design and can outlive the network | ||
| 174 | path that created it (a NAT drops the idle mapping, the relay restarts, a route | ||
| 175 | changes) with no FIN and nothing readable. TCP keepalive was measured and does | ||
| 176 | not reliably detect this. The service therefore imposes its own timeout on the | ||
| 177 | wait for the pairing byte and re-registers on expiry. Expiring is not a failure; | ||
| 178 | it costs one reconnect per interval and returns the loop to a known state. | ||
| 179 | |||
| 180 | ## 7. The encrypted channel | ||
| 181 | |||
| 182 | Once paired through the relay, the caller and service run an end to end handshake | ||
| 183 | and then exchange authenticated frames. The primitive is libsodium | ||
| 184 | `crypto_box_curve25519xchacha20poly1305` (X25519 key agreement, XChaCha20-Poly1305 | ||
| 185 | AEAD), the same construction as Rust's `crypto_box` ChaChaBox. | ||
| 186 | |||
| 187 | **Handshake (ephemeral to static).** | ||
| 188 | |||
| 189 | 1. The caller generates a fresh ephemeral X25519 keypair, computes | ||
| 190 | `shared = X25519(eph_sk, service_enc_pub)` reduced to a box key | ||
| 191 | (`crypto_box_beforenm`), and sends the 32 byte ephemeral public key over the | ||
| 192 | paired pipe. | ||
| 193 | 2. The service reads the ephemeral public key and computes the same | ||
| 194 | `shared = X25519(enc_sk, eph_pub)`. | ||
| 195 | |||
| 196 | Because the caller uses a new ephemeral key per connection, each session has an | ||
| 197 | independent key and forward secrecy against later compromise of the service | ||
| 198 | encryption key. | ||
| 199 | |||
| 200 | **Framing.** Every message is length prefixed: | ||
| 201 | |||
| 202 | ``` | ||
| 203 | frame = len[4] (big endian, length of the body that follows) | ||
| 204 | body = nonce[24] || ciphertext || mac[16] | ||
| 205 | ``` | ||
| 206 | |||
| 207 | The nonce is 24 random bytes per frame (XChaCha20), the MAC is 16 bytes | ||
| 208 | (Poly1305). A body shorter than `nonce + mac` or larger than 1 MiB is rejected. | ||
| 209 | Decryption that fails authentication drops the frame and tears the tunnel down. | ||
| 210 | |||
| 211 | The tunnel is transparent: the application is handed a local socket pair end and | ||
| 212 | reads and writes plaintext. Two pump threads move data between that socket and | ||
| 213 | the relay socket, encrypting outbound and decrypting inbound. | ||
| 214 | |||
| 215 | ## 8. End to end connection flow | ||
| 216 | |||
| 217 | To dial `X.btf`: | ||
| 218 | |||
| 219 | 1. Parse `X.btf` to the 32 byte identity key. | ||
| 220 | 2. Fetch and verify the descriptor over Nostr (kind 38501). Obtain | ||
| 221 | `meeting_node` and `enc`. | ||
| 222 | 3. Connect to `meeting_node`, send `'C' || X`, and wait for `0x01`. | ||
| 223 | 4. Generate an ephemeral X25519 key, derive the shared key against `enc`, send | ||
| 224 | the ephemeral public key. | ||
| 225 | 5. Exchange XChaCha20-Poly1305 frames. Hand the application a local socket that | ||
| 226 | speaks the ordinary peer protocol. | ||
| 227 | |||
| 228 | A service, symmetrically: registers at a relay (`'S' || own_pubkey`), publishes a | ||
| 229 | descriptor pointing at that relay, waits for `0x01`, reads the caller ephemeral | ||
| 230 | public key, derives the shared key with its encryption secret, and serves the | ||
| 231 | tunnel. | ||
| 232 | |||
| 233 | ## 9. Security properties | ||
| 234 | |||
| 235 | - **Address unforgeability.** The address is the identity key and the descriptor | ||
| 236 | is Schnorr signed by it, and the Nostr event is signed by it. A relay or Nostr | ||
| 237 | operator cannot publish a working descriptor for an address it does not own. | ||
| 238 | - **Relay confidentiality.** The relay forwards ciphertext and never holds the | ||
| 239 | channel key. It cannot read or modify traffic undetected; a tampered frame | ||
| 240 | fails the Poly1305 tag and the tunnel closes. | ||
| 241 | - **Caller anonymity to the service and relay.** The caller presents no identity | ||
| 242 | key, only the target it wants to reach. It connects outbound, so its address is | ||
| 243 | not published anywhere. | ||
| 244 | - **Forward secrecy.** The caller's per connection ephemeral key means a later | ||
| 245 | compromise of the service encryption key does not decrypt past sessions. | ||
| 246 | - **IP privacy.** Neither side needs a public IP or an inbound port. The observable | ||
| 247 | network fact is that each side has a TCP connection to a relay. | ||
| 248 | |||
| 249 | ## 10. Threat model and limitations | ||
| 250 | |||
| 251 | - **The relay is trusted for availability, not for confidentiality.** It can | ||
| 252 | refuse to pair, drop a connection, or go offline. This is why relays are | ||
| 253 | plural, auto discovered, and re-registration is periodic. It cannot read | ||
| 254 | traffic. | ||
| 255 | - **Traffic analysis is not addressed.** A network observer, or the relay, can | ||
| 256 | see connection timing and byte volumes. There is no padding or mixing. | ||
| 257 | - **Single slot registration causes churn.** One waiting slot per key per relay | ||
| 258 | means a race or a stale registration can briefly make a service unreachable at | ||
| 259 | that relay. Multiple relays and the wait timeout bound the damage, but this is | ||
| 260 | a known source of connection churn. | ||
| 261 | - **Descriptor freshness is best effort.** `created` is a self reported | ||
| 262 | timestamp; readers prefer the newest descriptor they have seen but there is no | ||
| 263 | global clock. | ||
| 264 | - **Discovery depends on public Nostr relays.** If the relays a node uses are all | ||
| 265 | unreachable, discovery stalls until one is reachable again. Relay TLS is | ||
| 266 | verified, which closes a man in the middle path on discovery, but availability | ||
| 267 | still rests on the relay set. | ||
| 268 | |||
| 269 | ## 11. Wire reference | ||
| 270 | |||
| 271 | | Item | Value | | ||
| 272 | |---|---| | ||
| 273 | | `.btf` Base32 alphabet | `abcdefghijklmnopqrstuvwxyz234567`, no padding | | ||
| 274 | | Address payload | `pubkey[32] || checksum[2]`, 55 Base32 chars | | ||
| 275 | | Address checksum | `SHA256(".btf-checksum-v1" || pubkey)[0..2]` | | ||
| 276 | | Descriptor domain sep | `.btf-descriptor-v2` | | ||
| 277 | | Descriptor version | `2` | | ||
| 278 | | Descriptor signature | BIP340 Schnorr, deterministic (no aux rand) | | ||
| 279 | | Nostr descriptor event | kind `38501`, `d = btf-descriptor-2` | | ||
| 280 | | Nostr relay event | kind `38502`, `d = btf-relay-2` | | ||
| 281 | | Nostr pool event | kind `38503`, `d = btf-pool-2` | | ||
| 282 | | Rendezvous port (default) | `8434` | | ||
| 283 | | Rendezvous header | `role[1] ('S'/'C') || pubkey[32]`, 33 bytes | | ||
| 284 | | Rendezvous pairing byte | `0x01` paired, `0x00` no service | | ||
| 285 | | Channel primitive | `crypto_box_curve25519xchacha20poly1305` | | ||
| 286 | | Nonce / MAC / key sizes | 24 / 16 / 32 bytes | | ||
| 287 | | Frame | `len[4 big endian] || nonce[24] || ct || mac[16]` | | ||
| 288 | | Max frame body | 1 MiB | | ||
diff --git a/docs/social.png b/docs/social.png index 19e2a3b..9a08b02 100644 --- a/docs/social.png +++ b/docs/social.png | |||
| Binary files differ | |||
diff --git a/docs/tor.md b/docs/tor.md new file mode 100644 index 0000000..087c822 --- /dev/null +++ b/docs/tor.md | |||
| @@ -0,0 +1,84 @@ | |||
| 1 | # Tor mode | ||
| 2 | |||
| 3 | Bitflash can route outbound discovery traffic through a local Tor SOCKS5 proxy. | ||
| 4 | This is transport plumbing only: it does not change consensus, wallet keys, | ||
| 5 | mining, `.btf` addresses, or the encrypted rendezvous protocol. | ||
| 6 | |||
| 7 | ## Client mode | ||
| 8 | |||
| 9 | Start Tor locally, then start Bitflash with: | ||
| 10 | |||
| 11 | ```bash | ||
| 12 | bitflash -tor | ||
| 13 | ``` | ||
| 14 | |||
| 15 | `-tor` is shorthand for: | ||
| 16 | |||
| 17 | ```bash | ||
| 18 | bitflash -socks=127.0.0.1:9050 | ||
| 19 | ``` | ||
| 20 | |||
| 21 | If Tor listens somewhere else: | ||
| 22 | |||
| 23 | ```bash | ||
| 24 | bitflash -tor=127.0.0.1:9150 | ||
| 25 | bitflash -tor=[::1]:9050 | ||
| 26 | ``` | ||
| 27 | |||
| 28 | When Tor mode is enabled: | ||
| 29 | |||
| 30 | - outbound Nostr relay dials use Tor; | ||
| 31 | - outbound `.btf` rendezvous relay dials use Tor; | ||
| 32 | - destination names are sent to Tor as SOCKS5 domain-name CONNECT requests, so | ||
| 33 | the local DNS resolver does not see them; | ||
| 34 | - direct `.onion` dials are refused unless a SOCKS5/Tor proxy is enabled; | ||
| 35 | - plain HTTP external-IP probes are skipped. | ||
| 36 | |||
| 37 | ## Onion rendezvous relay | ||
| 38 | |||
| 39 | An operator can expose a Bitflash rendezvous relay as a Tor hidden service. The | ||
| 40 | node does not create hidden services itself; Tor owns that configuration. | ||
| 41 | |||
| 42 | Example `torrc`: | ||
| 43 | |||
| 44 | ```text | ||
| 45 | HiddenServiceDir /var/lib/tor/bitflash-rendezvous/ | ||
| 46 | HiddenServicePort 8434 127.0.0.1:8434 | ||
| 47 | ``` | ||
| 48 | |||
| 49 | After restarting Tor, read the generated onion name: | ||
| 50 | |||
| 51 | ```bash | ||
| 52 | sudo cat /var/lib/tor/bitflash-rendezvous/hostname | ||
| 53 | ``` | ||
| 54 | |||
| 55 | Then run the relay on localhost/VPS as usual and announce the onion endpoint from | ||
| 56 | nodes that use Tor: | ||
| 57 | |||
| 58 | ```bash | ||
| 59 | bitflash -tor -rvrelay=exampleexampleexampleexampleexampleexampleexampleexample.onion:8434 | ||
| 60 | bitflash -tor -announcerelay=exampleexampleexampleexampleexampleexampleexampleexample.onion:8434 | ||
| 61 | ``` | ||
| 62 | |||
| 63 | For a pool operator that wants the pool descriptor to point at an onion | ||
| 64 | rendezvous relay, use both: | ||
| 65 | |||
| 66 | ```bash | ||
| 67 | bitflash -operator -tor \ | ||
| 68 | -rvrelay=exampleexampleexampleexampleexampleexampleexampleexample.onion:8434 \ | ||
| 69 | -announcerelay=exampleexampleexampleexampleexampleexampleexampleexample.onion:8434 | ||
| 70 | ``` | ||
| 71 | |||
| 72 | ## Full-node hidden service | ||
| 73 | |||
| 74 | The legacy P2P listener still binds to port `8433`. You can expose it through | ||
| 75 | Tor manually: | ||
| 76 | |||
| 77 | ```text | ||
| 78 | HiddenServiceDir /var/lib/tor/bitflash-node/ | ||
| 79 | HiddenServicePort 8433 127.0.0.1:8433 | ||
| 80 | ``` | ||
| 81 | |||
| 82 | Current automatic peer discovery is `.btf`/rendezvous based, not onion-address | ||
| 83 | gossip. Treat direct full-node onion access as an operator/admin tool unless a | ||
| 84 | future release adds first-class onion peer advertisements. | ||
diff --git a/docs/utxo-commitment.md b/docs/utxo-commitment.md new file mode 100644 index 0000000..ab43deb --- /dev/null +++ b/docs/utxo-commitment.md | |||
| @@ -0,0 +1,125 @@ | |||
| 1 | # Bitflash UTXO-set commitment | ||
| 2 | |||
| 3 | `scripts/verify-utxo-set.py` reconstructs a deterministic commitment to the | ||
| 4 | current UTXO set from local `blk*.dat` files. | ||
| 5 | |||
| 6 | This is intentionally not a consensus rule yet. It is an audit tool: independent | ||
| 7 | operators can run it against the same chain prefix and compare the resulting | ||
| 8 | root and canonical JSON hash. | ||
| 9 | |||
| 10 | ```bash | ||
| 11 | python3 scripts/verify-utxo-set.py --max-blocks 1000 --out utxo-report.json --json | ||
| 12 | ``` | ||
| 13 | |||
| 14 | Use `--max-blocks 0` or omit the option to scan every available block file: | ||
| 15 | |||
| 16 | ```bash | ||
| 17 | python3 scripts/verify-utxo-set.py --out utxo-report.json --json | ||
| 18 | ``` | ||
| 19 | |||
| 20 | ## What It Verifies | ||
| 21 | |||
| 22 | The verifier: | ||
| 23 | |||
| 24 | - parses Bitflash block files directly with the shared Python chain parser; | ||
| 25 | - streams `blk*.dat` records instead of loading full block files into memory; | ||
| 26 | - reconstructs the best chain rooted at the Bitflash genesis block; | ||
| 27 | - ignores side blocks that may also be present in `blk*.dat`; | ||
| 28 | - verifies every scanned block merkle root; | ||
| 29 | - applies spends and creates outputs in deterministic order; | ||
| 30 | - reports current UTXO count, current supply, mined coinbase total, fees, and | ||
| 31 | historical duplicate-output overwrites; | ||
| 32 | - computes a deterministic UTXO root. | ||
| 33 | |||
| 34 | ## Commitment Algorithm | ||
| 35 | |||
| 36 | The UTXO map is keyed by: | ||
| 37 | |||
| 38 | ```text | ||
| 39 | txid:vout | ||
| 40 | ``` | ||
| 41 | |||
| 42 | Keys are sorted lexicographically. Each leaf is: | ||
| 43 | |||
| 44 | ```text | ||
| 45 | SHA256("BTFUTXO1|" || outpoint || "|" || value_satoshis || "|" || | ||
| 46 | script_pub_key_hex || "|" || created_height || "|" || created_txid) | ||
| 47 | ``` | ||
| 48 | |||
| 49 | The tree is then reduced pairwise. If a level has an odd number of hashes, the | ||
| 50 | last hash is duplicated. Each parent is: | ||
| 51 | |||
| 52 | ```text | ||
| 53 | SHA256("BTFNODE1|" || left_hash || right_hash) | ||
| 54 | ``` | ||
| 55 | |||
| 56 | The final hash is the UTXO-set root. | ||
| 57 | |||
| 58 | ## Inclusion Proofs | ||
| 59 | |||
| 60 | After a root has been computed, `scripts/prove-utxo.py` can create a compact | ||
| 61 | inclusion proof for one unspent output: | ||
| 62 | |||
| 63 | ```bash | ||
| 64 | python3 scripts/prove-utxo.py TXID:VOUT --out utxo-proof.json --json | ||
| 65 | python3 scripts/verify-utxo-proof.py utxo-proof.json | ||
| 66 | ``` | ||
| 67 | |||
| 68 | The verifier does not need local block files. It recomputes the target leaf, | ||
| 69 | walks the sibling path, and compares the result with the expected root. See | ||
| 70 | [UTXO inclusion proofs](utxo-proofs.md). | ||
| 71 | |||
| 72 | ## Deterministic Output | ||
| 73 | |||
| 74 | `--json` and `--out` write canonical JSON with sorted keys, fixed separators, | ||
| 75 | ASCII escaping, and one trailing newline. It contains no local path and no | ||
| 76 | generation timestamp, so two auditors scanning the same chain prefix should | ||
| 77 | produce byte-identical reports. | ||
| 78 | |||
| 79 | ```bash | ||
| 80 | python3 scripts/verify-utxo-set.py --out utxo-report.json --json | ||
| 81 | sha256sum utxo-report.json | ||
| 82 | ``` | ||
| 83 | |||
| 84 | ## OpenTimestamps | ||
| 85 | |||
| 86 | The canonical JSON can be timestamped in Bitcoin without adding any dependency | ||
| 87 | to Bitflash consensus: | ||
| 88 | |||
| 89 | ```bash | ||
| 90 | ots stamp utxo-report.json | ||
| 91 | ots verify utxo-report.json.ots | ||
| 92 | ``` | ||
| 93 | |||
| 94 | ## Historical Duplicates | ||
| 95 | |||
| 96 | Bitflash inherited early Bitcoin behavior around duplicate transaction IDs. | ||
| 97 | The verifier reports duplicate-output overwrites as warnings and uses | ||
| 98 | last-write-wins semantics for the UTXO map, matching the legacy index model. | ||
| 99 | Those warnings are part of the report so auditors can see the historical debt | ||
| 100 | instead of relying on a polished summary. | ||
| 101 | |||
| 102 | For synthetic chains or future clean consensus ranges, auditors can make | ||
| 103 | duplicates a hard failure: | ||
| 104 | |||
| 105 | ```bash | ||
| 106 | python3 scripts/verify-utxo-set.py --strict-duplicates | ||
| 107 | ``` | ||
| 108 | |||
| 109 | The default remains compatible with the current historical chain because it has | ||
| 110 | known legacy duplicate-output overwrites. | ||
| 111 | |||
| 112 | ## Shared Parser | ||
| 113 | |||
| 114 | The audit scripts share `scripts/bitflash_chain.py` for block parsing, | ||
| 115 | main-chain selection, UTXO application, canonical JSON, and genesis | ||
| 116 | reconstruction. That keeps `verify-utxo-set.py`, `verify-fair-launch.py`, | ||
| 117 | `prove-utxo.py`, and the explorer on the same serialization rules. | ||
| 118 | |||
| 119 | ## Future Consensus Path | ||
| 120 | |||
| 121 | This tool is the low-risk first step toward consensus commitments. A future | ||
| 122 | hard-fork design can commit this root, or a Utreexo-style accumulator root, in | ||
| 123 | coinbase or block headers after a fixed activation height. Until then this | ||
| 124 | script provides reproducible supply and state commitments without changing what | ||
| 125 | nodes accept. | ||
diff --git a/docs/utxo-proofs.md b/docs/utxo-proofs.md new file mode 100644 index 0000000..4947c3c --- /dev/null +++ b/docs/utxo-proofs.md | |||
| @@ -0,0 +1,61 @@ | |||
| 1 | # Bitflash UTXO inclusion proofs | ||
| 2 | |||
| 3 | `scripts/prove-utxo.py` creates a deterministic inclusion proof for one | ||
| 4 | currently unspent output. `scripts/verify-utxo-proof.py` verifies that proof | ||
| 5 | against the UTXO root produced by `scripts/verify-utxo-set.py`. | ||
| 6 | |||
| 7 | This is intentionally outside consensus. It turns the audit root into something | ||
| 8 | useful for a specific coin without changing what nodes accept. | ||
| 9 | |||
| 10 | ## Create A Proof | ||
| 11 | |||
| 12 | Use an unspent output in `txid:vout` form: | ||
| 13 | |||
| 14 | ```bash | ||
| 15 | python3 scripts/prove-utxo.py TXID:VOUT --out utxo-proof.json --json | ||
| 16 | ``` | ||
| 17 | |||
| 18 | The proof contains: | ||
| 19 | |||
| 20 | - the target UTXO; | ||
| 21 | - its sorted leaf index; | ||
| 22 | - the leaf hash; | ||
| 23 | - the Merkle sibling path; | ||
| 24 | - the UTXO root and chain tip used to create it. | ||
| 25 | |||
| 26 | It does not contain the full UTXO set. | ||
| 27 | |||
| 28 | ## Verify A Proof | ||
| 29 | |||
| 30 | Verify against the root embedded in the proof: | ||
| 31 | |||
| 32 | ```bash | ||
| 33 | python3 scripts/verify-utxo-proof.py utxo-proof.json | ||
| 34 | ``` | ||
| 35 | |||
| 36 | Or verify against a root copied from another machine, dashboard, block explorer, | ||
| 37 | or future node export: | ||
| 38 | |||
| 39 | ```bash | ||
| 40 | python3 scripts/verify-utxo-proof.py utxo-proof.json \ | ||
| 41 | --root ROOT_FROM_REPORT_OR_EXPORT | ||
| 42 | ``` | ||
| 43 | |||
| 44 | The verifier does not need local `blk*.dat` files. It only needs the proof and | ||
| 45 | the expected root. | ||
| 46 | |||
| 47 | ## Scope | ||
| 48 | |||
| 49 | This first proof format supports inclusion only. Absence and range proofs are | ||
| 50 | left for a later design because they require a more careful commitment scheme | ||
| 51 | and are easier to get subtly wrong. | ||
| 52 | |||
| 53 | The proof uses the exact same leaf and node domains as the UTXO commitment: | ||
| 54 | |||
| 55 | ```text | ||
| 56 | BTFUTXO1 | ||
| 57 | BTFNODE1 | ||
| 58 | ``` | ||
| 59 | |||
| 60 | That keeps the verifier language-neutral and gives future implementations a | ||
| 61 | small, stable target. | ||