Key Epochs
Contracts last until revoked, but the key that encrypts traffic under them changes every day along a one-way hash chain.
A contract is consent, and consent doesn't expire: a contract stays valid until one side revokes it. The key that encrypts traffic under a contract is short-lived, though. It changes every day, and the old keys are deleted once nothing needs them any more. Both parties do this on their own, from the contract and their clocks, with no extra messages and no new contract.
How It Works
When a contract is completed, both parties derive the same root secret from the two ephemeral keys. It is used exactly once, to start a hash chain, and then wiped:
chain_0 = HKDF(root_secret, salt = contract_id, info = "decentrl/epoch-chain/v1")
chain_n+1 = HKDF(chain_n, info = "decentrl/epoch-chain-next/v1")
epoch_key_n = HKDF(chain_n, info = "decentrl/epoch-key/v1")Epoch n is day n since the contract's timestamp (86,400 seconds per epoch). Events sent during that day are encrypted with epoch_key_n. Each chain key is derived one-way from the previous one: from today's chain key you can compute tomorrow's, but never yesterday's.
Key State
Each device keeps, per contract, a small key state: the current epoch, its chain key, and the keys of a few earlier epochs that may still have events in flight. The key state lives only on the device, separate from the contract, and is never sent to a mediator.
- Sending: move the chain to today's epoch and encrypt with its key. The new state is saved only after the recipient's mediator accepted the event.
- Receiving: the event says which epoch it used, so the right key is picked directly. An event more than one epoch in the future waits in the queue until your clock catches up (
EPOCH_IN_FUTURE); one from an epoch whose key is already gone is dropped (EPOCH_EXPIRED). Moving forward happens only after the event decrypted and its signature checked out, so forged ciphertext can't make you throw keys away. - Pruning: after reading the whole pending queue, old epoch keys are deleted and quiet chains are moved forward. The previous day's key is kept for late arrivals, plus any key an event still waiting in the queue needs. Never more than 31 past keys are kept.
What It Protects
Key epochs protect transit ciphertext. Once both sides have moved past a day and deleted its key, stealing either device later can't decrypt that day's events, whether they were intercepted on the wire or are still sitting pending on a mediator.
What it doesn't do:
- It isn't per-message. Everything sent on the same day shares a key; there's no Double Ratchet.
- Stored history isn't covered. Events saved on your mediator are encrypted with your storage key. Whoever gets your device gets that key and your whole history, whatever the epoch.
- Replay isn't stopped by the keys. A malicious mediator can re-deliver an event it has already seen, and it still decrypts as long as that day's key is kept. What catches it is the event's signed ID: the copy is recognised as a duplicate, stored once and applied once (see Encryption).
- A device that doesn't sync doesn't move on. Chains move forward when you send, receive, or read your whole pending queue. A device left offline keeps its old keys until it syncs again.
One Device, One Key State
The root secret is gone after the handshake, so key state can't be re-derived. A device that didn't take part in the handshake, like a second browser or one with cleared storage, still sees the contract but holds no keys for it. The SDK marks such a contract unkeyed. To talk again from that device, request a new contract. The newer contract replaces the old one on both sides.
Trade-offs
| Signal (Double Ratchet) | Decentrl (Key Epochs) | |
|---|---|---|
| Key changes | Every reply | Every day |
| Coordination | Ratchet steps with each message exchange | None: the clock and contract timestamp decide |
| Message history | Lost on new device | Available on any device (storage key) |
| New device | Linked device setup | Requests a new contract |
| Implementation | Complex state machine | One hash chain per contract |
Decentrl accepts coarser, day-sized forward secrecy for transit traffic in exchange for key state that needs no coordination and a message history that any authorized device can read.