Skip to main content

Policy System

The policy system lets you attach enforceable on-chain conditions to commitments. Policies are checked during reveal — if the policy's validate() function returns false, the reveal is rejected.

How policies work

  1. Commit with policy — When committing, specify a policy contract address and parameters hash
  2. Policy is bound — The policyId and policyParamsHash are included in the Poseidon commitment hash and verified inside the ZK circuit. You cannot change the policy after commit.
  3. Reveal checks policy — During reveal, the vault calls policy.validate() via staticcall with a 100K gas cap
  4. Policy validates — The policy contract checks conditions (time, destination, witnesses, etc.) and returns true or false

Built-in policies

PolicyAddressDescription
TimelockExpiry0xae2307...39d6Reveals only allowed after/before certain timestamps
DestinationRestriction0x899E9f...9A3ee00Reveals restricted to specific recipient addresses
ThresholdWitness0xa89638...fEBD7Requires M-of-N witness signatures to reveal

Policy validation call

The vault calls:

(bool valid) = policy.staticcall{gas: 100_000}(
abi.encodeCall(IRevealPolicy.validate, (
commitment, nullifier, recipient, amount, token, policyParams
))
);

Key properties:

  • Read-onlystaticcall prevents state changes
  • Gas-limited — 100K gas cap prevents griefing
  • ABI-encoded paramspolicyParams carries arbitrary encoded data

Example: commit with a timelock

const timeLockPolicy = '0xae2307620840916a06A862A61BF2101d694539d6';
const unlockTime = Math.floor(Date.now() / 1000) + 3600; // 1 hour from now
const policyParams = ethers.AbiCoder.defaultAbiCoder().encode(
['uint256', 'uint256'],
[unlockTime, 0] // [notBefore, notAfter]
);
const policyParamsHash = poseidon([...policyParamsFields]);

const tx = await vault.commitNativeWithPolicy(
commitment,
ethers.ZeroHash,
timeLockPolicy,
policyParamsHash,
{ value: amount, gasPrice: 1_000_000_000n }
);

Next steps