drift Docs
Start
What is Drift?
The tour, if you are new here.
Use cases
Whether Drift does your thing.
Getting started
Nothing to deployed, in one command.
Architecture
How a slice is put together.
What it costs
The free grant, four unit prices, two rules.
Build
Canvas
Static sites, same origin as your API.
Tools
Operate
Auth
Route gates, API keys and your account.
Security
Boundaries, sandboxing and hardening.
Troubleshooting
Error codes
What went wrong, and what to do about it.
Legal
Acceptable use
What a slice may not be used for.
Data processing
The DPA, and every sub-processor.

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 byWhat it does
  • Begin(pubkey, metadata?)
    Called by
    the joining device
    What it does
    Opens a session and returns its id. metadata is an opaque string the attester can read back.
  • QR(text)
    Called by
    either
    What it does
    Renders a short string, in practice the session id, as SVG markup, server-side.
  • SessionInfo(sessionID)
    Called by
    the attesting device
    What it does
    Returns the session's new_pubkey and metadata, and needs JWT.SliceID beside it for the identity value the signed message also carries. Read-only and repeatable.
  • Attest(identity, sessionID, attestingPubkey, sig, sealed?)
    Called by
    an active device
    What it does
    Vouches for the session's device. sealed is an opaque payload relayed to the joiner.
  • Complete(sessionID)
    Called by
    the joining device
    What it does
    Polls the session: pending, then attested with the identity and any sealed payload.
  • Revoke(identity, targetPubkey, revokingPubkey, sig)
    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 plus this slice's own identity. The slice verifies both against values it holds itself and never trusts the request body, so the attester has to fetch them and rebuild the message exactly.

Go
// 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
slice := drift.Deed.JWT.SliceID()                  // the same value the joiner must sign
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: sorted keys, no whitespace, and this slice's own identity folded in as slice, filled in by the slice itself rather than trusted from the request. Every slice on the platform runs the identical binary, so without that field an attestation, or a revocation, signed for one slice would verify on every other one too.

JSON
{"domain":"drift-deed-link-attest-v1","identity":"<identity>","new_pubkey":"<joiner>","slice":"<this-slice>"}
{"domain":"drift-deed-link-revoke-v1","identity":"<identity>","slice":"<this-slice>","target_pubkey":"<device>"}

Revoking a device

Go
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. Revoking the identity's last active device is refused instead, even when that device is revoking itself: honouring it would leave the registry with nothing able to ever sign another Link operation for this identity again, and there is no self-service way back in.

Drift verifies, it never decides.

A device joins an identity's registry only on the strength of a signature from a device that is active in that same registry. An identity with no registry yet has exactly one implicit, active device: its own KeyAuth pubkey. The registry materialises the first time Attest succeeds. Revoking that implicit device by itself hits the same last-active-device refusal above, so it never writes a registry either. Snapshot restore is the one path that writes a registry with no attestation at all, and it is operator-only.