# ADR-008: Identity rotation via a signed continuity (migration) record

## Status

Accepted

## Date

2026-06-01

## Context

A user may want to **rotate their identity key** — for hygiene, after a suspected compromise, or for
unlinkability — without losing their contacts. Naively generating a new identity makes every contact
see `PinCheck::Changed` (indistinguishable from a MITM), forcing a full out-of-band re-verification.
We want an option where **contacts follow the rotation automatically** when it is cryptographically
proven to be the same person, while still supporting a clean break when desired. (This is the user's
early "reset the hash but tell my contacts it's still me" request.)

## Decision

A [`MigrationRecord`] (`pvtcoms_core::rotation`) states `old → new` and is signed by **both** keys:

- `sig_old = old.sign(ctx ‖ old ‖ new)` — the **old** key (already pinned by contacts) authorises the move.
- `sig_new = new.sign(ctx ‖ old ‖ new)` — the **new** key accepts, binding the two so the record cannot
  be half-forged by grafting someone else's new key onto Alice's authorisation.

A contact verifies the record **against the identity it currently has pinned** (`verify_migration`
checks `record.old_pub == pinned`), then both signatures; on success it re-pins to `new`
(`contacts::rotate_identity`). The **pairwise root `R` is unchanged** (it derives from the
handshake/invite, not the identity), so messaging continuity holds; only the **directory tokens**
(which bind the publisher identity) move to the new key, so the rotated user re-publishes their
directory record under the new identity.

**Clean reset** remains available: generate a new identity and *don't* publish a migration record —
contacts then see `Changed` and must re-verify (a deliberate break).

## Consequences

**Positive**
- Contacts follow a rotation automatically when it's proven same-person; no manual re-verification.
- Dual signature prevents a forged/grafted new key; verification is bound to what the contact pinned.
- Reuses only Ed25519 + the existing pin store; `R` and message history continuity preserved.
- Both modes supported (continuity migration vs clean reset).

**Negative / trade-offs**
- If the **old** key is compromised, the attacker can sign a migration to *their* key — same trust
  root as the rest of the system (possession of the identity key = control). Mitigations are
  out-of-band SAS re-check on rotation and (future) rotation rate-limits / revocation lists.
- A removed contact still holds `R`; rotation does not re-key `R` (only the identity). A future
  `R`-rotation would be a separate step.
- Old↔new linkability is intentional for *contacts*; for unlinkability across contacts, use distinct
  per-contact identities instead (separate feature).

## Future work

- Transport + CLI: publish the migration record per contact over the oblivious relay (sealed under
  `R`, blinded token — like directory records), and a contact-side apply path; replace the local
  identity at rest; re-publish the directory record under the new key.
- Optional `R`-rotation, rotation rate-limiting / revocation, and GUI surfacing of "contact rotated
  their key — re-verify?" prompts.
