Overview.
gitghost is a cryptographic attribution layer for git commits. It lets a developer sign a commit as one of N declared contributors. Verifiers can prove the commit came from the trusted set, but cannot tell which member signed.
The protocol is built on Linkable Spontaneous Anonymous Group (LSAG) signatures over secp256k1 — the same elliptic curve used by Bitcoin and Ethereum. The math is from a 2004 paper by Liu, Wei, and Wong (IACR ePrint 2004/027), with a practical sketch by rot256.
We ship two pieces: a TypeScript CLI (sign / verify / ring management) and this site (public ring registry, browser verifier, documentation). Both halves use the identical LSAG implementation. Anything signed by gitghost commit is verifiable by POST /api/verify, and vice versa.
What gitghost is (and isn't).
It is a way to produce git commits whose authorship is cryptographically provable to belong to a public set, while hiding which specific member signed.
It is not an identity service, a key-management product, an auditing platform, a vulnerability disclosure system, a privacy mixer for blockchains, a proxy for hiding network identity, or a replacement for git commit -S when you want full identity attribution.
If you want anonymity at the network layer (who pushed the commit), use Tor. gitghost only handles cryptographic attribution; it doesn't change TCP/IP packets.
Architecture.
gitghost ships in two halves. A CLI for signing and local verification, and a web app for the public ring registry, browser verifier, and docs. Both share the same LSAG implementation — the math is identical on both sides, so anything signed by gitghost commit verifies via POST /api/verify, and vice versa.
The cryptographic primitives are @noble/curves for secp256k1 and @noble/hashes for SHA-256. Same versions, same algorithms, same byte-level output across CLI and web.
No central server.
The cryptography.
LSAG produces a 1-out-of-N anonymous signature with a deterministic key image per (key, ring) pair. The key image is the linkability hook: the same signer in the same ring always produces the same key image, so reuse can be detected — without revealing identity.
Curve: secp256k1 (same as Bitcoin / Ethereum)
Hash: SHA-256
Hash-to-pt: try-and-increment over compressed-x with even y
Hash-to-sca: H_s(x) = sha256(x) mod n
Wire format: lsag1.<c0>.<s_concat>.<keyImage>
c0: 64 hex (1 scalar)
s_concat: n × 64 hex (n scalars, joined)
keyImage: 66 hex (1 compressed point)I = sk_j · H_p(pk_j ‖ ctx)
sk_j = signer's secret scalar
pk_j = signer's public point
ctx = sha256("gitghost.v1.context|" + ringName)
H_p(x) = hash-to-point of xinput: m, π = [pk_0..pk_{n-1}], j (signer index), sk_j, ctx
output: σ = (c_0, [s_0, …, s_{n-1}], I)
1. I = sk_j · H_p(pk_j ‖ ctx)
2. α ← random scalar
3. L_j = α · G
R_j = α · H_p(pk_j ‖ ctx)
4. c_{j+1} = H_s(m ‖ I ‖ L_j ‖ R_j)
5. for each i ≠ j (around the ring):
s_i ← random scalar
L_i = s_i·G + c_i·pk_i
R_i = s_i·H_p(pk_i ‖ ctx) + c_i·I
c_{i+1} = H_s(m ‖ I ‖ L_i ‖ R_i)
6. s_j = α − c_j · sk_j (mod n)
7. σ = (c_0, [s_0..s_{n-1}], I)input: m, π, ctx, σ
output: bool
c = c_0
for i in 0 … n-1:
L_i = s_i·G + c·pk_i
R_i = s_i·H_p(pk_i ‖ ctx) + c·I
c = H_s(m ‖ I ‖ L_i ‖ R_i)
return c == c_0Reference cryptography uses @noble/curves primitives in plain TypeScript. The implementation matches the LSAG paper directly — no custom curve math, no rolled-our-own crypto.
A commit, end to end.
# inside any git repo gitghost init linux-kernel-core # generates .gitghost/ + secp256k1 identity gitghost ring add-self # add your local pubkey to ring.json gitghost ring add torvalds # fetch github.com/torvalds.keys gitghost ring add gregkh # ...derive deterministic ghost pubkey gitghost ring list # inspect the ring
gitghost commit -m "fix: critical CVE-2026-XXXX" # composes LSAG over N keys # writes Ghost-Ring, Ghost-Ring-Root, Ghost-Key-Image, # Ghost-Signature trailers # runs git commit underneath
gitghost verify <sha> # parses trailers, recomputes ring root, # verifies LSAG, checks key image reuse # exit 0 = valid · exit 1 = invalid / unknown
POST /api/verify
{ "mode": "github", "input": "owner/repo@<sha>" }
# server fetches commit + ring.json from GitHub,
# runs identical verify path, returns JSON resultStandard git commit trailers.
The signature is embedded as RFC 5322-style trailers on the commit message body. Any git tooling that understands trailers will surface them. No fork. No extension.
fix: critical CVE-2026-XXXX Ghost-Ring: linux-kernel-core (4 members) Ghost-Ring-Root: bafkreih7q2zi73p9aplc4eov3iqbjnmhrhuw5kbphr2kk7v2v3iq6q3aaa Ghost-Key-Image: 02a1b2c3d4e5f6... Ghost-Signature: lsag1.<c0>.<s_concat>.<keyImage>
git interpret-trailers --parse <commit>
The public ring registry.
The rings shown at /rings are reproducible from public material. Each member's ghost pubkey is derived deterministically from their public github.com/<user>.keys listing via:
scalar = sha256("gitghost.v1.derive|" + username + "|" + fingerprint)
pk = scalar · G (compressed secp256k1 point)Ring roots are validated against the live cryptography on every boot — if the registry seed drifts from the math, the app fails to start rather than silently shipping wrong data.
The public index is the merged view — seed rings plus any user-created rings the owner has explicitly made public. Private rings stay in /dashboard/rings and never surface here. Stats at the top (rings / members / verifiable) reflect the merged registry and update live as new public rings land.
Ring management lives in the dashboard at /dashboard/rings. Create a ring, add members by GitHub username, toggle whether it is discoverable. Rings default to private — the owner must explicitly opt in to appear in the public index. Each ring exposes its own visibility toggle in the detail page, so privacy is a per-ring decision, not an all-or-nothing account setting.
Listed members have not endorsed gitghost.
The browser-side verifier.
POST /api/verify runs the same LSAG verify path as the CLI, in the Next.js Node runtime on this site. Two modes:
POST /api/verify
{ "mode": "github", "input": "<commit url or owner/repo@sha>" }
# server fetches the commit message + .gitghost/ring.json
# from the same GitHub commit, then runs verificationPOST /api/verify
{
"mode": "raw",
"message": "<full commit body with Ghost-* trailers>",
"ring": "<ring.json contents>"
}
# server runs verification against supplied data only{
"ok": true,
"trailers": { ringName, ringRoot, keyImage, ringSize, signaturePresent },
"ring": { name, context, members, computedRoot, rootMatches, sourceUrl },
"verification": { signatureValid, keyImage },
"commit": { source, owner?, repo?, sha?, message, htmlUrl? }
}Want browser-only verification?
Activity feed and repo ranking.
/feed is the public activity layer for gitghost. It does not store signer identity, author email, commit body, or private keys. It stores the minimum public proof metadata needed to show that ghost activity happened: ring name, ring root, ring size, key image, verification source, and — when the source is a public GitHub commit — owner, repo, and commit SHA.
successful /api/verify or /api/anchor event → recordVerification(event) → Redis-compatible activity ledger → in-memory fallback when Redis env is absent → /api/feed snapshot → /api/feed/rank repo scores
Repo ranking is computed from the same activity snapshot.GET /api/feed/rank reads recent events, groups them by owner/repo, then scores each repo using total verified ghost commits, anchor rate, ring diversity, recency, and how much activity landed in the last seven days.
score = 30% verified ghost commit volume 25% anchored commit rate 20% ring diversity 15% recency 10% seven-day freshness
When does a repo appear in ranking?
The new gitghost review command signs GitHub PR reviews with the same LSAG trailer format used by ghost commits. It posts the review to GitHub through the Reviews API using gh auth token or GITHUB_TOKEN. The web route GET /api/review?owner=&repo=&pr= can read those reviews back, parse the Ghost-* trailers, fetch the repo ring, and verify whether the review was ghost-signed.
gitghost review approve|reject|comment <github-pr-url> -m "..." → load local .gitghost identity + ring → LSAG-sign the review body → append Ghost-* trailers → POST signed review to GitHub → /api/review can verify signed reviews later
Ranking is proof activity, not identity.
Ring rewards · $GTG.
$GTG is the protocol's reward token. It's live on Base mainnet at the address above. 5% of supply is earmarked for a contributor reward pool. When a ring ships ghost commits, rewards flow to the ring's treasury, not to the signer's personal wallet.
This is deliberate. Auto-transferring tokens to the signer would create an on-chain wallet ↔ ghost commit linkage. Anyone watching the contract could correlate Transfer timestamps with ghost commit timestamps and deanonymize signers. That defeats the entire protocol.
✓ rewards distribute to RING TREASURIES, not signing wallets ✓ ring-treasury → individual claim happens via ring governance or anonymous proof — never auto-bound to signer wallet ✓ no airdrop based on per-commit signing (would invite sybil; rings would farm) ✓ no claim mechanism that requires signing wallet to match the wallet that anchored the commit
Reward mechanism is privacy-first.
What we defend against.
In scope:
- · Passive global observers reading every commit, public key file, and on-chain anchor.
- · Active forgery: an attacker without any ring secret tries to forge a valid signature.
- · Cross-ring deanonymization via key image collision.
- · Ring substitution: swapping a different ring config to falsely claim membership.
- · Replay (mitigated by linkability + anchor logs).
Out of scope:
- · Side channels in the signing host (timing, RAM, malware).
- · Compromise of the signer's secret key — gitghost cannot help if sk leaks.
- · Coercion attacks (someone forces the signer to reveal sk).
- · Network-layer deanonymization of who pushed the commit. Use Tor and a fresh git identity for that.
Cryptographic assumptions:
- · secp256k1 discrete log is hard.
- · SHA-256 is collision-resistant and behaves as a random oracle for hash-to-point and hash-to-scalar.
- · The ring contains ≥ 2 members, and the adversary doesn't control all-but-one.
Known limitations.
Demo-grade, by intent.
- SSH-key derivation is product-grade, not academic-grade. We derive a secp256k1 ghost pubkey from a username + SSH fingerprint. This is reproducible but not cryptographic authentication of the original keyholder. Production rings should use dedicated secp256k1 ghost keys published by each contributor — or, eventually, ed25519 SSH keys + LSAG over edwards25519 (no derivation needed).
- hash-to-point uses try-and-increment. Production should use RFC 9380 hash-to-curve (Simplified SWU) for constant-time and standards compliance.
- The signed message binds to ring root + commit text only, not to the git tree or parent. A future version will bind to tree and parent hashes for full non-malleability.
- No anti-replay binding to commit hash. Mitigated by linkability (same signer → same key image) and anchor logs, but a future version will optionally bind to commit hash.
- Ring root is a deterministic SHA-256, not a real IPFS CID. The format mimics CIDs (bafkrei…) for visual familiarity. Phase 2 swaps in a real CID by pinning the ring config to IPFS.
- On-chain anchoring is live on Base mainnet. The anchor command submits to a deployed GhostRegistry via a sponsored relayer at /api/anchor. Each call records (commit, ringRoot, keyImage) + emits an indexable event. ~$0.0001 per anchor at current Base gas.
- N=2 rings give only 50% anonymity. The CLI warns when ring size is below 5; below 8 you don't have meaningful cover.
What we use, what we don't.
No closed-source dependencies. No telemetry. No analytics beacons. The only outbound network calls are fetch(github.com/<user>.keys), fetch(api.github.com/repos/.../commits/...), and fetch(raw.githubusercontent.com/.../ring.json) — all requests on the user's explicit action.
Reproduce everything.
Every claim on this site is reproducible. Don't take our word for it — recompute it.
# in any git repo gitghost init <ring-name> gitghost ring add-self gitghost ring add <member-1> gitghost ring add <member-2> gitghost ring list # ring root must match what /rings/<slug> shows
# on the verify page, click "load live example" # then submit. our /api/verify returns ok: true. # the same signature verifies via the CLI: gitghost verify e098506b9118fd3abde1a74bd9bb0632ec125c15
# in the ui/ directory npx tsx scripts/seed-rings.mts # fetches latest GitHub keys, re-derives, # rewrites the seed file, validates ring roots
The agent surface.
@gitghost/mcp is the local stdio server that lets MCP-compatible agents operate gitghost inside a repo. Claude Desktop, Cursor, Cline, OpenCode, and any stdio-capable MCP client can call the same six primitives: init, ring add-self, ring add, ring list, commit, and verify.
The trust boundary is the working directory. Run the server from the repository the agent should modify, or configure the MCP client with that repo as cwd. Signing happens locally, and .gitghost/identity.json never gets returned to the model.
Launch rule.
# from the repo you want the agent to operate on npx @gitghost/mcp # or configure the MCP client to launch it with cwd set to the repo root
gitghost_init gitghost_ring_add_self gitghost_ring_add gitghost_ring_list gitghost_commit gitghost_verify
Build with AI. Ship as ghost.
The dashboard at /dashboard is a single-page workspace that turns a plain-English description into a ghost-attributed commit — no CLI, no manual repo setup.
Flow
- Describe — enter a prompt describing what you want to build
- Generate — AI produces code using your chosen provider (8 supported: OpenAI, Anthropic, DeepSeek, Gemini, Groq, Together, Mistral, OpenRouter). Bring your own API key.
- Preview — see the generated UI rendered live in a sandboxed iframe
- Review — inspect generated files, agent logs, and diffs
- Approve — no writes happen until you explicitly approve
- Commit as ghost — gitghost pushes a branch and opens a PR with Ghost-* trailers. The commit author is
ghost.
Every commit carries a real LSAG ring signature (Ghost-Ring, Ghost-Ring-Root, Ghost-Key-Image, Ghost-Signature) and is automatically anchored to the GhostRegistry on Base mainnet. The ring config (.gitghost/ring.json) is committed alongside the code so the verifier at /verify can reproduce the result.
Login with GitHub OAuth to enable auto-repo creation — just enter a repo name and gitghost creates it, commits the code, and opens a PR. No manual token or repo setup required.
Privacy boundary
Dashboard-generated repos appear in the feed and repo ranking with hash-based anonymous names. The commit author is always ghost. No GitHub username, no repo owner, no identity is ever exposed in the public feed or ranking.
Two newer surfaces extend the dashboard beyond the AI workspace. The ring management surface at /dashboard/rings lets you create multi-member rings and invite by GitHub username. The join flow at /join onboards invited members: they sign in with GitHub, a secp256k1 keypair is generated locally in their browser, and the compressed pubkey is registered against their account. The private key never leaves the device — the server is zero-knowledge.
Backup your key
Because the keypair lives in local browser storage, clearing site data loses your ghost identity — and any ring you are a member of keeps listing your ghost pubkey, but you can no longer sign. Export a JSON backup from /dashboard/settings and store it somewhere safe (password manager, encrypted drive). Restore is one click on any new device.