For the complete documentation index, see llms.txt. This page is also available as Markdown.

Proof of Innocence

Proof of Innocence is an off-chain compliance feature that acts as an AML policy. It allows users to prove their received funds did not originate from sanctioned addresses (e.g., OFAC list) without revealing transaction details.

Multi-layered compliance: zERC20 enforces sanctions compliance at two layers. The on-chain Blocklist contract prevents sanctioned addresses from participating in any token operation (transfer, mint, burn, teleport). Proof of Innocence adds a second layer by enabling recipients to cryptographically prove that none of their received funds originated from sanctioned sources, even when the sender's identity is hidden by the privacy mechanism.

Overview

Property
Description

Purpose

Prove sender addresses are not on a sanctions list

Proof System

Nova IVC proof

Verification

Off-chain CLI verifier

Data Structure

Sparse Merkle exclusion tree for sanctions list

How It Works

┌─────────────────────────────────────────────────────────────────┐
│                    Proof of Innocence Flow                      │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  1. Prove that `totalTeleported` per recipient (hash of         │
│     `GeneralRecipient`) is not originated from OFAC-sanctioned  │
│     sources.                                                    │
│                        ↓                                        │            
│  2. Commit the OFAC list using a commitment scheme that         │
│     supports non-membership proofs (e.g., Exclusion Tree).      │
│                        ↓                                        │
│  3. For each `from_address`, generate non-membership proof.     │
│                        ↓                                        │
│  4. For each teleport, prove that `transfer_leaf.from` is not   │
│     in the OFAC list, then aggregate the steps with Nova.       │
│                        ↓                                        │
│  5. The CLI verifier checks the Nova proof and confirms public  │
│     state: `recipient`, `totalTeleported`, `ofacRoot`, and      │
│     `transferRoot`.                                             │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Cryptographic Design

Exclusion Tree

A “OFAC non-membership tree” is a Merkle tree of sorted disjoint (start, end) pairs representing the gaps between the elements of the OFAC sanctions list. The gap boundaries are exclusive: an address is proven innocent only when start < from_address < end. Sanctioned addresses sit on gap boundaries and therefore do not satisfy any gap. The OFAC sanctions list is committed using an OFAC non-membership tree. The leaf of the tree is computed as:

If the sanctions list contains k addresses, the exclusion tree has k + 1 gaps (one before the first address, one between each consecutive pair, and one after the last). zERC20 fixes the OFAC exclusion tree height at 32 for the current proof_of_innocence circuit artifacts. The sparse tree does not allocate all 2^32 leaves; proof and circuit cost grow roughly with the Merkle path length.

Non-Membership Proof (Nova Step)

Each transfer to a recipient has a transfer_leaf = (from_address, burn_address, value) (where burn_address is the to field of the generic transfer leaf). To prove that totalTeleported per recipient is not originated from sanctioned sources, for each transfer we prove the sender (transfer_leaf.from) is not in the OFAC list: Public Inputs (constant across all steps):

  1. ofac_root - root of the OFAC non-membership tree

  2. recipient - hash of GeneralRecipient

  3. transfer_root - root of the transfer tree containing the proved transfer leaves Accumulator State:

  4. totalTeleported - running sum of transfer values Per-Step Private Inputs:

  5. from_address - sender of this transfer (transfer_leaf.from)

  6. value - transfer amount

  7. secret - burn-address secret binding the transfer to recipient

  8. leaf_index, transfer_siblings - transfer-tree Merkle witness

  9. start, end - gap boundaries containing from_address

  10. gap_index, siblings - Merkle proof for the OFAC exclusion gap Per-Step Constraints:

  11. Recipient binding: secret derives the transfer burn address for recipient

  12. Transfer inclusion: the transfer leaf is included in transfer_root

  13. Sender not sanctioned: start < from_address < end verified against ofac_root

  14. Anti-replay ordering: leaf_index increases strictly across steps

  15. Accumulate value: totalTeleported_new = totalTeleported_old + value

CLI Usage

Note: This is an MVP interface. Users must manually assemble witness files.

Generate Proof of Innocence

Generate a Nova proof that all transfers to a recipient originated from non-sanctioned addresses.

Argument
Description

--nova-artifacts-dir

Directory containing proof_of_innocence_nova_pp.bin and proof_of_innocence_nova_vp.bin; can also be set with NOVA_ARTIFACTS_DIR

--recipient

Hash of GeneralRecipient (from withdrawal flow)

--ofac-root

Trusted OFAC exclusion tree root

--transfer-root

Transfer tree root for all transfer witnesses

--transfers-file

JSON file with complete transfer witnesses

--exclusion-proofs-file

JSON file with exclusion proofs from OFAC tree service

--output

Path to write canonical uncompressed Nova IVC proof bytes

Verify Proof of Innocence

Argument
Description

--nova-artifacts-dir

Directory containing proof_of_innocence_nova_pp.bin and proof_of_innocence_nova_vp.bin; can also be set with NOVA_ARTIFACTS_DIR

--proof

Path to the proof file

--recipient

Expected recipient hash

--total-teleported

Expected total value (wei)

--ofac-root

Trusted OFAC exclusion tree root

--transfer-root

Expected transfer tree root

Example

Input File Formats

The two JSON arrays must have the same length. Entries are paired by array position, then the CLI sorts the paired witnesses by leaf_index before proving. Each paired entry must use the same from_address.

transfers.json - List of transfers to prove:

exclusion_proofs.json - Exclusion proofs from OFAC tree service:

transfer_siblings must contain 40 field elements. siblings must contain 32 field elements for the current OFAC circuit.

Last updated

Was this helpful?