oss.sarwagya.wtf

experimental@0xsarwagya/ghost

Why does every tiny app need an account?

I refuse to build an account system for an application that does not need to know who you are.

Most applications never needed your name, your email, or an identity provider. They needed continuity: is this the same browser that was here yesterday? Ghost answers exactly that. The browser generates a keypair and keeps the private half where JavaScript cannot read it. The Ghost ID is the stable identity; the public key is the active credential. Your server verifies signatures. There is no user table, and no Ghost service — that absence is the product.

Install
pnpm add @0xsarwagya/ghost# or: npm install @0xsarwagya/ghost

The whole API

// browser
import { createGhost } from "@0xsarwagya/ghost";
 
const ghost = await createGhost();
ghost.id;                         // ghost_1_crhgcniramqtgfpib5uiaautocwdigbt
ghost.credentialId;               // cred_1_…
const proof = await ghost.sign(challenge);
 
// your server
import { createChallenge, verifyGhostProof } from "@0xsarwagya/ghost/server";
 
const challenge = createChallenge({ audience: "https://app.example", action: "login" });
const result = await verifyGhostProof(proof, {
  expectedAudience: "https://app.example",
  challengeStore: store,
});
// result.ok → the same ghost is back

First visit, every return

No email. No password. No OAuth. No user database.

Ghost authenticates a key, not a person.

That sentence is the security model. A verified proof means the requester holds an active credential for this identity — not that they are a human, a unique individual, or anyone in particular. Identities are origin-bound and pairwise, so nothing follows anyone across the internet. Recovery is optional and user-held, without accounts.

Compatibility

Reported from what CI actually exercises in Chromium, Firefox, and WebKit — including the load-bearing assumption that a non-extractable key survives IndexedDB across a reload.

FeatureChromiumFirefoxWebKit
Non-extractable Ed25519 identityTested in CITested in CITested in CI
Key persists across reloadsTested in CITested in CITested in CI
Sign in browser, verify on serverTested in CITested in CITested in CI
Web Crypto Ed25519 baseline: Chrome 137+, Firefox 129+, Safari 17+. Older browsers get a typed UNSUPPORTED, never a silent fallback.

Used in Local

Local uses Ghost as the identity primitive between two chatting browsers. Each peer signs a nonce chosen by the other; the conversation history is pinned to the peer's Ghost ID, and reconnecting with a mismatched public key refuses to attach silently. Source: github.com/0xsarwagya/local.