The Nullmask smart contract provides the following security guarantees regardless of the behavior of off-chain components.
Proof Verification
Every shielded action (transfer, withdrawal, swap) requires a valid ZK proof verified by an on-chain verifier contract. The proof cannot be forged without breaking the UltraHonk proof system.
Copy if ( ! IVerifier ( verifier ). verify ( proof , publicInputs )) revert InvalidProof (); Nullifier Uniqueness
Note Nullifiers
Each note can only be spent once. The contract maintains a permanent record of all spent nullifiers:
Copy mapping ( uint256 => bool ) public noteNullifiers ; Attempting to spend a note whose nullifier is already recorded reverts with NullifierAlreadySpent().
Transaction Nullifiers
Each signed transaction can only be used once:
Copy mapping ( bytes32 => bool ) public txNullifiers ; Attempting to replay a transaction reverts with TxNullifierAlreadyUsed().
Merkle Root Validity
The proof's Merkle root must match one of the last 64 historical roots:
This prevents proofs from using fabricated Merkle trees while allowing a window for concurrent transactions.
Field Modulus Validation
All public inputs are validated to be within the BN254 scalar field:
This prevents arithmetic overflow attacks in the proof verification.
Reentrancy Protection
All state-mutating functions use OpenZeppelin's ReentrancyGuardTransient:
Deposit Data Integrity
Pending deposits store a hash of the deposit parameters. The guard must provide matching parameters when approving or rejecting:
This prevents the guard from modifying deposit parameters during approval.
UUPS proxy pattern with _authorizeUpgrade() hook
Upgrade authorization delegated to IUpgradeController
Storage gap (__gap[45]) reserved for future state variables
Controller change restricted to current controller only