Skip to main content
Publishing a payment policy can tip off the customers it is meant to screen. A public blocklist reveals who has been flagged. A visible transaction limit can tell someone how to split payments to stay below it. The rules themselves can be sensitive, alongside the customer data they evaluate. MIP410 lets a virtual asset service provider (VASP) prove that its registered policy permits a transaction while keeping the rules and their private inputs hidden. The pool verifies that proof before it executes the transaction. A merchant’s blocklist is one example of a rule that could be included in its VASP’s policy. The merchant may want to refuse payments from flagged customers without publishing the list or revealing which check caused a rejection. MIP410 registers policies at the VASP level, so the merchant’s rule would be enforced through the VASP serving it.

Policy wrapper

A policy proof shows that the requested transaction satisfies the rules in the policy circuit, the program containing the VASP’s checks. Zero knowledge keeps the circuit’s private inputs hidden. Direct verification, however, requires the policy’s verification key. A recognizable key can identify which policy was used even when its inputs remain hidden. Protecting those inputs alone does not hide the choice of policy. The policy wrapper lets the pool verify a registered policy without exposing its verification key or inner proof. It is a second circuit that checks the policy proof and confirms that the key’s hash matches a VASP’s policy registration. The key and policy proof are private inputs to this wrapper. The pool receives a proof of those checks, called the policy-wrapper proof, and verifies it through the common wrapper circuit. This gives different VASPs the same public verification mechanism while allowing their rules to remain private. The wrapper also connects the policy result to the payment being verified and its stage: initiation, acceptance, or rejection. A result for one payment cannot authorize another. The pool learns that the registered policy permits this transaction while the policy itself remains private.

Policy registration

Registration connects a VASP to the policy its payments are checked against. The pool’s registry records the institution’s identifier, the hash of its policy verification key, and its messaging public key. The hash records which policy applies without publishing the policy circuit or verification key. These records form a Merkle tree:
The authority admits the VASP to this registry. Later policy changes are authorized by the VASP’s current messaging key. Institutions can therefore revise their rules while the pool can still verify whose policy applies to a payment. Each VASP has one policy circuit covering initiation, acceptance, and rejection. It can apply different rules at each stage. For example, an acceptance rule could check a merchant’s blocklist while a rejection rule permits the VASP to release the reserved notes. A blocklist check depends on which list it uses. If the party creating the proof could choose any list, an empty one would let everyone pass. Specifying the list in the policy, or verifying its source, prevents that replacement. Changes to a list or amount limit built into the circuit change the policy’s verification key, so the registry identifies the revised policy through its new key hash. The pool’s registry identifies each institution’s private policy. The separate Circuit Registry stores the shared wrapper verification key used by the pool.

Recursive verification

The wrapper uses recursive verification: a proof is verified inside another circuit, and a second proof shows that this verification succeeded. MIP410 uses these two layers to separate the private policy from the pool’s shared verification mechanism:
The VASP supplies its registry record and the Merkle path proving that record is in the registry. The wrapper checks that path against a registry root accepted by the pool. Binding the policy verification key to this record prevents the prover from substituting a more permissive, unregistered policy. The recursive check and its payment bindings look like this in a simplified Noir excerpt:
verify_inner constrains the policy proof to verify under the supplied key and public inputs. MIP410’s proving backend also checks that the key’s hash equals vk_hash, which the wrapper binds to the VASP’s registry record. The five equalities implement the payment-context comparisons described above. phase is 0 for initiation, 1 for acceptance, and 2 for rejection. The policy’s public inputs become private witness values when passed into the wrapper. The wrapper then exposes the five context values above, together with the chain ID, pool contract address, and recipient VASP binding commitment. It also verifies the VASP’s signature over the chain ID, pool contract address, payment effect hash, phase, and payload commitment using the registered messaging key. At acceptance or rejection, it checks that the VASP matches the recipient VASP binding commitment recorded on the hold. This ties the private policy result to an authorized VASP and the requested transaction.

Binding the rules to the payment

A policy result describes a payment only when its checks are tied to that payment’s data. An amount limit depends on a connection between the private amount being checked and the payment’s notes. A rule about customer information depends on a connection between the record being read and the payment’s record commitment. Without those connections, a proof could pass using an unrelated amount or customer record. The payment proof and policy-wrapper proof describe the same proposed change. At initiation, the pool checks both against the submitted payment. At resolution, it checks them against the stored hold. The sender or recipient proof shows that the protocol’s payment and sanctions checks passed. The sender VASP supplies the policy-wrapper proof at initiation, and the recipient VASP supplies it at resolution. It proves approval under the VASP’s registered policy. Both proofs pass before the pool makes the change. Rejection is also a policy decision backed by a proof. If neither acceptance nor rejection can be proven, the notes remain reserved. After the unresolved hold expires, a reclaim transaction makes them available again.