Backup and Recovery
DraftRule: holding the recovery secret restores every asset, even after every device is lost.
Two separate things are called “backup”:
- The backup artifact — a portable, encrypted export of a library’s assets.
- The master-key escrow — a small server-side blob a passphrase unwraps into the key hierarchy.
A client MAY drop its only local copy only after the Transfer — Durability endpoint confirms the server durably holds it.
Backup Artifact
Section titled “Backup Artifact”A self-describing, versioned, streamable archive, encrypted and independent of device keys and MLS ratchet state. Its format is a cross-platform contract: an artifact exported anywhere MUST restore everywhere.
Container Format
Section titled “Container Format”Uncompressed POSIX tar:
backup.tar├── VERSION # plaintext: format version, crypto_suite_id, min_protocol_version├── MANIFEST.cbor # entry list, hashes, sizes, exporter identity; HMAC + hybrid signature├── keys/amk-ledger.cbor # every needed AMK version, wrapped under the backup wrap key├── library/attestation-keys.cbor # pinned server attestation-key history, verbatim (optional)└── <sorted by (album_id, asset_id, blob_role)> ├── blobs/{hash} # ciphertext blobs ├── meta/{blob_id} # encrypted metadata blobs ├── create-meta/{asset_id} # encrypted metadata blob the create binds, once an edit replaced it (optional) ├── provenance/{asset_id} # full per-asset provenance chains ├── authority/{album_id} # admin verifying key + admin-signed epoch ledger (format 2+) └── docs/{album|stack}/{id} # album/stack label record, sealed under its album's AMK (optional)- Self-sufficient. The AMK ledger is wrapped under the passphrase-derived backup wrap key, not a device key: the passphrase alone decrypts every included blob, with no server or MLS state.
- Authority is public, not a capability.
authority/makes restored assets verifiable; it carries no write-tier or admin seed, so an album new to the library restores read-only. Restore re-verifies the ledger’s admin signature chain, adopts it only for an album with no authority of its own, and never replaces existing authority. Format-1 artifacts restore readable but unverifiable. - Label records ride sealed.
docs/carries each exported album’sAlbumDocV1(its name and labels) and each labelled stack’sStackDocV1, still sealed under the album’s AMK, whose epoch the ledger escrows. Restore merges them, so a recovered album keeps its name. The entries are additive within format 2: an older reader verifies and skips them. - The create’s metadata blob rides sealed. An upload carries an asset’s
create, which only the exact blob itsmetadata_blob_hashnames answers, and the first edit replaces that blob inmeta/. So an asset whose create binds a blob other than its head’s carries that blob increate-meta/, and restore keeps it beside the chain before writing the chain, so an asset edited and never uploaded can still upload its create on the restoring device. A carried blob that does not answer the create’s signed hash is dropped with a warning, never a reason to refuse the asset. An artifact without the entry restores as before. Additive within format 2: an older reader verifies and skips it. - Key pins ride verbatim.
library/attestation-keys.cborcarries the library’s pinned attestation-key history of each server (Custody Receipts), so a restored library keeps checking receipts under the pinned keys instead of trusting the server’s next publication on first use. Restore keeps the union per server only where one history extends the other; histories that each hold a key the other lacks refuse the restore before anything is written. A pin whose schema is newer than the restoring build reads is skipped with a warning, never a reason to refuse the assets. Additive within format 2: an older reader verifies and skips it. - Uncompressed (ciphertext is incompressible) and streamable (no central directory; terabyte exports without seeking — why not ZIP).
- Deterministic. Sorted entries and normalized ustar headers (
mtime/uid/gid= 0, empty names, fixed modes, no PAX) — re-exports of the same content are byte-identical. - Manifest first.
MANIFEST.cborfollowsVERSIONand precedes every blob, so a streaming reader has the integrity list up front. Both checks MUST pass before restore:- HMAC under the wrap key (password-based KDF) — catches truncation, reordering, corruption before decrypt.
- Hybrid signature by the exporter’s DSK — defeats a wrap-key thief who re-HMACs. The exporter MUST be in the device directory at restore; a revoked exporter is rejected.
- Versioned per Versioning Identifiers: old artifacts stay restorable; an unknown
crypto_suite_idis rejected.
Master-Key Escrow
Section titled “Master-Key Escrow”The account master key (Keys) is escrowed server-side, wrapped by a key derived from the recovery secret via the password-based KDF.
- The secret MUST carry ≥128 bits (random code or BIP39-style phrase): the blob is offline-attackable and Argon2id adds only linear cost.
- A shorter PIN is permitted only behind an attested enclave that rate-limits unwraps (Signal SVR pattern). Never a short numeric code otherwise.
- One active escrow per account; replacing it deletes the old blob.
CLI (capsule auth escrow / capsule auth recover): setup binds the library to the signed-in account and refuses a library that already holds assets (its manifests name its own id); it shows a fresh 256-bit secret once behind a type-back gate; it refuses to replace an existing escrow without --replace. Every refusal writes nothing. Once the library is bound, setup makes its device a member of the account’s published device directory (publishing the library’s own when the account has none), as recovery does for a recovered device, so the server accepts what the device uploads.
Album Key Escrow
Section titled “Album Key Escrow”The master key and the sealed identity open a recovered library as the account, but AMKs are random per album and epoch. Each album’s key state is therefore escrowed beside the master-key escrow, so a device recovered from the recovery secret alone can open every album.
- What is escrowed. One blob per album: every epoch’s AMK, the album’s write-tier and admin seeds, and its authority, prefixed with a domain separator and sealed under the master key. It adds no root (Single-Root Invariant). A recovered device holds the album as the device that escrowed it did, so it can still write to the album.
- Where.
PUT /v1/auth/escrow/albums/{album_id}stores or replaces one album’s slot.GET /v1/auth/escrow/albumslists every slot the account holds. Slots are opaque, per account, replaced in one operation, never versioned and never deleted. Each slot is capped at 512 KiB, and an account at 4096 slots and 32 MiB in total. A store past either bound is409 error.escrow.album_keys_fulland writes nothing. That also bounds the listing a push reads. - Kept current before any asset leaves. Setup escrows every album. Every push first lists the escrows, then stores each album the server lacks or that this device strictly extends (a new album, or a new epoch). It does this before any asset is sent, and also when there is nothing to push. A refused escrow fails the push. The server must never hold an asset whose album key it does not also hold.
- Never shrunk. An escrow is replaced only by one holding every epoch it holds, under the same keys. Suppose another device rotated further, so the escrow holds epochs this device lacks. Then this device’s push leaves the escrow alone. Its own epochs are already covered.
- Adopted before any album is resolved.
capsule auth recoverfetches the escrows before deriving anything. Once the library is created, it adopts every escrowed album before anything callsensure_album. That ordering is what stops a recovered library minting a fresh AMK under the derived default-album id, which the account’s other devices already wrote under. - Failure handling. An escrow that opens but is inconsistent refuses the recovery, and the new library is removed. Inconsistent means it is served under another album’s id, or it is not an album escrow, or its authority does not re-verify. An escrow that does not open under the recovered master key is skipped: another library sealed it before the escrow was replaced. Those albums belong to that library’s key, and refusing them would lock the account out for good.
- Known limit. Two devices can each hold an album and rotate it independently. Each then holds an epoch the other lacks, or the same epoch under another key. Neither may replace the other’s escrow. The escrow stays as the first writer left it, and the album is uncovered on the other device. That device’s push holds its assets in the album back (
album_keys_not_escrowed) rather than sending them unrecoverable. The same applies to an escrow that does not open under this device’s master key, and to an account whose escrows are full. Reconciling diverging epochs waits on the MLS membership work.
Single-Root Invariant
Section titled “Single-Root Invariant”Every recovery path terminates at the one account master key, and the recovery secret (or a quorum of its Shamir shares) is the only user-held secret class. No feature may add a second escrowed secret class or a path that bypasses the master key.
The escrow, the artifact wrap key, and Shamir shares all derive from the same secret. OGK and Drop-Key escrows, the album key escrows and the sealed user identity are wraps reachable from the master key, not roots. Sponsored accounts hold no root; they route through the sponsor’s (so they are never prompted to verify).
An exported artifact stays bound to the secret in force at export; rotating the secret therefore ends with “re-export or destroy old artifacts” guidance.
Recovery Mechanisms
Section titled “Recovery Mechanisms”These are the user-invoked mechanisms among the redundant recovery paths.
Default Mechanisms
Section titled “Default Mechanisms”- Recovery secret shown at setup; unwraps the escrow.
- Cross-device recovery — a surviving signed-in device re-bootstraps a new one over a verified channel (ceremony shared with cross-device add).
Opt-in: Shamir Secret Sharing
Section titled “Opt-in: Shamir Secret Sharing”- The recovery seed is split 2-of-3 by default (custom
m-of-nallowed); fewer thanmshares reveal nothing. - Each share is additionally wrapped under its own passphrase via the password-based KDF.
- Reconstruction is client-side only; the server never sees more than one share or the seed.
Share Wire Format
Section titled “Share Wire Format”A share is x ‖ y: one nonzero byte x, then one GF(256) byte per seed byte (33 bytes for a 32-byte seed), byte-wise Shamir over polynomial 0x11d with uniformly random coefficients from a CSPRNG. Malformed, zero-x, or duplicate-x shares are a typed error, never a panic. Shares issued by the retired sharks crate (RUSTSEC-2024-0398, ~0.18 bits leaked per share) use the same encoding and stay recoverable; guided re-wrap re-issues them.
Recovery Verification Cadence
Section titled “Recovery Verification Cadence”Periodically the user proves they still hold the secret. The check MUST NOT become a lock-out or a guessing oracle.
Local Verification
Section titled “Local Verification”- Local-only. The client unwraps a cached copy of the escrow blob and compares an HKDF-derived tag against one derived from the device-held master key (never raw key bytes). No server round-trip; a failure locks nothing.
- Stale cache. Before recording a failure, refresh the blob and retry once. If the client has seen a
stored_at(fromPUT /v1/auth/escrow) newer than its copy, refresh before comparing. replaceddistinguishes first setup from rotation; only rotation shows the old-artifact guidance.
Schedule and Triggers
Section titled “Schedule and Triggers”- 7 days after setup, then 90 days, backing off to 180 days after two consecutive successes.
- Re-arm to 7 days on: new device enrolled (prompt on the new device), secret rotated, restore from escrow.
- Snooze 24 h or 7 d, at most 3 in a row. Delivered as the pre-armed
recovery_check_duealert class; snooze/badge mechanics are owned by Notifications. The alert’srecoveryparameter ischeckorrewrap(an explicit “I lost it” is due immediately).
On Repeated Failure: Guided Re-Wrap
Section titled “On Repeated Failure: Guided Re-Wrap”After 3 failures across ≥2 sessions, or an explicit “I lost it”: mint a fresh ≥128-bit secret, re-wrap the same master key, replace the server escrow (old blob deleted), repeat the type-back gate, re-issue Shamir shares if enrolled (old ones marked invalid), and show the old-artifact guidance.
This is wrap rotation, not key rotation — no re-encryption, no blob-hash changes. Master-key rotation is the compromise procedure in Keys.
Backup Verification
Section titled “Backup Verification”Restore is dry-run by default; writing requires explicit --commit (UI: typed-phrase confirm after the report).
| Stage | Checks | Writes |
|---|---|---|
| Preview | Counts, sizes, readable titles | No |
| Dry-run (default) | Decrypt, hash match, decode in the sandboxed decoder; diff vs live library | No |
| Signature chain | MANIFEST.cbor HMAC + exporter signature; every asset manifest against the device directory, every device cert to a user IK. Any break refuses restore | No |
| AMK completeness | Every referenced amk_version present in the artifact’s own ledger | No |
| Commit | Chain reconciliation below | Yes |
Commit reconciles each manifest against the live latest_provenance_hash and never silently overwrites newer local state:
- Identical head → no-op (idempotent).
- Live head chains forward from restored head → not applied; offered read-only as an older version.
- Divergent, behind, or tombstoned later locally → not applied; sent to the “restore conflicts” quarantine (Damage Scenario #23).
- Absent locally → applied; the restored chain becomes the local chain.
Backup Provenance
Section titled “Backup Provenance”MANIFEST.cbor records exporter device, export time, source library version, crypto_suite_id, and every per-asset chain head; it is itself a library-level provenance record. Restore re-injects per-asset chains, so a restored library knows it was restored, when, and by whom.
Validation
Section titled “Validation”- Local verify is offline and side-effect free; stale-cache refresh after rotation; cadence and re-arm under a mocked clock.
- Guided re-wrap keeps the master key (hashes unchanged) and rejects the old escrow.
- Single-root tripwire: no second secret class in the schema.
- Artifact round-trip; byte-identical re-export; deterministic dry-run report.
- Tampering is caught by HMAC, then signature, then directory membership; a missing AMK version is caught at dry-run.
- Each recovery path and each reconciliation case; an old backup never overwrites later deletes or edits.
Full backup → fresh-client restore is an E2E case in Module Map. So is push → auth recover onto a fresh library → restore: every album’s originals come back byte for byte, every epoch included.