Rooms & Group Messaging
A Room is an on-chain group conversation: one admin, up to millions of members, end-to-end encrypted messages. Unlike a thread (exactly two wallets), a room has a dynamic member set — and the encryption is designed so that membership changes are enforced cryptographically, not just by access control:
- New members cannot read history from before they joined.
- Removed members cannot read anything sent after their removal.
Rooms reuse Packet's encryption building blocks (the same wallet-derived and registered-key identities) but replace the per-recipient envelope of a 1:1 thread with broadcast group encryption (BGW): one small header per epoch, decryptable by every current member, instead of one ciphertext per recipient. That is what lets a single message scale to millions of members. For the cryptography behind it — how the broadcast header works, the membership-enforcement guarantees, and the security properties — see Group Encryption (BGW).
Receivers are stateless. A member keeps nothing locally — given their wallet, a Photon RPC, and the room id, the SDK reconstructs their membership, every epoch key, and the full readable history from on-chain state alone. There is no per-device session to back up or migrate.
This page explains the moving parts. For code, start with the SDK room quickstart.
Anatomy of a room
| Piece | On-chain form | What it holds |
|---|---|---|
| Room | PDA account | Counters (epoch, member version, message count), the admin, and a pinned reference to the BGW params artifact |
| RoomMember | Compressed account (one per wallet) | Member slot, active/removed status, and the member's encrypted BGW user secret |
| RoomEpochHeader | Compressed account (one per epoch) | The encrypted epoch key material plus a verifiable descriptor of who the recipients are |
| RoomMessage | Compressed account (one per message) | Encrypted content, sender, sequence number, and the crypto epoch it was sent under |
The room is identified by a random 32-byte roomId; the PDA address is derived from it.
Slots
Every member gets a slot — a 1-based number assigned in join order. Slots come from the room's nextSlot counter and are permanent: if a member is removed and later re-added, they keep their original slot. The maximum slot count is the capacity of the BGW params artifact the room was created with (default capacity is 1,048,576).
Slots are what the broadcast encryption operates on: an epoch header doesn't list public keys, it describes a set of slots that can decrypt.
Epochs and headers
A room advances through numbered epochs. Every epoch has exactly one header, and every header carries a fresh epoch key encapsulated to the current recipient set (BGW broadcast encryption — one small header decryptable by any current member, no per-member ciphertexts).
A new header is published when:
- a member is added (header covers the
Activatemutation), - a member is removed (header covers the
Removemutation), - the admin rotates keys without changing membership (a reuse header).
Each header describes the recipient set in one of four ways:
| Header kind | Meaning |
|---|---|
reuse | Recipient set unchanged from the previous epoch |
delta | One add/remove relative to the previous epoch |
inlineCheckpoint | Full recipient set, encoded inline (up to 512 bytes) |
externalCheckpoint | Full recipient set, too large for inline — staged as separate pages (up to 700 bytes each) before the header lands |
Deltas keep mutations cheap; after at most 64 deltas a checkpoint is forced so that readers never replay long chains. Readers reconstruct the recipient set of any epoch by walking back to the nearest checkpoint and replaying deltas forward, verifying a hash chain at every step — a tampered or skipped header fails closed.
Membership mutations are gated on-chain: a new add/remove is rejected (MutationRequiresHeader) until the previous mutation's covering header has been published. The SDK handles this ordering automatically.
The epoch-key chain (break-on-add)
Epoch keys within a stretch of epochs form a segment: a backward hash chain where the key for epoch i can be derived from the key of any later epoch in the same segment. The one-way direction matters:
- Knowing a newer key gives you all older keys of the segment — so a member who recovers the latest key can read the whole segment's history with no extra cryptography.
- Knowing an older key tells you nothing about newer keys — so a removed member, who can still decrypt the last header they were part of, cannot step forward to keys published after their removal.
When a member is added, the chain breaks: a brand-new segment starts at that epoch (chainStartEpoch is reset). The new member receives keys only from the new segment, so everything sent before their join stays unreadable to them, forever — re-adding a previously removed member also breaks the chain, and the messages sent during their absence stay locked.
Removals do not break the chain (the one-way chain already provides forward secrecy); they just publish a new epoch whose header the removed slot can no longer decapsulate. A segment can span at most 4,096 epochs before a checkpoint with a chain break is forced.
Every message records the epoch it was encrypted under (cryptoEpoch). Reading a message means recovering that epoch's key — either by decapsulating its header (if you were a recipient) or by chain-deriving from a newer key you already have.
Message buckets
Room messages get a program-assigned global sequence number and are grouped into buckets of 100 (bucket = floor((globalSeq - 1) / 100)). A bucket is fetched with a single indexer query, and completed buckets are immutable — clients cache them forever. This makes paging through large rooms cheap: loading the latest 100 messages is one query, regardless of room size.
BGW params
The broadcast encryption needs a public parameters artifact (a few hundred MB at full capacity) generated once per capacity and reused by every room. Rooms pin the artifact's identity (paramsId/paramsRoot) at creation; clients load the artifact locally or over HTTP and verify every chunk against the manifest. See BGW params.
Going deeper
This page covers the room mechanics. For the cryptographic construction behind the broadcast encryption — the BGW header, the epoch-key chain, recipient-set integrity, the trust model, and the security properties — see Group Encryption (BGW).