From 28c88908b88c58cba4de08710f6bb268ede02d57 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Wed, 14 Oct 2026 22:20:15 +0200 Subject: feat(node): accept a device link request before the proof (MNP 6.3) A new device of a known account holds no identity the node pinned, so it cannot complete a handshake until a pinned device approves it: it now files device_add_request in the pre-proof window, as the desktop app's case already required. The request stays inert until countersigned; at most 10 are pending per account, the oldest dropped. A 6.2 node answers it "Handshake required", which a client reads as "cannot link here". Co-Authored-By: Claude Opus 5.5 --- docs/MESHBAY_NODE_PROTOCOL.md | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) (limited to 'docs/MESHBAY_NODE_PROTOCOL.md') diff --git a/docs/MESHBAY_NODE_PROTOCOL.md b/docs/MESHBAY_NODE_PROTOCOL.md index 207d640..8372ee7 100644 --- a/docs/MESHBAY_NODE_PROTOCOL.md +++ b/docs/MESHBAY_NODE_PROTOCOL.md @@ -625,20 +625,21 @@ operator's real choice (I8). ## 7. The pre-proof window Between `handshake_challenge` and a valid `handshake_response` the peer is -*authorized* but not *authenticated*. Three message families are served there, each +*authorized* but not *authenticated*. Four message families are served there, each because the peer provably cannot compute a proof without it. **Everything else is answered `Handshake required`**: the dispatcher's authenticated branch begins -immediately after these three, so the table below is the window, exhaustively. +immediately after these four, so the table below is the window, exhaustively. | Message | Why it must precede the proof | Bound | |---|---|---| | `keypair_bundle_fetch` | the client's own identity keys for this node live in an encrypted bundle stored on it | counts against `MAX_PRE_PROOF_FETCHES` = 4; audited | | `gek_bundle_fetch` | the wrapped group key is what the proof is computed with | same counter | | `join_request` | a first-time member holds no group key at all. Accepted after the proof as well — an operator pairing a browser is already connected — because its authority comes from the pairing code and the signature, never from the session state | 5 attempts per connection; wrong codes: 5 per account and 20 node-wide per 600 s, consulted only when a code is tried | +| `device_add_request` | a new device of a known account holds no identity this node pinned, so the node wraps it no group key (§9). Inert: it admits nothing until a pinned device countersigns. Filed for the account the token names. Accepted after the proof as well | the device budget, 5 attempts per connection; 10 pending requests per account, the oldest dropped; audited | -Device linking (§9) is **not** in this window. `device_add_request` and every message -after it are answered only on an authenticated session, and the device budget of 5 -attempts per connection applies there. +The rest of device linking (§9) is **not** in this window: `device_lookup`, +`device_add`, `device_list`, `device_revoke` and `device_hello` are answered only on +an authenticated session, by the device budget of 5 attempts per connection. Exceeding the fetch budget is audited as `pre-proof fetch flood` and answered `Too many requests`. Every fetch in this window is written to the audit log with the @@ -960,9 +961,12 @@ two keys on the same node. A second device is admitted by **a key the node alrea pinned** — never by the hub, which stores no user keys and therefore cannot countersign anything. -Every message in this section is answered on an **authenticated** session only (§7): -both the device filing a request and the device approving it have completed a handshake -on their own connection. +The new device files its request in the **pre-proof window** (§7): it holds no +identity the node pinned, so it cannot complete a handshake until the request is +approved. Every other message in this section is answered on an **authenticated** +session only — the approving device has completed a handshake on its own connection. +Once approved, the new device's next `join_request`, with no code, is recognised and +answered with the group key, like any pinned device's. ### 9.1 Exchange @@ -2187,7 +2191,7 @@ message: ## 13. Versioning and compatibility -MNP versions independently of the package version. Current: **`6.2`**; oldest peer +MNP versions independently of the package version. Current: **`6.3`**; oldest peer accepted: **`4.0`**. 4.0 is the floor: a member presents a short-lived node-audience token bound to one node @@ -2217,6 +2221,10 @@ answer, as for any unknown type, and nothing else changes. the roots table, additively: a 6.1 node gives no answer to either message, and a client that ignores `paused` shows a paused root as an ordinary one. +6.3 serves `device_add_request` in the pre-proof window (§7, §9), additively: a 6.2 +node answers it `Handshake required` there, which a client reads as "this node cannot +link a new device", and nothing else changes. + The two numbers are separate on purpose. `MNP_VERSION` says what this build speaks; `MNP_MIN_SUPPORTED` says what it will talk to, and moving the second is a decision about whether an older peer can still do anything useful: -- cgit v1.2.3