Skip to Content
DocumentArchitectureProof system
Architecture

Groth16 over BN254, four circuits, ~15K constraints per side.

zkScatter uses Groth16 proofs over the BN254 pairing-friendly curve (scalar field modulus exposed as FIELD_MODULUS from @zkscatter/sdk/zk). This page maps each circuit to the SDK function that drives it.

Circuits

CircuitWhenPublic inputs (high level)Constraints
depositUser deposits into the poolnew commitment, token, amountsmall (~5K)
authorizeUser signs an orderorder hash, nullifier, claims root, new commitment~15K
claimRecipient claims a payoutclaims root, nullifier, recipientsmall (~3K)
cancelMaker cancels an active orderold nullifier, new commitment, commitment rootmedium (~10K)

All circuits share the same Poseidon-on-BN254 hash, EdDSA-on-Baby-Jubjub signature scheme, and depth-20 incremental Merkle tree.

Domain tags

To prevent hash collisions across roles, every Poseidon use is prefixed with a domain tag:

import { TAG_ESCROW_NULL, TAG_NONCE_NULL, TAG_CLAIM_NULL, TAG_COMMITMENT_V2, } from "@zkscatter/sdk/zk";

These constants are consensus-critical — never recompute them locally.

Proving stack

Proofs run inside a Web Worker so the UI thread stays responsive:

┌─────────────────────────┐ │ React component │ │ ↓ generateXxxProof() │ └─────────────────────────┘ ┌─────────────────────────┐ │ Web Worker (workerRuntime)│ │ ├── circomlibjs (Poseidon, EdDSA)│ │ ├── snarkjs (groth16.fullProve) │ │ └── zkeyCache (IndexedDB) │ └─────────────────────────┘

The first proof of a session pays a one-time cost: ~50–150 ms for the Poseidon table build, plus the .zkey download (cached in IndexedDB via zkeyCache). Subsequent proofs reuse both.

Use warmupPoseidon() early in the page lifecycle to amortize the table build off the critical path.

Circuit assets

Every generateXxxProof accepts a CircuitAssets argument:

type CircuitAssets = { wasm: string | ArrayBuffer; zkey: string | ArrayBuffer; };

Pass URLs (/zk/deposit.wasm) or pre-fetched ArrayBuffers. URLs hit the zkeyCache first; cache miss falls back to network fetch.

Performance budget

Measured on M1 MacBook (Chrome 120, prod build):

OperationColdWarm
Poseidon table build~120 ms0 (cached singleton)
Deposit proof~600 ms~400 ms
Authorize proof~3.5 s~2.8 s
Claim proof~250 ms~180 ms
Cancel proof~1.5 s~1.2 s

Mobile is ~3–5× slower depending on device class. The native prover in mobile/native-prover short-circuits the Web Worker path on iOS/Android for ~2× speedup on authorize.

Instrumentation

Wrap any prove call with timeProve to emit zk-perf:prove events:

import { timeProve } from "@zkscatter/sdk/zk"; const result = await timeProve("authorize", () => generateAuthorizeProof(input, assets), ); // → window.dispatchEvent(new CustomEvent("zk-perf:prove", { detail: { circuit, ms } }))

Drop a listener in your analytics layer to track real-world prover latency.

Last updated on