> ## Documentation Index
> Fetch the complete documentation index at: https://docs.metalntwx.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Privacy-Preserving Policy

> How MIP410 verifies an institution's payment rules while keeping its policy and private data hidden

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](https://docs.metalntwx.com/learn/mip410/proof-and-verification.md#the-circuit-and-its-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:

```text theme={null}
policy_key_hash = backend_hash(policy_verification_key)

Registry leaf = Poseidon2(
    DOMAIN_FI_LEAF,
    fi_id,
    policy_key_hash,
    messaging_key_x,
    messaging_key_y
)
```

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](https://docs.metalntwx.com/reference/privacy/circuit-registry.md) 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:

```text theme={null}
Private policy + payment data
    -> proof that the policy permits this stage of the payment

Policy wrapper
    -> verifies that proof and its connection to the VASP's registered policy
    -> produces a proof of those checks

Pool
    -> verifies the wrapper proof against the payment being executed
```

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:

```rust theme={null}
verify_inner(
    witness.policy_vk,
    witness.policy_proof,
    witness.inner_publics,
    witness.vk_hash,
);

assert(witness.inner_publics[0] == publics.payment_effect_hash);
assert(witness.inner_publics[1] == publics.phase);
assert(witness.inner_publics[2] == publics.payload_commitment);
assert(witness.inner_publics[3] == publics.commitment_root);
assert(witness.inner_publics[4] == publics.registry_root);
```

`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](https://docs.metalntwx.com/learn/mip410/shielded-payments.md#expiry-and-reclaim)
makes them available again.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.