How Zylem Works
Zylem

How Zylem works

From the block header down to every note a wallet scans.

Zylem has three parts. Ring files carry Zcash history, a shared header proof stands in for every historical header, and the client checks both before its wallet scans anything. This page explains each part and what the client has to trust.

The chain of checks

Every check hangs from the block header.

Zcash headers anchor the commitments Zylem uses to authenticate wallet data. Zylem walks from the header down to the notes, so nothing a wallet uses is taken on the server's word.

A tampered block fails at the first check it touches, with a typed reason such as TxIdMismatch or TreeRootMismatch. The wallet only ever sees blocks that passed.

Rings

History travels as Rings, and any host will do.

A Ring holds one epoch of 4,096 blocks: compact blocks, the digests that rebuild their transaction IDs, and the state to start checking from. Rings are named by their hash and rebuilt byte for byte by any builder, so a CDN, a mirror or a friend's server can host them. Hosts cannot alter authenticated data undetected, but they can withhold it or replay older valid data.

4,096blocks per Ring, about 28 hours at NU7's 25-second blocks
~7 MBfor a recent testnet Ring, compressed
Identicalbytes from two independent builds of the same Rings
1 URLan unmodified wallet switches by changing its server address

Ring files

A builder turns blocks from any node into sealed, self-checking files.

a new Ring about every 28 h
4,514,178 4,518,274 4,522,370 4,526,466 sealed Ring sealed Ring this Ring · 4,096 blocks open tail tip Cut every 4,096 blocks from the upgrade's activation height sealed once its last block is 100 deep start block 4,522,370 block 4,522,371 block 4,526,465 after the last block end Start state tree frontiers history peaks from the last Ring header compact block digests header compact block digests … header compact block digests Next header authenticates the last block's tree roots Trailer file hash ring-4522370-4526465-9bd762020e0052b1.zst about 7 MB, named by the hash of its bytes. Digests: a few hundred bytes per transaction that rebuild its ID from compact data (v4 transactions travel whole). Builder A Builder B identical file
Anyone can host a Ring because the wallet re-checks every block in it, and anyone can rebuild one to check a host's copy.

Header proofs

One shared proof replaces historical header verification.

Headers are 1,487 bytes each, gigabytes since NU5. Zylem proves the header chain in the SP1 zkVM instead: leaves of 512 headers each check every rule with full Equihash, and recursive aggregation joins them into one proof that commits to each block's record. A wallet checks that proof and the Rings it downloads, and never fetches a historical header.

Measured on an RTX 5090 and an M2 Pro laptop. The mainnet cost is a projection from those runs.

1.88/sheaders proved per second with Equihash on one RTX 5090
~$323to prove every mainnet header since NU5, at $1.19 an hour
0.8 MBa compressed SP1 proof (1.27 MB measured) after zstd; the size is constant
No setupno trusted setup behind the proof wallets verify

Header proof

GPU provers replace every historical header with one proof.

a new leaf about every 3.6 h
A leaf proves, for each header, in SP1 link · encoding · Equihash · target difficulty rule · median time · work and commits the block's record What every proof states first and last block, difficulty context total work, Equihash checked a commitment to every block's record History proof headers 1–2,048 append, 2.2 s New history proof headers 1–2,560, same size Join checks they meet Join checks they meet Leaf1 to 512 Leaf513 to 1,024 Leaf1,025 to 1,536 Leaf1,537 to 2,048 New leaf2,049 to 2,560 Headers at least 100 blocks deep, in leaves of 512 counted from the upgrade height. Leaves are independent, so many GPUs prove them at once: about 4.5 minutes per leaf on one RTX 5090.
A join is itself a proof: SP1 verifies the two child proofs inside it, so the history proof never grows, however many leaves it covers.

Verification

The client checks everything before the wallet scans it.

Built by anyone, independently checked by clients. The wallet, or the Zylem proxy beside it, starts from what it ships with and works outward.

Verification

The wallet, or the proxy beside it, checks everything before scanning it.

every sync
SHIPPED IN THE APP FROM ANY HOST Trust anchor program keys checkpoint block minimum chain work History proof verified in 37.6 ms gives the last block and the records commitment Ring records bound to the proof with a few hashes; give each block's roots Ring contents transaction IDs rebuilt, hashed to the Merkle root; tree states reproduced Scanner verified data only PAST THE PROOF its last block Newest headers checked here: Equihash, difficulty, time Tip policy minimum work, confirmed by two or more sources Each box authenticates the next: the wallet starts from what it ships with and never takes a server's word. A failed check rejects that data with a reason, so the scanner never sees it.
Two paths reach the scanner: history through the proof and the Rings, and the newest blocks through direct checks and the tip policy.

No trusted Ring builder or proof producer

Anyone can run a builder or a prover. A wrong Ring or a wrong proof fails verification on the wallet's side, so nobody has to vouch for them.

Builders keep each other honest

Builds are deterministic. Two independent builders publish the same Ring hashes, and any difference is visible at once.

Cheap to serve

Static files on any CDN. Serving more wallets adds bandwidth, not nodes, databases or per-user servers.

Pay once, then pennies

Proving every mainnet header since NU5 is projected at about $323 of GPU time. Keeping up is about $0.30 for a short GPU run every 12 hours.

Light for wallets

One proof of about 0.8 MB replaces 1,487 bytes of header per block, gigabytes since NU5. Wallets download only the Rings they need.

Natively verified live tail

Proofs cover only blocks at least 100 deep. Ordinary reorganizations touch only the newest blocks, which the wallet checks itself.

Rings are built and proved on testnet today, and the live demo verifies them in your browser. Mainnet costs are projections from these runs.

Trust model

What you still trust, and what you no longer have to.

You trust

  • A checkpoint block and the minimum chain work shipped with it
  • The proof system, and the published programs that define the rules
  • That most Zcash hashpower is honest

You no longer trust

  • The light-wallet server you sync from
  • Ring hosts, mirrors and CDNs
  • Whoever produced the header proof

Servers and hosts can still withhold or delay data, or replay old data that is valid. The tip policy limits that: the newest blocks must carry enough work and agree across independent sources, or the wallet sees an error instead of a weak chain.

Measurements

Where each number comes from.

Every figure on these pages comes from a run recorded in the Zylem repository. Mainnet costs are projections from testnet runs.

Build it yourself

Build Rings, prove them, verify them.

Anyone can run each step, and no client has to trust whoever did. Testnet commands below; mainnet takes --network main. The full guide is docs/build.md in the repository.

1 · Build Rings from a node

Needs Rust and a Zakura or zcashd node's JSON-RPC. A pruned Zakura node works: the first build bootstraps once from a light-wallet server for blocks the node has pruned. Rings land in data/testnet as plain files for any static host, and two builders produce byte-identical files.

1 Build every sealed Ring and the open tail
cargo build --release
./target/release/zylem-build backfill --rpc http://127.0.0.1:18232/ \
    --lwd https://testnet.zec.rocks:443 --out data/testnet --finality 100
2 Check them as a client does, then host them
./target/release/zylem-build verify data/testnet
./target/release/zylem-build serve --out data/testnet --rpc http://127.0.0.1:18232/ --finality 100

2 · Prove the headers in SP1

Needs SP1 6.9.0 (sp1up) and, for real proofs, an NVIDIA GPU with about 26 GB of memory: an RTX 5090 proves a leaf of 512 headers with Equihash in about 5 minutes. Leaves are independent, so any number of GPUs share the work, and every step is safe to rerun.

1 Cut the chain into leaves of 512 headers, in 4 shards
./target/release/zylem-build jobs --rpc http://127.0.0.1:18232/ \
    --activation 4465026 --shards 4 --out data/jobs/nu7
spikes/header-proof/pipeline/pack.sh data/jobs/nu7
2 On each GPU machine: prove a shard
zylem/spikes/header-proof/pipeline/setup.sh
zylem/spikes/header-proof/pipeline/run-shard.sh 0
3 Join the shards into one history proof, and wrap it for contracts
zylem/spikes/header-proof/pipeline/join.sh ~/out ~/out/shard-0.proof ~/out/shard-1.proof \
    ~/out/shard-2.proof ~/out/shard-3.proof --groth16
4 Publish the proof, its block records and the Ring bindings
spikes/header-proof/sp1/target/release/export-proof ~/out/history.proof data/proofs/history
./target/release/zylem-build publish --jobs data/jobs/nu7 --history data/proofs/history \
    --vks ~/out/vks.json --rings data/testnet --groth16 ~/out/history.groth16.bytes --out data/publish/nu7

With the program registry (generation 1), set PROGRAMS=programs/testnet before the pod scripts and publish with --registry programs/testnet. Without a GPU, SP1_PROVER=mock runs the same flow and checks every claim.

3 · Verify

WhereUseChecks
An existing walletzylem-proxy, a drop-in light-wallet serverEvery Ring from the checkpoint; the wallet only changes its server address
Browsers and phonesThe WebAssembly client in spikes/header-proof/wasm-verify (1.8 MB)RingFile.verify() for a whole Ring; verify_history or verify_history_v2 for the 1.27 MB proof; verify_groth16 for the 356-byte proof; check_all_records and verify_binding to match a Ring to a proof
Contracts on EVM chainsSP1's Groth16 verifier for circuit v6.1.0: SP1VerifierGroth16.sol from succinctlabs/sp1-contractsverifyProof(programKey, publicValues, proof) with Zylem's aggregation key: 303,762 gas in a local EVM. Not deployed on a chain yet
1 Point a wallet at the proxy
./target/release/zylem-proxy --rings http://127.0.0.1:8090 \
    --headers-rpc http://user:pass@127.0.0.1:18232/ \
    --upstream https://testnet.zec.rocks:443 \
    --checkpoint "$(jq -r .checkpoint checkpoints/testnet.json)"
zcash-devtool wallet -w my-wallet sync --server 127.0.0.1:9067
2 Build the WebAssembly client (needs wasm-pack and LLVM's clang)
cd spikes/header-proof/wasm-verify
wasm-pack build --release --target web --out-dir pkg-web
3 Check the 356-byte proof with SP1's contract in a local EVM
cd spikes/header-proof/onchain && forge test -vv

Program keys: aggregation 0x007d30f1…876f34cb, leaf 0x00b1908c…85b8daa0 (generation 0); generation 1 in programs/testnet/registry.json. Each path trusts the cryptography, a checkpoint and honest-majority hashpower; proofs add SP1 and these programs, and the 356-byte form adds SP1's Groth16 setup.

Technical details

Tip policy, inclusion proofs and other chains.

Tip policy

Valid proof of work alone is not enough: Zcash adjusts difficulty every block, so a private fork can grow cheaply. The proxy presents a tip only when the chain after the checkpoint carries the shipped minimum work, at least two independent sources confirm it, and no verified source shows more blocks beyond it or a heavier fork. Headers dated more than two hours ahead are rejected, and a long silence raises a stale-tip alarm.

Inclusion and absence

The proof commits to every block's record, including its Merkle root, so a transaction's inclusion can be shown with its Merkle path, its record's path and the history proof. Absence is checkable too: a verified Ring authenticates every transaction in each block, so a client holding the Rings for a range can confirm a transaction or nullifier did not appear there. What does not exist yet is a compact proof of absence that could be handed to a contract.

What cross-chain use still needs

The first piece works: SP1's v6.1.0 verifier contract accepts Zylem's 356-byte Groth16 history proof in a local EVM for 303,762 gas, and an example contract reads the claim and stores the proven block hash for 334,361 gas (spikes/header-proof/onchain). Nothing is deployed on a chain yet. The rest is still to build. A contract cannot ask independent sources for the tip, so it needs a freshness rule, such as accepting only facts a set depth below a proven tip that meets a minimum work, and someone who keeps the on-chain client updated. Wrapping runs on the GPU prover, once per history proof. Checking v5 and v6 transaction IDs on-chain means ZIP 244's BLAKE2b hashing. Facts about a shielded payment's amount or recipient need selective disclosure.