Skip to content

Security model

The design never hands the operator your keys, so the operator cannot read your content. That is the whole trust story, and this page spells out its edges: what is encrypted and how, what the relay and directory necessarily see, who holds which keys, and the discipline that keeps our claims from drifting past the architecture.

There are two encryption layers, one per job:

Your own seat’s sync. tally push seals log deltas with a sync key derived from your seed, using AES-256-GCM, before anything leaves the machine. What crosses the wire and what the relay stores is ciphertext. tally pull on another of your devices decrypts locally with the same seed-derived key. See What syncs for the full walk-through.

Multi-party channels. Rooms with more than one member use MLS (RFC 9420). Each channel is one MLS group; members derive shared keys through the group’s ratchet tree, and key material rotates on every membership change. By protocol design this gives forward secrecy (old epoch keys are deleted, so compromising a member today does not decrypt yesterday) and post-compromise security (a key update locks a past intruder out of the future). The relay carries MLS ciphertext and cannot join the group it is carrying.

In both layers, encryption and decryption happen in the client. The server-side components move opaque bytes.

Be precise here, because “end-to-end encrypted” is not “invisible.” The operator (us, or whoever runs the relay you point at) necessarily sees:

  • Ciphertext: the sealed batches and MLS messages themselves, opaque but present.
  • Channel names: channels are registered in the directory under a readable org/namespace/service name, and the transport group-id is a SHA-256 of that name. Do not put secrets in channel names.
  • Timing and sizes: when a seat published or fetched. The precise state: every tally send path (tally push batches, tally msg, rooms) pads its ciphertext to fixed size buckets (the skytale-padding crate: 8 buckets from 256B to 32KB, Padme for larger payloads), so the relay sees quantized sizes, not real ones. Long-lived connections (message watch, open rooms) additionally emit cover traffic: bucket-shaped random frames indistinguishable from real ones on the wire, on by default (tally config set cover_traffic off disables). What remains observable: THAT a short one-shot operation (a single push or pull) happened and when; cover flows only while a connection is open, so one-shot timing is not masked. We state that limit rather than claim more.
  • Account metadata: the email you signed up with, hashed API keys, which channels your account registered, join requests and device grants, and revocation state.
  • Membership shape: who requested to join which channel, because the directory brokers the join handshake.

What the operator cannot see, ever: the content of your log or messages, your seed, your sync key, or any MLS group key. Those exist only on member devices.

  • Your identity and every key derive from a 24-word BIP-39 recovery phrase generated on your machine by tally init. The deliberate reveal ceremony is tally backup.
  • Keys never leave your devices. They are not escrowed, not synced, not recoverable by us.
  • The consequence, stated plainly: losing the seed means losing the data. There is no server-side reset, because a server that could reset your keys could also read your data. Do the backup ceremony.

What the design defends against:

  • Relay or directory compromise. An attacker who owns the entire server side gets what the operator has: ciphertext and metadata. No content, no keys.
  • Stolen transport credentials. The credential tally connect stores is channel-scoped and is a transport credential, not a decryption key. Holding it lets a party move sealed batches on one channel; it does not let anyone read them.
  • A dishonest operator. The property is structural, not contractual. You do not have to trust our policy, because the architecture never gives us the material a policy would govern.

What the design does not defend against:

  • Your compromised device. Plaintext and keys live on your machines. An attacker with your device and your session has what you have.
  • Metadata analysis. As above: channel names, sizes, timing, and membership shape are visible to the operator and, in aggregate, say things.
  • Denial of service. The operator, or an attacker who becomes the operator, can drop or withhold ciphertext. That is an availability failure, not a confidentiality one; self-hosting the relay is the escape hatch (see Self-hosting the relay).

We separate two claims that the industry often blurs, and we hold the line between them:

Level 1: structural end-to-end encryption. This is what we claim today. The clients are open source, keys are generated and held client-side, and the server components handle only ciphertext. You can verify this yourself by reading the code and watching the wire: grep the stores for your plaintext, inspect what tally push emits, confirm the relay archive holds nothing decryptable. The property holds because of how the system is built, not because of who runs it.

Level 2: attested execution (TEE). This is a different property, and we do not claim it. Hardware attestation would let you verify what code a remote machine is running, which matters when an agent executes off your devices. Nothing in the current product runs your plaintext off your devices, so there is nothing to attest and no attestation claim to make. If a hosted execution tier ships later, it gets its own claims with its own proof, labeled separately.

The rule we enforce on ourselves: never use level-2 words for level-1 properties. The relay is structurally blind, not “attested.” If you catch these docs blurring that line, it is a bug; report it.