> ## Documentation Index
> Fetch the complete documentation index at: https://crosslink.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> How devices prove their identity

## Identity model

Every Crosslink device (host or client) has a long-term Ed25519 identity:

```text theme={null}
Identity = {
  privateKey: Ed25519 private key (32 bytes)
  publicKey:  Ed25519 public key (32 bytes)
  deviceId:   SHA-256(publicKey)[:16] (hex, 16 chars)
}
```

The identity is:

* Generated once per installation
* Persisted across restarts
* Used to sign all key material
* Verifiable by any peer

## Pairing authentication

### Step 1: Code generation

The host generates a single-use pairing code:

```text theme={null}
code = random 9-digit number
record = {
  code,
  hostFingerprint: SHA-256(hostPublicKey)[:16],
  expires: now + 2 minutes,
  used: false
}
```

### Step 2: Code resolution

The client sends the code to the signaling service:

```text theme={null}
Client -> Signaling: { code }
Signaling -> Client: { hostConnId, app, psid }
```

### Step 3: Fingerprint verification

The client verifies the host fingerprint from the QR code matches:

```text theme={null}
if (!hostFingerprint.startsWith(qrFingerprint16)) {
  throw new Error("SECURITY: host fingerprint does not match");
}
```

<Warning>
  This is the primary MITM defense during pairing. If the fingerprints don't match, an attacker is likely intercepting the signaling traffic.
</Warning>

### Step 4: Signed claims

Both parties exchange signed public keys:

```text theme={null}
Client -> Host: {
  kind: "claim",
  deviceId: clientDeviceId,
  publicKey: clientPublicKey,
  signature: Ed25519_sign(clientPrivateKey, claimData)
}
```

### Step 5: Challenge-response

The host sends a challenge, and the client signs it:

```text theme={null}
Host -> Client: { kind: "challenge", nonce: random }
Client -> Host: { kind: "challenge_response", signature: Ed25519_sign(clientPrivateKey, nonce) }
```

### Step 6: SAS verification

Both parties derive the same Short Authentication String from their Ed25519 identity public keys (order-independent, so both sides compute the same value regardless of role):

```text theme={null}
sas = deriveOkm(
  IKM: hostIdentityPubKey || clientIdentityPubKey,  // sorted, not role-based
  info: "crosslink-sas-v1",
  context: appId
)
// formatted as three 3-digit groups, e.g. "482 019 337"
```

The user must visually confirm both sides show the same digits.

## Session authentication

Once paired, sessions are authenticated via:

1. **Encrypted frames** -- Only parties with the session key can read/write
2. **Sequence numbers** -- Prevent replay attacks
3. **Nonce uniqueness** -- Prevent frame substitution

```text theme={null}
Frame authentication:
  valid = XChaCha20_Poly1305_verify(key, nonce, ciphertext, tag)
```

## Key hierarchy

```text theme={null}
Long-term identity (Ed25519)
        |
Ephemeral keys (X25519)
        |
Shared secret (ECDH)
        |
HKDF-SHA256
        |
Session key (XChaCha20-Poly1305)
        |
Frame encryption
```

## Revocation

### Host revocation

The host can revoke a client's access by:

1. Calling `server.revokeDevice(deviceId)`
2. This prevents future sessions from being established -- revocation is re-checked on every handshake, so reconnection is refused even though the device still holds a valid Ed25519 key

### Client revocation

The client can forget a paired host by:

1. Calling `client.forget(appId)`
2. This removes the paired-app record
3. Future pairing requires a new QR scan

## Security properties

| Property              | Mechanism                              |
| --------------------- | -------------------------------------- |
| **Authentication**    | Ed25519 signatures                     |
| **Forward secrecy**   | Ephemeral X25519 keys                  |
| **Key confirmation**  | Both derive same session keys          |
| **Replay protection** | Nonces + sequence numbers              |
| **MITM detection**    | SAS verification + fingerprint pinning |
| **Revocation**        | Per-device revocation lists            |
