Consent You Can't Forge โ Group Calls Where the Crypto Enforces the Rules
Group calls are end-to-end encrypted with real MLS (RFC 9420), and every media frame binds the group's key epoch and the consent state into its own signature โ so a peer can't silently swap your consent stamp or replay a stale-key frame without the packet failing verification. Honest scope: the crypto is wired and verified by code path; a finished multi-party video call over the network is still an open rung.
Start with the lie we're trying to make impossible.
You're on a call with three other people. One of them is compromised โ a hostile process is riding their device. In most systems, the facts that matter socially here are just application data: who is in the room, who agreed to be recorded, which encryption key is current. That data is metadata the software reports in good faith. A compromised peer can lie about it. They can tell your client you consented to recording when you set the switch the other way. They can quietly feed you frames sealed under a key the group already rotated away from, because your client trusts the label on the packet.
The usual defense is "well, we sign the media." But a signature only proves who sent these bytes. It says nothing about whether the consent stamp riding alongside them is the one you actually set, or whether the key is the one the group is on right now. The bytes can be authentic and the story around them still false.
What we built for group calls closes that gap by refusing to keep those facts as metadata at all. The consent state and the key epoch aren't labels on the frame. They are folded into the thing the signature covers. Alter either one and the signature stops verifying, and the frame is dropped. Consent and key-freshness become properties of the bytes, not promises about them.
Real MLS, not a hand-rolled group secret
The room itself is sealed with MLS โ Messaging Layer Security, standardized as RFC 9420, the same modern group-encryption machinery that sits under serious secure messengers. We didn't reimplement it; we drive a real Rust MLS library, on the Curve25519 + AES-128 ciphersuite, through a standard crypto provider.
The part that fits this project's grain: your decentralized identity is the MLS credential. There's no membership roster on someone's server. The thing that authenticates you into the group is the same self-owned identity that authenticates you everywhere else in the system. The group runs the full lifecycle you'd expect from MLS โ add a member, remove a member, a member updating their own keys, welcoming a new joiner, and encrypting and decrypting the actual application messages โ and, crucially, the group key rotates every epoch. An epoch is just MLS's word for "the current era of the group's key material." Add someone, remove someone, or rotate โ the epoch advances, and the old key is gone.
That rotation is what makes replay dangerous to defend against naively. If a member leaves, you want their old key to stop working immediately. MLS gives you that. But it only helps if every media frame is tied to the epoch it was actually produced under โ otherwise a saved old-epoch frame could be replayed into the stream after the key moved on, and a client with a lazy check might still accept it.
The frame that carries its own proof
Media doesn't travel over MLS itself โ MLS seals the room, and the audio and video travel over QUIC, under a dedicated media protocol, using a peer-to-peer transport with no server relaying your call. Each frame carries a compact header: a sequence number, a timestamp, a type tag, and then the three things that make this design what it is โ the MLS epoch (eight bytes), the consent-state version (eight bytes), and an Ed25519 signature.
Here's the move. When the sender builds the message the signature covers, it doesn't sign only the media payload. It folds the consent-state version into the signed preimage. So the signature is a commitment over "these media bytes, at this epoch, under this exact consent state." A receiver verifies against its view of the current epoch and the consent version it expects. Swap the consent stamp and the signature no longer matches its preimage. Replay a frame from a superseded epoch and the epoch bound into the signed material betrays it. Either tamper breaks verification, and a frame that fails verification is rejected, not played.
flowchart TD
subgraph Group["Sealed room (MLS, RFC 9420)"]
K["Group key for epoch N
rotates on add / remove / update"]
end
K --> S
subgraph Sender
P["Capture media frame"] --> S["Build signed preimage:
media bytes + epoch N + consent version V"]
S --> SIG["Ed25519 signature over the preimage"]
SIG --> H["Frame header:
seq ยท timestamp ยท type ยท epoch N ยท consent V ยท signature"]
end
H -->|QUIC, peer-to-peer, no relay| R
subgraph Receiver
R["Receive frame"] --> V{"Signature verifies against
this epoch + this consent version?"}
V -->|yes| PLAY["Decrypt & play"]
V -->|no โ swapped consent stamp
or stale / replayed epoch| DROP["Reject the frame"]
end
The consent version being an eight-byte counter, not a free-text field, is deliberate. It's small, it's ordered, and it's bound. A compromised peer can't present frame data with your consent set to "yes, record me" while the signed material says otherwise, because there is no version of the frame where the two disagree and the signature still holds.
The one-to-one path, and a real cross-host bug
A two-party call is a simpler problem than a group, and it uses a separate, also modern path: an X25519 key agreement, run through HKDF-SHA256 to derive keys, protecting the media with XChaCha20-Poly1305. Different shape, same standard of primitive.
Building the group path surfaced exactly the kind of bug that only shows up across real machines. MLS join messages โ the key-packages a new member presents โ carry a validity window, a "not valid before" time. When two devices' clocks were even slightly skewed relative to each other, the receiver would judge a perfectly good key-package as "not valid yet" and reject it. About half of all join attempts were failing this way in cross-host testing. The fix is unglamorous and correct: a 30-second leeway on that not-before check, enough to absorb ordinary clock drift between devices without meaningfully widening the window. It's the sort of detail that never appears in a protocol diagram and decides whether the thing works when the two people aren't on the same laptop.
What's real, and what isn't โ said plainly
This is the part that matters most, because the temptation with a crypto story is to imply the product around it is done.
The crypto is wired and verified by code path. The MLS group lifecycle, the per-epoch key rotation, the media frames that bind epoch and consent version into an Ed25519 signature, the QUIC media transport, the separate one-to-one channel โ these exist in the real Rust media stack and are exercised by the code, not sketched. And the system is honest about its own crypto to your face: a posture check on the status surface reports whether real cryptography is required โ which is the production default โ or whether a test-mode simulation is in force. That posture endpoint exists precisely so the system can never quietly pass off simulated test-mode crypto as the genuine article. The only "stub" in the picture is that simulated fallback, and it is surfaced, not hidden. That is the Honesty axiom expressed as an endpoint.
And the honest limit: the voice and video epic as a whole is not finished, and not celebrated. It's shipping as milestone rows on the main line, still in implementation. The full multi-party call โ many people, over the network, at scale, as a feature you open and use โ is an open rung. The consent-binding and the MLS wiring are proven by the code path; they are not yet a headline "start a group call and everyone's in" product. If you take one factual claim from this piece, take that one, stated without varnish: we built the layer where consent and key-freshness become unforgeable properties of each packet, and we have not yet shipped the finished group-calling experience on top of it.
That's the honest shape of it. Most systems leave "who's in the room and who agreed to what" as metadata a bad actor could rewrite. We made the answer part of the signature. What remains is the ordinary, unglamorous work of turning a proven crypto layer into a call you'd actually place โ and we'd rather tell you where that line is than let a good mechanism imply a finished product.
Related reading: The Database Falls, and Nothing Spills on making at-rest data unreadable by anything without a grant, and Trust Is Directional on why consent in this system is never a symmetric, ambient property.
Written by AI agents from real project logs; owned and edited by Mujo.
Written by AI agents from real project logs; owned and edited by Mujo.