Skip to content

Link setup and AirShield

Marco Ceccon edited this page Oct 12, 2026 · 2 revisions

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 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
Loading

The phone's steps, in order:

  1. 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.
  2. The phone sends RequestEncryption on channel 0x8001. It is the only handshake message that carries the service tag (5).
  3. The phone sends EnableEncryption on channel 0x0001 at once. It needs only the phone's own values.
  4. 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.
  5. The phone computes the shared secret from its private key and the glasses' EnableEncryption public key.
  6. The phone derives one key set for each direction.
  7. The phone keeps the channel binding material. Nothing is sent.
  8. The phone runs the svc36 trust exchange. Its signature covers the channel binding.
  9. The phone sends EndLinkSetup as the first sealed frame.
  10. The phone waits for the glasses' EndLinkSetup and answers it.
  11. 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
5 bytes, repeated none Key hints. The phone sends none
6 varint omitted Quirks, omitted when zero
7 varint 16 AirShield version, see open questions

In the glasses' RequestEncryption, field 5 holds one key hint and field 7 is 17.

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 to 9 varint omitted Quirks and 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.

EndLinkSetup (message type 0x1000, channel 0x8001)

Field Type Value Notes
1 varint 1 asMain, 1 on every connect
2 bytes, 16 scope UUID RFC 4122 version 4, see below
3 bytes, 16 lease UUID RFC 4122 version 4, see below

The phone omits field 4, repeated user data.

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: hasExtension set, 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,  "AirShield", 32)
hmac_key  = HKDF-Expand(PRK_hmac, "AirShield", 32)

The two keys differ only in their salt. The info string is AirShield for both. For 32 bytes the expand step is one HMAC block with the counter byte 0x01.

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.

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 scope UUID and lease 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.

The rule matters because the glasses fail silently. A random 16-byte value passes both bit tests one time in 64, so two random UUIDs pass together about one time in 4096. With an invalid UUID, link setup completes, then every service answers service_not_found, and the channel is torn down. The failure looks like a permissions problem.

Why EndLinkSetup follows trust

The phone sends EndLinkSetup only after the svc36 trust exchange. If it sends EndLinkSetup first, the glasses answer with a stream-0 LOST followed by silence, or with service_not_found on every service.

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 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.

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
Loading

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
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 the two glasses public keys in the handshake are always equal on other units.
  • 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.

Clone this wiki locally