Overview
TheNullifierRegistry is a critical security contract that tracks nullifiers used in zero-knowledge proofs to prevent double-spending attacks. It maintains a record of all nullifiers that have been used to claim payments, ensuring each proof can only be used once.
Contract Location: contracts/registries/NullifierRegistry.sol
Purpose
The NullifierRegistry serves as a double-spend prevention mechanism:- Nullifier Tracking: Records nullifiers from ZK proofs to prevent reuse
- Access Control: Restricts write access to authorized contracts only
- Global State: Provides a shared nullifier registry across all payment verifiers
- Security Enforcement: Prevents the same off-chain payment proof from being used multiple times
Key Concepts
What is a Nullifier?
A nullifier is a unique identifier derived from a zero-knowledge proof that represents a specific off-chain payment. Once a nullifier is recorded, any attempt to use the same proof again can be detected and rejected.Writer Permissions
The registry implements a permission system where only authorized “writer” contracts (typically payment verifiers) can add nullifiers.State Variables
isNullified: Tracks whether a specific nullifier has been usedisWriter: Maps addresses to their write permission statuswriters: Array of all addresses with write permissions
Core Functions
Adding Nullifiers
_nullifier: The nullifier hash from the ZK proof
- Caller must be a registered writer
- Nullifier must not already exist
NullifierAdded(bytes32 nullifier, address indexed writer)
Reference: contracts/registries/NullifierRegistry.sol:41
Managing Write Permissions
_newWriter/_removedWriter: Address to grant or revoke permissions
- Only callable by contract owner
- Writer must not already have/lack permissions (prevents duplicates)
WriterAdded(address writer) or WriterRemoved(address writer)
Reference: contracts/registries/NullifierRegistry.sol:56 and :70
Querying Writers
contracts/registries/NullifierRegistry.sol:81
Integration with Core Contracts
Payment Verifiers
Payment verifier contracts (e.g.,UnifiedPaymentVerifier) are the primary users of the NullifierRegistry:
- Verifier receives a ZK proof containing a nullifier
- Verifier checks if nullifier has been used:
isNullified[nullifier] - If proof is valid and nullifier is unused, verifier calls
addNullifier() - Nullifier is permanently marked as used
contracts/unifiedVerifier/BaseUnifiedPaymentVerifier.sol:64
Orchestrator Registry
Payment verifiers that write to the NullifierRegistry must be authorized by being granted write permissions during deployment or configuration. Reference:contracts/unifiedVerifier/UnifiedPaymentVerifier.sol:98
Access Control
Modifiers
addNullifier() to authorized writer contracts only.
Reference: contracts/registries/NullifierRegistry.sol:21
Owner Functions
addWritePermission(): Add authorized writersremoveWritePermission(): Remove writer access
Writer Functions
addNullifier(): Record used nullifiers
Public View Functions
isNullified[nullifier]: Check if nullifier has been used (publicly accessible)isWriter[address]: Check if address has write permissionsgetWriters(): Get list of all writers
Events
- Tracking which verifier added which nullifiers
- Monitoring changes to write permissions
- Auditing nullifier usage patterns
Security Model
Double-Spend Prevention
The registry prevents double-spending through:- Uniqueness Enforcement: Each nullifier can only be added once
- Revert on Duplicate: Attempting to add an existing nullifier will revert
- Permanent Record: Nullifiers cannot be removed once added
Access Control Security
- Restricted Writers: Only authorized contracts can add nullifiers
- Owner-Controlled: Only owner can modify writer permissions
- No Removal: Nullifiers are permanent (cannot be deleted)
Integration Security
Proper integration requires:- Verifiers must check
isNullified[nullifier]before processing - Verifiers must call
addNullifier()after successful verification - Writer permissions must only be granted to audited verifier contracts
Usage Pattern
Typical flow in a payment verifier:Deployment Considerations
- Single Registry: Typically one NullifierRegistry per chain for all payment methods
- Writer Setup: Grant write permissions to all payment verifier contracts
- Immutable Nullifiers: Once deployed, nullifiers cannot be removed (plan for long-term storage)
- Gas Optimization: Uses OpenZeppelin’s
AddressArrayUtilsfor efficient array operations
Related Contracts
- PaymentVerifierRegistry - Manages verifier contracts that write to this registry
- UnifiedPaymentVerifier - Primary writer to this registry
- OrchestratorRegistry - Controls which orchestrators can authorize verifiers