Add end-to-end encryption to an app
End-to-end encryption means a message is encrypted on the sender's device and decrypted only on the recipient's. The protocol is the part people name. An app needs several more parts around it before the first message can move.
What an app has to have
Each of these is needed whichever protocol implementation you choose. Each entry ends with how Tacenta handles that part.
- A device identity
- Each device holds its own long-term keys, created on the device and never sent to a server. Tacenta's client creates them on the device and lets you export and restore them with the session state.
- A key directory
- To start a conversation with someone who is offline, a sender fetches their public keys and a prekey bundle from a server. Tacenta runs a public-key directory that holds identity keys and prekey bundles.
- Session setup and the ratchet
- The two devices agree keys, then keep changing them as messages flow, which limits what a single stolen key can expose. Tacenta implements the published Signal designs: the PQXDH handshake, the Double Ratchet and a sparse post-quantum ratchet.
- Delivery
- A server stores and forwards envelopes it cannot read, and holds them for a device that is not connected. Tacenta's hosted relay does this. It sees ciphertext and delivery metadata, such as who messages whom and when, never message contents.
- Users and access
- Your service decides who may sign in. With Tacenta, your tenant's API key scopes your users, and each client is one signed-in user on one device.
- State that survives a restart
- Session state changes with every message and must be kept safely, without restoring an old copy. The production state guide covers storage on each platform.
The calls
With those parts in place, an app connects to its tenant, signs users in, finds the recipient and sends. This is the TypeScript head; Swift, Kotlin and Rust have the same calls.
import { Tacenta } from "@tacenta/sdk";
const tenant = await Tacenta.connect("tct_your_api_key");
await tenant.signUp("alice", "correct horse");
await tenant.signUp("bob", "correct horse");
const alice = await tenant.signIn("alice", "correct horse");
const bob = await tenant.signIn("bob", "correct horse");
const toBob = await alice.find("bob");
if (toBob) await alice.send(toBob, "hello from the browser");
for await (const m of bob.inbound()) {
console.log(m.from, m.text());
} To see the round trip before writing code, the quickstart sends one from the command line in two commands.
Build it yourself or use a service
A protocol library on its own gives you session setup and the ratchet. You then run the directory, delivery, accounts and state handling yourself. That is the right choice when you need full control of the servers. Tacenta provides those parts as a hosted service with SDKs, and its source is public under Apache-2.0 if you want to run the server.
Know the limits first
- Not yet independently audited
- No external audit has issued a report. The assurance record sets out what is proven, what is only tested and what is assumed.
- One to one only
- Group messaging is not implemented.
- Built from source
- No SDK is on a package registry yet. Each head builds from the public repository.
- Delivery is to connected devices
- There is no mobile push integration, so a device collects its messages when it next connects.