On-Chain Guarantees

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.

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:

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:

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.

Upgrade Safety

  • 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

Last updated