aboutsummaryrefslogtreecommitdiffstats
path: root/docs/MESHBAY_DESIGN.md
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-23 17:14:26 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-23 17:14:26 +0200
commit339cb427f886a0177014126bb684335837eff067 (patch)
tree5f79dc0df617be66287a06fc4f0c5dcc61ceb167 /docs/MESHBAY_DESIGN.md
parentcd85808c13926c89a97987d320ac26391eae3267 (diff)
downloadmeshbay-339cb427f886a0177014126bb684335837eff067.tar.gz
feat: the node signs its handshake challenge (MNP 3.4)
node_pk in handshake_challenge is now signed over the channel binding and both nonces, so a client can check the node key before a join rather than only at the ack. Both transports; the browser and the QUIC client refuse a wrong signature and treat an absent one as an older node. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'docs/MESHBAY_DESIGN.md')
-rw-r--r--docs/MESHBAY_DESIGN.md18
1 files changed, 14 insertions, 4 deletions
diff --git a/docs/MESHBAY_DESIGN.md b/docs/MESHBAY_DESIGN.md
index f42fa69..4b99fb4 100644
--- a/docs/MESHBAY_DESIGN.md
+++ b/docs/MESHBAY_DESIGN.md
@@ -16,7 +16,7 @@
> them — it names the invariant that holds today, not the incident that produced
> it. §13 is the register of those labels.
>
-> Wire versions at the time of writing: **MNP 3.3** (oldest peer accepted 3.0),
+> Wire versions at the time of writing: **MNP 3.4** (oldest peer accepted 3.0),
> **MHP 0.1**, packages **0.15.0**. The normative source for the wire format is
> `MESHBAY_NODE_PROTOCOL.md`; this document states the design the protocol
> serves, not its byte layout.
@@ -858,7 +858,7 @@ implementations of one security check is **C6** waiting to happen.
```
client → node handshake {token, group_id, nonce_c, v, v_min}
node authorize_token() JWT · scope · denylist · group_id · membership · hosting
-node → client handshake_challenge {nonce_s, node_pk}
+node → client handshake_challenge {nonce_s, node_pk, sig} sig: Ed25519 over the challenge (3.4)
── pre-proof window: bundle fetch, join ──
client → node handshake_response {proof}
node verify HMAC(GEK, client transcript)
@@ -876,6 +876,16 @@ client verify HMAC(GEK, node transcript) + Ed25519(node_p
Every field is length-prefixed so the concatenation is unambiguous, and the role
is bound in so a client proof can never be replayed as a node proof (**L4**).
+**Since 3.4 the node signs its challenge**, with the key it announces, over
+`"meshbay:mnp:challenge:v1" ‖ len‖group_id ‖ len‖nonce_c ‖ len‖nonce_s ‖ len‖binding`.
+A join is sent in the pre-proof window, before the ack that proves the node, so
+without it an invitation code went to whichever peer answered signaling. What it
+buys, per the convention at the top: a client that knows which node it means to
+reach can refuse to send a code anywhere else — against a hijacked signaling path
+and against a second host of the same group. It proves *a* key, not the *right*
+one: it helps only a client that already knows which key to expect. A wrong
+signature is refused; an absent one is an older node, discovered from its answer.
+
**Channel binding is mandatory and an absent one is refused** — never degraded to
nonce-only, which would silently drop MitM detection:
@@ -1159,8 +1169,8 @@ it**.
> the branches come back.**
**The floor is not the current version, and MINOR additions are why.** It is
-`MNP_MIN_SUPPORTED` in `handshake.py`, it equals the last MAJOR, and 3.1, 3.2 and
-3.3 have all been added above it without moving it. So a peer can be reachable and
+`MNP_MIN_SUPPORTED` in `handshake.py`, it equals the last MAJOR, and 3.1, 3.2,
+3.3 and 3.4 have all been added above it without moving it. So a peer can be reachable and
still not do something the current version can, and the client has to cope with
that — **by reading the peer's own answer, never by comparing version numbers**.
3.2's audio tracks are the worked example: the node lists them in `stream_init`,