oss.sarwagya.wtf

Identity

What a ghost ID is, where it lives, and why reset is not logout.

A Ghost identity is a stable ID with one active browser credential. The credential's private key is a non-extractable CryptoKey in IndexedDB. The Ghost ID is the durable application handle:

ghost_1_crhgcniramqtgfpib5uiaautocwdigbt
└───┬──┘└──────────────┬───────────────┘
 version    stable identity material,
  prefix    hashed and base32

The ID is URL-safe, case-safe, and versioned — the 1 names the exact cryptographic suite, so the scheme can evolve without ambiguity. Full byte-level detail lives in the protocol reference.

The active credential has its own ID:

cred_1_3dveqtnobb556nsxhfyhh6qbn5lhsseb

Proofs authenticate the credential. Your application stores which credentials are active for a Ghost, so recovery can rotate to a fresh non-extractable key without changing the Ghost ID.

One origin, one identity

An identity belongs to a browser profile and an origin, because that is how browser storage works — and Ghost treats it as a feature. The same browser gets a different, unlinkable ghost on app-a.example and app-b.example. There is no universal identifier following anyone across the internet; Ghost is pairwise by default.

Ghost is pseudonymous, not anonymous. Your application can still link a ghost to an IP address, behaviour, or anything a user types. Ghost adds no tracking — it also cannot subtract any.

Durability and recovery

Without recovery, the local credential is exactly as durable as the origin's storage:

  • Clearing site data destroys it.
  • Browser resets, profile loss, and storage eviction destroy it.
  • Private browsing windows get an identity that dies with the window.
  • If recovery was never enabled, the Ghost may be unrecoverable.

Recovery is opt-in. Calling ghost.enableRecovery() creates a user-held recovery secret and an app-stored recovery record. Later, recoverGhost({ recoverySecret, recoveryRecord }) verifies the secret and generates a fresh local non-extractable credential for the same Ghost ID.

No email, password, OAuth provider, Ghost-hosted account, or automatic cloud backup is involved. If the user loses both the browser credential and the recovery secret, Ghost cannot reconstruct the identity.

Reset is not logout

await ghost.reset();

reset() deletes the local key. The old identity becomes permanently inaccessible from this browser, and the next createGhost() mints a fresh one unless the user recovers with a previously saved recovery secret. It is deliberately not called "logout": there is no account to log out of.