From 9fa2117de1caf4d713cc0b7a310b9549467738c3 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Fri, 14 Aug 2026 14:43:48 +0200 Subject: docs: Argon2id, the multi-browser property, and what a browser found MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit draft-v5 §7 rewritten around the keypair bundle, because that is where the last open finding actually lives. New §7.1 states the adversary (an operator holding their own node's disk), what cracking a bundle yields (identity keys, hence content on *other* nodes and the ability to sign as that user — not the content they host in the clear by design), and the measured numbers rather than adjectives: PBKDF2 241 ms vs Argon2id 88 ms natively, a GPU ceiling moving from ~8k to ~2k guesses/s, six days for a 10⁹ dictionary run, four random words outlasting the sun. The honest summary is in there too — a factor of four on one card, not a thousand; what it buys is the cost of scale. §2 gains the row the table never had: **your identity keys stay yours**, ⚠️ against a malicious node operator. An operator hosts your content by design, and that was documented; that they can also try to become *you* was not. That is the difference between reading what they host and reading what other operators host. §4 records that the challenge now carries `node_pk`, why (a first-time member signs a transcript naming the node and has no GEK to complete a handshake with), and that it is checked against the ack rather than trusted. Also that refusals carry a code, and what `not_a_member` usually means. §8.1 states the multi-browser property plainly — one identity across browsers, recovered with the passphrase, no second code — together with its cost, since it is the same mechanism as C4. invite-pairing-v1 is no longer "a proposal": it shipped. §9bis gains the four browser-found failures and their common thread — e2e.py is a second implementation of the client, written in the right order by construction, so it proves the protocol and nothing about app.js. CLAUDE.md gets the two things a future session must not rediscover the hard way: the KDF parameters live in three places held identical by a parity test, and an unbounded await on the hub socket makes a node silently unreachable (three found). second-review: C4 marked reduced, not closed. devel-phases-next: 12.2's CSP must keep `wasm-unsafe-eval`, or the strict policy locks every user out of their keys. Co-Authored-By: Claude Opus 5 --- docs/invite-pairing-v1.md | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) (limited to 'docs/invite-pairing-v1.md') diff --git a/docs/invite-pairing-v1.md b/docs/invite-pairing-v1.md index 56fccbe..290c756 100644 --- a/docs/invite-pairing-v1.md +++ b/docs/invite-pairing-v1.md @@ -1,6 +1,8 @@ # MeshBay — Invitation and Pairing (design) -> Status: **proposal, not implemented.** Written 2026-08-13. +> Status: **implemented and deployed** (2026-08-14). Written 2026-08-13 as a proposal; +> §9bis records what shipped, what departed from the plan, and what a browser found +> that the automated run could not. > Supersedes the invite flow described in `meshbay-draft-v5.md` §5.1 and the > Phase 12.1 milestone in `devel-phases-next.md`, if adopted. > @@ -86,7 +88,7 @@ signature ceremony, no operator required to be online when someone joins. ``` operator (SSH) meshbay-node operator pair node prints PAIR-CODE: K7M2-QX4P (also written to data_dir/pair-code) -operator (SPA) Settings → "Pair this browser with my node" → types the code +operator (SPA) group → Members tab → "Pair this browser" → types the code SPA → node join_request {role_hint: operator, code, pk_ed25519, pk_x25519, sig} node code valid, unused, unexpired → pins the keys, role = operator node writes the pin to its roster DB, prints it in `status` @@ -120,7 +122,7 @@ platform. ``` bob (SPA) opens the group; client has no GEK for it bob → node handshake {token, group_id, nonce_c} (pre-proof window) -node → bob handshake_challenge {nonce_s} +node → bob handshake_challenge {nonce_s, node_pk} ← bob signs over both bob → node join_request {group_id, pk_ed25519, pk_x25519, code, sig} node 1. rate-limit + attempt count on this connection 2. code matches a pending invite for this user_id and group @@ -414,6 +416,24 @@ DataChannel, the operator gets a code, and a member who has never held the group key redeems it in the pre-proof window and receives the key wrapped for a key only they can open. +### What a browser found that the harness could not + +The design was exercised end to end by `QE/deploy/e2e.py` and passed. It was then tried in +a browser and failed four times, each for a different reason, none of them the pairing code: + +| Symptom | Cause | +|---|---| +| No code ever requested, "no GEK available" | the browser held no identity keys — session restore never re-read the encrypted bundle sitting in its own localStorage | +| "No nodes available" | the node had gone deaf: its hub socket sat in CLOSE-WAIT because the WebRTC offer handler was awaited inside the read loop (a Phase 11 bug) | +| "Not a member of this group" | the token predated being added to the group; `groups` is baked in at sign-in, and nothing refreshed it | +| "Handshake incomplete — reconnect and retry" | `joinGroup()` read `node_pk` and `nonce_node` before they were assigned | + +The common thread is worth keeping: **e2e.py is a second implementation of the client**, +written in the right order by construction, so it proves the protocol and nothing about +`app.js`. Three of those four were ordering or plumbing mistakes it could never see. Hence +`packages/meshbay-hub/tests/test_spa_ordering.py`, which asserts on source order — inelegant, +and the only thing that catches them. + ### Deliberate departures from this document | Written | Built | Why | -- cgit v1.2.3