Menu 0.6.0 preview

Reference

Agent Docs: Protocol and wire format

Protocol

The TrueSeal protocol is what a device and a relay must agree on, byte for byte, for a message to get from one device to another. It has no library of its own. trueseal-sync implements the device side and trueseal-relay the relay side, and the Sync-level test vectors pin down the bytes both must produce.

This page describes the protocol as the Developer Preview decides it. Where today’s code still differs, each Reference page lists the gap at its end.

Two layers, two versions

The protocol has two layers, and each carries its own version number.

LayerBetweenWhat it coversVersion
TransportDevice and relayThe Noise handshake, the frames inside it, acks, refusals and heartbeatsTransport Version
End-to-endDevice and deviceThe sealed Envelope, addressed encryption, signatures and the Group ManifestEnd-to-End Version

Both versions start at 1, and 0 is reserved. In the preview the Transport Version is 1 and the End-to-End Version is 1. Each version is bound into the cryptography as well as sent as a number. The Transport Version goes into the Noise prologue, and the End-to-End Version goes into the key derivation, the AEAD associated data and every signing input.

There is no negotiation. A component speaks exactly one version of each layer and rejects anything else:

  • A relay that gets a Transport Version it doesn’t speak replies with a plaintext unsupported min..max and closes the connection. The SDK reports it as connection = relayVersionUnsupported{min, max}.
  • A device that receives a message in a newer End-to-End Version leaves it unacknowledged on the relay, so it can be handled after the app upgrades. It holds up to 100 Blobs per device, then acknowledges and drops the oldest.
  • A device that receives a message in an older End-to-End Version, or one it can’t decode, acknowledges and drops it.

The device reports each case to its own app, and nothing is sent back to the device that sent the message. A version bump means every device upgrades together, and a Transport Version bump means the relay upgrades with them; Upgrading covers what that does to queued messages.

The relay is always in the path

Devices never connect to each other. Each device opens two kinds of outbound connection to the relay, on two TCP ports, and both run the Noise Protocol Framework with the cipher suite 25519_ChaChaPoly_BLAKE2s. The device checks the relay’s static public key on both, against the key in its Relay Address.

Push SessionReceive Session
PurposeHand messages to the relayTake messages from the device’s Inbox
Noise patternNKXX
Device key sentNone. A fresh ephemeral key per session.The device’s static X25519 key
Default port77017700
LengthShort. The relay closes it after 30 seconds.Open as long as the device is online

Push Session

A device opens a Push Session when its outbox has messages for the relay.

  1. The device connects to the push port and runs the NK handshake. It has to finish within 10 seconds.
  2. The device sends one Push frame per message. Each frame names the recipient’s public key.
  3. For each Push, the relay writes the message to the recipient’s Inbox on disk, then replies Ack. If it refuses the message, it replies Error with a reason code and stores nothing.
  4. The device deletes each acknowledged message from its outbox and closes the session.

The relay closes any Push Session still open 30 seconds after it accepted the connection. A message the relay didn’t acknowledge, and didn’t refuse for good, stays in the outbox and goes out again in a later Push Session.

The NK handshake gives the relay no key that belongs to the sending device. The relay can still often tell which device pushed from the timing of the connection, and the server it runs on also sees its IP address. The How it works page lists what the relay sees.

Receive Session

A device keeps one Receive Session open while it’s online.

  1. The device connects to the receive port and runs the XX handshake within 10 seconds. The relay learns the device’s static public key, which is also the address of its Inbox.
  2. The relay sends each message in the Inbox as a Deliver frame, in the order they arrived, then new messages as they arrive. The device takes nothing from its Inbox before the app has registered its message handler.
  3. The device handles each message, then replies DeliverAck. The relay deletes the message only then, and only from that device’s own Inbox.
  4. The device sends a Heartbeat every 25 seconds and the relay echoes it. The relay closes a Receive Session when it has received nothing from the device for 75 seconds.

The relay sends each message once per Receive Session. A message without a DeliverAck stays in the Inbox and is delivered again on the next Receive Session. The relay allows 4 Receive Sessions per device key at once.

Sending and receiving gives the delivery promise this adds up to. Limits and quotas lists the relay’s limits and the refusal each one causes.

Where today’s code differs

The relay doesn’t yet enforce the handshake and session deadlines, the 75-second idle limit or the cap on Receive Sessions per key (trueseal-relay#43). Neither layer carries a version yet (trueseal-sync#93, trueseal-relay#42). Wire Format lists the differences in the frames.

What the protocol leaves to the app

The protocol moves opaque bytes between the members of a group. What the bytes mean, how two conflicting changes merge, and what a new member should receive from the past stay with the app.