How your data stays yours

Everything you put into jellysuite is locked on your device before it reaches us. We store the locked version and never hold a key to it. This page shows how that works, in plain words with the exact names alongside, and where it stops.

What we can and can't read, app by app

The same message, twice.

On the left, what you see in Jelly Mail. On the right, what our database holds for it.

One password, a chain of keys

The whole design is one idea. Every key is locked with the key before it, and the first is locked with your password. The chain is opened on your device, link by link. Our servers store every link locked, and have nothing to open the first one with.

  1. 1Your password stays on your device. It opens your account key.
  2. 2Your account key opens your two identity keys. One signs what you do. The other receives the keys people share with you.
  3. 3Those open the keys of your folders. Each folder's key opens the keys inside it, down to every message, page, file and event.
  4. 4Each item's key opens that one item. We store all of it and can open none of it.

Your password never leaves your device

When you log in, your device and our server run an exchange that proves you know your password. The password isn't sent, and neither is anything it could be worked out from. Someone who stole our database would still have to guess passwords one at a time, and every guess is made slow on purpose.

The same exchange leaves your device holding a secret our server never sees. That secret opens your account key, which we only ever store locked. So the moment your data becomes readable is the moment you log in, and it happens on your device.

Under the hood

OPAQUE, an augmented PAKE, with Argon2id stretching inside it. It gives your device an export key the server never learns, and a BLAKE2b subkey of that wraps your 32-byte account key. In the browser, the keys kept for your session rest under a WebCrypto key that can't be exported.

Every item has its own key

Each folder, message, page, file and event is locked with a key of its own. An item's key is stored locked with the key of the folder it sits in, so opening a folder opens what's inside it and nothing beside it. Sharing is exact for the same reason: share one folder and you hand over one key.

Every locked item is sealed together with what it is, which item it is and which version of its key locked it. If stored data were moved to another item or passed off as something else, by anyone including us, your app would refuse to open it instead of showing you the wrong thing.

Under the hood

XChaCha20-Poly1305 with a 32-byte key and a fresh random 24-byte nonce every time. Each envelope starts with a format byte and a type tag, and is authenticated with the item's id and key version as associated data.

Sharing with the person you meant

To share, your device locks the item's key so that only the other person's device can open it, and signs it so they know it came from you. We store that sealed key and hand it over. We can't open it, and we can't slip in a different one without their app seeing that the signature is wrong.

What's left is the risk of being given the wrong person's key to begin with. So every account has twelve safety words, worked out from its key. Compare them once, in person or on a call, and you know the key is theirs. Your app remembers every key it has seen and stops with a warning if one changes.

When you stop sharing, access ends at once and the item gets a new key. Items inside it get new keys the next time they're changed.

Under the hood

The item's key goes into an X25519 sealed box for the other person, with your Ed25519 signature over the item, its key version, the recipient and their role. Keys are pinned on first contact. Safety words are twelve words from the BIP39 list, taken from a SHA-256 hash of the signing key.

Mail, inside and outside jellysuite

Between jellysuite accounts, mail is end to end. The sender's device locks the message for each recipient and signs it, and the app shows a green shield once it has checked the sender is who they say.

Email is older than encryption, so mail with everyone else can't work that way unless they use PGP, and we'd rather say exactly what happens. A message from outside arrives as ordinary email. Our server reads it once, in memory, locks it with your key and stores only that. A message you send outside is read in our server's memory while it's signed and handed on, then travels like any other email. Neither is written to disk unlocked, and the app marks both as outside mail.

Two details. If the outside address has a PGP key, your browser signs and encrypts the message itself and it stays locked the whole way. And a message to an outside address that you schedule for later waits on our server locked with a key the server holds, and is deleted once it's sent.

Under the hood

Inside jellysuite, the message key is sealed for each recipient with X25519 and signed with Ed25519. With PGP, your browser builds the message with OpenPGP, and our servers add only outer headers and a DKIM signature over the ciphertext.

A recovery key, and no back door

We store your account key twice: once locked with your password, and once locked with a recovery key you're shown when you sign up. If you forget the password, the recovery key lets you set a new one.

There is no third copy. We can't reset your password for you, because we never had what it protects. Lose both the password and the recovery key and nobody can get your data back, including us. It's the price of a service that can't be made to read your things.

The same holds in a team. Your organisation can create an account at its domain, suspend it or delete it, but it can't read it or recover it. No admin holds a key to it.

Under the hood

A random 32-byte key, shown as base32 groups. One BLAKE2b subkey of it wraps your account key. Another is stored as a SHA-256 hash and proves you hold the key when you recover.

What encryption doesn't protect

Locking content is a strong promise and a narrow one. These are the things it doesn't cover.

The patterns around your data

Our servers see sizes, times and who you share with. That's less than most services, but it's not nothing. The transparency page lists it for every app.

Who pays for an account

Paying links a real name and billing address to your handle. Those details are held by Stripe, our payment provider, not on our servers, which keep only your plan and whether it's paid. It changes nothing about your content: neither we nor Stripe can read it.

A device someone else controls

Your data is unlocked on your device so you can use it. Malware on that device, or someone with your unlocked laptop, can see what you see.

Mail that leaves for the outside

Once a message reaches someone on another service, it's in their mailbox under their provider's rules.

The app code in your browser

On the web, your browser runs the app code our server sends it. The desktop apps carry their own code instead, and the code that matters most is open for anyone to check.

Check it yourself

The encryption library and the client core are open source under MIT. Nothing is home-made: it's built on libsodium and OPAQUE, both widely reviewed, with OpenPGP.js for PGP mail.

The technical names

Locking items
XChaCha20-Poly1305, 32-byte keys, a fresh random 24-byte nonce each time
Stamped envelopes
A format byte and type tag, authenticated with the item's id and key version
Locking a key for a person
X25519 sealed boxes, signed with Ed25519 by the sender
Logging in
OPAQUE (an augmented PAKE), with Argon2id inside
Deriving keys
BLAKE2b keyed hashing with fixed context strings
Safety words
Twelve words from the BIP39 list, from a SHA-256 hash of the person's signing key
Link and recovery secrets
32 random bytes; BLAKE2b subkeys to wrap and to prove, the proof stored as SHA-256
PGP mail
OpenPGP v4 keys (Ed25519 and X25519), signed and encrypted in the browser
Libraries
libsodium; OPAQUE via @serenity-kit/opaque; OpenPGP.js
Read the code