Contracts, trusted setup and verifying a proof
Contracts
There is one pool per asset. Solidity 0.8.26, compiled for Cancun, sources published at /contracts on this site. The pools have no upgrade path and no admin function can touch a note.
| HYPE pool | published here when deployed |
|---|---|
| USDC pool | published here when deployed |
| Groth16 verifier | published here when deployed |
| Poseidon (T3) | published here when deployed |
Sources: LethePool.sol, LetheVerifier.sol, PoseidonT3.sol.
What the owner can and cannot do
- Can: pause new shields, change the per-shield and per-pool limits, set the exit fee at or under the 1% hard cap, and choose the address the fee goes to.
- Cannot: pause withdrawals or sends, reach any note, change a recipient, or upgrade the pool. No owner function moves users' funds, and the limits and the pause apply to new shields only.
The exit fee accrues inside the pool and collectFees() pays it out. A withdrawal never depends on the fee address, so no choice of that address can block an exit. The contracts are unaudited at launch.
The circuit
Transaction(20, 2, 2) in circom 2: two notes in, two out, a Poseidon tree of depth 20, 7 public signals, 12,698 constraints, a join-split in the Tornado Nova design. sum(in) + publicAmount = sum(out), with publicAmount = extAmount - fee.
Trusted setup
Phase 1 is the perpetual powers of tau ceremony run by the Privacy and Scaling Explorations team, 80 contributions (ppot_0080_14, 2^14). Phase 2 is one contribution made for Lethe, labelled lethe-1, with its entropy read once and never stored, followed by a public beacon labelled lethe beacon, built from the text lethe.fi lethe pool v1 08-11-2026.
A Groth16 setup is safe if at least one contributor discarded their randomness. Phase 2 here has one contributor. If you want not to rely on that, verify phase 1 yourself and run your own phase 2; the zkey below is what the pools verify against.
| transaction_final.zkey | f8a59059e1df4f45e47647f898d9ae9d97585fa3724726b068329bba6ff73312 |
|---|---|
| transaction.wasm | 4c6f5b60bc7d74c080d45b70b7cadcd236c023997a4249bc6ef1e062cd5df4ce |
| verification_key.json | 031702e57d0e0409e20ee4e3204662ac0e41a5356b3940a09ddb1ae7a51ba1b5 |
Verifying a proof
The verifying key is served from /zk/verification_key.json, and the proving files are hashed above. With snarkjs and a proof you made:
curl -O {site{"}"}/zk/verification_key.json
snarkjs groth16 verify verification_key.json public.json proof.jsonAnd on chain, read the root and check a nullifier directly:
cast call <pool> "getLastRoot()(uint256)" cast call <pool> "nullifierHashes(uint256)(bool)" <nullifier> cast call <pool> "exitFeeBps()(uint256)"