> For the complete documentation index, see [llms.txt](https://zerc20.gitbook.io/zerc20/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://zerc20.gitbook.io/zerc20/for-developers/proof-of-innocence.md).

# 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:

```rust
leaf = poseidon2(start, end)
```

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.

```bash
# Generate proof for a recipient
zerc20-cli proof-of-innocence generate \
  --nova-artifacts-dir <NOVA_ARTIFACTS_DIR> \
  --recipient <GENERAL_RECIPIENT_HASH> \
  --ofac-root <OFAC_TREE_ROOT> \
  --transfer-root <TRANSFER_TREE_ROOT> \
  --transfers-file <TRANSFERS_JSON> \
  --exclusion-proofs-file <EXCLUSION_PROOFS_JSON> \
  --output <PROOF_OUTPUT_PATH>
```

| 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

```bash
# Verify a proof
zerc20-cli proof-of-innocence verify \
  --nova-artifacts-dir <NOVA_ARTIFACTS_DIR> \
  --proof <PROOF_PATH> \
  --recipient <GENERAL_RECIPIENT_HASH> \
  --total-teleported <TOTAL_VALUE> \
  --ofac-root <OFAC_TREE_ROOT> \
  --transfer-root <TRANSFER_TREE_ROOT>
```

| 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

```bash
# 1. Generate proof
zerc20-cli proof-of-innocence generate \
  --nova-artifacts-dir ./nova_artifacts \
  --recipient 0x1a2b3c...recipient_hash \
  --ofac-root 0x5678...ofac_root \
  --transfer-root 0x9abc...transfer_root \
  --transfers-file ./my_transfers.json \
  --exclusion-proofs-file ./exclusion_proofs.json \
  --output ./innocence_proof.bin

# 2. Verify proof
zerc20-cli proof-of-innocence verify \
  --nova-artifacts-dir ./nova_artifacts \
  --proof ./innocence_proof.bin \
  --recipient 0x1a2b3c...recipient_hash \
  --total-teleported 1000000000000000000 \
  --ofac-root 0x5678...ofac_root \
  --transfer-root 0x9abc...transfer_root

# Output:
# Proof of Innocence proof verified
```

### 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:

```json
[
  {
    "from_address": "0xabc...",
    "value": "500000000000000000",
    "secret": "0x1234...",
    "leaf_index": 42,
    "transfer_siblings": ["0x...", "0x..."]
  }
]
```

`exclusion_proofs.json` - Exclusion proofs from OFAC tree service:

```json
[
  {
    "from_address": "0xabc...",
    "start": "0x000...",
    "end": "0x111...",
    "gap_index": 5,
    "siblings": ["0x...", "0x..."]
  }
]
```

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


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://zerc20.gitbook.io/zerc20/for-developers/proof-of-innocence.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
