aboutsummaryrefslogtreecommitdiffstats
path: root/CLAUDE.md
diff options
context:
space:
mode:
Diffstat (limited to 'CLAUDE.md')
-rw-r--r--CLAUDE.md3
1 files changed, 2 insertions, 1 deletions
diff --git a/CLAUDE.md b/CLAUDE.md
index ec5b1b5..f06281e 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -792,7 +792,7 @@ SFR residential Fedora 44 → meshbay.org OVH VPS:
| Key bundle (web) | `meshbay_common.keyderive` | `keyderive.py` + `static/keyderive.js` |
| GEK wrap/unwrap (ECIES) | `meshbay_common.crypto` | `crypto.py` |
| Double Ratchet (1:1 DM, future) | `meshbay_common.ratchet` | `ratchet.py` |
-| Chat encryption (group chat) | `meshbay_common.chatbox` | `chatbox.py` + `sealChat`/`openChat`/`verifyChatSignature` in `static/crypto.js`. One key per group, per epoch, per **device**, derived by name from an epoch key the node generates and delivers wrapped under the GEK — so rotating the GEK is a re-wrap and does not destroy the archive, and two devices can never share an AES key. Messages are signed over the **ciphertext** with the device's pinned Ed25519 key |
+| Chat encryption (group chat) | `meshbay_common.chatbox` | `chatbox.py` + `sealChat`/`openChat`/`verifyChatSignature` in `static/crypto.js`. One key per group, per epoch, per **device**, derived by name from an epoch key the node generates and delivers wrapped under the GEK — so rotating the GEK is a re-wrap and does not destroy the archive. **No mutable sending state**, which is the C1/§15.0b hazard removed rather than partitioned — and not the same claim as "two devices never share a key", which is false: two clients of one account normally recover the *same* identity key from the keypair bundle, so they share a subkey. Safe because the nonce is 96 random bits and never a counter. Messages are signed over the **ciphertext** with the device's pinned Ed25519 key |
| Chat epochs (node) | `meshbay_node.ops` | `open_chat_epoch` / `ensure_chat_epoch` / `chat_epoch_keys`. Epoch 1 is opened at group load (`daemon._ensure_chat_epoch`) — a group with no epoch is a group nobody can speak in. A new epoch on every removal (member, device, unpin, `gek_rotate`); **old epochs are kept and still delivered**, which is what keeps history readable, and nothing anywhere deletes one. Keys are wrapped to the node's own X25519 key in `bundles.db`, never stored raw |
| ~~Sender Keys (group chat)~~ | `meshbay_common.senderkeys` | **Unused.** Kept for a possible future 1:1 DM, like `ratchet.py` — see the corrections above |
| AES-GCM (browser) | `meshbay_common.webcrypto` | `webcrypto.py` + `static/crypto.js` |
@@ -812,6 +812,7 @@ SFR residential Fedora 44 → meshbay.org OVH VPS:
| Public group cap (hub) | `meshbay_hub.api.groups` | `_check_public_group_quota` — 10 live public groups per owner, staff exempt. **Checked at creation only, because PATCH refuses to change visibility** |
| Uploads on/off (node) | `meshbay_node.roster` + `transport.webrtc_server` | `member_upload_allowed` / `set_member_upload`, gate in `_do_file_upload`. Per group, **operator-signed** (`OP_MEMBER_UPLOAD`), stored in `roster.db`, cached in the group context because the upload path is synchronous. **Absent means allowed** at every layer |
| Node presence (hub) | `meshbay_hub.api.groups` | `node_online` on `/v1/groups/mine`, read from the signaling registry — no poll, no timer |
+| Account → device pinning (Tier 2) | `meshbay_node.roster` + `static/transport.js` | `group_devices` relays each live device of each active member **with the countersignature that admitted it** (`add_sig`/`add_nonce`/`add_ts` — kept since 2026-09-07; before that the proof was verified and discarded, which is what blocked this). `group_roster_req/resp` is sealed and answers **any member**. The client walks the chain itself (`_verifyRoster`) — the node decides nothing, because it is the party the property holds against. **Once a client has seen an account, a later key substitution is detected; nothing is gained at first sight** |
| Chat message handling (node) | `meshbay_node.transport.webrtc_server` | `_do_chat_message` + `_check_chat_envelope`. `sender_id` from the session (NS6); the *device* claim is checked against the connection's own `device_hello`, or a member could sign as anyone. Replay refused by a unique `(device, nonce)` in `chat.db` — a replay is a validly signed copy, so nothing about the signature refuses it |
| Which device is on a connection | `meshbay_node.transport.webrtc_server` | `_do_device_hello` (MNP 1.2, additive). The handshake proves the *account*; this proves the *device*. Before it, `_load_pinned_pk` used the account's oldest key and recorded it as the uploader of every file |
| Chat paging (node) | `meshbay_node.chat.store` | `get_recent` / `get_before` / `has_before`. `get_messages` pages *forwards* and is not what a chat opens with |