How the protocol works

One issuer, many clients. The issuer holds a private key and publishes its parameters: a public key plus a domain separator that names this deployment and its key epoch. Every token is bound to those parameters; tokens from different epochs or deployments are different currencies. What follows is the classical scheme's mechanics; the post-quantum scheme keeps the same shape with different building blocks.

Issuance: a signature on secrets the issuer never sees

The client generates secret material: a random token identifier (from which the nullifier will later be derived) and blinding factors. It commits to these and sends the commitment with a proof that it is well formed. On this service, the request also carries a mined proof-of-work solution that pays for the grant.

The issuer verifies and signs the commitment together with the granted amount. The amount is public at issuance; the client's secrets stay hidden inside the commitment. The client unblinds the result into a credit token: secrets, balance, and a signature that binds them. The issuer has signed a token it will never be able to recognize.

Spending, always with change

A token is spent whole. To pay s credits from a token with hidden balance c, the client sends one spend proof that reveals exactly two things: the token's nullifier and the public amounts of the transaction. Everything else stays inside a zero-knowledge argument establishing that:

The proof also carries a fresh hidden commitment for the remainder. The issuer signs it and returns that signature: a re-issued change token holding the new balance. The old token is dead once its nullifier is recorded; the change token is a full token in its own right, and nobody but its holder can connect the two.

Refundation: the issuer gives change back

The spend proof names a charge s and a top-up a (credits added during a spend after an out-of-band purchase; this deployment requires a = 0). When the issuer signs the change token it may add a return t chosen from [0, s + a], making the final balance c - s + a + t. The point is that t is chosen by the issuer after seeing the request: charge the maximum up front, refund the unused part once the real cost is known. Useful when the price of a request is not known until the work is done, such as metered compute or variable-length responses.

On this service the client states the return it wants in the spend envelope's requested_return field and the issuer grants at most that, capped at s. The post-quantum scheme goes further with a deferred-return mode in which the return is fixed only after the proof verifies; see the post-quantum page.

A worked example

StepWhat happensClient holds
Issue 100Issuer signs a fresh token for 100 credits.Token A: 100
Spend 30, request 5 backClient proves 100 - 30 = 70 is in range and reveals token A's nullifier. Issuer records the nullifier, grants the return t = 5, and signs a change token for 70 + 5.Token B: 75

The service accounts a net payment of 25. The issuer saw a valid spend of 30 against some token, returned 5, and signed one fresh hidden balance. Which issuance funded it, and which other spends share its history, it cannot tell.

Double-spend prevention

Every token has exactly one nullifier, unpredictable to the issuer until revealed and identical for every spend of that token. The first accepted spend stores it permanently. Any later spend of the same token, even with a freshly randomized proof, reveals the same nullifier and is refused with the final error double_spend. The nullifier is the only join key in the system, and it only ever joins a token to itself.

A retry of the same spend also lands on a stored nullifier. Telling that apart from a real double spend, without knowing who anyone is, has an exact answer: when the connection dies.