diff options
Diffstat (limited to 'docs/devel-phases-next.md')
| -rw-r--r-- | docs/devel-phases-next.md | 31 |
1 files changed, 22 insertions, 9 deletions
diff --git a/docs/devel-phases-next.md b/docs/devel-phases-next.md index e842b62..cbee283 100644 --- a/docs/devel-phases-next.md +++ b/docs/devel-phases-next.md @@ -986,7 +986,18 @@ to know why. --- -## Phase 15 — Chat encryption (Sender Keys) + retention +## Phase 15 — Chat encryption + retention + +> **Superseded 2026-09-07 by `docs/chat-sender-keys.md`, which is the +> specification and the decision record. Built.** Read that document before this +> section: the milestone table below is kept for the history of the decision and +> is wrong in three places, each marked. In particular 15.1–15.3 put encryption +> *in the node* — the node is a relay and an archive, and messages are composed +> and read in the client, so built as written the feature would have protected +> nothing it claimed. +> +> The protocol is no longer Sender Keys. `senderkeys.py` is unused and kept for a +> possible future 1:1 DM, alongside `ratchet.py`. > Was Phase 13 before the 2026-08-13 renumbering. @@ -1102,15 +1113,17 @@ flags. Additions that belong in the user-facing docs: | # | Component | Description | |---|---|---| -| 15.0 | **Distribution decision** | ✅ **DECIDED 2026-09-03** — GEK-wrapped. The GEK is the group secret; files and chat share the same access boundary | -| 15.0b | **Per-device chains** | `sender_id` becomes a device identifier; fix `GroupSenderKeyStore`'s silent overwrite. **Blocking, and depends on device linking (Stage C) landing first** | -| 15.1 | Node: sender key init | Generate a sender key **per device** on group join, distribute GEK-wrapped to the group | -| 15.2 | Node: encrypt chat on send | Encrypt payload with that device's chain key before broadcast | -| 15.3 | Node: decrypt chat on receive | Decrypt incoming chat messages, handle out-of-order | -| 15.4 | Key rotation on removal | Member removed **or device revoked or unpinned** → all remaining devices rotate | -| 15.5 | Chat retention config | Per-group `max_age_days` setting, periodic cleanup in ChatStore | +| 15.0 | **Distribution decision** | ✅ DECIDED 2026-09-03 (GEK-wrapped), then **overtaken 2026-09-07**: the epoch key is delivered wrapped under the group key, but nothing is *stored* under it — which is what makes a group-key rotation a re-wrap instead of the destruction of the archive | +| 15.0b | **Per-device chains** | ✅ **Obtained without chains.** One key per device, derived by name from the epoch key, so there is no shared mutable sending state to reuse a nonce and nothing to persist per device | +| 15.1 | ~~Node: sender key init~~ | ❌ **Wrong as written** — the node is a relay and an archive. It generates and delivers the epoch key (`ops.open_chat_epoch`, `chat_keys_req`); the client seals | +| 15.2 | ~~Node: encrypt chat on send~~ | ❌ **Wrong as written.** Encryption is in `static/crypto.js`; the node stores what it cannot read | +| 15.3 | ~~Node: decrypt chat on receive~~ | ❌ **Wrong as written.** Only clients decrypt. Out-of-order does not arise: there is no chain to advance | +| 15.4 | Key rotation on removal | ✅ Member revoked, unpinned, device revoked, or `gek_rotate` → a new epoch, pushed to everyone connected. Old epochs kept, or the removal would take the history with it | +| 15.5 | Chat retention config | ✅ `meshbay-node chat prune <days>` / `ops.prune_chat`. Deletes messages, never epoch keys | | 15.6 | MNP version negotiation | ✅ **DONE 2026-09-03**, and not here: it shipped with **MNP 1.0** (the sealed index and ack), which forced a coordinated deployment anyway. `handshake` and `handshake_challenge` each carry `v` and `v_min`; `check_version` refuses with `version_too_old` / `version_too_new` / `version_unreadable`, shaped like `not_a_member`. The flag day was already being paid for, so the next breaking change costs a refusal message instead of a second one. See `MESHBAY_NODE_PROTOCOL.md` §13.1 | -| 15.7 | Chat attachments | Attachments are ordinary files on the node and remain plaintext at rest. Either encrypt them under the sender key, or document the asymmetry explicitly. Note they now land in the **operator-designated upload root** (§6.7 of the desktop-client doc) | +| 15.7 | Chat attachments | **Documented, not encrypted.** Attachments are ordinary files on a shared root and stay plaintext on disk; the *reference* to one is inside the sealed payload, but the file and its name are in the index. Encrypting them is a different feature with a different blast radius — `docs/chat-sender-keys.md` §5.8 states the asymmetry rather than hiding it | +| 15.8 | **The switch** | ✅ Per group, operator-signed (`OP_CHAT_ENCRYPTED`), reported inside the sealed handshake ack. Off by default — a node upgraded into a running group must refuse nobody. On, the node refuses plaintext outright | +| 15.9 | **`chat encrypt-history`** | ✅ Explicit CLI command, backs `chat.db` up first, one transaction. Deliberately not done by the switch: it rewrites the only copy of a conversation, and a toggle that does that is one somebody flips twice | --- |