# Onboarding Quick Reference

Adding/verifying a contact. Full detail: `DESIGN.md` §16.

## Architecture
- **Invite = rendezvous address + ephemeral PUBLIC key** (never private/long-term keys). Single-use; locks on first redeem so link theft self-detects.
- Invite contents are signed-bound (PQ hash + hybrid sig) → relay can't substitute a key.
- **SAS verification** over a second channel (recognise voice / in person) DETECTS first-contact MITM. ZRTP-style commitment keeps the string short.
- **Trust provenance** per contact: `unverified` (default) → `sas-verified` / `scanned` / `introduced-by-X`; `key-changed` blocks until re-verify.

## Implementation (`core::invite`, ADR-007)
- **Async friend-request/accept** establishes the pairwise root `R` with an **offline** peer — no synchronous handshake. X3DH-style on the existing hybrid primitives (X25519 + ML-KEM-768 + Ed25519, same combiner/root as the sync handshake).
- Invite = signed one-time **prekey** (ephemeral X25519 + ML-KEM ek) + rendezvous secret. `request_friend` → `accept_friend` → `verify_accept`; both derive identical `K`/`R` and **SAS** (`safety_emoji(K)`) for out-of-band MITM check.
- Rides the **oblivious relay**: request/accept sealed under `seal_key(rendezvous)`, deposited under `request_token`/`accept_token` (key-separated). The retained `InviteSecret` is small (X25519 secret + 64-byte ML-KEM seed), stored **sealed at rest**.
- CLI: `invite-create` / `invite-request <relay> <invite> <name>` / `invite-accept <relay>`. The resulting `R` works with the directory (publish / `dial`). Single-use invite (one prekey per request).

## Common Mistakes
- Treating a link shared over WhatsApp as final trust (it's a bootstrap; require SAS).
- Marking a contact verified without an out-of-band check.
- Not alerting on identity-key change.
- Reusing a single-use invite for multiple contacts (one prekey per request — create a fresh invite).

## Full Patterns
→ `DESIGN.md` §16; "authentication is harder than encryption."
