ZUL://DOCSPRIVACY LAYER 2 — SETTLED ON SOLANA
INDEX
SHIELDED POOL — /docs/shielded/proofs

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

CIRCUITENFORCES
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.
unshieldSpend 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

FLOW
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 value

A 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.
TRUSTED SETUP

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.