Menu 0.6.0 preview

Reference

Agent Docs: Protocol and wire format

trueseal-noise

trueseal-noise turns a byte stream, such as a TCP socket, into an encrypted, authenticated channel. TrueSeal uses it for every connection between a device and the relay. It is a small Rust wrapper around snow, an implementation of the Noise Protocol Framework, and it offers exactly two handshake patterns.

It knows nothing about groups, messages or the relay. It takes any connection that implements Read + Write + Send and returns a session with send(), receive() and close().

Two patterns

PatternUsed forWho proves their key
Noise_XX_25519_ChaChaPoly_BLAKE2sReceive SessionsBoth sides
Noise_NK_25519_ChaChaPoly_BLAKE2sPush SessionsOnly the responder, which is the relay

The two patterns return two different session types. Only the XX session has remote_public_key(), because in NK the responder never learns a key for the initiator. The compiler enforces the difference, in Rust and in the Swift and Kotlin bindings that UniFFI generates.

What a session gives you

Once the handshake completes:

  • Nobody on the network path can read the contents of what the session carries. The size and timing of each message stay visible.
  • Any change to a message in transit makes it fail to decrypt, and the session reports an error.
  • With NK, the handshake completes only if the responder holds the private key for the static key the initiator passed in. With XX, each side proves its static key to the other, and the caller decides whether to trust the key it receives.
  • Each session derives its keys from fresh ephemeral keys, so someone who steals a static private key later still can’t decrypt a recorded session.

That last property, forward secrecy, covers the connection to the relay only. The messages inside are sealed separately to each recipient’s long-lived key and have no forward secrecy. Envelopes and Blobs explains that layer.

Cipher suite

Every session uses X25519 for key agreement, ChaCha20-Poly1305 for encryption, and BLAKE2s for hashing. There is no negotiation. ChaCha20-Poly1305 runs in constant time in software, so a device without hardware AES support doesn’t leak timing the way AES-GCM would.

Framing and size

Each Noise message goes on the wire behind a 2-byte big-endian length. A Noise message is at most 65,535 bytes, including the 16-byte authentication tag, so send() takes at most 65,519 bytes and refuses anything larger. trueseal-noise never splits a payload; a caller with more data sends several. TrueSeal’s own size limits follow from this ceiling, as Wire Format shows.

Prologue

In the preview, callers pass a prologue to the handshake, and both sides must pass the same bytes for it to complete. trueseal-sync and trueseal-relay put the Transport Version in it, so a connection between mismatched versions fails the handshake.

Test vectors

tests/cacophony.rs checks the snow handshakes trueseal-noise builds against one Noise_XX_25519_ChaChaPoly_BLAKE2s and one Noise_NK_25519_ChaChaPoly_BLAKE2s vector from the cacophony test vectors listed on the Noise wiki, kept in testdata/cacophony.json. The vectors fix every key, ephemeral keys included, so a correct implementation produces byte-identical output. The test doesn’t run dial, accept or the framing.

Release

trueseal-noise is versioned on its own, starting at 0.2.0 in the preview, and released only as git tags. It isn’t published to crates.io.

Source: github.com/julianbonomini/trueseal-noise.

Further reading