Deed Link
Multi-device continuity for a KeyAuth identity: enroll a
second, third, … device without ever handing over the original private key. Not to be confused
with cross-slice calling (drift slice link, an unrelated feature for one of your
own slices calling another); this Link enrolls a device for one identity.
- Called by
- the joining device
- What it does
- Opens a session and returns its id.
metadatais an opaque string the attester can read back.
- Called by
- either
- What it does
- Renders a short string, in practice the session id, as SVG markup, server-side.
- Called by
- the attesting device
- What it does
- Returns the session's
new_pubkeyandmetadata. Read-only and repeatable.
- Called by
- an active device
- What it does
- Vouches for the session's device.
sealedis an opaque payload relayed to the joiner.
- Called by
- the joining device
- What it does
- Polls the session:
pending, thenattestedwith the identity and anysealedpayload.
- Called by
- an active device
- What it does
- Deactivates a device, including itself.
Enrolling a second device
SessionInfo is not optional in practice. The attesting device only ever learns the session
id, from a scanned or typed code, and the message it has to sign is over the joiner's
new_pubkey. The slice verifies against its own stored copy of that value and never trusts
the request body, so the attester has to fetch it and rebuild the message exactly.
// on the NEW device: fresh keypair, open a session, show the code
sessionID, _ := drift.Deed.Link.Begin(newDevicePubkey, boxPubkeyHex) // metadata: a key to seal for
svg, _ := drift.Deed.Link.QR(sessionID)
// on an ALREADY-ACTIVE device: read the session, sign client-side, attest
info, _ := drift.Deed.Link.SessionInfo(sessionID) // info.NewPubkey, info.Metadata
drift.Deed.Link.Attest(identity, sessionID, activePubkey, sig, sealedForJoiner)
// back on the NEW device, polled until an active device has vouched
status, _ := drift.Deed.Link.Complete(sessionID)
// status.Status == "attested" → status.Identity, status.Sealed
sealed is how the joining device receives key material. Deed relays it and never
opens it: seal a payload for the ephemeral key the joiner published as metadata, and
Complete hands it back once the attestation lands. A session lives 5 minutes.
Link's two signatures are made client-side over a fixed domain of Link's own, not your app's in the same canonical form as KeyAuth's, with sorted keys and no whitespace:
{"domain":"drift-deed-link-attest-v1","identity":"<identity>","new_pubkey":"<joiner>"}
{"domain":"drift-deed-link-revoke-v1","identity":"<identity>","target_pubkey":"<device>"}Revoking a device
drift.Deed.Link.Revoke(identity, targetPubkey, revokingPubkey, sig)Any currently-active device can revoke another, or itself. The revoked key still produces cryptographically valid signatures; KeyAuth refuses it anyway, which is what the registry is for.
Drift verifies, it never decides.
Attest succeeds, or the first time that implicit device
revokes itself, which writes a one-entry registry marked revoked. Snapshot restore is the one path
that writes a registry with no attestation at all, and it is operator-only.