How gitghost worksthe whole thing.

Open documentation for the protocol, the CLI, the verifier, and the reward design. Specific, falsifiable, FUD-resistant — if it's not in code we ship, it's not in here.

01 / Overview

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.

02 / Scope

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.

03 / Architecture

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.

/ note

No central server.

The verifier API is stateless. It reads ring config from the same git commit it's verifying (or from a paste). We store no signatures, no keys, no logs. The protocol doesn't change whether you trust this site or not.
04 / Cryptography

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.

parameters
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)
key image
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 x
sign
input:  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)
verify
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_0

Reference cryptography uses @noble/curves primitives in plain TypeScript. The implementation matches the LSAG paper directly — no custom curve math, no rolled-our-own crypto.

05 / Lifecycle

A commit, end to end.

setup
# 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
sign
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
verify (cli)
gitghost verify <sha>
# parses trailers, recomputes ring root,
# verifies LSAG, checks key image reuse
# exit 0 = valid · exit 1 = invalid / unknown
verify (web)
POST /api/verify
{ "mode": "github", "input": "owner/repo@<sha>" }
# server fetches commit + ring.json from GitHub,
# runs identical verify path, returns JSON result
06 / Trailer format

Standard 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.

example commit body
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>
inspect with git
git interpret-trailers --parse <commit>
07 / Registry

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.

/ caveat

Listed members have not endorsed gitghost.

The seed rings are demonstration assemblies. They are derived from public SSH keys without member consent. Real rings should be assembled by communities themselves; the registry just demonstrates what the math looks like in practice.
08 / Verifier

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:

github mode
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 verification
raw mode
POST /api/verify
{
  "mode": "raw",
  "message": "<full commit body with Ghost-* trailers>",
  "ring":    "<ring.json contents>"
}

# server runs verification against supplied data only
response shape
{
  "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? }
}
/ note

Want browser-only verification?

The same LSAG / ringRoot / trailer logic that runs on the server is browser-safe — only @noble/* primitives, zero Node dependencies. It ports cleanly into any client-side bundle so you can verify without our server.
09 / Tokens

Ring rewards · Bankr launch.

At Bankr launch, we're committing 5% of token supply to 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.

design constraints
✓ 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
/ caveat

Reward mechanism is privacy-first.

Mechanism, claim process, and anti-sybil are documented before launch. If a proposed flow would deanonymize signers or invite farming, it doesn't ship.
10 / Threat model

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.
11 / Limitations

Known limitations.

/ caveat

Demo-grade, by intent.

We ship the protocol in plain TypeScript with widely-audited primitives. The cryptography is sound. But the productized parts have specific tradeoffs you should know.
  • 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.
12 / Stack

What we use, what we don't.

LSAG / curves@noble/curves 1.6.0 · @noble/hashes 1.5.0
CLI runtimeNode 18+ (TypeScript, ESM)
CLI argscommander 12 · ora · chalk · simple-git
Web frameworkNext.js 14 (App Router, RSC)
On-chain anchorGhostRegistry.sol on Base mainnet · viem 2.50
Activity ledgerRedis-compatible REST · in-memory fallback for dev
StylingTailwind CSS 3.4 · brutalist tokens
Landing 3Dthree.js 0.160 · GSAP 3.12 · Lenis 1.1 (landing only)
Subpageszero 3D / animation libs · IntersectionObserver only
Tokens (planned)Bankr launch · Base · ring treasury contracts
LicenseMIT

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.

13 / Reproduce

Reproduce everything.

Every claim on this site is reproducible. Don't take our word for it — recompute it.

reproduce a ring root
# 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
reproduce the live verify
# 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
regenerate the seed registry
# in the ui/ directory
npx tsx scripts/seed-rings.mts
# fetches latest GitHub keys, re-derives,
# rewrites the seed file, validates ring roots
14 / FAQ

FAQ.

Is the cryptography real?+
Yes. LSAG is a 2004 academic construction with two decades of analysis. Our implementation uses @noble/curves, the same library that well-known wallets ship for production secp256k1 ops. Anyone can take a ghost commit and verify it offline with 100 lines of TypeScript.
What if my secret key leaks?+
Anyone with your sk can sign as you, and everything you previously signed becomes attributable to that key. gitghost cannot defend against secret-key compromise. Treat .gitghost/identity.json like ~/.ssh/id_rsa: never commit it, never share it, lock down filesystem permissions.
Will tokens deanonymize signers?+
No. Rewards distribute to ring treasuries, not signer wallets. There is no on-chain link from Transfer events back to specific commits or signers. See §09 Ring rewards.
Does this work outside GitHub?+
Yes. Ghost-* trailers are plain RFC 5322 trailers — every git host preserves them. The browser verifier currently fetches commits from GitHub or accepts raw paste. CLI verification works against any git host.
Can I run this without trusting your servers?+
Yes. The CLI is self-contained — no network calls during sign or verify. The web verifier is open source; clone and host yourself. The crypto libraries we use (@noble/*) are reviewed and reproducible.
What does "linkable" mean here?+
Same signer + same ring + same context bytes → same key image. So observers can tell "these five commits were all signed by the same person inside this ring", but cannot tell which person. That gives you reputation without identity.
Why secp256k1, not ed25519?+
Because secp256k1 is the curve that downstream tooling (wallets, on-chain anchoring on Base) already understands. A future version may add an LSAG-over-edwards25519 mode so existing ed25519 SSH keys can be used directly.