Group messaging combines sender keys — one symmetric chain per member's
outbound traffic, ratcheted forward through
HKDF every message — with post-quantum rotation: every 100
messages, and on every membership change, a fresh sender key is
encapsulated to each current member via ML-KEM-1024.
Every sender-key handoff is signed under ML-DSA-87 over the conversation, sender, recipient, epoch and payload, and a receiver refuses an unsigned or mis-bound install; individual frames are authenticated by the sender-key chain itself, with per-frame authorship signatures implemented in crypto-core and staged for the client. Removed members cannot decrypt what comes
next; new members cannot decrypt what came before.
The construction
chain_0 = HKDF(SK_i, "QERYX_SENDER_KEY_RATCHET_v1", 32)
chain_{n+1} = HKDF(chain_n, "QERYX_CHAIN_RATCHET", 32)
msg_key_n = HKDF(chain_n, salt = index_be32, "QERYX_SENDER_MSG_KEY_v1", 32)
ciphertext = ChaCha20-Poly1305(
key = msg_key_n, nonce = random(12),
aad = none in the frame — the header rides inside a per-recipient sealed
envelope whose AAD is the recipient Profile ID; frame-bound AAD lands with wire version 2)
Bootstrap distributes each member's initial sender key as one ML-KEM-1024 ciphertext per recipient — one encapsulation to that member's transparency-verified bundle key, signed under the sender's ML-DSA-87 identity. The chain and message-key derivations use
distinct labels, so compromise of one message key reveals neither the
next chain key nor any earlier one — HKDF is one-way in both directions
that matter.
Rotation triggers
- Cadence. Every 100 messages the sender generates a fresh key and re-encapsulates to all current members — post-compromise security at the 100-message boundary, post-quantum because the new distribution rides fresh ML-KEM ciphertexts.
- Membership change. Removal forces rotation before the next outbound message. The new key was never encapsulated to the removed member's KEM key; their old chain ratchets forward into irrelevance.
- A new member reads forward only. Each arrival opens a fresh epoch: the new member's first key is a root nobody has used, and the retired epoch is never re-distributed. Messages sent before a member arrives are unrecoverable for that member — the safe default for a security messenger.
Out-of-order delivery rides a 512-key skip buffer per (channel, sender) chain — 2,048 per room, 8,192 per device, and a forward walk that stops at 4,096 steps; skipped keys are zeroized when consumed or evicted.
Authentication
Every sender-key handoff is signed under ML-DSA-87 over the conversation, sender, recipient, epoch and payload, and a receiver refuses an unsigned or mis-bound install; individual frames are authenticated by the sender-key chain itself, with per-frame authorship signatures implemented in crypto-core and staged for the client. A stolen handoff cannot be installed: the distribution signature is verified against the key-transparency-pinned identity of the claimed sender, fail-closed.
What the server sees
| Visible to the relay | Never visible |
|---|---|
| row counts of distributed key blobs | plaintext of any group message |
| opaque encrypted blob bytes | the relationship between successive sender keys (client-only memory) |
| delivery identifiers needed for fan-out | group names and descriptions — end-to-end encrypted content; the avatar, the admin and owner identifiers and the roster are held for routing |
Sender identifiers are stripped from the stored message row per the relay's carrier model; the relay still authenticates the sender at send time, and undelivered messages queue with routing metadata under a published retention ladder. Server-blind membership — blind capability addressing — is designed and modelled in crypto-core; it is not built into the app, and no deployment can select it — the honest boundary is stated in §6.
The claims table
| Claim | Mechanism |
|---|---|
| Past epochs stay confidential after a sender-key leak | one-way HKDF chain; the epoch root is held while the epoch is live and for three epochs of backlog, so the boundary is the epoch, not the message |
| Future messages recover confidentiality after a leak | ML-KEM re-encapsulation every 100 messages |
| Removed members cannot decrypt new traffic | forced rotation to the remaining member set |
| The server cannot decrypt any message | only encapsulated blobs and AEAD ciphertext ever arrive |
| New members cannot read history | past sender keys are never re-distributed |
| A stolen sender-key handoff cannot be installed | ML-DSA-87 distribution signature verified against the key-transparency-pinned identity, fail-closed |
Cost at scale: a full rotation for a 100-member group is roughly 600 KiB on the wire once per 100 messages — about 6 KiB per outbound message amortized, the overhead of post-quantum FS and PCS in groups.
What groups do not defend against
Sender keys bound to a roster answer for the roster. They do not answer for the people on it, and these are the limits that follow from that.
- A compromised member. A current member can leak any plaintext they legitimately received. No group construction defends against this.
- Membership inference at the relay. A server-blind membership construction is implemented in the shared Rust library and is not yet wired to any client, so the path in code exposes delivery identifiers to the relay for fan-out. That is the residual, and it is the one this chapter does not soften.
References
-
QERYX Protocol Specification §9 —
docs/SPEC/07-group-messaging.md; client implementation in the sender-key service, backend distribution tables in the relay's boot schema. - NIST FIPS 203 (ML-KEM), FIPS 204 (ML-DSA); RFC 8439 (ChaCha20-Poly1305).
- Identity and Key Transparency — the log that authenticates every group signature.