Circuit Specification
Overview
Lateo uses a single Circom circuit: policy_tx_2_2 (PolicyTransaction with 2 inputs and 2 outputs).
| Parameter | Value |
|---|---|
| Circuit | policy_tx_2_2 |
| Inputs | 2 (max UTXOs consumed per transaction) |
| Outputs | 2 (max UTXOs created per transaction) |
| Merkle tree depth | 10 levels (1,024 max commitments) |
| ASP membership tree | 10 levels |
| ASP non-membership tree | 10 levels (sparse) |
| Constraints | 37,616 |
| Public inputs | 11 |
| Proving system | Groth16 |
| Curve | BN254 |
Transaction Types
The same circuit handles deposits, withdrawals, and transfers. The publicAmount (ext_amount) determines the type:
| Type | publicAmount | Real inputs | Real outputs |
|---|---|---|---|
| Deposit | Positive | 0 (2 dummies) | 1 real + 1 dummy |
| Withdrawal | Negative | 1-2 real | 1 change + 1 dummy |
| Transfer | 0 | 1-2 real | 1-2 real |
Public Inputs (11 total)
root — Pool Merkle tree root (validates input commitment existence)
publicAmount — Net amount entering/leaving the pool (signed)
extDataHash — Poseidon2 hash of external data (recipient, amount, encrypted outputs)
inputNullifiers[2] — Nullifiers for spent inputs (prevents double-spend)
outputCommitments[2] — Commitments for new outputs (inserted into Merkle tree)
membershipRoots[1][1] — ASP membership tree root
nonMembershipRoots[1][1] — ASP non-membership tree rootConstraints
The circuit enforces:
- Balance conservation:
sum(input_amounts) + publicAmount = sum(output_amounts) - Commitment validity:
commitment = Poseidon2(amount, pubKey, blinding, 0x01) - Nullifier correctness:
nullifier = Poseidon2(commitment, pathIndices, privKey, 0x02) - Merkle inclusion: Each input commitment has a valid Merkle path to
root - ASP membership: Prover’s leaf is in the membership tree
- ASP non-membership: Prover is NOT in the exclusion tree
- Signature: Prover knows the private key corresponding to the public key in the commitment
- ExtData binding:
extDataHashcommits to the recipient and encrypted outputs (prevents front-running)
Domain Separators
All Poseidon2 hashes use domain separators to prevent cross-domain attacks:
| Domain | Separator | Usage |
|---|---|---|
| Commitment | 0x01 | Poseidon2(amount, pubKey, blinding, 0x01) |
| Nullifier | 0x02 | Poseidon2(commitment, pathIndices, privKey, 0x02) |
| Public key | 0x03 | Poseidon2(privKey, 0x03) |
| Signature | 0x04 | Poseidon2(commitment, privKey, 0x04) |
Proof Format
Groth16 proof = 3 uncompressed BN254 points:
| Point | Size | Description |
|---|---|---|
| A (G1) | 64 bytes | Y-coordinate negated for verification equation |
| B (G2) | 128 bytes | Two Fp2 elements (c1 then c0) |
| C (G1) | 64 bytes | Standard G1 point |
| Total | 256 bytes | Constant regardless of circuit size |
Last updated on