Proofs & circuits
Every private operation carries a Groth16 proof over BN254. The circuits are written in circom 2; proving happens in the user's browser via snarkjs (WASM); verification happens in the node in native Rust via the arkworks stack. One proof system, three implementations, pinned together by cross-tests.
The circuits
| CIRCUIT | ENFORCES |
|---|---|
| transfer (2-in / 2-out join-split) | Merkle inclusion of both input notes (depth 26), ownership (secret key → owner), nullifier correctness, asset-id equality across all inputs and outputs, per-asset value conservation, and an optional public fee output. |
| unshield | Spend note(s) to a public recipient and amount: same inclusion/ownership/nullifier constraints, with the exit value and recipient as public inputs the node pays out from the vault. |
Shield needs no proof. A deposit is public by nature — the public inputs bind the new commitment to the deposited value, and the node checks the arithmetic directly.
Proving in the browser
wallet (browser) node (Rust)
---------------- -----------
build witness from local notes
prove with snarkjs (WASM, seconds) → parse instruction
verify Groth16 (arkworks, native)
check nullifiers, append commitments
move vault valueA 2-in/2-out Groth16 circuit proves in seconds-scale time in WASM — acceptable for an interactive wallet. If it ever isn't, the fallback is a native prover sidecar, not a protocol change.
Cross-language pinning
The dangerous part of a multi-language zk stack is silent drift: a proof that verifies in snarkjs but not in arkworks, a Poseidon with different round constants, an encoding that disagrees on endianness. The repository treats these as first-class tests:
- A snarkjs-generated proof is verified by the node's arkworks verifier in a Rust test.
- The TypeScript SDK's instruction encoding is byte-compared against the Rust types.
- The L1 program's SMT hashing is pinned to the node's by shared constants and a cross-test.
Groth16 needs a circuit-specific setup. ZUL's keys come from the public Hermez powers-of-tau ceremony plus a local phase-2 — explicitly dev-grade and documented as such. A compromised setup threatens soundness (forged proofs → theft), not zero-knowledge (privacy survives). A production ceremony is a launch-blocking roadmap item, tracked honestly in Trust model territory.