Anonymous credentials · BBS+ selective disclosure
Signs one six-field credential with BBS over BLS12-381, then lets you reveal any subset of fields — or just prove you're over 18 — while the verifier checks a proof that never contains the signature or the hidden fields.
A signed credential — a driver's license, a JWT, a mobile ID — is an all-or-nothing artifact: to prove one field, you hand over the whole document and the signature that binds it. BBS+ signatures break that. The issuer signs the six fields once; the holder can then prove, as many times as they like, that a valid signature exists over any subset of fields they choose to reveal — without ever showing the signature itself. And because each presentation is freshly randomized, two showings of the same credential share nothing a verifier could correlate.
Honest scope: this is a teaching demo, not production crypto. The BBS signatures, proofs, pairings, Ed25519 baseline and the age range-proof on this page are all real and run in your browser (KAT-tested against the official draft-irtf-cfrg-bbs-signatures fixtures). The credential data, issuer, and wallet are stage props: keys live only in this tab's memory. What this page does not prove: that a real deployment is private — issuer collusion, network metadata, and revealing identifying values (your name is your name) all sit outside what the math can protect.
One issuer, one credential, six fields. Everything below uses this exact credential — signed once with BBS over BLS12-381 and once with Ed25519 for comparison.
Generating issuer keys…
This is what a signed JWT or mobile driver's license does today. The credential is Ed25519-signed as one blob. Try to prove just one thing — that you're over 18 — and watch what the verifier actually receives.
Same credential, BBS-signed. Check exactly the fields you want the verifier to see, then step through what actually happens. The hidden fields aren't blacked out on the verifier's side — they're absent. The signature never leaves your wallet.
You're the tamperer now: change a revealed value after the proof was made, then hand the altered presentation to the real verifier.
Present the same credential three times, revealing the same field. Then flip to the Ed25519 baseline. One of these is a tracking cookie; the diff tells you which.
The credential's date of birth is signed as its day count, so the holder can prove
DOB ≤ cutoff with a bit-decomposition range proof linked into the BBS proof — the verifier
learns exactly one bit. Bulletproofs make the same statement logarithmically smaller; this lab uses the
inspectable O(n) construction and links the sibling for the efficient one.
Revocation needs the verifier to check something about this credential — and anything checkable is potentially correlatable. Below is a status list: one published bit per issued credential. It works. Look at what it costs.
The tradeoff, stated plainly: showing the verifier your index (#17) at every presentation
restores exactly the correlation handle the BBS proof removed. Cryptographic accumulators can prove
"my bit is 0" in zero knowledge, repairing this — that machinery is out of scope here and the tension is
not solved by the status list you just used.
| Approach | How it works | Verifier learns | The cost |
|---|---|---|---|
| Status list (this exhibit) | Issuer publishes one bit per credential; verifier reads bit i | Your stable index i, forever | Unlinkability is gone — the index re-links every presentation the crypto just unlinked |
| Issuer-online check | Verifier (or holder) asks the issuer "is this one still good?" per presentation | Nothing extra — but the issuer sees every place the credential is used | Trades verifier correlation for an issuer who can log, profile, or veto every showing; issuer availability becomes a liveness dependency |
| ZK accumulator membership | Issuer maintains a cryptographic accumulator of non-revoked credentials; holder proves membership in zero knowledge inside the presentation | One bit: "not revoked", with no index and no callback | Real machinery — pairing-based accumulators with per-epoch witness updates every holder must fetch (a fetch pattern that itself needs care, e.g. via PIR or broadcast), plus more prover time |
What a privacy-preserving deployment would actually require, in one breath: an accumulator commitment in each epoch's issuer bulletin, a membership witness per credential that updates when anyone is revoked, a ZK proof of that witness linked into the BBS presentation the same way this lab links its age predicate — and a story for how holders fetch witness updates without the fetch itself becoming the tracking signal. Each piece exists in the literature; the honest point of this exhibit is that none of them is free, and a deployment that skips them silently lands back on this page's status list.
| Scheme | Issuer sees at issuance | Verifier sees at showing | Showings linkable? | Reveal a subset? |
|---|---|---|---|---|
| Plain JWT / mDL (this page's baseline) | All fields | All fields + the signature | Yes — identical signature every time | No |
| BBS+ (this lab) | All fields | Chosen fields + a fresh proof | No, at the crypto layer | Yes — any subset, plus predicates |
| Blind signatures (blind-sign) | Nothing — signs blind | The whole token + signature | Not to the signer; verifier sees a stable token | No — still all-or-nothing at showing |
| Ring signatures (ring-sign) | n/a — self-signed among a set | The message + which ring | Scheme-dependent (linkable variants exist) | No — hides who, not what |
e(P,Q) itself.