Mint a secret. Claim it. Try to claim it twice.
Every hash below is real SHA-256 from crypto.subtle.digest. Secrets are random 32-byte demo values created in this tab.
Your wallet: mint secret tickets
Each ticket has a random secret s that only its holder knows. The set stores only the commitment C = SHA-256(s). The holder can also compute the nullifier N = SHA-256(s || "nullifier") but nobody sees it until a claim.
Claim
Use the Claim button on a ticket, press it again to attempt a double-claim, or try a forged secret. Only the nullifier is published, never s.
The public ledger: what everyone sees
Two lists and a log. No secrets, no ticket numbers, no link between the lists. Both lists are shown sorted by value, not in time order, so list position does not leak mint or claim order.
Commitment set
Spent nullifiers
Observer: try to link a nullifier to a commitment
An observer holds both public lists. This tries four obvious ways to connect a spent nullifier to a commitment by hashing, on every pair. A real link needs the secret s, which means inverting SHA-256.
Prove you hold a ticket without showing which.
Commitment
C = SHA-256(s) goes in a public set. It reveals nothing about s because SHA-256 cannot be run backwards.
Nullifier
N = SHA-256(s || "nullifier") is the same for the same secret every time, so a second claim collides with the first and is rejected. It looks unrelated to C.
Unlinkable
Anyone can see N was spent. Nobody can tell which C it came from, so they cannot tell who claimed.
Private (witness)
The secrets and the Merkle path showing its commitment sits in the set.Public inputs
The set's Merkle rootR and the nullifier N.What the ZK proof asserts
"I know ans such that C = H(s) is a leaf under R and N = H(s || "nullifier")." The verifier checks the proof and that N is not already spent. It never learns s, C, or which leaf.Hidden (in a real build)
- Which commitment was claimed
- The secret itself
- Any link between a claim and the earlier mint
Public
- That some valid claim happened, and its nullifier
- When it happened, and which account paid the fee
- The whole commitment set and its root
About the primitives. This demo uses plain SHA-256. A Solana build would use a ZK-friendly hash (e.g. Poseidon) inside a Groth16 or PLONK circuit, with the nullifier stored in an on-chain account or bitmap so a second claim fails. Web Crypto has no ZK prover, so none is shown here.
Known limitations — please read
- No ZK proof in this demo. The page checks membership by hashing your secret directly, so it can link a claim to a commitment. Only the public ledger view is unlinkable by construction.
- Reduces linkability, not anonymity. Timing, the fee-paying account, IP addresses and claim order can all re-identify a claimer even when the nullifier reveals nothing.
- Demo secrets only. Never paste a real seed phrase or private key. The secrets here are random throwaway values shown on screen.
- Small anonymity set. A set with few commitments hides you among few. Privacy grows with the number of honest tickets.
- State lives in this tab. Reload and everything is gone. Nothing is stored or sent.
- Unaudited, no token. No program is deployed and no audit exists. The $NULL ticker is a placeholder only. Not connected to mainnet or real funds.