Privacy-critical statements live in the circuit. Already-public policy parameters are enforced by the contract. Never attribute a check to the circuit that the contract performs, or vice versa.
The split
Why the split is drawn here
The circuit only needs to prove the things that must stay private: that the prover holds a real credential, that this credential is in the approved set, and that they derived their nullifier and bound it to this exact action — all without revealing the secret or the identity. Everything else is already public: the max amount, the asset, the expiry, whether the policy is active. There is no privacy value in proving them in-circuit, and doing so would only make the circuit larger and slower. The contract checks them cheaply, in the clear, where anyone auditing the ledger can see the same values.How the two halves connect
The proof binds to the action viaaction_id, which is folded into the nullifier. The contract independently recomputes context_hash and action_id from the submitted action and checks they match the proof’s public inputs.
Neither half trusts the other blindly: the circuit proves the private facts, and the contract re-derives the public binding from the raw action it is about to execute.
Next: the Privacy Receipt
The inspectable artifact every decision emits — success and rejection alike.