upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
Diffstat (limited to 'docs')
-rw-r--r--docs/dependencies.md18
-rw-r--r--docs/fair-launch.md70
-rw-r--r--docs/mining-calculator.md86
-rw-r--r--docs/pool-directory.md43
-rw-r--r--docs/release-verification.md78
-rw-r--r--docs/rendezvous.md288
-rw-r--r--docs/social.pngbin208957 -> 162267 bytes
-rw-r--r--docs/tor.md84
-rw-r--r--docs/utxo-commitment.md125
-rw-r--r--docs/utxo-proofs.md61
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
3Bitflash links consensus-adjacent cryptographic and proof-of-work code from
4external repositories. Fresh builds must not follow upstream `master` silently:
5a changed upstream branch can break reproducible builds or change behavior in
6code that every node relies on.
7
8The 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
15When updating either dependency, rebuild from a clean dependency directory and
16run the full selftest suite before opening the PR. The PR should explain why the
17dependency changed and whether the update can affect consensus, addresses,
18signatures, 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
3Bitflash ships a deterministic fair-launch audit script:
4
5```bash
6python3 scripts/verify-fair-launch.py --max-blocks 1000 --out bitflash-fair-launch-report.json --json
7```
8
9The verifier reads `blk*.dat` directly from the local Bitflash data directory.
10It does not use Berkeley DB, does not call the node binary, and does not need
11network access. It uses the same shared Python chain parser as the UTXO
12commitment and proof tools, so block serialization fixes land in one place.
13
14The 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
22For machines that do not have a synced data directory yet, the source constants
23can be checked without disk blocks:
24
25```bash
26python3 scripts/verify-fair-launch.py --reconstruct-genesis
27```
28
29## Deterministic output
30
31Use `--json` or `--out` for canonical JSON. The JSON is written with sorted
32keys, fixed separators, ASCII escaping, and one trailing newline. It deliberately
33does not include local paths or wall-clock generation time, so two auditors
34scanning the same block range produce the same bytes.
35
36That makes the report shareable:
37
38```bash
39python3 scripts/verify-fair-launch.py --max-blocks 1000 --out report.json --json
40sha256sum report.json
41```
42
43If another auditor runs the same command against the same chain prefix, the
44`sha256sum` should match.
45
46## OpenTimestamps
47
48OpenTimestamps is optional and external. It anchors the report in Bitcoin
49without adding any bridge or dependency to Bitflash consensus.
50
51After generating a canonical JSON report:
52
53```bash
54ots stamp report.json
55ots verify report.json.ots
56```
57
58The `.ots` file proves that the exact report bytes existed no later than the
59Bitcoin block that confirms the timestamp.
60
61## What this does not claim
62
63This is not a retroactive genesis commitment. The Bitflash genesis block already
64exists, so adding a new on-chain commitment to genesis would create a different
65chain. This verifier instead audits the launch state that is already on-chain
66and makes the result reproducible and timestampable.
67
68It also does not prove real-world miner identity. It proves the genesis state,
69the absence of non-coinbase premine in genesis, and the deterministic coinbase
70totals 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
4for Bitflash.
5
6It does not fetch market prices, does not talk to exchanges, and does not
7change consensus. Price is a manual input until Bitflash has a reliable public
8market.
9
10## Examples
11
12Estimate from network hashrate:
13
14```bash
15python3 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
22Estimate from Bitflash difficulty:
23
24```bash
25python3 scripts/bitflash-mining-calculator.py \
26 --hashrate 21 --hashrate-unit kh/s \
27 --difficulty 45600 \
28 --watts 140 --kwh-cost 0.10
29```
30
31Machine-readable output:
32
33```bash
34python3 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
42Bitflash does not use Bitcoin's SHA256 difficulty-1 constant for pool shares.
43The chain's easiest RandomX target is `bnProofOfWorkLimit = ~uint256(0) >> 12`,
44so difficulty 1 expects about `4096` hashes.
45
46The calculator uses:
47
48```text
49network_hashrate = difficulty * 4096 / block_time_seconds
50```
51
52At the default two-minute block target:
53
54```text
55network_hashrate = difficulty * 4096 / 120
56```
57
58If you already know network hashrate, pass `--network-hashrate` directly and
59the script derives the equivalent Bitflash difficulty for display.
60
61## Reward Estimate
62
63The expected daily reward is:
64
65```text
66blocks_per_day = 86400 / block_time_seconds
67miner_share = miner_hashrate / network_hashrate
68gross_btf_per_day = blocks_per_day * block_reward * miner_share
69net_btf_per_day = gross_btf_per_day * (1 - pool_fee_percent / 100)
70```
71
72Electricity is:
73
74```text
75electricity_per_day = watts / 1000 * 24 * kwh_cost
76```
77
78This is an expectation, not a promise. Solo mining variance can be very large,
79especially when the miner has a small share of the network.
80
81## Future Website Use
82
83The script is intentionally standalone so the same math can later back a
84website widget or status dashboard. Until a public price exists, a website
85calculator should keep price as a manual field and label fiat profit as
86speculative.
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.
4Nodes discover pools through Nostr and `.btf`; the website only helps humans
5compare operators.
6
7Operators can publish the `pool_status.json` and `pool_rounds.json` files
8produced by:
9
10```bash
11bitflash -nogui -gen -operator \
12 -poolstatusfile=/var/www/pool_status.json \
13 -poolroundsfile=/var/www/pool_rounds.json
14```
15
16Build a static directory from one or more status files:
17
18```bash
19python3 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
24The generated output contains:
25
26- `index.html`: human-readable ranking page.
27- `pools.json`: machine-readable API for `api.bitflash.network/pools.json`.
28
29When 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,
31block hash, coinbase txid, coinbase value, pool fee, total shares, per-address
32share counts, computed payouts, and payout txids once paid.
33
34Ranking order is intentionally simple:
35
361. online pools first;
372. higher reported hashrate;
383. more authorized miners;
394. lower fee;
405. name as a stable tie-breaker.
41
42An entry is considered online when the source says the pool is running and its
43status 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 @@
3Bitflash releases publish binaries and a `SHA256SUMS` file. The checksum file 3Bitflash releases publish binaries and a `SHA256SUMS` file. The checksum file
4proves that the file you downloaded matches the file the release page names. 4proves that the file you downloaded matches the file the release page names.
5A detached signature, `SHA256SUMS.asc`, proves that the checksum file itself was 5A detached signature, `SHA256SUMS.asc`, proves that the checksum file itself was
6signed by a trusted release key. 6signed by the Bitflash release key.
7 7
8That gives users two separate checks: 8That gives users two separate checks:
9 9
101. `gpg --verify SHA256SUMS.asc SHA256SUMS` checks who signed the checksums. 101. `gpg --verify SHA256SUMS.asc SHA256SUMS` — who signed the checksums.
112. `sha256sum -c SHA256SUMS` checks the binaries against those checksums. 112. `sha256sum -c SHA256SUMS` — the binaries against those checksums.
12 12
13## User Check 13But the first check is only worth something if you require a **specific** key.
14A good signature by *some* key proves nothing; anyone can make one.
14 15
15From a shell with `curl` or `wget` and `sha256sum`: 16## The release signing key
16 17
17```bash 18```text
18scripts/verify-release.sh latest 19fingerprint 910A 2B4C CA87 9E81 FB4B 2AEA 1D4A 53D3 B78A A4B8
20uid Bitflash Releases (bitflash.network release signing)
21 <releases@bitflash.network>
19``` 22```
20 23
21For a specific release: 24The 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
27repository rather than on the server it verifies.
28
29## User check
30
31From a shell with `curl` or `wget`, `sha256sum` and `gpg`:
22 32
23```bash 33```bash
24scripts/verify-release.sh v1.2.13 34scripts/verify-release.sh latest
25``` 35```
26 36
27For release audits, require the signature: 37For a specific release:
28 38
29```bash 39```bash
30scripts/verify-release.sh v1.2.13 --require-signature 40scripts/verify-release.sh v1.2.15
31``` 41```
32 42
33Older releases may not have `SHA256SUMS.asc`. In that case the script warns and 43The script downloads the release, imports the pinned key into a throwaway
34still checks file integrity. New release audits should use `--require-signature`. 44keyring, and **refuses anything that is not a valid signature by the key above**
35 45before checking the hashes. Releases from before signing was introduced ship
36## Maintainer Flow 46only `SHA256SUMS`; without `--require-signature` those are checked for integrity
47and reported as unsigned.
37 48
38After building release assets in the repository root: 49By hand, if you would rather not run the script:
39 50
40```bash 51```bash
41scripts/make-release-checksums.sh --sign --local-user RELEASE_KEY_ID 52curl -O https://releases.bitflash.network/bitflash-release-key.asc
53gpg --import bitflash-release-key.asc
54gpg --verify SHA256SUMS.asc SHA256SUMS # must name the fingerprint above
55sha256sum -c SHA256SUMS
42``` 56```
43 57
44Upload all built assets plus: 58## Maintainer flow
45 59
46```text 60The signing key lives on a dedicated location, not in the build tree. After
47SHA256SUMS 61building the release assets in the repository root, write and sign the
48SHA256SUMS.asc 62checksums:
63
64```bash
65sha256sum Bitflash-*.zip Bitflash-*.AppImage bitflash-node-* > SHA256SUMS
66gpg --armor --detach-sign --output SHA256SUMS.asc SHA256SUMS
49``` 67```
50 68
51Keep the private signing key offline or on a dedicated release machine. Publish 69Publish, alongside the assets, `SHA256SUMS` and `SHA256SUMS.asc`, and update
52the 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
53future releases unless there is a clearly announced rotation. 71is ever rotated, announce the new fingerprint clearly and update the embedded
72key 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
58replacing both a binary and the checksum file. Signing `SHA256SUMS` means an 77replacing both a binary and the checksum file. Signing `SHA256SUMS`, and
59attacker must also have the release signing key to make the replacement verify. 78requiring a pinned key, means an attacker must also hold the release signing key
79to make the replacement verify. Serving from bitflash.network makes this the
80guarantee that matters: without a pinned key, "trust the site" is the only
81thing standing behind a download.
60 82
61This is not reproducible builds yet. It is the smaller, immediate step that 83This is not reproducible builds yet. It is the smaller, immediate step that
62makes every release asset auditable by users before they run it. 84makes 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
3This is the specification of how Bitflash nodes reach each other, and how miners
4reach pools, without revealing an IP address and without any central directory.
5It documents the protocol as implemented in `src/btfaddr.cpp`, `src/btfchan.cpp`,
6`src/btfrv.cpp`, `src/btftunnel.cpp` and `src/nostr.cpp`.
7
8The design is ported from the Itzal hidden service design. A `.btf` address names
9a public key, a node proves ownership of that key by signing a service
10descriptor, other nodes find the descriptor over Nostr, and the two sides then
11meet through an untrusted relay over an end to end encrypted tunnel. The relay
12carries ciphertext only and never learns the plaintext.
13
14## 1. Overview
15
16A connection to a `.btf` service has three layers:
17
181. **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.
222. **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.
253. **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
29The caller ends up holding an ordinary local socket that behaves like a direct
30TCP connection to the peer. The node treats it exactly like any other peer
31socket.
32
33## 2. Identities and keys
34
35A 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
44The identity key names the service and authenticates the descriptor. The
45encryption key secures the tunnel. They are separate so the long term identity
46never performs Diffie-Hellman and the encryption key can be rotated in a new
47signed descriptor.
48
49## 3. The `.btf` address
50
51An address is the identity public key plus a short checksum, Base32 encoded, with
52the suffix `.btf`.
53
54```
55label = Base32( pubkey[32] || checksum[2] )
56address = 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
66The address is self certifying: it *is* the public key, so no registry has to be
67trusted to map a name to a key.
68
69## 4. Discovery over Nostr
70
71Nodes publish and read three kinds of parameterized replaceable Nostr events.
72Parameterized replaceable means the newest event for a given author and `d` tag
73supersedes 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
81The Nostr event is signed with the node's identity key, so the event author
82equals the `.btf` public key. That binds the descriptor to the address at the
83Nostr layer, and the descriptor is *also* signed inside its own content (see
84below), so a relay operator cannot forge or swap a descriptor for an address it
85does not control.
86
87**Resolving an address.** To reach `X.btf`, a caller parses the address to the 32
88byte 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
90uses its `meeting_node` and `enc`.
91
92**Relay auto discovery.** A relay operator runs a rendezvous relay and publishes a
93kind 38502 event carrying `host:port`. Every node that reads it adds the relay to
94the set it can register at and dial through. No seed list edit or maintainer
95approval is needed. A small curated seed list is still shipped for bootstrap:
96
97```
9892.246.128.180:8434
9931.44.4.249:8434
10090.156.222.107:8434
101```
102
103The default rendezvous port is `8434`.
104
105## 5. The service descriptor
106
107The descriptor is a JSON object, signed with the identity key over a domain
108separated 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
121The signed digest is:
122
123```
124digest = 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
131The signature is a deterministic BIP340 Schnorr signature (no auxiliary
132randomness) over `digest` by the identity key.
133
134**Verification** (untrusted input, must never throw): parse the JSON, require all
135fields with the right types, confirm `pubkey` equals the address being resolved,
136recompute the digest, and verify the Schnorr signature. Any failure rejects the
137descriptor.
138
139`created` lets a reader prefer the freshest descriptor when more than one is
140seen. 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
145The relay is a small TCP server. Every connection opens with a fixed 33 byte
146header:
147
148```
149byte 0 role: 'S' (service) or 'C' (client)
150bytes 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
155public key and leaves it open, waiting. There is one slot per key: a new
156registration for the same key replaces and closes the old one. The service does
157not block waiting to be paired at registration time; it is considered registered
158the moment the relay reads the header, which is what lets it publish a descriptor
159pointing 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
169Both sides connect *outbound* to the relay, so neither needs a public IP or an
170open inbound port. The relay never sees the identity of the caller (the caller
171sends 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
174path that created it (a NAT drops the idle mapping, the relay restarts, a route
175changes) with no FIN and nothing readable. TCP keepalive was measured and does
176not reliably detect this. The service therefore imposes its own timeout on the
177wait for the pairing byte and re-registers on expiry. Expiring is not a failure;
178it costs one reconnect per interval and returns the loop to a known state.
179
180## 7. The encrypted channel
181
182Once paired through the relay, the caller and service run an end to end handshake
183and then exchange authenticated frames. The primitive is libsodium
184`crypto_box_curve25519xchacha20poly1305` (X25519 key agreement, XChaCha20-Poly1305
185AEAD), the same construction as Rust's `crypto_box` ChaChaBox.
186
187**Handshake (ephemeral to static).**
188
1891. 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.
1932. The service reads the ephemeral public key and computes the same
194 `shared = X25519(enc_sk, eph_pub)`.
195
196Because the caller uses a new ephemeral key per connection, each session has an
197independent key and forward secrecy against later compromise of the service
198encryption key.
199
200**Framing.** Every message is length prefixed:
201
202```
203frame = len[4] (big endian, length of the body that follows)
204 body = nonce[24] || ciphertext || mac[16]
205```
206
207The 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.
209Decryption that fails authentication drops the frame and tears the tunnel down.
210
211The tunnel is transparent: the application is handed a local socket pair end and
212reads and writes plaintext. Two pump threads move data between that socket and
213the relay socket, encrypting outbound and decrypting inbound.
214
215## 8. End to end connection flow
216
217To dial `X.btf`:
218
2191. Parse `X.btf` to the 32 byte identity key.
2202. Fetch and verify the descriptor over Nostr (kind 38501). Obtain
221 `meeting_node` and `enc`.
2223. Connect to `meeting_node`, send `'C' || X`, and wait for `0x01`.
2234. Generate an ephemeral X25519 key, derive the shared key against `enc`, send
224 the ephemeral public key.
2255. Exchange XChaCha20-Poly1305 frames. Hand the application a local socket that
226 speaks the ordinary peer protocol.
227
228A service, symmetrically: registers at a relay (`'S' || own_pubkey`), publishes a
229descriptor pointing at that relay, waits for `0x01`, reads the caller ephemeral
230public key, derives the shared key with its encryption secret, and serves the
231tunnel.
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
3Bitflash can route outbound discovery traffic through a local Tor SOCKS5 proxy.
4This is transport plumbing only: it does not change consensus, wallet keys,
5mining, `.btf` addresses, or the encrypted rendezvous protocol.
6
7## Client mode
8
9Start Tor locally, then start Bitflash with:
10
11```bash
12bitflash -tor
13```
14
15`-tor` is shorthand for:
16
17```bash
18bitflash -socks=127.0.0.1:9050
19```
20
21If Tor listens somewhere else:
22
23```bash
24bitflash -tor=127.0.0.1:9150
25bitflash -tor=[::1]:9050
26```
27
28When 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
39An operator can expose a Bitflash rendezvous relay as a Tor hidden service. The
40node does not create hidden services itself; Tor owns that configuration.
41
42Example `torrc`:
43
44```text
45HiddenServiceDir /var/lib/tor/bitflash-rendezvous/
46HiddenServicePort 8434 127.0.0.1:8434
47```
48
49After restarting Tor, read the generated onion name:
50
51```bash
52sudo cat /var/lib/tor/bitflash-rendezvous/hostname
53```
54
55Then run the relay on localhost/VPS as usual and announce the onion endpoint from
56nodes that use Tor:
57
58```bash
59bitflash -tor -rvrelay=exampleexampleexampleexampleexampleexampleexampleexample.onion:8434
60bitflash -tor -announcerelay=exampleexampleexampleexampleexampleexampleexampleexample.onion:8434
61```
62
63For a pool operator that wants the pool descriptor to point at an onion
64rendezvous relay, use both:
65
66```bash
67bitflash -operator -tor \
68 -rvrelay=exampleexampleexampleexampleexampleexampleexampleexample.onion:8434 \
69 -announcerelay=exampleexampleexampleexampleexampleexampleexampleexample.onion:8434
70```
71
72## Full-node hidden service
73
74The legacy P2P listener still binds to port `8433`. You can expose it through
75Tor manually:
76
77```text
78HiddenServiceDir /var/lib/tor/bitflash-node/
79HiddenServicePort 8433 127.0.0.1:8433
80```
81
82Current automatic peer discovery is `.btf`/rendezvous based, not onion-address
83gossip. Treat direct full-node onion access as an operator/admin tool unless a
84future 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
4current UTXO set from local `blk*.dat` files.
5
6This is intentionally not a consensus rule yet. It is an audit tool: independent
7operators can run it against the same chain prefix and compare the resulting
8root and canonical JSON hash.
9
10```bash
11python3 scripts/verify-utxo-set.py --max-blocks 1000 --out utxo-report.json --json
12```
13
14Use `--max-blocks 0` or omit the option to scan every available block file:
15
16```bash
17python3 scripts/verify-utxo-set.py --out utxo-report.json --json
18```
19
20## What It Verifies
21
22The 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
36The UTXO map is keyed by:
37
38```text
39txid:vout
40```
41
42Keys are sorted lexicographically. Each leaf is:
43
44```text
45SHA256("BTFUTXO1|" || outpoint || "|" || value_satoshis || "|" ||
46 script_pub_key_hex || "|" || created_height || "|" || created_txid)
47```
48
49The tree is then reduced pairwise. If a level has an odd number of hashes, the
50last hash is duplicated. Each parent is:
51
52```text
53SHA256("BTFNODE1|" || left_hash || right_hash)
54```
55
56The final hash is the UTXO-set root.
57
58## Inclusion Proofs
59
60After a root has been computed, `scripts/prove-utxo.py` can create a compact
61inclusion proof for one unspent output:
62
63```bash
64python3 scripts/prove-utxo.py TXID:VOUT --out utxo-proof.json --json
65python3 scripts/verify-utxo-proof.py utxo-proof.json
66```
67
68The verifier does not need local block files. It recomputes the target leaf,
69walks 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,
75ASCII escaping, and one trailing newline. It contains no local path and no
76generation timestamp, so two auditors scanning the same chain prefix should
77produce byte-identical reports.
78
79```bash
80python3 scripts/verify-utxo-set.py --out utxo-report.json --json
81sha256sum utxo-report.json
82```
83
84## OpenTimestamps
85
86The canonical JSON can be timestamped in Bitcoin without adding any dependency
87to Bitflash consensus:
88
89```bash
90ots stamp utxo-report.json
91ots verify utxo-report.json.ots
92```
93
94## Historical Duplicates
95
96Bitflash inherited early Bitcoin behavior around duplicate transaction IDs.
97The verifier reports duplicate-output overwrites as warnings and uses
98last-write-wins semantics for the UTXO map, matching the legacy index model.
99Those warnings are part of the report so auditors can see the historical debt
100instead of relying on a polished summary.
101
102For synthetic chains or future clean consensus ranges, auditors can make
103duplicates a hard failure:
104
105```bash
106python3 scripts/verify-utxo-set.py --strict-duplicates
107```
108
109The default remains compatible with the current historical chain because it has
110known legacy duplicate-output overwrites.
111
112## Shared Parser
113
114The audit scripts share `scripts/bitflash_chain.py` for block parsing,
115main-chain selection, UTXO application, canonical JSON, and genesis
116reconstruction. 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
121This tool is the low-risk first step toward consensus commitments. A future
122hard-fork design can commit this root, or a Utreexo-style accumulator root, in
123coinbase or block headers after a fixed activation height. Until then this
124script provides reproducible supply and state commitments without changing what
125nodes 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
4currently unspent output. `scripts/verify-utxo-proof.py` verifies that proof
5against the UTXO root produced by `scripts/verify-utxo-set.py`.
6
7This is intentionally outside consensus. It turns the audit root into something
8useful for a specific coin without changing what nodes accept.
9
10## Create A Proof
11
12Use an unspent output in `txid:vout` form:
13
14```bash
15python3 scripts/prove-utxo.py TXID:VOUT --out utxo-proof.json --json
16```
17
18The 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
26It does not contain the full UTXO set.
27
28## Verify A Proof
29
30Verify against the root embedded in the proof:
31
32```bash
33python3 scripts/verify-utxo-proof.py utxo-proof.json
34```
35
36Or verify against a root copied from another machine, dashboard, block explorer,
37or future node export:
38
39```bash
40python3 scripts/verify-utxo-proof.py utxo-proof.json \
41 --root ROOT_FROM_REPORT_OR_EXPORT
42```
43
44The verifier does not need local `blk*.dat` files. It only needs the proof and
45the expected root.
46
47## Scope
48
49This first proof format supports inclusion only. Absence and range proofs are
50left for a later design because they require a more careful commitment scheme
51and are easier to get subtly wrong.
52
53The proof uses the exact same leaf and node domains as the UTXO commitment:
54
55```text
56BTFUTXO1
57BTFNODE1
58```
59
60That keeps the verifier language-neutral and gives future implementations a
61small, stable target.