Every message encrypted, anonymized, and stored immutably.
On a network that can’t read what it’s holding, behind a rule anyone can audit.
Why we built on it
Lortnoc Tahc hides real messages inside platforms that scan everything. Those conversations still have to live somewhere — and if that somewhere is ours, we’ve just made ourselves the easiest thing in the system to subpoena.
So we needed two properties, together: storage that can’t read what it holds, and an access rule we can’t quietly change.
Walrus gives the first — erasure-coded fragments spread across nodes, none of which ever holds a whole blob. Seal gives the second — decryption gated by seal_approve, a Move function running on Sui, rather than a conditional inside our backend.
Neither alone would have been enough. We needed the pair.
Where we currently sit on the trust curve. Seal supports a key-server committee; we run against one server at threshold 1 of 2, so today the honest description is “encryption behind an on-chain access policy” rather than “threshold encryption across a committee”. The policy is real and enforced by a party that is not us — but a single operator could still refuse, or be compelled. Raising the threshold is a configuration change, not a redesign, and it is gated on a client-version conflict rather than on anything we would have to rebuild.
How it works here
- Encrypt. Seal encrypts each message to an identity namespaced under its conversation object —
headAddress || nonce, so a member of one conversation cannot request shares for another. - Scatter. Walrus erasure-codes it across the network. Every node holds a fragment it can’t read.
- Point. A
ConversationHeadSui object tracks the current blob. Blobs never change; only the pointer moves. - Find. Conversations announce themselves as
ConversationCreatedevents on Sui, so any device holding the user’s secret rebuilds their inbox from chain with nothing stored by us. - Open. Your Seal session key is signed by a Sui keypair derived on-device from your master secret — so the key servers answer to you, not to us. Reading your own history is always free.
Access follows membership on-chain. We hold no key and cannot override the answer — verified end to end in scripts/seal-live.mjs.
Three properties this depends on
seal_approve is arbitrary Move, not a fixed vocabulary
The usual shape for access control over encrypted data is a menu: allowlist, time-lock, token-gate. Seal makes the policy a Move function evaluated by dry-run, so it can be anything expressible on Sui.
That is what lets our rule be conversation membership rather than an address list we maintain. Add a participant on-chain and they can read; remove them and they cannot, with nothing re-keyed client-side and no list for us to be asked to edit.
Immutable blobs, mutable pointer
A durable append-only log separated from a small object that moves is the right decomposition for a conversation, and it is one we would otherwise have had to invent badly. ConversationHead is that primitive with two extra fields. RedStuff’s self-healing is what makes “your data outlives us” a statement rather than a hope.
Per-blob pricing, which is why we do not batch yet
Walrus bills an encoded floor per blob regardless of size, so every blob under ~217 KB costs the same. Chat is nothing but small objects — a message is a few hundred bytes, which expands about 220,000×. One blob per message is therefore a pricing problem at scale, and Quilt is the answer: at 660 items per blob it is roughly a 650× saving.
We ship one blob per message anyway, and the measurement is why. We wrote a three-message quilt to testnet and it came back at 445,556 bytes — Quilt pads to its sliver structure, so a small quilt carries a large floor of its own and can cost more than the blobs it replaces. The 650× assumes a saturated quilt, and a 1:1 conversation emits messages one at a time. Batching would mean delaying delivery to the recipient to save storage we are not yet spending. The switch is costed and the read path already verified against per-patch aggregator reads, so this is a threshold to cross on volume, not a rewrite.
Specifics
| Storage | Walrus — erasure-coded, self-healing, immutable |
|---|---|
| Encryption | Seal, identity namespaced per conversation object (headAddress || nonce) |
| Access rule | seal_approve, a Move function dry-run by the key servers against live chain state |
| Key servers | One, at threshold 1 of 2 — an on-chain policy, not yet a committee |
| Batching | One blob per message — Quilt measured and deliberately deferred until message volume justifies it |
| Pointer | ConversationHead Sui object; blobs immutable, only the pointer moves |
| User key | Sui keypair derived on-device from the master secret — signs the Seal session key, never transmitted |
| Network | Sui testnet; Walrus writes via Mysten’s upload relay |
PS: There is a business model behind this.
An account is bought once, not funded by reading what you send. The unit economics are public.
See the numbers