Reference
Ships today

Wire contract

The device/host protocol. You do not need this to build an app.

Everything the device and the companion say to each other rides one link, in one framing, whether the transport is Bluetooth or USB. This page describes that framing — for people porting the protocol to a new host or a new transport, or bringing up firmware against it.

You do not need this to build an app

An app talks to glances, capabilities, and the assistant — never to the wire. The SDK and ZephOS carry every byte below. Read on only if you are implementing the link itself.

The device and the companion share a single framed link. Every frame is one tag byte and a body:

TagNameBody
0x02STREAM_DATAa stream id, then the payload
0x10PINGan 8-byte nonce
0x11PONGthe nonce, echoed

STREAM_DATA is where the work happens. The stream id says what the payload is — audio one way, control another, snapshots a third — so one link multiplexes every concern without a second socket.

Transport, secure channel, streams

TransportBluetooth or USB.
framed bytes
Secure channelEvery frame encrypted and authenticated.
stream frames
StreamsAudio, control, input, snapshots, transfer.
Add a transport and you add one endpoint. Add a feature and you add one stream. There is no second path.

The streams

Stream ids are identical on both sides. Each has a fixed direction and a QoS class the link scheduler honors — realtime beats interactive beats bulk beats diagnostics, and a diagnostics stream drops its oldest frame under pressure rather than stall a newer one.

idnamedirectioncarries
0diagdevice → hostthe capability descriptor and health
1audio_downhost → deviceplayback audio
2smpbothcontrol (SMP / mcumgr)
4audio_updevice → hostmicrophone audio
5telemetrydevice → hostcounters
6inputdevice → hostbutton, gesture, voice, and nav events
7audio_ctrlhost → deviceuplink start/stop, PTT overlay
8appshost → deviceglance and screen snapshots
9flow_creditdevice → hostUSB receive-window grants
10xferbothreliable windowed blob transfer (apps, assets, updates)
11app_msgbothmessages between an app's two halves
12secureboththe handshake, before encryption is up

Every frame is encrypted

Before any stream carries data, stream 12 runs a three-message Noise_XX_25519_ChaChaPoly_SHA256 handshake in the clear — the host opens it, the device answers. The moment it completes, every frame on every stream is a Noise transport message: the plaintext frame followed by a 16-byte authentication tag. The link is fail-closed — until the handshake finishes the device sends nothing and accepts nothing but the handshake itself, and a frame that fails to decrypt is dropped, not acted on.

The device's static key is its identity; the host pins it the first time and refuses a changed key on reconnect. Security covers the trust model in full.

The handshake, message by message

The prologue is the 14 bytes zeph-secure-v1. The host is the initiator, the device the responder.

  • msg1 (host → device): -> e — 32 bytes.
  • msg2 (device → host): <- e, ee, s, es — 96 bytes, carrying the device's encrypted static key.
  • msg3 (host → device): -> s, se — 64 bytes.

The device arms the responder when the link comes up — L2CAP connected on Bluetooth, the DTR edge on USB — reads msg1, sends msg2, reads msg3, and is established. After that the per-direction nonces advance in lockstep; any gap tears the session down and a reconnect re-handshakes from scratch. USB receive-window grants (stream 9) are the one exception that stays in cleartext: they are transport-layer flow control that sits below the cipher, and the host reads them before decryption.

Audio is Opus

Microphone and playback audio both ride as Opus frames — up on audio_up, down on audio_down, control on audio_ctrl. The codec parameters are fixed, so a host decodes without negotiating them per connection.

Control is SMP

Everything the companion configures or commands — settings, glances, the app registry, notifications, storage, bonds, power — travels as SMP (mcumgr) requests on stream 2, under vendor groups the device answers. New capabilities arrive as new group ids, so a host that does not know one simply does not call it.

Framing, per transport

Both transports length-prefix each frame with a 2-byte little-endian count — the same on Bluetooth's L2CAP channel and on the USB CDC byte stream. A host must reassemble by that prefix in both directions: the stream is byte-oriented and coalesces or splits frame boundaries under load, so one read is not one frame. The length counts the encrypted bytes once the secure channel is up.

It is versioned

The device reports a contract version in its capability descriptor on stream 0. Additive changes — a new stream, a new SMP group, a new field — keep older hosts working: they skip what they do not recognize. A framing change is a flag day, and both sides move together. A host reads the descriptor and adapts; it never assumes a wire detail it did not read from the device.

Next

On this page