Skip to main content

Overview

Crosslink’s wire protocol defines three layers, all JSON-encoded and sent as WebSocket text/binary messages:
  1. Pairing frames — exchanged before any trust exists, to enroll a new device.
  2. The CLX1 handshake — an authenticated ephemeral key exchange that establishes a fresh session between an already-paired device and the host.
  3. Session frames — encrypted application messages (RPC calls, responses, events) once a session is established.

Pairing frames

Frames are plain JSON objects with a kind discriminator (no envelope version field at this layer).

pair_hello / pair_ready

Sent when pairing happens directly over a socket to the host (no signaling broker) — resolves the scanned 9-digit code to a live session id.

pair_claim

Client sends a signed identity claim.

pair_challenge

Host replies with its own identity and the capabilities it is willing to grant, signed.

pair_complete / pair_done

Client confirms the Short Authentication String (SAS) with the user and sends the completion frame; host acknowledges.

pair_error

The CLX1 handshake

Once a device is paired, every reconnect runs a fresh authenticated key exchange (“CLX1”):
sinit carries the client’s static X25519 key (sx), a fresh ephemeral X25519 key (epk), a 32-byte nonce (nc), a timestamp, and an Ed25519 signature over the transcript. sack carries the host’s ephemeral key (epk), its own 32-byte nonce (nh), and its signature. A srej { kind: "srej", code, message } frame rejects a handshake outright.

Key derivation

Fresh ephemeral keys on every reconnect give forward secrecy; the static-static term binds the session to the paired identities; signatures over the canonical transcript prevent MITM, cross-application relay, and replay.

Session frames

Once a session’s traffic keys are derived, every application message is sealed independently with XChaCha20-Poly1305.
  • Algorithm: XChaCha20-Poly1305 (AEAD; the 16-byte Poly1305 tag is appended to ct)
  • Nonce (iv): 24 bytes, random per frame
  • n: a strictly monotonic per-direction counter, starting at 1 — used as part of the AAD, so replay, reordering, and cross-direction substitution all fail authentication

Application messages

The plaintext inside each session frame is one application message, sharing a common envelope: v (protocol version, e.g. "1.0") and t (message type). i is the request id, m the method name, p the payload, s a subscription id, e an event/method name depending on context. See Error Codes for the values err.e.code may take.

Version negotiation

Current protocol version: 1.0 (PROTOCOL_VERSION). A hello message carries the sender’s supported versions (highest last); the negotiated version is the highest one both sides support. There is no floor below which a connection silently downgrades.