Skip to content

Key custody

The protocol is silent on where a principal’s hybrid private key lives. It just specifies the signing operations the key must support. This page describes the three custody modes the SDK and Verify support, the tradeoffs, and when to use each.

┌─────────────────────────────────┐
│ Principal's hybrid private │
│ key │
│ │
│ ▸ Ed25519 component │
│ ▸ ML-DSA-65 component │
└────────────┬────────────────────┘
┌─────────────────────┼─────────────────────┐
▼ ▼ ▼
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────────┐
│ Self-custody │ │ Custodial │ │ Self-custody upgrade │
│ │ │ │ │ │
│ Key lives on │ │ Key lives in │ │ Started custodial. │
│ user's device. │ │ Verify backend, │ │ Generated a new key │
│ Never sent over │ │ envelope-encr- │ │ on device. Signed a │
│ the wire. │ │ ypted by Cloud │ │ rotation statement │
│ │ │ KMS. │ │ by BOTH old +new. │
│ Strongest. │ │ │ │ Continuous identity │
│ │ │ Convenient for │ │ with strictly │
│ │ │ most teams. │ │ stronger custody │
│ │ │ │ │ going forward. │
└──────────────────┘ └──────────────────┘ └──────────────────────┘

The user generates their hybrid keypair locally — on their device, in their browser, on their phone, on their server — and the private key never leaves that environment.

import { generateHumanRoot, issueDelegation } from "@identities-ai/ratify-protocol";
// User generates keypair locally
const { root, privateKey } = await generateHumanRoot();
// Only the public root.id and root.public_key are ever shared
await publishToRegistry(root.id, root.public_key);
// Delegations are signed locally with the private key
const cert = { /* fields */ };
await issueDelegation(cert, privateKey);
// privateKey never leaves this environment
ThreatMitigation
Server-side key extractionNot applicable — the key isn’t on a server
Insider at Identities AI accesses keysNot applicable — Identities AI never has the key
Compromised laptop/deviceReal threat. Use OS keychain / Secure Enclave / TPM.
Lost device with no backupReal threat. The user must back up the key (encrypted at rest) or accept that losing the device means losing the identity.

Self-custody is the strongest mode by definition: there is no third party to compromise. But it puts operational burden on the user (backups, device sync, recovery). For technical users this is acceptable; for typical enterprise end-users it’s a UX challenge.

The Verify backend generates the keypair server-side and stores it under envelope encryption:

┌──────────────────────────────────────────────┐
│ Ratify Verify backend │
│ │
│ 1. Generate hybrid keypair in-memory │
│ 2. Generate a fresh DEK (data enc key) │
│ 3. Encrypt private key bytes with DEK │
│ using AES-256-GCM │
│ 4. Encrypt DEK with Cloud KMS KEK │
│ (key encryption key) │
│ 5. Store {encrypted_priv, encrypted_DEK} │
│ in DB │
│ 6. Wipe in-memory plaintext │
│ │
└────────────┬─────────────────────────────────┘
│ To sign a delegation:
│ - Fetch encrypted record
│ - Decrypt DEK via Cloud KMS API
│ - Decrypt private key with DEK
│ - Sign in-memory
│ - Wipe plaintext
┌──────────────────────────────────────────────┐
│ At rest, Verify only has: │
│ - encrypted private key (AES-GCM) │
│ - encrypted DEK (Cloud KMS-wrapped) │
│ │
│ Cannot sign without both DB access AND │
│ IAM permission on the Cloud KMS KEK. │
└──────────────────────────────────────────────┘
ThreatMitigation
Database leak aloneEncrypted keys are useless without the Cloud KMS KEK
Cloud KMS compromise aloneDEKs are useless without the DB
Identities AI insiderRequires both DB access AND KMS access. Audit-logged. Need-to-know.
Verify service compromiseReal threat. Verify must be hardened. SOC 2 / ISO 27001 in progress.
Quantum adversary in the futureSame as self-custody — hybrid signatures defend against this

Custodial is the right default for enterprise SaaS use cases: users sign in, the system handles keys, no one loses their identity to a dead laptop. The tradeoff is trust: you trust Identities AI’s operational controls, audit posture, and incident response.

A user who started in custodial mode can migrate to self-custody at any time without losing their identity. The mechanism is a KeyRotationStatement signed by both the old (custodial) key and the new (device) key.

┌──────────────────────────────────────────┐
│ User's account in Verify: │
│ - custodial key (in KMS) — old_root │
│ - delegations issued by old_root │
└────────────────┬─────────────────────────┘
│ User decides to migrate
┌──────────────────────────────────────────┐
│ User generates fresh hybrid keypair │
│ on their device → new_root │
└────────────────┬─────────────────────────┘
┌──────────────────────────────────────────┐
│ KeyRotationStatement: │
│ old_id: old_root.id │
│ old_pub_key: old_root.public_key │
│ new_id: new_root.id │
│ new_pub_key: new_root.public_key │
│ rotated_at: now │
│ reason: "self_custody_upgrade" │
│ signature_old: signed by old_root │
│ signature_new: signed by new_root │
└────────────────┬─────────────────────────┘
┌──────────────────────────────────────────┐
│ Anyone with old_root.public_key OR │
│ new_root.public_key can verify the │
│ continuity: same person, new key. │
│ │
│ Existing delegations issued by old_root│
│ stay valid until expiry. New ones must │
│ be issued by new_root. │
└──────────────────────────────────────────┘
import { generateHumanRoot, issueKeyRotationStatement } from "@identities-ai/ratify-protocol";
// User generates a NEW keypair on their device
const { root: newRoot, privateKey: newPrivateKey } = await generateHumanRoot();
// Rotation statement signed by BOTH old (custodial) and new (device) keys
const stmt = {
version: 1,
old_id: oldRoot.id,
old_pub_key: oldRoot.public_key,
new_id: newRoot.id,
new_pub_key: newRoot.public_key,
rotated_at: Math.floor(Date.now() / 1000),
reason: "self_custody_upgrade",
signature_old: { ed25519: new Uint8Array(), ml_dsa_65: new Uint8Array() },
signature_new: { ed25519: new Uint8Array(), ml_dsa_65: new Uint8Array() },
};
await issueKeyRotationStatement(stmt, oldCustodialPrivateKey, newPrivateKey);
// Verify accepts new_root signatures going forward.
// Old_root signatures still verify against old delegations until those expire.

The double-signature is what proves continuity. Without the old key’s signature, anyone could claim to be the rotation target. Without the new key’s signature, anyone could replay the statement. Both are required.

You’re building…Recommended mode
A research prototypeSelf-custody
A consumer agent (browser/mobile)Custodial, with optional self-custody upgrade later
Enterprise SaaS where users sign in via SSOCustodial — that’s what enterprise IT expects
A regulated deployment (SOX, HIPAA, FINRA)Custodial initially, with a documented self-custody upgrade path for users who request it
An embedded device (drone, vehicle)Self-custody using the device’s secure element (TPM, ARM TrustZone, Secure Enclave)
An OSS project distributing identitiesSelf-custody, no Verify needed