Link setup and AirShield
Link setup is the handshake on service 5. It runs once per link, before any DataX message is encrypted, and it installs the keys that AirShield frames and ciphers uses for every later frame. This page covers the three link-setup messages, the key exchange, the parameters and quirks, and the channel binding. It also covers EndLinkSetup and its UUID rule, the protocol phases, and the failures and what they look like on the wire.
Where the handshake sits
Each handshake message is a DataX message sent as a whole SDU. There is no AirShield frame byte in front of it. The first byte is the DataX header, and its top bit (hasExtension) is set because every handshake message carries tags. Only the sealed frames that follow the handshake carry the AirShield type byte.
Link setup makes no network call. Meta's servers take no part in anything on this page.
Message order
The phone sends two messages, reads the glasses' two messages, computes the keys, runs trust and only then sends EndLinkSetup. The glasses' own EndLinkSetup comes after ours, and the phone answers it.
sequenceDiagram
participant Phone
participant Glasses
Phone->>Glasses: RequestEncryption (ch 0x8001, svc 5, type 1)
Phone->>Glasses: EnableEncryption (ch 0x0001, type 2)
Glasses-->>Phone: RequestEncryption (ch 0x8001, svc 5, type 1)
Glasses-->>Phone: EnableEncryption (ch 0x0001, type 2)
Note over Phone,Glasses: ECDH and key schedule, nothing sent
Phone->>Glasses: svc36 trust exchange (sealed DATA)
Phone->>Glasses: EndLinkSetup (sealed, ch 0x8001, type 0x1000)
Glasses-->>Phone: EndLinkSetup (sealed, ch 0x8001, type 0x1000)
Phone->>Glasses: status 0x1000 on mirror ch 0x0001 (sealed)
Note over Phone,Glasses: READY
The phone's steps, in order:
- The phone draws an ephemeral P-256 key pair, a 16-byte challenge, a 32-byte seed, a 16-byte IV and a random 32-bit sequence start.
- The phone sends RequestEncryption on channel 0x8001. It is the only handshake message that carries the service tag (5).
- The phone sends EnableEncryption on channel 0x0001 at once. It needs only the phone's own values.
- The phone reads the glasses' messages. It matches them by message type 1 and 2, not by channel. Any DATA frame that arrives in this loop is held until the handshake ends.
- The phone computes the shared secret from its private key and the glasses' EnableEncryption public key.
- The phone derives one key set for each direction.
- The phone keeps the channel binding material. Nothing is sent.
- The phone runs the svc36 trust exchange. Its signature covers the channel binding.
- The phone sends EndLinkSetup as the first sealed frame.
- The phone waits for the glasses' EndLinkSetup and answers it.
- The phone is ready for DataX traffic.
Message encodings
The messages use protobuf field numbers. Varints use wire type 0, and length-delimited bytes use wire type 2.
RequestEncryption (message type 1, channel 0x8001)
| Field | Type | Phone sends | Notes |
|---|---|---|---|
| 1 | bytes, 64 | ephemeral public key, x then y, 32 bytes each, no 0x04 prefix |
Big-endian, fixed width |
| 2 | bytes, 16 | challenge | The nonce for the phone's receive keys |
| 3 | varint | 0 | Curve, 0 is secp256r1 |
| 4 | varint | 31 | Supported parameters, see Parameters |
| 5 | bytes, repeated | none | Key hints. The phone sends none |
| 6 | varint | omitted | Quirks, omitted when zero, see Quirks |
| 7 | varint | 16 | AirShield version, see open questions |
In the glasses' RequestEncryption, field 5 holds one key hint and field 7 is 17. The glasses send the same public key in both of their messages.
EnableEncryption (message type 2, channel 0x0001)
| Field | Type | Phone sends | Notes |
|---|---|---|---|
| 1 | bytes, 64 | ephemeral public key, same as in RequestEncryption | Used for ECDH |
| 2 | bytes, 32 | seed | Random |
| 3 | bytes, 16 | IV | Random, the initial CBC IV for the phone's direction |
| 4 | varint, unsigned 32 bit | base | Random, the initial sequence number for the phone's direction |
| 5 | varint | 31 | Parameters. The glasses drop the phone's frames when this is 1 |
| 6 | varint | omitted | Quirks, omitted when zero |
| 7 to 9 | varint | omitted | Link setup fields, omitted when zero |
The glasses also send fields 7 and 8, phased link setup support and a supported services bitmask. Whether they change the outcome is unknown. Field 9 is the link switch version the sender supports, and the glasses log the version the two sides agree on. What each version changes is unknown.
EndLinkSetup (message type 0x1000, channel 0x8001)
| Field | Type | Value | Notes |
|---|---|---|---|
| 1 | varint | 1 | The link's state: 1 MAIN, the link to use now, or 0 READY, a link that joins the session beside the main one. The phone sends 1 on every connect |
| 2 | bytes, 16 | stream UUID | The sender's UUID for the link's root stream. RFC 4122 version 4, see below |
| 3 | bytes, 16 | link UUID | The sender's UUID for the link. The glasses file the link under both sides' link UUIDs, and a switch names the link by it. RFC 4122 version 4. Meta's app leaves it out |
Field 4 is a map of user data. The phone leaves it out.
Sizes and header bytes
The DataX header is 4 bytes. Each tag adds 4 bytes. The payload length in the header counts everything after the 4 fixed bytes. The table gives the SDU size for each message.
| Message | Channel | Tags | Protobuf bytes | SDU bytes |
|---|---|---|---|---|
| RequestEncryption, no key hint | 0x8001 | svc 5, type 1 | 90 | 102 |
| EnableEncryption | 0x0001 | type 2 | 121 plus the length of field 4 | 129 plus that length |
| EndLinkSetup, plaintext | 0x8001 | type 0x1000 | 38 | 46, then 58 on the wire when sealed |
The EnableEncryption size depends on the random base. The field 4 varint is 1 to 5 bytes. A 5-byte base gives 134 bytes.
RequestEncryption header, 12 bytes:
80 62 80 01 81 00 00 05 02 00 00 01
80 62:hasExtensionset, payload length 0x062 = 98 (8 bytes of tags plus 90 bytes of protobuf).80 01: channel 0x8001.81 00 00 05: tag 1 (service id), continuation bit set, value 5.02 00 00 01: tag 2 (message type), last tag, value 1.
EnableEncryption header, 8 bytes, with a 5-byte base:
80 82 00 01 02 00 00 02
The payload length is 0x82 = 130, which is 4 bytes of tags plus 126 bytes of protobuf. There is no service tag.
EndLinkSetup plaintext header and body, 46 bytes:
80 2A 80 01 02 00 10 00
08 01
12 10 <16 bytes scope UUID>
1A 10 <16 bytes lease UUID>
The message type tag holds 0x1000 in its 16-bit value.
Key exchange and key schedule
Both sides use secp256r1 (NIST P-256). The public key on the wire is 64 bytes, x then y, with no 0x04 prefix. Each coordinate is exactly 32 bytes, big-endian, with leading zeros kept.
The shared secret S is the 32-byte x coordinate of the ECDH point. The phone uses its ephemeral private key and the glasses' EnableEncryption field 1. It does not use the glasses' RequestEncryption key. Each direction then derives its own keys with HKDF over SHA-256:
salt_aes = SHA-256(nonce || seed)
salt_hmac = SHA-256(seed || nonce || "hmac_derive")
PRK_aes = HMAC-SHA-256(key = salt_aes, data = S)
PRK_hmac = HMAC-SHA-256(key = salt_hmac, data = S)
aes_key = HKDF-Expand(PRK_aes, info, 32)
hmac_key = HKDF-Expand(PRK_hmac, info, 32)
The two keys differ only in their salt. The info is the 9 bytes AirShield for both, unless the direction uses the QCS7 quirk. For 32 bytes the expand step is one HMAC block, HMAC-SHA-256(PRK, info || 0x01).
Without Multikey there is no separate hmac_key: the AES key is also the HMAC key. See Parameters.
The inputs cross between the two peers. The nonce is the receiver's challenge. The seed, IV and starting sequence come from the sender:
| Direction | Nonce | Seed | Initial IV | Starting sequence |
|---|---|---|---|---|
| Glasses to phone (receive) | the phone's RequestEncryption challenge | glasses EnableEncryption field 2 | glasses field 3 | glasses field 4 |
| Phone to glasses (send) | glasses RequestEncryption field 2 | the phone's seed | the phone's IV | the phone's random sequence |
The two directions share S and nothing else. Each has its own AES-256 key, HMAC-SHA-256 key, IV chain and sequence counter.
Parameters
RequestEncryption field 4 and EnableEncryption field 5 hold a bit set of encryption parameters. A link uses what both sides offer. The phone ANDs its own value with the values in the peer's two messages.
| Peer | Parameters |
|---|---|
| Glasses | 31 |
| Neural Band, Ray-Ban Meta Burbank Audio | 27 |
| Bit | Name | Effect |
|---|---|---|
| 0x04 | Multikey | A separate HMAC key from the hmac_derive salt. Without it, one HKDF key is both the AES key and the HMAC key |
| 0x08 | Sets the first two bytes of the MAC word. See AirShield frames and ciphers | |
| 0x01, 0x02, 0x10 | Set by every peer seen. With them off the key schedule changes, and this page does not cover that case |
A phone that keeps two keys against a peer that offers 27 fails at the first frame with a MAC error.
Quirks
Field 6 of both messages is the quirks mode: 0 for none, 1 for QCS7. Ray-Ban Meta Burbank Audio send 1 in both messages. QCS7 changes two things for a direction:
- The HKDF info is 136 bytes instead of
AirShield: the ASCIIAirShieldpadded with zeros to 128 bytes, then the little-endian u32 32, then the little-endian u32 256. - The last byte of the MAC word is 1, so the word is
02 02 00 01. See AirShield frames and ciphers.
Each side takes the quirks for a direction from the peer message that keys that direction, when the value there is not zero. Otherwise it keeps its own value.
| Direction | Quirks from |
|---|---|
| Glasses to phone (receive) | the glasses' EnableEncryption field 6 |
| Phone to glasses (send) | the glasses' RequestEncryption field 6 |
The phone sends no quirks. A QCS7 peer then keeps its own value, 1, for both directions, and the phone uses QCS7 for both.
Channel binding
The channel binding is a 32-byte digest of the handshake values. The svc36 trust exchange signs this digest on every connect. The digest mixes the glasses' challenge and RequestEncryption key with the phone's seed and key:
binding = SHA-256( SHA-256(glasses challenge || glasses RequestEncryption key)
|| SHA-256(phone seed || phone public key) )
The phone signs the digest with NONEwithECDSA over the prehashed digest. Coordinates are fixed at 32 bytes, and s is in the low half. Any mistake shows up as 0x1043 INVALID_APP_IDENTITY. The message layout is on Identity and trust.
Every input is a public handshake value. The digest needs no private key, so anyone holding the handshake messages can recompute it. Each connect makes new ephemeral keys, so each connect makes a new digest and a new signature.
Two alternate inputs draw 0x1043 INVALID_APP_IDENTITY from the glasses. The phone sends only the primary digest.
Unconfirmed: whether the official app signs the same value.
EndLinkSetup and the UUID rule
The phone sends EndLinkSetup after trust. Its stream UUID and link UUID must both be RFC 4122 version 4 UUIDs. The glasses check the two UUIDs with atc::UUID::valid(), which tests:
(b[6] & 0xf0) == 0x40(b[8] & 0xc0) == 0x80
The phone generates both UUIDs as version 4 values, so they pass both tests.
Why EndLinkSetup follows trust
Unconfirmed: the glasses assign a scope to the peer at EndLinkSetup. The peer has no scope until trust gives it one. That would explain these symptoms.
Answering the glasses' EndLinkSetup
The glasses send their own EndLinkSetup on channel 0x8001 after ours. The Neural Band sends it on a channel of its own, 0x9001, with no service tag. The phone must answer with a DataX status frame on the mirror channel. The mirror clears the secure bit, so channel 0x8001 maps to 0x0001. The status carries error id 0x1000 with no payload:
80 04 00 01 03 00 10 00
That is 8 bytes of plaintext. It is padded to 16 bytes and sealed, so the frame on the wire is 26 bytes. The glasses emit a stream-0 LOST about 1 ms after an unanswered EndLinkSetup.
On a link set up as READY the phone does not answer: see Wi-Fi Direct.
Protocol phases
Link setup runs in five phases. Each phase depends on the one before it.
stateDiagram-v2
[*] --> Handshake
Handshake: RequestEncryption and EnableEncryption, both ways
Handshake --> Keys
Keys: ECDH and key schedule, nothing sent
Keys --> Trust
Trust: svc36 signed channel binding
Trust --> EndLinkSetup
EndLinkSetup: both sides send it, the phone answers with status 0x1000
EndLinkSetup --> Ready
Ready: sealed DataX traffic
The phone never sends EndLinkSetup before the trust exchange. A failed link setup leaves no keys. The next attempt makes new ones.
Timings
Times count from the first handshake message.
| Step | Time |
|---|---|
| Four handshake messages, both directions | 0 to 87 ms |
| Trust exchange | 91 to 103 ms |
| Phone's EndLinkSetup | 103 ms |
| Glasses' EndLinkSetup and the phone's answer | 104 to 106 ms |
| Glasses' stream-0 LOST after an unanswered EndLinkSetup | about 1 ms |
Failure modes
| Symptom | What the phone does | What the wire shows |
|---|---|---|
| The glasses send no handshake reply, or stop answering | The phone ends the attempt | The phone's two messages, then nothing |
| Peer key or IV has the wrong length | The phone ends the attempt | The glasses' two messages, then the phone closes the link |
| Parameters or quirks not followed | The phone ends the attempt | The handshake completes, then the first sealed frame fails its MAC check |
| Trust verdict incomplete | The phone goes on to the ready phase | The glasses decide. Services answer service_not_found |
| The glasses send no EndLinkSetup after the phone's | The phone ends the attempt | The phone's EndLinkSetup, then silence |
| UUID with a bad version or variant | The phone must not send it | The glasses tear down the channel silently, or answer service_not_found |
| EndLinkSetup sent before trust | The phone sends trust first | Stream-0 LOST, then silence, or service_not_found on every service |
| Status answer to their EndLinkSetup missing | The phone must send it | The glasses emit stream-0 LOST about 1 ms later |
Reference code: SecureLink.kt, Messages.kt, Kdf.kt, Binding.kt
Open questions
- Unknown: whether the glasses accept a field 7 value other than 17 in RequestEncryption.
- Unknown: how the glasses react to a peer public key that is not on the curve.
- Unknown: whether the glasses ever send fewer than two handshake messages.
- Unknown: whether the glasses' fields 7 and 8 ever change the outcome.
- Unknown: whether QCS7 changes anything besides the HKDF info and the MAC word. Setup on Ray-Ban Meta Burbank Audio with both changes is not yet confirmed.
- Unknown: whether a QCS7 peer needs the phone to send quirks 1 in its own messages.
- Unknown: whether the status answer to the glasses' EndLinkSetup is the only answer they need.
- Unknown: why EndLinkSetup must follow trust. The section above gives the likely reason.
- Unknown: whether COMMAND frames can arrive during link setup over Bluetooth. See AirShield frames and ciphers.