Skip to content

Research: unlinkable Ghost Key presentation via anonymous credentials / ZK #25

@sanity

Description

@sanity

Research: unlinkable Ghost Key presentation (anonymous credentials / ZK)

Status: research note, not currently planned. Documenting the design space so it isn't lost.

Background

Ghost Key certificates as currently issued give two different kinds of privacy:

  1. Donor → key unlinkability at issuance. Blind signing means the donation server never sees the public key it's authorising. Whoever took the payment cannot later connect a payment record to a Ghost Key certificate.
  2. (Everything after issuance is linkable.) The certificate contains a stable Ed25519 public key. If a user presents the same cert to two different apps, those apps — or an observer watching both — can trivially correlate the two uses. The cert is anonymous in "who paid?", not anonymous in "is this the same user?".

The second property is fine for some use cases (stable pseudonymity, accumulating reputation against a handle) and bad for others (anonymous voting where you want one-Ghost-Key-one-vote without letting the vote-recorder link your votes across polls, or anonymous posting where correlation across apps is a leak).

This issue documents what it would take to add multi-show unlinkability: each presentation of a Ghost Key reveals nothing that ties it to any other presentation, while the verifier still learns "this was backed by a valid donation".

Properties a solution needs

  • Proof of possession. Verifier learns that the prover holds a valid Ghost Key credential, chained to Freenet's master key.
  • Unlinkability across presentations. Two proofs by the same user to different verifiers (or to the same verifier at different times) cannot be linked.
  • Scope-bound nullifiers (optional but important for Sybil-resistant use cases). In a context like voting, the prover must be able to produce a deterministic nullifier per (key, context) pair, so duplicate votes are detectable, while nullifiers across contexts are mutually unlinkable.
  • Replay binding. A proof generated for verifier A must not be replayable against verifier B. This is the job of a verifier challenge or a scope commitment in the proof.
  • Cheap enough to generate in a browser. A multi-second proving step during a donation or posting flow is acceptable; a 60-second one is not.
  • Selective disclosure of metadata. The verifier may need to see donation amount (for tier-gated features) without seeing the identifier.

Three approaches, ranked by effort-to-reward

1. BBS+ / anonymous credentials (recommended if we ever do this)

Replace the RSA blind signature + Ed25519 chain with a pairing-based anonymous credential scheme — BBS+, CL, or Pointcheval-Sanders. The notary issues a signature over a set of attributes {user_secret, donation_amount, issuance_date}; the user presents by emitting a zero-knowledge proof of knowledge of a valid signature, optionally selectively disclosing attributes and optionally emitting a scope-bound nullifier.

BBS+ has the exact property we want: proofs of the same underlying signature are mutually unlinkable, by construction of the scheme. It's not a SNARK on top of a signature — the signature is the credential, and unlinkable presentation is a native operation.

Maturity: production-grade. BBS+ signatures are standardised by the IRTF (draft-irtf-cfrg-bbs-signatures), shipped in Hyperledger AnonCreds (Indy), and have Rust implementations (bbs, pairing-plus, or via blst). W3C Verifiable Credentials has a data integrity suite for BBS+. This is the "boring path".

Crypto: pairing-friendly curves (BLS12-381). New dependency, but a well-worn one.

Proving cost: milliseconds in native code, maybe tens of milliseconds in WASM. Cheap enough for interactive flows.

Compat: requires a new certificate format. The user's presentation flow changes (no longer "here is my cert", but "here is a proof and selectively disclosed attributes"). Existing RSA-signed certs would not be convertible; users would need to re-donate or migrate at a cutoff. This is a hard break, so it should be bundled with any other format bump we're planning (see the notary rename issue).

Effort: weeks to prototype for someone comfortable with pairing crypto; a few months to productionise, audit the integration, and retrofit the donation + presentation + CLI flows. Not a side project, but not a research expedition either.

2. zk-SNARK over the existing RSA + Ed25519 certificate

Keep the current cert format and prove, in zero knowledge:

"I know a secret key sk such that pk = sk·G on Ed25519, I know an RSA blind signature σ on pk that verifies under notary public key N, and I know a certificate chain binding N to Freenet's master Ed25519 public key M."

Plus a nullifier of the form H(sk || context) exposed as a public input.

Pros: no migration; existing Ghost Keys keep working; unlinkable presentation is opt-in; old apps keep verifying old certs the old way.

Cons — significant:

  • RSA verification inside a SNARK circuit is expensive. Each modular multiplication over a 2048-bit modulus costs many thousands of constraints. Practical but painful.
  • Ed25519 verification in circuit is also expensive (Edwards curve arithmetic over a non-SNARK-friendly base field).
  • Proving time in a browser is measured in seconds to tens of seconds depending on proof system (Groth16 faster but requires trusted setup per circuit; PLONK/Halo2 more flexible but slower).
  • Toolchain is still maturing. Recent work (Nova/SuperNova folding, efficient RSA gadgets from zk-email-style projects) makes it feasible but not easy.

Effort: several months of focused work by someone with ZK circuit-engineering experience. Research-adjacent. High maintenance burden as the ZK stack evolves.

When to pick it: only if preserving the existing cert format is a hard requirement.

3. Semaphore-style: Merkle-tree membership + nullifiers

Rearchitect issuance to look like Tornado Cash / Semaphore:

  • The donation server maintains a public Merkle tree. Each leaf is a commitment C = H(secret_i || randomness_i) the donor computes client-side and submits after their donation is confirmed.
  • To use the Ghost Key, the holder proves in ZK: "I know (secret, randomness) such that H(secret, randomness) is a leaf in the current tree, and here is nullifier H(secret, context)."
  • Cross-context nullifiers diverge → unlinkable across apps. Same-context nullifiers converge → Sybil-resistant within a context.

Pros: Semaphore is a finished, audited, production system. Circuits, libraries, browser provers, and documentation all exist. Proving times are acceptable on browsers. It's the fastest path to a working prototype.

Cons: it's a different privacy model from blind signing.

  • The donation server sees each commitment as a distinct entry as it goes into the tree. The server doesn't learn the secret, but it learns that some commitment was registered at time t for payment p. Blind signing's property ("the server doesn't even learn that an unblinded public key exists") is traded away.
  • For most threat models this trade is acceptable — Semaphore's unlinkability is what users actually care about, and the commitment leaks no more than blind-signed issuance leaks in practice. But it is a design shift that should be discussed openly.

Effort: probably the shortest path to working code (Semaphore does most of the heavy lifting), but it reshapes issuance semantics, not just presentation semantics.

Cross-cutting concerns

These apply to any approach that ships.

  1. Presentation-format migration. All three approaches change what a "use my Ghost Key" payload looks like on the wire. This must be bundled with any other format bump (e.g. the notary rename) rather than done as a separate round of breakage.
  2. Scope-bound proofs / replay protection. Every proof must bind to a verifier-provided challenge (or a commitment to the requesting app's identity and the payload being signed). Getting this wrong is subtle and has historically bitten similar systems — anonymous credential libraries generally provide this but it must be wired through the delegate's SignMessage API and the verifier side.
  3. Nullifier design. For contexts that need one-key-one-action (votes, rate limits), deterministic nullifiers are required; for contexts that don't, they should be suppressed to avoid leaking a stable cross-session identifier. The delegate API needs a way for apps to request nullifiers and specify the context.
  4. Verifier tooling. The ghostkey CLI and any offline verification tool need to understand the new proof format. Old-style verification becomes a legacy path.
  5. Tier-gated features. Selective disclosure of donation amount (for "donated ≥ $X gets feature Y" tiers) must survive the migration. BBS+ handles this natively; SNARK approaches need the circuit to compute range proofs; Semaphore would need attribute leaves.
  6. Multi-device / backup UX. If the underlying "secret" is the thing that matters, users must still be able to back it up and restore it, and the backup format should not itself become a correlation vector.

Tentative preference (if and when we pursue this)

BBS+. Reasons:

  • Mature, audited, production-deployed in credentialling systems today.
  • Cheap enough for browsers.
  • Best match for the "anonymous verifiable identity" framing the landing page already uses.
  • Natural fit with a format bump that's already on the table for the notary rename.
  • Smallest research risk and smallest ongoing maintenance burden.

Semaphore is the backup plan if BBS+ productionisation turns out harder than expected and we're willing to reshape issuance semantics. SNARK-over-existing-cert is a research project and I don't recommend it unless a specific constraint forces it.

Not currently planned

This is documentation, not a work item. Filing so the research isn't lost and so future discussions have a starting point. Before we'd pick this up, we'd want:

  • Clearer use-cases that require multi-show unlinkability (voting? cross-app reputation without correlation? anonymous payments on Freenet?). Current Ghost Key users get the blind-signing privacy property and haven't specifically asked for unlinkable presentation yet.
  • A concrete threat model for who we're defending against with unlinkable presentation (colluding apps? a single malicious app? network observers? the donation server itself?).
  • Acceptance that this is a meaningful format bump, not a small change.

Closing thoughts welcome — particularly if anyone has a use case that would clearly benefit.

[AI-assisted - Claude]

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status

    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions