ATSMS Group Chat: End-to-End Encryption Without the Lock-In

When I introduced ATSMS, I wrote that we were:

waiting for DCGKA (Decentralized Continuous Group Key Agreement) to mature before adding group chat support in a version of ATSMS that supports larger groups.

That wait ended when I saw this post from @expede.wtf.

View post on Bluesky

With the help of Fable, I updated the original ATSMS library to use BeeKEM, and I am excited to share an update about this effort.

We now have a proof of concept. It does encrypted group messaging and calls with no server deciding anything: no sequencer, no centralized delivery service, no privileged party anywhere in the middle.

Groups converge on the same keys from operations that arrive in whatever order the network feels like delivering them.

The point of building it that way is not the cryptography. It is that no company sits between you and the people you talk to, so there is nothing to be locked into and nothing to leave behind if you switch apps.

What Is ATSMS?

ATSMS is a protocol for end-to-end encrypted messaging on AT Protocol. What SMS is for phone numbers, ATSMS is for AT Protocol DIDs.

It is an open protocol with an Apache-2.0-licensed reference implementation, so any application built on AT Protocol can use it to add encrypted messaging without asking anyone for permission. It is built for direct messages and small groups, and it grows to larger groups by configuring a group relay. A group picks its own relay and can change it later without disturbing the conversation. Because the relay only ever holds sealed envelopes, it cannot read the messages it carries.

What We Are Announcing Today

Two things, both aimed at developers rather than end users:

A TypeScript reference implementation of the ATSMS library, implementing the protocol.

A proof-of-concept web client which you can sign into with the Bluesky account you already have.

Alongside them we are publishing the specifications, a reference relay you can run yourself, and a terminal client that is small enough to read in one sitting.

One thing to be clear about before you form an opinion: the cryptography has not been through an independent security review. We have built it carefully, and the core is a port of published research checked automatically against the original implementation, but nobody outside the project has audited the composition. That review needs to happen before any of this carries traffic that matters. Until then, treat the demo as something to try, not something to rely on.

What Makes ATSMS Different

Group messaging needs no central server. Most encrypted group messaging quietly depends on a server to referee. When two people change the group at the same moment, something has to decide which change counts. ATSMS does not go without a referee. It replaces the server with a protocol: every device works the answer out for itself, and they agree because the mathematics makes them agree rather than because anyone said so.

That is possible because of decentralized continuous group key agreement (DCGKA), and specifically because of BeeKEM.

Standing on Ink & Switch's Shoulders

The hard problem in decentralized group encryption is this: two members change the group at the same instant, neither has seen the other's change, and every device still has to end up holding identical keys. With a server, that is a queue. Without one, it is a research problem.

BeeKEM, from Ink & Switch, solves it. It is a concurrent variant of the TreeKEM structure MLS uses, and it merges overlapping changes deterministically, so every replica lands on the same tree. Their implementation is in Rust and ours is in TypeScript, so we ported it. To check that the port is faithful, we built a harness that runs both implementations through the same sequence of group operations and compares the key material they produce, byte for byte. When the two disagree, we assume ours is the one that is wrong.

We are also building on the DCGKA work of Weidner, Kleppmann, Hugenroth and Beresford (CCS 2021), which defines what a group manager has to guarantee, and on p2panda's strong-remove semantics for removals that race other operations.

None of the beautiful part is ours. We are grateful to be able to build on it in the open, and any mistake in what we built on top belongs to us.

Why DCGKA Fits ATSMS

Three properties matter for what we are building.

It tolerates the network we actually have. Members issue operations whenever they want, each naming what it had seen beforehand, and every device replays the resulting partial order to the same result.

Nothing needs to arrive in order. Nothing needs to arrive at the same time. A phone that has been off for a day catches up without anyone waiting on it.

Nobody has to be online for the group to work. When a device replaces its keys after a compromise, the change takes hold the moment each other device processes it — there is no acknowledgement to wait for. If we required acknowledgements, we would have reintroduced the coordinator by the back door.

Removals are real. When someone leaves or is removed, the keys rotate immediately, and the operation holds even if it races other changes. Nobody gets un-removed by a concurrent update, and a removed member cannot read what comes afterward, even running modified software.

Why Not MLS

We skipped MLS deliberately, and not because MLS is bad. MLS needs somebody to decide the order of operations, and that requirement pushes a protocol toward centralization. Centralization produces user lock-in. Avoiding that outcome is why ATSMS exists.

MLS moves a group through linear epochs and requires everyone to apply the same commit for a given epoch. Concurrent commits fork the group, so the losers get rejected and retried, and something has to pick the winner. In practice that something is a centralized Delivery Service, one per group.

It is good engineering, and for a deployment that already has a server it is the right answer. It is simply not compatible with a protocol whose whole point is that no participant is privileged.

Identity You Keep

Your identity in ATSMS is your AT Protocol DID, did:plc or did:web. Not a phone number, not a username in someone's database.

The practical consequence is portability. If you do not like the app you are using, switch. Your conversations come with you, because they were never the app's to hold. That is the same bargain AT Protocol already made for your posts and your follows, extended to your private messages, and it is specifically designed so that no single company can lock everyone into their service.

How the ATSMS Protocol Works

Your devices do the decryption. End-to-end means exactly that: keys live on end-user devices, and ATSMS supports several devices per person, each with its own key material.

The Inbox Record

at.atsms.inbox is a record in your PDS that tells others where you want to receive ATSMS messages. Think of it the way you think of an email inbox. In fact, one of the protocols ATSMS supports is SMTP, the email protocol.

The record holds an ordered list of options, and a sending app picks the first one it supports. Today there are two:

HTTPS. A POST endpoint, and the one every record must list. That guarantee is what keeps independently built apps able to reach each other: whatever else an account advertises, any app can deliver over HTTPS. A popular account could put that endpoint behind x402 and require an anonymous micropayment, which prices spam without identifying the sender.

SMTP. Recommended alongside it rather than required. It reaches networks HTTPS does not, and it puts a mail server between you and the recipient's relay.

Supporting more than one protocol is about interoperability. An app should not have to run particular infrastructure to talk to an app somebody else wrote.

This public address only has to carry first contact — an invitation to a DM or a group, or a one-shot (see below) from somebody who has nothing but your DID to go on. When a client starts or joins a conversation, it tells the other members, inside the encrypted channel, where it wants that conversation's messages delivered from then on. So the address that carries your day-to-day traffic never has to appear in a public record, and a conversation can move its delivery without your public record changing at all.

Whatever is listed there, the ATSMS standard requires messages to be encrypted in a way that reveals very little to the service hosting the inbox. Relays store messages until an offline device comes back, and they are also where notifications originate. That is the whole job.

The Delegated Identity Key

at.atsms.x509 holds a per-device identity key. Each device has its own key pair, which means the private key can stay in secure hardware and never move.

We encode those keys as X.509 certificates. That gives us a standards-based container for additional metadata that has to travel with a key, including the URI of the PDS collection that cross-signs the key to an ATSMS identity, for example at://did:plc:xxyyzz/at.atsms.x509/[sha-pub-key]. Using an old format here is deliberate: the parsing and validation code already exists and has been attacked for decades.

Prekeys

at.atsms.prekey is the part people tend to skip when they talk about interoperable end-to-end encryption, and then discover they needed.

A prekey lets somebody establish an encrypted channel with you where the key protecting the messages is different for every new connection. That is what gives you forward secrecy at first contact. The usual way to get it is a dedicated key-distribution server with its own protocol, which every implementation then has to build. We publish and rotate prekeys as ordinary PDS records instead, which trades a little freshness for not inventing an additional server that everyone must run.

Forward Secrecy, and Recovery from Compromise

Two properties, from two different mechanisms, and they are easy to run together.

Forward secrecy means that stealing keys today should not hand over yesterday's messages. Inside a conversation, message keys come from a per-sender ratchet that advances with every message, and each key is discarded once it has been used. Prekeys extend the same property to first contact, so even the opening message of a conversation is protected by a key that exists only for that connection.

Recovery from compromise runs the other direction: once an attacker has your keys, how do you get privacy back? That comes from re-keying the group tree. A new root secret reseeds every ratchet, and an attacker holding yesterday's material is locked out. It takes effect when the update is processed rather than when everyone acknowledges it, which is what keeps the property workable in a protocol with no coordinator.

Forward secrecy depends on old key material actually being deleted, and that is one of the places our implementation trails the specification. The eviction rules are written and are not yet enforced in code. It is on the known-issues list.

Sealed Sender

Sealing hides the sender, along with the other metadata a relay could use to work out who is talking to whom.

There are two kinds of sealing key:

a prekey, which seals welcome messages and one-shots

symmetric keys derived from the group secret, which seal the large majority of ATSMS traffic

Messages are padded into fixed-size buckets, so length reveals which bucket and nothing finer. Messages sent over SMTP have to conform to a strict ATSMS format, because an email that carries the same content in a slightly different shape would leak through its headers what the encryption was protecting.

ATSMS does not protect every layer. A relay sees the IP address that connects to it, and it sees timing and volume. Traffic analysis over enough time is a real attack and we do not defend against it. There is one place where the design works in your favor: over SMTP, the address the relay sees belongs to a mail server rather than to you, so your own is hidden from the ATSMS relay unless it and your mail provider compare notes.

The Message Format: MIMI, CBOR Underneath

ATSMS carries a rich message format rather than a bag of strings, so replies, reactions, edits and structured content are part of the format instead of conventions each app bolts on itself. It follows MIMI, the IETF's work on interoperable message content, which is still in progress rather than a finished standard.

Underneath that, the encoding is CBOR — the same serialization AT Protocol repos already use. Partly so that nobody has to learn a new format to read our wire, and partly because we need from it exactly what AT Protocol needs: an operation's identity in ATSMS is a hash of its own bytes, and the causal history is built out of those identities, so two encoders that disagreed about how to write the same value would fork the group. Content addressing always brings that hazard, and it wants the same answer here that it wanted there.

We are strict about it in a specific way. The base wire is restricted to DRISL — a published deterministic-CBOR profile edited inside the AT Protocol ecosystem — rather than to a rule set of our own, and then constrained further to be map-free. Every structure is a fixed positional array. Key ordering and duplicate keys are the subtlest class of canonicalization bug, and removing maps deletes that class by construction instead of policing it at runtime.

Audio and video calls are one of those types. Call setup runs over the same encrypted channel as everything else. WebRTC carries the media today. We are also looking at Iroh, both as an alternative media path and for its content-addressed blob transfer, which is close to what we need for content too large to seal into a single message.

The media path needs metadata protection of its own: sealing call setup does not help if the stream that follows announces who is on the call. That is its own piece of work, and we will write about it separately.

Not Everything Is a Conversation: One-Shots

Encrypted messaging usually assumes a conversation: two parties, a session, keys agreed and kept alive.

A great deal of what actually arrives on your phone is not that. A payment cleared. A prescription is ready. Someone signed into your account from a new device. One message, from an organization you have no conversation with, and nothing to reply to.

Today those arrive by SMS or email — unencrypted, and trivially forgeable. That is not a small thing. It is the reason phishing works: you cannot tell a real message from your bank from a fake one, because neither carries any proof of who sent it.

ATSMS has a second kind of message for exactly this, which we call a one-shot. It is sealed to the recipient, needs no conversation and starts none, and reaches anyone who has a DID. There is no session to establish and nothing left behind afterwards.

It also carries both halves of a property that usually comes as a choice. The recipient knows exactly who sent it; nobody else does. The message is signed, and the signing key must be published under the DID it claims, so an unpublished or mismatched sender is dropped rather than delivered — a one-shot cannot be forged, and its sender cannot afterwards deny it. And the whole signed message travels inside a sealed envelope, so the relay carrying it learns no more about the sender than it does about any other ATSMS traffic: nothing.

One-shots are also the one thing every ATSMS client must be able to send and receive. An application that never implements group encryption at all can still reach every ATSMS user this way, which makes them the cheapest way to start building on the protocol.

Groups Without a Server Work, Until They Don't

A small group needs no infrastructure at all. Nothing to deploy, nothing to pay for, nothing anyone can switch off. Each device delivers to each recipient's own inbox, and the group exists because its members hold the keys.

Delivery is O(n) in the sender's upload. Each recipient device gets its own sealed copy, roughly a kilobyte, and the sending device uploads all of them — on every message, whether it says "ok" or a paragraph. Ten devices is nothing. Fifty is noticeable on a phone. Past that, one participant's uplink is carrying the whole conversation.

That makes the practical limit a property of the sender's connection rather than a constant in the protocol. We recommend this arrangement up to around 25 devices — a dozen or so people with a phone and a laptop each — which stays comfortable on any connection and leaves room for the group to be chatty.

Beyond that, a group can add a group relay: one shared place where the sender uploads a single copy and members collect it. The sender's upload becomes O(1), and the relay still cannot read anything. The group relay allows groups to scale past the sizes above.

It is a proposal in draft, though, not a finished part of the protocol. The groundwork is there. The protocol has reserved room for it from the start, and it builds on the same encryption groups already use. What remains is the part worth taking time over: making a group relay something any group can adopt, host anywhere, and walk away from, held to the same standard as everything else here.

We have published the proposal with its open questions rather than waiting until it is finished, because this is the part of the design where a second opinion would help most. Today we deliver one sealed copy per device, and that covers the groups most people actually have — the family thread, the friends planning a weekend away.

Where We Need Help

If you build on AT Protocol and you want open encrypted messaging in your app, or you want to build the next WhatsApp, we would like your help maturing the specs. Interfaces get better when a second implementer finds the places where they are ambiguous.

If you are a cryptographer, we would like your help auditing ATSMS. The composition, sealed sender over a concurrent group key agreement, is the novel part.

The repository carries our own known-issues list and a brief covering what we think is worth attacking, including the questions we cannot answer ourselves.

If you are neither, ask the apps you already use whether your private messages are actually private, and what happens to those conversations if you stop using the app. A protocol like this spreads because people ask for it, not because the specification was well written. The apps that adopt it will be the ones whose users wanted it. Those questions are worth asking whatever we do.

And if you know someone building on AT Protocol, pass this along. The people who can act on it are a small community, and it reaches them by word of mouth or not at all.

The last few weeks of building this have been exhilarating. But the part I am most excited about is what an open protocol makes possible. It belongs to everyone who builds on it, so where encrypted group messaging goes from here is not locked in either.