Skip to content

design: bundled one-time-use Ghost Keys for per-action unlinkability #2

Description

@sanity

Motivation

Today a Ghost Key is anonymous at issuance (blind signing) but pseudonymous in use: reusing one key across messages or apps makes those uses cryptographically linkable to the same holder. See freenet/web#27 for the disclosure side.

One-time-use keys — a fresh Ghost Key per signed action — are the cleanest mitigation available short of ZK, and they avoid the substantial redesign a ZK-over-certificate approach would need.

Problem with naive per-transaction issuance

If each action required its own donation, the UX would be terrible and the project would look like a money grab even though per-action keys are genuinely the right privacy unit for many use cases.

Proposal

Decouple donation size from key count. A single donation issues a bundle of N blinded Ghost Keys in one pass. The user pays once for a supply and spends them locally, one per action, at their own pace. The donation server still never sees any key (blind signing is per-key), and bundle size is purely a policy / UX knob — it does not change the anonymity property at issuance.

Rough shape:

  • `ghost-key request --bundle 100` blinds 100 candidate keys, submits to the donation server, unblinds 100 signed certificates.
  • Local storage holds the bundle; the delegate / CLI draws one when the user takes a signable action.
  • The user refills when the bundle runs low. Replenishment is just another donation, so the economic model is unchanged — the project gets paid for a pack, not per stamp.

When not to use one-time-use keys

Per-action unlinkability is correct for votes, donations, ephemeral posts, anonymous feedback. It is wrong for apps where continuity is the feature — River room membership, long-lived reputation, anything where "this is the same person as yesterday" is load-bearing. For those, per-persona (one stable key, scoped to one context) is the right privacy unit.

The guidance, and what the CLI/delegate should support, is: match key lifetime to the privacy unit you actually want. Both modes need to be first-class.

Open questions

  1. What's a reasonable default bundle size? Tradeoff between UX friction (too few → constant refills) and donation-server workload / storage (too many per donation).
  2. Should bundles be tagged with a soft expiration to prevent long-term key hoarding breaking future format migrations?
  3. How does the delegate pick which key to spend — random, oldest-first, tagged-by-context? Random is safest against any ordering side-channel.
  4. How does a holder back up a bundle without creating a correlation oracle (e.g. cloud backup that sees all their keys)?
  5. Does the CLI need a "give me another" primitive that blinds + submits + unblinds in one shot, so apps can top up transparently?

Non-goals

  • ZK proof of certificate validity (separate, larger work item; tracked as future direction).
  • Changing the blind-signing protocol itself.
  • Making per-action the default for apps where continuity is correct.

Next step

Sketch the bundle-issuance CLI flow and delegate storage schema; confirm the donation server can sign N keys in one request without breaking its existing rate/abuse controls. Not urgent; driven by freenet/web#27 and follow-up demand from privacy-sensitive app authors.

[AI-assisted - Claude]

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions