A pool that trades for you and cannot tell who you are.
Three pieces: notes, a circuit, and a contract with no keys. Everything below is published in this folder, compiled by the scripts in it, and checked by a test that makes a real proof. Read the last card before you put money in.
What the pool stores is a hash of what you own.
A note is four numbers: a secret and a nullifier your browser draws at random, the token, and the amount. The pool never sees them. It receives one commitment:
commitment = Poseidon( Poseidon(secret, nullifier), Poseidon(token, amount) )
and appends it as a leaf of a depth-20 Merkle tree, hashed with Poseidon over BN254 so the same hash is cheap inside the proof and on chain. The note string the app shows you (zkswap-note-v1:…) is the only way back to the money. There is no recovery because there is nobody who could do it.
Prove one leaf is yours. Do not say which.
Groth16, circom, 12,367 constraints. Public inputs: the tree root, the nullifier hash, the token, the amount, and a hash of the transaction the proof is for. Private: the secret, the nullifier, and the Merkle path.
The circuit checks that the leaf built from the private values sits under the root, that the nullifier hash is the Poseidon of that nullifier, and squares the transaction hash so it cannot be dropped. The verifier on chain is the contract snarkjs exports from the proving key.
Proving takes about half a second in node and a few seconds in a browser. The wasm and the zkey (5.5 MB) load on first use.
Three moves. No fourth.
deposit(token, amount, commitment)
ETH or any ERC-20. The pool checks it actually received the amount (a fee-on-transfer token is refused) and inserts the leaf.
swap(proof, …, target, data)
Spends a note of tokenIn, executes data on target from the pool's own address, measures what came back, keeps the privacy fee, and inserts a new leaf for the output whose key you chose. The proof is bound to tokenOut, minOut, the route bytes and a deadline, so nobody can lift it and swap elsewhere.
withdraw(proof, …, recipient, relayer, fee)
Spends a note and pays any address, minus a fee to the relayer that submitted it. The proof is bound to the recipient, so a relayer cannot redirect it.
What the contract cannot do: change its verifier, its hasher, its routers, its fee or its treasury (all immutable), pause, upgrade, or move a balance without a valid proof. The compile script refuses an artifact with any state-changing function other than the three moves. Targets are the routers fixed at deploy plus any Pons V2 curve the Pons factory recognises for the token being traded.
source · contracts/ZkSwapPool.sol
loading
- Which deposit funded a swap. The proof ranges over every note in the tree with that token and amount.
- Which swap funded a withdrawal. Same mechanism, second hop.
- The wallet behind a trade. On chain the pool is the sender and the recipient of the route.
- The note itself. Neither the secret nor the nullifier ever leaves your device; the pool sees hashes.
- Token and amount at spend time. They are public inputs, because the route needs them. Your anonymity set is the notes of the same token and size: shield round amounts, and prefer sizes others use.
- Timing. A deposit followed one block later by a swap of the same size is a weak link. Wait.
- The gas payer. Until the relayer ships, the wallet that submits the swap or withdrawal is visible. Use one that means nothing, not the one that deposited.
- The trusted setup. The proving key on this site comes from a one-party ceremony run on a laptop. It is fine for a testnet and for trying the flow; it is not what mainnet money should rely on.
Three transactions, from your wallet, in this order.
Poseidon (the hasher, raw bytecode from circomlibjs), then the Groth16 verifier, then the pool with the addresses of both, the Pons factory, the treasury, the fee and the router list. Each address is printed when mined; paste the three into site.js and redeploy the site. Nothing here needs anyone's permission.
- Treasury (immutable)
- Privacy fee (bps, max 100)
- Routers
- KyberSwap · Uniswap V3 SwapRouter02 · Uniswap V2 Router02
- Poseidon
- —
- Verifier
- —
- Pool
- —
The folder ships with the tests. Run them before trusting a word of this page.
npm install sh tools/zk-build.sh # circuit, setup, verifier node tools/zk-test.mjs # real proof, verified, tamper rejected node tools/compile.mjs # pool + verifier, exit check
Files: circuits/spend.circom, contracts/ZkSwapPool.sol, contracts/Verifier.sol, assets/zk/zk.js (the client: Poseidon in BigInt, the tree, the witness), assets/zk/spend_vkey.json.
source · circuits/spend.circom
loading