Contact exchange (invite redeem)
Two strangers become mutual contacts through a slim ephemeral-key invite and a three-leg redeem handshake. The invite blob itself carries no identity material — only an ephemeral pubkey and correlation data (see Invites & contacts for the invite shape). Identity bundles move inside boxes on legs 1 and 3, both of which are bare sends: the two sides are not each other's contacts yet, so the encrypted channel cannot carry them.
Traced from a2a_messaging.mm (generate_invite / mint_eph_invite, add_contact, handle_submit_invite_response, handle_complete_invite).
Key properties visible in the flow
- Single-use: the first valid leg 2 consumes
pending_invites[id]andpending_invite_keys[id]together. A replayed leg 1 aborts withalready-redeemedand mutates nothing; a leg 1 that fails a gate (bad box, forged bundle) consumes nothing. - Disclosure order: the responder discloses its identity first (leg 1); the inviter answers with its own bundle only after the responder's bundle verified (leg 3).
- Why bare sends: on leg 1 the inviter is not registered on the responder side (and vice versa on leg 3), so
send_encrypted_txcould not resolve a source key. The box to the ephemeral key is the confidentiality; envelope signing plus the cid-bind and proof-of-possession checks are the authenticity. - Role invites: when either side is a delegated role, its bundle also carries the delegation cert, root profile, and optional root-CP binding — verified with
verify_identity_bundle, so each side learns the other's verified root linkage (contact_roots).
An invite can also be minted for a hosted child via the cluster contact verb — same construction path (mint_eph_invite), see Cluster lifecycle.