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.md34
1 files changed, 13 insertions, 21 deletions
diff --git a/docs/MESHBAY_NODE_PROTOCOL.md b/docs/MESHBAY_NODE_PROTOCOL.md
index 642e966..07b179c 100644
--- a/docs/MESHBAY_NODE_PROTOCOL.md
+++ b/docs/MESHBAY_NODE_PROTOCOL.md
@@ -728,9 +728,9 @@ wrap_key = HKDF-SHA256(shared, salt = pk_eph, info = "meshbay:gek_wrap:v
wrapped = AES-256-GCM(wrap_key).encrypt(nonce_96, GEK, aad = pk_recipient)
```
-AES-GCM because WebCrypto has no ChaCha20-Poly1305; a `chacha20-poly1305` variant with
-`info = "meshbay:gek_wrap:v1"` exists for native clients. The recipient's public key is
-the AEAD's associated data, so a bundle cannot be re-addressed.
+AES-GCM because WebCrypto has no ChaCha20-Poly1305, and one cipher serves every
+client. The recipient's public key is the AEAD's associated data, so a bundle cannot be
+re-addressed.
`found: false` is the normal answer: per-member bundles are not stored, and the key is
produced on demand by the join path (§8). The node keeps one stored bundle of its own
@@ -1290,9 +1290,9 @@ ciphertext. That is the same statement the index makes, one step stronger.
`salt = <none>` is Python's `salt=None` and WebCrypto's `salt: new Uint8Array(0)`;
RFC 5869 extracts with a zero key either way. The subkeys are purpose-separated
-rather than borrowed from a file's key space — `GroupIndex.serialize()` reuses
-`chunk_key_aes` with a pseudo-file ("the index as chunk 0 of a virtual index file"),
-which is a hack this deliberately does not repeat.
+rather than borrowed from a file's key space — reusing `chunk_key_aes` with a
+pseudo-file ("the index as chunk 0 of a virtual index file") is a hack this
+deliberately does not repeat.
**What stays in clear, and why each one has to:**
@@ -1337,10 +1337,6 @@ purpose and a fresh 96-bit random nonce per message: at one message per 48 KiB c
reaches 2⁻³², and `gek_rotate` exists. Deriving the nonce from the payload instead
would be worse, not better — two chunks of identical bytes are ordinary in a file.
-`GroupIndex.serialize()` is not a candidate for reuse here: it compresses with zstd,
-which no browser can decompress (`DecompressionStream` offers gzip and deflate only),
-so reusing it would mean shipping a WASM decoder to every client for no gain.
-
**Failure is fatal, never degraded** (I8). A client that cannot open an index message
ends the session naming the message type; it never reports an empty index, because "the
group has no files" is a state a real group can be in.
@@ -1531,8 +1527,9 @@ nonce = 12 random bytes
ct = AES-256-GCM(chunk_key).encrypt(nonce, plaintext) no AAD
```
-* The `:aes` suffix keeps AES keys distinct from the ChaCha20 variant
- (`chunk_key`/`encrypt_chunk`, `info` without the suffix) derived from the same GEK.
+* The `:aes` suffix is part of every chunk key: it once kept these distinct from a
+ ChaCha20 variant derived from the same GEK, which no longer exists, and it stays
+ because removing it would change every key.
* `nonce` and `ct` are msgpack **binary**, not base64. Every message that carries
content carries it this way, and none carries it outside an AEAD.
* The key is a pure function of (GEK, file hash, index), so chunks are cacheable,
@@ -2094,7 +2091,7 @@ speak, and every message above is available on it.
**QUIC is in development** (§5.2): a partial message set, no client, and not a shipped
feature. Nothing about it is a compatibility commitment yet.
-Two rules hold across transports, and both are about there being exactly one of each
+One rule holds across transports, and it is about there being exactly one of each
message:
* **One encoder per message type, shared by every transport.** `file_chunk` comes from
@@ -2103,11 +2100,6 @@ message:
its own. Two encoders for one type is a type free to drift, with a name that no longer
says which shape will arrive — and, when one of them is a sealed envelope, a second
construction site that goes on sending cleartext.
-* **`GroupIndex.serialize()` / `deserialize()` describes no MNP message.** It is a
- signed, compressed, encrypted at-rest and interchange format, and reading it as a wire
- contract is a mistake worth naming: the sealed envelope of §11.1a is what index
- messages travel under, and it is deliberately not this, because zstd decompresses in
- no browser.
---
@@ -2371,7 +2363,7 @@ LP(x) = uint32be(len(x)) || x every field, no exceptions
| `MAX_CHAT_CIPHERTEXT` / chat rate | 64 KiB / 60 per 60 s per account per group | `webrtc/chat.py` |
| GEK | 256-bit, node CSPRNG | `crypto.py` |
| Chat epoch key | 256-bit, node CSPRNG, one per group per epoch | `chatbox.py` |
-| Chunk cipher | AES-256-GCM, 96-bit nonce (ChaCha20-Poly1305 variant for native) | `webcrypto.py`, `crypto.py` |
+| Chunk cipher | AES-256-GCM, 96-bit nonce | `webcrypto.py` |
| GEK wrap | X25519 + HKDF-SHA256 + AES-256-GCM, AAD = recipient public key | `crypto.py` |
| Sealed message envelope | HKDF-SHA256 subkey per purpose, AES-256-GCM, 96-bit random nonce | `groupbox.py`, `static/crypto.js` |
| Chat envelope | HKDF-SHA256 subkey per device per epoch, AES-256-GCM, 96-bit random nonce, Ed25519 over the ciphertext | `chatbox.py` |
@@ -2391,8 +2383,8 @@ meshbay-common/ protocol.py message types, chunk and upload codecs
adminop.py the admin transcript and the operation catalogue
join.py join transcript and pairing codes
device.py device request / add / hello transcripts
- crypto.py GEK, chunk keys, ECIES wrap, BLAKE3 ids
- webcrypto.py the AES variants the browser can also compute
+ crypto.py GEK, ECIES wrap, keystore, BLAKE3 ids
+ webcrypto.py the content cipher: per-chunk AES-GCM keys
tokens.py the two token audiences (node vs hub API)
meshbay-node/ transport/webrtc_server.py the reference implementation of MNP,