# Circuit spec

## File

`apps/contracts/circuits/withdraw.circom`

## Statement

> The prover knows `(nullifier, secret)` such that:
>
> * `commitment = Poseidon(nullifier, secret)` is a leaf of the Merkle tree with the claimed `root`, witnessed by `(pathElements, pathIndices)`.
> * `nullifierHash = Poseidon(nullifier)` matches the claimed `nullifierHash`. AND additionally binds:
> * `recipient`, `relayer`, `fee`, `refund` to the proof as public inputs, so the relayer cannot substitute alternative values for them.

## Public inputs (in order)

| # | Name            | Type   | Notes                                               |
| - | --------------- | ------ | --------------------------------------------------- |
| 0 | `root`          | bigint | A recent root from the pool's root-history ring.    |
| 1 | `nullifierHash` | bigint | Poseidon(nullifier).                                |
| 2 | `recipient`     | bigint | Address-as-field-element (`addressToFieldElement`). |
| 3 | `relayer`       | bigint | Address-as-field-element.                           |
| 4 | `fee`           | bigint | nanoTON or jetton-base-units. `≤ denomination`.     |
| 5 | `refund`        | bigint | nanoTON refund to recipient. `= 0` for native pool. |

## Private inputs

| Name               | Type        | Notes                                             |
| ------------------ | ----------- | ------------------------------------------------- |
| `nullifier`        | bigint      | 31-byte random.                                   |
| `secret`           | bigint      | 31-byte random.                                   |
| `pathElements[20]` | bigint\[20] | Sibling hashes at each tree level.                |
| `pathIndices[20]`  | int\[20]    | Bit per level: 0 = leaf is left child, 1 = right. |

## Constraints

Approx. 30,000 R1CS constraints (final count depends on Poseidon parameters):

* 1 Poseidon(2) for commitment
* 1 Poseidon(1) for nullifierHash
* 20 Poseidon(2) for Merkle path verification (`HashLeftRight` per level)
* 20 bit-decomposition checks (`s * (1 - s) = 0`)
* 4 squarings (`recipient^2`, `relayer^2`, `fee^2`, `refund^2`), used purely to bind these signals into the proof; the squarings have no semantic meaning but make the values un-droppable by the prover.

The squaring trick is borrowed from the original Tornado circuit. See `apps/contracts/circuits/withdraw.circom` for the exact source.

## Hash function: Poseidon on BLS12-381

We import `circomlib/circuits/poseidon.circom`. circomlib's Poseidon round constants and MDS matrix were originally derived for the BN254 scalar field. The Poseidon spec is field-parameterised; the same round counts and MDS *structure* are valid over BLS12-381's scalar field, with constants reduced modulo the new field's prime.

The Poseidon parameters circomlib chose (R\_F = 8 full rounds, R\_P partial rounds depending on arity) were security-analysed against generic algebraic attacks (Grobner basis, interpolation, statistical) on the *permutation*, which is mostly field-independent. The reduction mod BLS12-381's scalar field doesn't introduce known weaknesses.

**Implementation cross-check:** the pool no longer hashes on-chain (the off-chain-root design moved Poseidon entirely into the off-chain prover and circuit; see [off-chain-root.md](/protocol-reference/off-chain-root.md)). What still needs to agree byte-for-byte is:

1. The off-chain Poseidon in `packages/core/src/poseidon.ts` (used to compute commitments and Merkle paths off-chain).
2. The circuit's Poseidon (used inside the proof).

(1) is in fact the circom-compiled poseidon wasm, loaded at runtime, so (1) and (2) are the same artifact and parity is by construction. The `check-poseidon-parity.ts` script in `apps/contracts/scripts/` drives the wasm directly to catch artifact-staleness regressions; CI runs it as a separate job.

## Why no compliance / "secret message" extension

The original Tornado Cash circuit eventually added an optional encrypted "compliance note" to support voluntary disclosure to auditors. We omit this in v1; it can be added later without breaking existing notes by introducing a v2 pool with the extended circuit.


---

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

```
GET https://docs.tonadocash.com/protocol-reference/circuit-spec.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
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.
