Zcash

The browser wallet

librustzcash compiled to WebAssembly scans, proves and signs in a Web Worker, and nothing about your notes leaves the device.

umbra-trade.vercel.app
The Portfolio screen of a new wallet in Umbra Trade: Shielded ZEC 0.00, the Deposit, Withdraw and Wallet buttons, and the sync line.
Umbra Trade workflow illustration — not a live balance or transaction receipt.
The Portfolio screen of a new wallet in Umbra Trade: Shielded ZEC 0.00, the Deposit, Withdraw and Wallet buttons, and the sync line.
Umbra Trade workflow illustration — not a live balance or transaction receipt.

Inside the worker

StepWhat happensCode
StartThe 7.9 MB zwallet.wasm loads in a Web Worker; the wallet database lives in an in-memory WASI directorypackages/zwallet/src/core.ts
Create24 words (BIP 39, 256 bits) make one Zcash account with ZIP 32 keys; a new wallet's scan starts 20 blocks below the tipwallet.rs, zwallet/src/constants.ts
SyncEvery 30 seconds, 100 blocks per batch, single-threaded (WASI has no threads), up to 50 full transactions fetched per syncsync.rs, sync.ts
RestNotes that came from a public address are locked 2–6 h by the wallet's own output locksnotes.rs, packages/engine/src/rest.ts
SendA ZIP 321 payment request, ZIP 317 fee, no memo, the outgoing viewing key discarded, change to Ironwoodsend.rs
ProveThe Orchard proving key warms up in the background after the wallet opens; proofs run in the browserprover.rs
SaveThe database is encrypted (AES-GCM 256, a fresh 12-byte IV per write) into IndexedDB after every changeapps/web/src/features/vault/vault.ts

Rules it keeps

  • No sync right after a send. After a broadcast the wallet waits a random 1 to 10 minutes before its next sync, so the gateway never sees a send followed at once by the same wallet's block reads.
  • Sapling is never spent. Its provers are stubs, which keeps about 50 MB of parameters out of the browser.
  • Restores scan from Ironwood. A wallet restored from words scans from block 3,428,143, the Ironwood activation height on mainnet.
  • Reorgs. The wallet rewinds 10 blocks on a broken chain and gives up after 5 tries.

The light client

The wallet reads the chain through six lightwalletd methods: GetLatestBlock, GetTreeState, GetSubtreeRoots, GetBlockRange, GetTransaction and SendTransaction. Every one is an Oblivious HTTP message, because even a block range says something about a wallet (where its scan starts is its birthday). The compact blocks come from ChainSafe's public gRPC-web servers, which only ever see Umbra Trade's gateway.

One pool per transaction

A payment spends Ironwood notes only, with change to Ironwood, so no payment publishes a pool crossing's value beside it. ZEC in the old Orchard pool (a note from before Ironwood, or from a wallet that still builds older transactions) shows on Portfolio as in the old Orchard pool. Move to Ironwood moves it the ZIP 318 way, after you agree to the schedule.

On this page