aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-common/src/meshbay_common/senderkeys.py
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-common/src/meshbay_common/senderkeys.py')
-rw-r--r--packages/meshbay-common/src/meshbay_common/senderkeys.py51
1 files changed, 38 insertions, 13 deletions
diff --git a/packages/meshbay-common/src/meshbay_common/senderkeys.py b/packages/meshbay-common/src/meshbay_common/senderkeys.py
index 932e2e6..9ad5107 100644
--- a/packages/meshbay-common/src/meshbay_common/senderkeys.py
+++ b/packages/meshbay-common/src/meshbay_common/senderkeys.py
@@ -1,21 +1,40 @@
"""
-MeshBay — Sender Keys protocol for group messaging.
+MeshBay — Sender Keys protocol. **Not used by group chat. Not used at all.**
-Signal Groups approach: each member maintains their own sending chain.
-Advantages over shared Double Ratchet:
- - O(N) state per group (one chain per member) vs O(N^2) pairwise
- - Single encrypt per message (not N encryptions)
- - No key/nonce reuse — each sender has an independent chain
+Kept the way `ratchet.py` is kept: a working implementation of a protocol that
+may earn a place in a future 1:1 DM, where there is no server-side history to
+contradict it. Group chat is `chatbox.py`, and the decision to build that
+instead is `docs/chat-sender-keys.md` §4 (operator, 2026-09-07). Do not read a
+green test run here as evidence that group chat is encrypted; nothing in
+production imports this module.
-Key components:
- - Chain key ratchet: HKDF per message, provides forward secrecy
+**Why it is not what group chat uses.** With sender keys distributed under the
+group key, and a node that serves history to devices which were not present when
+a message was sent, the node must retain and hand out each chain's *earliest*
+key — and a chain key at iteration *i* yields every message key from *i* onward
+by pure HKDF. Forward secrecy is then zero, and what is left is a large amount
+of stateful client code whose failure modes are silent. Three of them are real
+and reproduced in the design document:
+
+ * `GroupSenderKeyStore.add_sender` accepts any distribution for any
+ `sender_id` and overwrites what is there, and `SenderKeyRecord.create`
+ invents a signing key bound to nothing — so under group-key distribution any
+ member can replace another member's chain and sign as them (F1);
+ * a second device registering under one `sender_id` drops the first device's
+ chain, and its messages then fail signature verification rather than failing
+ visibly at registration (F2);
+ * `SenderKeyState.advance_to` caches every skipped message key and nothing
+ trims `_skipped_keys` (F3).
+
+They are findings about a module nothing calls, and are deliberately not fixed
+here. Anyone bringing this back for 1:1 DM must fix all three first — and must
+bind the distribution to a key the node pinned, which is what F1 is really about.
+
+Key components, as implemented:
+ - Chain key ratchet: HKDF per message
- Message key derivation: separate HKDF from chain key
- Ed25519 signing: each sender signs their ciphertext
- AES-256-GCM encryption: browser-compatible symmetric cipher
-
-Key distribution:
- - On join: admin wraps each sender's SenderKeyDistribution with GEK
- - On leave: all remaining members rotate their chain keys
"""
import os
@@ -169,7 +188,13 @@ class SenderKeyRecord:
# ── Group store ──────────────────────────────────────────────────────────────
class GroupSenderKeyStore:
- """All sender key states for one group, held by one member."""
+ """All sender key states for one group, held by one member.
+
+ One chain per **device**, were this ever used: a shared per-person chain
+ advanced by two devices produces key and nonce reuse, which is `first-
+ review.md` C1 one level down. `add_sender` does not enforce that — see the
+ module docstring, F2.
+ """
def __init__(self, group_id: str):
self.group_id = group_id