aboutsummaryrefslogtreecommitdiffstats
path: root/docs/MESHBAY_NODE_PROTOCOL.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/MESHBAY_NODE_PROTOCOL.md')
-rw-r--r--docs/MESHBAY_NODE_PROTOCOL.md39
1 files changed, 29 insertions, 10 deletions
diff --git a/docs/MESHBAY_NODE_PROTOCOL.md b/docs/MESHBAY_NODE_PROTOCOL.md
index 9efef08..e7fce90 100644
--- a/docs/MESHBAY_NODE_PROTOCOL.md
+++ b/docs/MESHBAY_NODE_PROTOCOL.md
@@ -102,7 +102,8 @@ Identical on every transport:
| Bound | Value | Where |
|---|---|---|
| Max frame **before** the client's group-key proof | 64 KiB | `PRE_HANDSHAKE_MAX_MSG` |
-| Max frame **after** the proof | 64 MiB | `MAX_MSG` |
+| Max frame **after** the proof | 8 MiB | `MAX_MSG` — eight times the largest message a client sends (a sealed playlist blob, 1 MiB) |
+| Decoding a frame | arrays 100 000, maps 10 000, strings 1 MiB, binaries 8 MiB, no extension types | `UNPACK_LIMITS` |
| File chunk (plaintext) | 1 MiB | `CHUNK_SIZE` |
| Video segment (plaintext, before encryption) | 256 KiB | `STREAM_SEGMENT_SIZE` |
| Upload chunk sent by the browser | 48 KiB | fits the aiortc SCTP limit after msgpack overhead |
@@ -115,6 +116,10 @@ into it, holding that much memory per connection for as long as it liked; a hund
such connections is the node's memory, from peers that have proved nothing. Exceeding
the limit is a hard protocol error and the buffer raises rather than truncating —
truncating would hand a parser a valid-looking prefix of something it never received.
+A frame refused for its size or its decoding ends the session: the buffer still starts
+with it, so nothing after it could be read. The decoding bounds exist because a frame of
+many tiny elements decodes to many times its size in memory; with them, an 8 MiB frame
+costs tens of megabytes at worst.
### 3.3 Versioning field
@@ -245,7 +250,7 @@ answer, and a discriminator of the message's own (`file_id`, `url`, `upload_id`,
v
+-------------------------+
| AUTHENTICATED | `_user_id` / `_group_id` set,
- | full message set | frame limit raised to 64 MiB
+ | full message set | frame limit raised to 8 MiB
+-----------+-------------+
| channel closes
v
@@ -416,7 +421,7 @@ fails if a transport skips a step.
| compare_digest(proof); |
| roster admits the user |
| 9. session authenticated: |
- | frame limit -> 64 MiB, |
+ | frame limit -> 8 MiB, |
| peer registry, audit |
| |
| 10. handshake_ack |
@@ -680,11 +685,13 @@ the node that proved it, for the account that stored it.
(`keyderive.js` in a browser, `keyring.js` in the desktop application's main
process), and optionally a second copy under the account recovery key. The node
stores bytes and serves them back to the same `user_id`.
-* **A client reads `MBK3` and nothing else.** A bundle in an earlier format was
- sealed under the passphrase alone; the client refuses it by name
- (`bundle_format_retired`) and does not mint a replacement identity, which would
- leave the node pinning a key nobody holds. `member unpin` drops the bundle, and
- the next join is a first contact.
+* **A client writes `MBK3` and nothing else.** It reads one earlier format once,
+ to replace it (*transitional*): `MBK2`, `"MBK2" ‖ nonce (12) ‖ AES-GCM` under the
+ passphrase's Argon2 key, no associated data. The identity in it is stored again
+ as `MBK3` with `keypair_bundle_store` once the session is up. Anything older is
+ refused by name (`bundle_format_retired`), and the client does not mint a
+ replacement identity, which would leave the node pinning a key nobody holds;
+ `member unpin` drops the bundle, and the next join is a first contact.
* `keypair_bundle_store` is accepted **after** authentication (it is not in the
pre-proof list); the fetch is what happens before.
* A `store` omitting `bundle_enc_recovery` leaves any existing recovery copy in place.
@@ -1001,6 +1008,9 @@ D_req = "meshbay:device_req:v1" || LP(node_pk) || LP(user_id) || LP(pk_ed) ||
D_add = "meshbay:device_add:v1" || LP(node_pk) || LP(user_id) || LP(pk_ed) ||
LP(pk_x) || LP(nonce_s) || LP(ts) signed by a PINNED device
+D_rev = "meshbay:device_revoke:v1" || LP(node_pk) || LP(user_id) || LP(pk_ed) ||
+ LP(nonce_s) || LP(ts) signed by a PINNED device
+
code_hash = sha256( code "\x1f" pk_ed25519_b64 "\x1f" pk_x25519_b64 )
```
@@ -1009,6 +1019,11 @@ code_hash = sha256( code "\x1f" pk_ed25519_b64 "\x1f" pk_x25519_b64 )
* `D_add` deliberately **omits the code**: the code is a bearer secret used to find the
request, never signed, never echoed. What is signed is the key pair being admitted,
so a signature collected for one device cannot admit another.
+* `device_add` **must name a pending request** (`code_hash`) filed by the same
+ `pk_ed25519` and `pk_x25519`; without one, or with one filed by other keys, it is
+ refused and the request is not spent. A countersignature alone admits nothing.
+* `D_rev` has a prefix of its own. Were a retirement signed over `D_add`, a signature
+ given to retire a key would admit that key wherever it is not pinned yet.
* Because both keys go into `code_hash`, a node cannot answer the approver with a
substituted key: the approver recomputes the hash from what it typed and what it was
given. Nothing here rests on a human comparing digits.
@@ -1022,7 +1037,7 @@ code_hash = sha256( code "\x1f" pk_ed25519_b64 "\x1f" pk_x25519_b64 )
N -> C device_list_result {pending, devices: [{pk_ed25519, label, pinned_at,
pinned_via, added_by_pk, is_this_one}]}
- C -> N device_revoke {pk_ed25519, ts, sig over D_add for the victim's keys}
+ C -> N device_revoke {pk_ed25519, ts, sig over D_rev for the victim's key}
N -> C device_add_ack {revoked: pk_ed25519}
```
@@ -2361,6 +2376,10 @@ device add "meshbay:device_add:v1" LP(node_pk) LP(user_id) LP(pk_ed25519) LP
LP(nonce_s) LP(ts)
-> Ed25519 by an ALREADY-PINNED device of the same account
+device rev "meshbay:device_revoke:v1" LP(node_pk) LP(user_id) LP(pk_ed25519)
+ LP(nonce_s) LP(ts)
+ -> Ed25519 by an ALREADY-PINNED device of the same account
+
device hello "meshbay:device_hello:v1" LP(node_pk) LP(group_id) LP(user_id)
LP(pk_ed25519) LP(nonce_s) LP(ts)
-> Ed25519 by the device claiming this connection
@@ -2410,7 +2429,7 @@ LP(x) = uint32be(len(x)) || x every field, no exceptions
| Device attempts | 5 per connection | ” |
| `MAX_DEVICES_PER_USER` | 5 | `roster.py` |
| `MAX_LINK_INVITES_PER_GROUP` | 20 unredeemed invitation links | `roster.py` |
-| `PRE_HANDSHAKE_MAX_MSG` / `MAX_MSG` | 64 KiB / 64 MiB | `webrtc/core.py` / `webrtc/limits.py` |
+| `PRE_HANDSHAKE_MAX_MSG` / `MAX_MSG` | 64 KiB / 8 MiB | `webrtc/core.py` / `webrtc/limits.py` |
| `CHUNK_SIZE` | 1 MiB | `webrtc/limits.py` |
| `DOWNLOAD_BUFFER_HIGH` | 2 MiB | `webrtc/files.py` |
| `MAX_UPLOAD_BYTES` | 8 GiB, default only — `max_upload_gb` overrides it per node | `webrtc/upload_handlers.py` |