NullifierRegistryLib
Title: NullifierRegistryLib
Author: David Dada
Append-only spent-set for double-spend protection in nullifier-based privacy schemes (shielded transfers, anonymous voting). A nullifier is a one-time tag a ZK circuit derives from a secret; spending it once and reverting on reuse is what prevents replay / double-spend without revealing which note was consumed.
Stateless helper — no own ERC-7201 slot; Registry is held inline in the
consumer's namespaced storage (same pattern as EnumerableSet). Always
spend a nullifier BEFORE any external interaction (strict CEI) so a reentrant
call cannot replay it.
A nullifier is a BN254 field element, so spend enforces canonicality
(0 < nullifier < SNARK_SCALAR_FIELD). This closes the non-canonical-field-element
double-spend: an EC scalar mult is inherently mod-p, so n and n + p are the
SAME circuit-level nullifier even though they are distinct uint256 keys — accepting
a non-reduced value would let one witness be spent more than once. Mirrors the
< SNARK_SCALAR_FIELD leaf invariant of IncrementalMerkleTreeLib/LeanIMT.
Constants
SNARK_SCALAR_FIELD
BN254 / alt_bn128 scalar field modulus (the public SNARK field constant).
uint256 internal constant SNARK_SCALAR_FIELD =
21888242871839275222246405745257275088548364400416034343698204186575808495617;Functions
isSpent
Returns whether nullifier has been spent.
function isSpent(Registry storage self, uint256 nullifier) internal view returns (bool);Parameters
| Name | Type | Description |
|---|---|---|
| self | Registry | The registry storage reference. |
| nullifier | uint256 | The nullifier to query. |
Returns
| Name | Type | Description |
|---|---|---|
| <none> | bool |
spend
Marks nullifier spent, reverting if it is zero, out of field, or already spent.
Call BEFORE any external interaction (CEI) to block reentrant replay. Pass only the canonical field element the verifier validated as a public input, never raw calldata.
function spend(Registry storage self, uint256 nullifier) internal;Parameters
| Name | Type | Description |
|---|---|---|
| self | Registry | The registry storage reference. |
| nullifier | uint256 | The nullifier to consume. |
Errors
NullifierAlreadySpent
Thrown when spending a nullifier that was already spent.
error NullifierAlreadySpent(uint256 nullifier);Parameters
| Name | Type | Description |
|---|---|---|
| nullifier | uint256 | The already-spent nullifier. |
NullifierIsZero
Thrown when spending the zero nullifier (rejected as a reserved sentinel).
error NullifierIsZero();NullifierOutOfField
Thrown when spending a non-canonical nullifier (>= the SNARK scalar field).
error NullifierOutOfField(uint256 nullifier);Parameters
| Name | Type | Description |
|---|---|---|
| nullifier | uint256 | The out-of-field nullifier. |
Structs
Registry
struct Registry {
mapping(uint256 nullifier => bool spent) _spent;
}Properties
| Name | Type | Description |
|---|---|---|
| _spent | mapping(uint256 nullifier => bool spent) |