Encryption
Two layers of encryption protect every event — transit encryption between parties and storage encryption at rest.
Formal specs: DCTRL-0002 — Cryptographic Operations and DCTRL-0005 — Event Encryption and Storage define the exact algorithms, wire formats, and security trade-offs.
Decentrl uses two independent layers of encryption. Transit encryption protects data in flight between Alice and Bob. Storage encryption protects data at rest on each party's mediator. The two layers use different keys and serve different threat models.
Cryptographic Primitives
The protocol builds on three Curve25519-based primitives:
| Primitive | Purpose |
|---|---|
| Ed25519 | Digital signatures — authentication, non-repudiation, identity verification |
| X25519 | Key agreement — derives shared encryption keys via elliptic curve Diffie-Hellman |
| AES-256-GCM | Authenticated encryption — each operation generates a fresh random 96-bit nonce |
AES-GCM output format: nonce(12 bytes) || ciphertext || tag(16 bytes), base64-encoded.
Transit Encryption
Events sent to a recipient are encrypted with the contract's root secret — derived from the ephemeral X25519 key agreement during contract establishment:
ciphertext = AES_GCM_encrypt(plaintext_event, root_secret)All events under a contract share the same root secret. AES-GCM generates a fresh random nonce for each encryption operation, producing unique ciphertext even for identical plaintext.
Storage Encryption
Events stored on your own mediator use your storage key — a local AES-256 key that never leaves your device:
storage_ciphertext = AES_GCM_encrypt(plaintext_event, storage_key)This decouples transit encryption (scoped to one contract) from storage encryption (using a local-only key). The implications:
- Compromised root secret → only that contract's transit traffic is exposed. Stored data is safe.
- Compromised mediator → exposes nothing. The mediator has neither the root secret nor the storage key.
- Compromised storage key → requires device access. Attacker can read stored events but cannot impersonate you or intercept live traffic.
Event Envelope
The wire format for transit events wraps the encrypted payload with metadata:
{
"contract_id": "<SHA256 hash — tells recipient which root secret to use>",
"event": "<JSON-stringified plaintext event>",
"timestamp": 1699123456,
"signature": "<Ed25519 over canonical JSON of { contract_id, event, timestamp }>"
}The contract_id allows recipients to look up the correct root secret when multiple contracts exist with the same party (e.g., during contract rotation). The event-level signature makes each event independently verifiable — preventing a compromised mediator from swapping payloads or replaying old events.
Encrypted Tags
Events stored on mediators carry encrypted tags that enable querying without exposing metadata:
encrypted_tag = Ed25519_sign("chat.abc", signing_private_key)The result is deterministic for a given key and tag string, allowing exact-match lookups. The mediator stores and indexes these opaque blobs without knowing what they represent. Only the identity that created them can produce a matching tag for a query.
When Alice wants to query "all messages in chat abc", she computes Ed25519_sign("chat.abc", signing_key) and sends that as a tag filter. The mediator performs an exact-match lookup on opaque byte strings — it has no idea what "chat.abc" means.
Why Not Per-Event Forward Secrecy?
Protocols like Signal use sequential key derivation chains (the Double Ratchet) where each message key is derived from the previous chain state and old keys are deleted. This protects against a two-stage attack: compromise the server to record ciphertext, then later compromise the device to get keys.
In Decentrl, events are re-encrypted with the storage key and stored back on the mediator. Device compromise exposes the storage key, which decrypts every stored event regardless of how transit encryption was constructed. The two-stage attack that forward secrecy defends against does not apply.
Instead, Decentrl uses contract rotation to limit exposure windows. This is a deliberate trade-off: a longer break-in recovery window in exchange for stateless encryption that enables seamless multi-device support, device-independent message history, and significantly simpler implementation.
| Property | Signal | Decentrl |
|---|---|---|
| 1:1 key agreement | X3DH + Double Ratchet | Ephemeral X25519 per contract |
| Per-message forward secrecy | Yes | No (contract rotation instead) |
| Break-in recovery | Per-reply (DH ratchet) | Per-rotation (periodic) |
| Multi-device | Limited (linked devices) | Native (stateless encryption) |
| Storage encryption | Device-level (local SQLite) | Storage key (re-encrypted on mediator) |