Backup Service
The Backup Service is a Nethermind-hosted REST service. Tools for Humanity has no production access; the service holds ciphertext and metadata only. Its open source repository isbackup-service.
Role and guarantees
The service:- Stores the and .
- Authenticates clients via per-operation challenges signed by the relevant factor.
- Enforces state transitions on sync via the manifest hash (see Structure & Sync).
- Maps factor public identifiers back to
backup_idso a recovering client can locate its backup from a single Main Factor.
- Hold the ‘s decryption key, any , or any factor private key.
- Inspect the contents of the .
- Authenticate users beyond verifying signatures and tokens — there is no concept of a Backup Service user account.
Storage layout
- S3 — blobs and JSON, keyed by
backup_id. - DynamoDB — A
FactorLookuptable using a composite primary key combining the scope (MAIN/SYNC) and a factor identifier (credential ID for passkey, iss+sub for OIDC, public key for EC keypair) mapping back tobackup_id. This is a lookup utility — the source of truth for which factors authorize a backup is the metadata in S3. - Redis — Ephemeral state: used-challenge hashes for replay protection (15 min TTL), hashed OIDC nonces (1 year TTL), short-lived per-
backup_idlocks during create/sync, and post-recoverysync_factor_tokens used to register a new without re-prompting a Main Factor.
ChallengeContext enum live in backup-service/src/routes; the OpenAPI spec is served at /docs on each environment.
Authorization
Three factor types are recognized:
The challenge token is bound to the operation. A
Sync challenge cannot be replayed against /v1/delete-factor because the embedded ChallengeContext differs. Challenges are single-use (Redis enforces).
What the service rejects
A handful of named errors are surfaced in client code paths and worth knowing by name:manifest_hash_mismatch— client tried to sync from a stale state (see BF-5).unauthorized_factor— factor identifier is inFactorLookupbut no longer in the backup metadata; the device is no longer authorized (see Unauthorized Device).backup_does_not_exist— the metadata is gone (the user deleted their backup); local state should be cleared.backup_account_id_already_exists—/v1/createcollision; some prior creation attempt registered this backup ID. The client retries with a fresh sync factor authorization on the existing record.factor_already_exists— attempted to add a factor whose public identifier is already enrolled.invalid_challenge_context— challenge token didn’t match the operation it was submitted for.
backup-service/src/types/error.rs.
Turnkey
Turnkey is the custodian of s for OIDC factors. It runs key material inside TEE-backed enclaves and exposes a REST API; we picked it because no other provider met the combined requirements of OIDC-aware authentication, non-custodial key handling, and an API-first integration model.All short-lived keys used with Turnkey are set for a duration of 5 minutes unless otherwise specified.
Turnkey User Setup
The initial release of this feature relied on a slightly different user setup. Some early World App Users will have a slightly different setup (with the Root Quorum being
auth_user_main solely) before a migration is introduced.-
Ephemeral. A
root_user_genesisuser is created by World App’s backend to bootstrap the sub-organization.- It is a root user.
- It is created with a short-lived keypair provided by the client.
- It is also registered with a fallback
0key (Curve25519) because Turnkey requires every user to have at least one valid authenticator. After the genesis user is no longer needed, the keypair is discarded — the fallback key remains but no one holds its private half. - The client deletes this user immediately after
auth_user_mainis set up.
-
auth_user_main. Represents the user’s primary authentication and holds their credentials (passkeys and OIDC providers).- Permission policy: explicit
ALLOWfor all activities (condition = true).
- Permission policy: explicit
-
break_glass_user. Represents the — thesecp256k1keypair whose public key is encoded into the Backup Account ID. It exists so the Turnkey sub-organization can be deleted during a Reset. Because the Backup Account Key is deterministic and known ahead of time, this user is namedbreak_glass_user_<last 6 hex chars of the Backup Account Key's public key>. It is not a member of the Root Quorum. The user’s policy permits deletion-only of the sub-organization: -
sync_factor_user_<suffix>. Represents operations. Each device gets its own user, holding that device’s long-lived API keypair, and its own policy bound to it. Every such policy permits deletion-only activities, so a Sync Factor cannot grant itself recovery powers.While the has permissions to delete credentials (e.g. passkey or OIDC factors), it cannot do so onauth_user_mainbecause Turnkey rejects updates from non-root users for root users even when granted by a policy. This is a current limitation with the Turnkey setup, the user must authenticate with a to remove an OIDC Factor or use the/resetdisaster recovery.The policy filters on
activity.actionandactivity.resourceinstead ofactivity.typebecause the latter is version-specific. The current expansion coversACTIVITY_TYPE_DELETE_API_KEYS(CREDENTIAL),ACTIVITY_TYPE_DELETE_AUTHENTICATORS(CREDENTIAL),ACTIVITY_TYPE_DELETE_OAUTH_PROVIDERS(CREDENTIAL),ACTIVITY_TYPE_DELETE_SUB_ORGANIZATION(ORGANIZATION),ACTIVITY_TYPE_DELETE_USERS(USER),ACTIVITY_TYPE_DELETE_PRIVATE_KEYS(PRIVATE_KEY),ACTIVITY_TYPE_DELETE_PRIVATE_KEY_TAGS(PRIVATE_KEY),ACTIVITY_TYPE_DISABLE_PRIVATE_KEY(PRIVATE_KEY).USERlets a Sync Factor delete itself on logout and clean up stale sibling Sync Factor users. It cannot deleteauth_user_mainbecause Root Quorum validation rejects that.
auth_user_main as its sole member.
Turnkey Migrations
A sub-organization is created once, by whichever client version the user onboarded with, and is only writable by the user’s own factors. So when the expected setup above changes, only the client can update the account state. Bedrock’sTurnkeyManager.run_migrations is the mechanism that does this: clients call it after every login and periodically, and it applies only the difference between the account and the expected state. An account already in shape performs zero writes.
Reads are stamped by the ; changes need a . When no Main Factor is supplied the user is prompted to authenticate. See BF-10.
The migration list, the per-environment audience tables, and the safety guards that make each migration skip rather than guess live in bedrock/src/backup/turnkey.
What Turnkey holds vs what the Backup Service holds
Neither holds the ‘s secret key — that is destroyed at backup creation.