feat(groups): group chats, and a mesh rather than a star; release v6.1.1
CodeQL Analysis / Analyze CodeQL (push) Waiting to run
Deploy Application / deploy (push) Waiting to run
Mirror to Codeberg / mirror (push) Waiting to run
Mirror to PrivacyGuides / mirror (push) Waiting to run

A group is an orchestration layer over the pairwise sessions the app already
holds. It owns no transport and no shared key: every frame leaves over a chat
that is already SAS-verified and already ratcheted, so a removed member simply
stops being sent anything. Membership is a roster the admin signs, ordered by
epoch, and the safety code is a commit-then-reveal round over every member's
fingerprint and nonce.

Delivery was the part that did not match its own description. The admin held a
link to everyone and nobody else held a link to anybody, so the relay path — the
documented fallback — was in fact the entire topology, and the admin going away
partitioned the group. Now, once the code is confirmed, each pair without a link
dials one over that relay path. The descriptors are compact enough to ride a
group frame and are signed with the sender's group identity key, so the relaying
member can drop a dial but cannot substitute one. The member with the smaller
fingerprint dials, which is the whole glare protocol.

Mesh links are released without a human comparing digits. Twenty-eight codes for
a group of eight is not a check anyone performs; the guarantee moves rather than
disappears, since the descriptor was signed by a key the signed roster names and
the group code covers. markGroupLinkVerified refuses any session whose in-band
exchange has not completed and whose peer has not proved possession of that key.

An existing 1:1 chat between two members is adopted instead of re-dialled, via a
probe bound to that session's own key fingerprint so it cannot be replayed onto
another chat to impersonate its author.

Security fix: g_hello was accepted on any session from anyone who knew the group
id, so any member could publish an identity the admin never invited and have the
admin sign and broadcast a roster containing it. It is now accepted only on a
session an invitation went out on, which also confines it to a direct link.

Mesh connections are kept out of the chat registry and muted from the document
events the header listens to, so a routing detail cannot tear down the display of
a conversation the user actually opened.
This commit is contained in:
lockbitchat
2026-08-25 16:27:08 -04:00
parent 6a98e2eb00
commit e00c3bd413
30 changed files with 11635 additions and 421 deletions
+72 -2
View File
@@ -7,7 +7,7 @@ this document describes.
| | |
| --- | --- |
| Release | v5.9.2 |
| Release | v6.1.1 |
| Protocol version | 4.1 |
| Ratchet wire version | 1 |
@@ -236,7 +236,77 @@ what protects it. Functions that cannot wipe say so in their logs instead of
reporting success, because a cleanup path that reports work it did not do is
worse than one that reports nothing.
## Groups
A group owns no transport and no key of its own. Every byte it sends leaves over
one of the pairwise sessions described above, already ratcheted and already
authenticated by that session. There is no shared group key, so there is nothing
to rotate when a member leaves: a removed member simply stops being sent
anything.
Each member holds a per-group ECDSA P-384 identity key, generated in the browser,
non-extractable, and discarded with the group. A member is identified by the
SHA-256 fingerprint of that key's SPKI encoding — never by a session, never by a
name a peer supplied.
### Membership
The admin signs the full member set for an epoch: group id, epoch, operation,
name, and the member fingerprints in canonical order. Members verify that
signature against the key whose fingerprint they recorded when they were invited,
not against whatever key the frame carries, and accept only an epoch that has not
gone backwards. Identity keys travel in separate frames because eight SPKIs would
overrun the transport's frame budget; each is checked against the fingerprint the
signed roster commits to, so a substituted key is refused whichever frame carried
it.
### Group safety code
Every member commits to a random 32-byte nonce, and nonces are published only
once every commitment has arrived. The code is derived with HKDF-SHA-256 over the
full set of fingerprints and nonces for that epoch, and rendered as seven digits.
The commit-then-reveal ordering is what makes seven digits safe: a member who
learnt the others' nonces first could otherwise grind its own until the digits
came out however it liked. Membership changes open a new epoch, so the code
changes with the member set and the old one no longer says anything about who is
in the room.
### Messages
Group messages are signed with the sender's identity key over group id, epoch,
sequence number, sender fingerprint and the SHA-256 of the body. Signing is not
about confidentiality — each copy already travels inside a pairwise session — but
about consistency: a member could otherwise send different text to different
people under one sequence number and no recipient could tell. Two valid
signatures on one sequence number are non-repudiable evidence of that, which is
what a group without a shared transcript can honestly offer. It makes the split
detectable, not impossible.
### The mesh
A group starts as a star and dials itself into a mesh: once the code is
confirmed, each pair with no link between them opens one, with the compact
descriptors travelling over the relay path that already exists. The member with
the smaller fingerprint dials, which is the whole glare protocol.
A relayed descriptor is signed with the sender's group identity key, over the
direction, both fingerprints, a per-attempt nonce and the descriptor bytes. The
relaying member can drop a dial or delay it; it cannot substitute one, so it
cannot place itself inside the link built to route around it. On the new
connection the ordinary in-band key exchange runs unchanged, and the link is then
released on the group's authority rather than by a human comparing digits — the
descriptor was signed by a key the roster names and the group code already
covers. The release path refuses any session whose in-band handshake has not
completed and whose peer has not proved possession of that key.
An existing 1:1 chat between two members is claimed instead of re-dialled, using
a probe signed over the group id, epoch, member fingerprint and that session's
own key fingerprint. Both endpoints of a session derive the same key fingerprint
from the shared secret and nobody else can, so a probe replayed onto a different
chat does not verify — which is what stops a member claiming to be someone else
and receiving their group traffic.
## Scope
This describes the browser implementation as it stands in v5.9.2. It is not a
This describes the browser implementation as it stands in v6.1.1. It is not a
substitute for independent cryptographic review.