diff options
| author | Nsable-sh <nsable-sh@noreply.git.bitflash.network> | 2026-08-13 11:11:59 -0300 |
|---|---|---|
| committer | Nsable-sh <nsable-sh@noreply.git.bitflash.network> | 2026-08-13 11:11:59 -0300 |
| commit | d2b86473a267c960a1f0c012a983176e5d962187 (patch) | |
| tree | 002423e5987a3c544f469befae83519ccb29fffb /docs | |
| parent | 851811b43be38bea48ba5d04d16987bd541dca53 (diff) | |
net: add signed direct onion peer endpoints
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/rendezvous.md | 70 | ||||
| -rw-r--r-- | docs/tor.md | 39 |
2 files changed, 93 insertions, 16 deletions
diff --git a/docs/rendezvous.md b/docs/rendezvous.md index ac8e598..e1888b9 100644 --- a/docs/rendezvous.md +++ b/docs/rendezvous.md | |||
| @@ -18,11 +18,16 @@ A connection to a `.btf` service has three layers: | |||
| 18 | 1. **Discovery (Nostr).** The service publishes a signed descriptor as a Nostr | 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 | 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 | 20 | signature. The descriptor says which rendezvous relay the service is waiting |
| 21 | at and which encryption key to use. | 21 | at, which encryption key to use, and optionally which Tor hidden service can |
| 22 | be dialed directly. | ||
| 22 | 2. **Rendezvous (TCP).** Caller and service both connect out to the same relay | 23 | 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 | 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 | After pairing the relay is a blind byte pipe. |
| 25 | 3. **Encrypted channel (X25519 + XChaCha20-Poly1305).** Over the paired pipe the | 26 | 3. **Direct onion fallback bypass.** If the descriptor carries a valid signed |
| 27 | `.onion:port` endpoint and the caller is running with Tor mode, the caller | ||
| 28 | tries that endpoint before using rendezvous. This removes the rendezvous | ||
| 29 | relay from the data path for peers that choose to run hidden services. | ||
| 30 | 4. **Encrypted channel (X25519 + XChaCha20-Poly1305).** Over the paired pipe the | ||
| 26 | caller and service run an ephemeral to static handshake and then exchange | 31 | caller and service run an ephemeral to static handshake and then exchange |
| 27 | authenticated frames. The relay sees only ciphertext. | 32 | authenticated frames. The relay sees only ciphertext. |
| 28 | 33 | ||
| @@ -107,6 +112,8 @@ The default rendezvous port is `8434`. | |||
| 107 | The descriptor is a JSON object, signed with the identity key over a domain | 112 | The descriptor is a JSON object, signed with the identity key over a domain |
| 108 | separated digest of its own fields. | 113 | separated digest of its own fields. |
| 109 | 114 | ||
| 115 | Legacy descriptor: | ||
| 116 | |||
| 110 | ```json | 117 | ```json |
| 111 | { | 118 | { |
| 112 | "v": 2, | 119 | "v": 2, |
| @@ -118,7 +125,22 @@ separated digest of its own fields. | |||
| 118 | } | 125 | } |
| 119 | ``` | 126 | ``` |
| 120 | 127 | ||
| 121 | The signed digest is: | 128 | Onion-capable descriptor: |
| 129 | |||
| 130 | ```json | ||
| 131 | { | ||
| 132 | "v": 3, | ||
| 133 | "pubkey": "<hex 32-byte x-only identity key>", | ||
| 134 | "enc": "<hex 32-byte X25519 public key>", | ||
| 135 | "meeting_node": "host:port", | ||
| 136 | "onion": "<56-char-v3-name>.onion:8433", | ||
| 137 | "created": <unix seconds>, | ||
| 138 | "sig": "<legacy v2 signature>", | ||
| 139 | "sig3": "<signature over the onion-capable digest>" | ||
| 140 | } | ||
| 141 | ``` | ||
| 142 | |||
| 143 | The legacy signed digest is: | ||
| 122 | 144 | ||
| 123 | ``` | 145 | ``` |
| 124 | digest = SHA256( ".btf-descriptor-v2" | 146 | digest = SHA256( ".btf-descriptor-v2" |
| @@ -128,17 +150,37 @@ digest = SHA256( ".btf-descriptor-v2" | |||
| 128 | || created (8 bytes, big endian) ) | 150 | || created (8 bytes, big endian) ) |
| 129 | ``` | 151 | ``` |
| 130 | 152 | ||
| 131 | The signature is a deterministic BIP340 Schnorr signature (no auxiliary | 153 | `sig` is kept on v3 descriptors so old nodes can still resolve and dial through |
| 132 | randomness) over `digest` by the identity key. | 154 | the rendezvous relay. The onion-capable digest is: |
| 155 | |||
| 156 | ``` | ||
| 157 | digest3 = SHA256( ".btf-descriptor-v3-onion" | ||
| 158 | || pubkey[32] | ||
| 159 | || enc (raw bytes of the string) | ||
| 160 | || meeting_node (raw bytes of the string) | ||
| 161 | || onion (raw bytes of the normalized string) | ||
| 162 | || created (8 bytes, big endian) ) | ||
| 163 | ``` | ||
| 164 | |||
| 165 | `sig` and `sig3` are deterministic BIP340 Schnorr signatures (no auxiliary | ||
| 166 | randomness) over their respective digests by the identity key. | ||
| 133 | 167 | ||
| 134 | **Verification** (untrusted input, must never throw): parse the JSON, require all | 168 | **Verification** (untrusted input, must never throw): parse the JSON, require all |
| 135 | fields with the right types, confirm `pubkey` equals the address being resolved, | 169 | 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 | 170 | recompute the digest, and verify the Schnorr signature. Any failure rejects the |
| 137 | descriptor. | 171 | descriptor. |
| 138 | 172 | ||
| 173 | If `onion` is present, readers accept it only when it is a normalized v3 onion | ||
| 174 | endpoint and `sig3` verifies. A bad or tampered onion field is ignored; the v2 | ||
| 175 | descriptor can still be used for rendezvous fallback if `sig` verifies. | ||
| 176 | |||
| 139 | `created` lets a reader prefer the freshest descriptor when more than one is | 177 | `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 | 178 | seen. A node without a direct onion endpoint only publishes a descriptor once it |
| 141 | `meeting_node`, so it never advertises a relay it cannot be reached at. | 179 | has actually registered at a `meeting_node`, so it never advertises a relay it |
| 180 | cannot be reached at. A node with a direct onion endpoint may publish before | ||
| 181 | rendezvous registration using `meeting_node = "rendezvous-pending"`; Tor peers | ||
| 182 | can still dial the signed onion endpoint, and rendezvous becomes fallback once | ||
| 183 | registration succeeds. | ||
| 142 | 184 | ||
| 143 | ## 6. Rendezvous transport | 185 | ## 6. Rendezvous transport |
| 144 | 186 | ||
| @@ -243,8 +285,9 @@ tunnel. | |||
| 243 | not published anywhere. | 285 | not published anywhere. |
| 244 | - **Forward secrecy.** The caller's per connection ephemeral key means a later | 286 | - **Forward secrecy.** The caller's per connection ephemeral key means a later |
| 245 | compromise of the service encryption key does not decrypt past sessions. | 287 | 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 | 288 | - **IP privacy.** Neither side needs a public IP or an inbound port for |
| 247 | network fact is that each side has a TCP connection to a relay. | 289 | rendezvous. With a signed `.onion` endpoint, the reachable side exposes only a |
| 290 | Tor hidden service and the relay is not in the peer connection path. | ||
| 248 | 291 | ||
| 249 | ## 10. Threat model and limitations | 292 | ## 10. Threat model and limitations |
| 250 | 293 | ||
| @@ -265,6 +308,9 @@ tunnel. | |||
| 265 | unreachable, discovery stalls until one is reachable again. Relay TLS is | 308 | 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 | 309 | verified, which closes a man in the middle path on discovery, but availability |
| 267 | still rests on the relay set. | 310 | still rests on the relay set. |
| 311 | - **Direct onion is not automatic Tor configuration.** Bitflash signs and | ||
| 312 | advertises a hidden-service endpoint, and Tor-mode peers can dial it. The Tor | ||
| 313 | daemon still owns hidden-service creation, private keys, rotation and uptime. | ||
| 268 | 314 | ||
| 269 | ## 11. Wire reference | 315 | ## 11. Wire reference |
| 270 | 316 | ||
| @@ -273,9 +319,9 @@ tunnel. | |||
| 273 | | `.btf` Base32 alphabet | `abcdefghijklmnopqrstuvwxyz234567`, no padding | | 319 | | `.btf` Base32 alphabet | `abcdefghijklmnopqrstuvwxyz234567`, no padding | |
| 274 | | Address payload | `pubkey[32] || checksum[2]`, 55 Base32 chars | | 320 | | Address payload | `pubkey[32] || checksum[2]`, 55 Base32 chars | |
| 275 | | Address checksum | `SHA256(".btf-checksum-v1" || pubkey)[0..2]` | | 321 | | Address checksum | `SHA256(".btf-checksum-v1" || pubkey)[0..2]` | |
| 276 | | Descriptor domain sep | `.btf-descriptor-v2` | | 322 | | Descriptor domain sep | `.btf-descriptor-v2`; optional onion sep `.btf-descriptor-v3-onion` | |
| 277 | | Descriptor version | `2` | | 323 | | Descriptor version | `2`; `3` when a signed onion endpoint is present | |
| 278 | | Descriptor signature | BIP340 Schnorr, deterministic (no aux rand) | | 324 | | Descriptor signature | BIP340 Schnorr, deterministic (no aux rand), v3 keeps `sig` plus `sig3` | |
| 279 | | Nostr descriptor event | kind `38501`, `d = btf-descriptor-2` | | 325 | | Nostr descriptor event | kind `38501`, `d = btf-descriptor-2` | |
| 280 | | Nostr relay event | kind `38502`, `d = btf-relay-2` | | 326 | | Nostr relay event | kind `38502`, `d = btf-relay-2` | |
| 281 | | Nostr pool event | kind `38503`, `d = btf-pool-2` | | 327 | | Nostr pool event | kind `38503`, `d = btf-pool-2` | |
diff --git a/docs/tor.md b/docs/tor.md index 69b29f1..b4f88f9 100644 --- a/docs/tor.md +++ b/docs/tor.md | |||
| @@ -40,6 +40,8 @@ When Tor mode is enabled: | |||
| 40 | 40 | ||
| 41 | - outbound Nostr relay dials use Tor; | 41 | - outbound Nostr relay dials use Tor; |
| 42 | - outbound `.btf` rendezvous relay dials use Tor; | 42 | - outbound `.btf` rendezvous relay dials use Tor; |
| 43 | - outbound direct peer dials prefer signed `.onion` endpoints when a peer | ||
| 44 | advertises one; | ||
| 43 | - destination names are sent to Tor as SOCKS5 domain-name CONNECT requests, so | 45 | - destination names are sent to Tor as SOCKS5 domain-name CONNECT requests, so |
| 44 | the local DNS resolver does not see them; | 46 | the local DNS resolver does not see them; |
| 45 | - direct `.onion` dials are refused unless a SOCKS5/Tor proxy is enabled; | 47 | - direct `.onion` dials are refused unless a SOCKS5/Tor proxy is enabled; |
| @@ -98,7 +100,7 @@ bitflash -operator -tor \ | |||
| 98 | -announcerelay=exampleexampleexampleexampleexampleexampleexampleexample.onion:8434 | 100 | -announcerelay=exampleexampleexampleexampleexampleexampleexampleexample.onion:8434 |
| 99 | ``` | 101 | ``` |
| 100 | 102 | ||
| 101 | ## Full-node hidden service | 103 | ## Direct full-node hidden service |
| 102 | 104 | ||
| 103 | The legacy P2P listener still binds to port `8433`. You can expose it through | 105 | The legacy P2P listener still binds to port `8433`. You can expose it through |
| 104 | Tor manually: | 106 | Tor manually: |
| @@ -108,6 +110,35 @@ HiddenServiceDir /var/lib/tor/bitflash-node/ | |||
| 108 | HiddenServicePort 8433 127.0.0.1:8433 | 110 | HiddenServicePort 8433 127.0.0.1:8433 |
| 109 | ``` | 111 | ``` |
| 110 | 112 | ||
| 111 | Current automatic peer discovery is `.btf`/rendezvous based, not onion-address | 113 | After restarting Tor, read the generated onion name: |
| 112 | gossip. Treat direct full-node onion access as an operator/admin tool unless a | 114 | |
| 113 | future release adds first-class onion peer advertisements. | 115 | ```bash |
| 116 | sudo cat /var/lib/tor/bitflash-node/hostname | ||
| 117 | ``` | ||
| 118 | |||
| 119 | Then start Bitflash with Tor outbound enabled and advertise that hidden service: | ||
| 120 | |||
| 121 | ```bash | ||
| 122 | bitflash -tor \ | ||
| 123 | -onionservice=exampleexampleexampleexampleexampleexampleexampleexample.onion:8433 | ||
| 124 | ``` | ||
| 125 | |||
| 126 | That onion endpoint is signed into this node's self-certifying `.btf` | ||
| 127 | descriptor. New peers that resolve the descriptor and also run with `-tor` try | ||
| 128 | the `.onion:8433` P2P connection first. If it fails, they fall back to the | ||
| 129 | ordinary encrypted rendezvous path. Older nodes ignore the onion field and still | ||
| 130 | use the rendezvous relay because the descriptor keeps the legacy signature too. | ||
| 131 | |||
| 132 | If the node has `-onionservice` but has not registered at any rendezvous relay | ||
| 133 | yet, it can still publish an onion-capable descriptor with | ||
| 134 | `meeting_node = rendezvous-pending`. Tor-capable peers can dial the hidden | ||
| 135 | service directly from that descriptor. Rendezvous becomes a fallback, not a | ||
| 136 | precondition for being discoverable. | ||
| 137 | |||
| 138 | This means rendezvous relays stop being the only way to reach a node that has | ||
| 139 | published a hidden service. They remain useful bootstrap/fallback infrastructure, | ||
| 140 | but an already-discovered onion-capable peer can be reached directly over Tor. | ||
| 141 | |||
| 142 | The node does not create or rotate Tor hidden services itself. Tor owns the | ||
| 143 | private key under `HiddenServiceDir`; back it up if you want the onion name to | ||
| 144 | stay stable. | ||