Rime

A privacy-hardened Unified Address light client for Zcash. Addressing linkability that exists outside the cryptography.

Why Rime Exists

The privacy gap: Zcash light clients leak metadata through multiple vectors:

  • Block range queries reveal wallet age, sync frequency, and transaction density to lightwalletd
  • Selective memo fetching exposes exactly which transactions a wallet received
  • Transaction broadcasting reveals which transactions were sent with linkable timing
  • Connection metadata (IP addresses, timing patterns) enables correlation attacks by both compromised servers and passive network observers

These metadata leaks allow transaction graph reconstruction and timing correlation attacks, revealing exactly what shielded transactions are designed to hide.

ZIP-314 (reserved) identified these issues, and community discussions around it proposed solutions. RIME implements the client-side subset: full memo download, PIR with dummy queries, and Tor integration, plus additional privacy defenses: circuit isolation, bucketing, note density smoothing, and ephemeral/stateless modes.

Reference ZIP-314 analysis

Privacy modes

Privacy features address different adversaries: compromised servers, network observers, or both.

Full-memo privacy Downloads all memos in requested block ranges, preventing servers from learning which transactions are relevant.
cargo run -p rime-cli -- sync --sync-mode full-memo --endpoint https://testnet.zec.rocks:443
PIR with fixed-interval dummy fetches Uses Private Information Retrieval with fixed-interval dummy queries to hide both which memos you fetch and when you fetch them.
cargo run -p rime-cli -- sync --sync-mode pir --pir-server https://pir1 --pir-server https://pir2 --pir-dummy-interval 60s --pir-bucket-size 1000 --endpoint https://testnet.zec.rocks:443
Constant-cost scan Adds dummy decryptions and per-block delays to hide timing patterns during trial decryption, preventing observers from determining how many transactions you received.
cargo run -p rime-cli -- sync --const-cost --min-block-delay 50ms --dummy-decryptions 64 --endpoint https://testnet.zec.rocks:443
Bucketed ranges Rounds sync start/end heights to fixed bucket sizes, preventing servers from inferring your exact wallet birthday or sync patterns.
cargo run -p rime-cli -- sync --bucket-size 1000 --endpoint https://testnet.zec.rocks:443
Tor with circuit isolation Routes all traffic through Tor with circuit isolation to prevent session linking and query correlation.
cargo run -p rime-cli -- sync --tor-only --tor-isolate --endpoint https://testnet.zec.rocks:443
Ephemeral mode Keeps the SQLite wallet and decrypted state in memory only. Nothing persists to disk after exit.
cargo run -p rime-cli -- sync --ephemeral --ufvk <uview> --birthday-height <h> --endpoint https://testnet.zec.rocks:443
Stateless stream mode Streams and prints decrypted notes without creating a database, computing witnesses, or saving checkpoints. Each note is printed then discarded.
cargo run -p rime-cli -- sync --stateless --ufvk <uview> --birthday-height <h> --endpoint https://testnet.zec.rocks:443

Architecture

Receive-only UFVK client with encrypted local state. All privacy defenses execute deterministically, producing identical metadata patterns regardless of note detection outcomes.

CLI + privacy config Pick normal/full-memo/PIR, Tor-only or isolation, smoothing and bucket rounding, and opt into stateless or ephemeral runs. Handles UFVK import and Orchard migration prompts.
Transport layer lightwalletd gRPC (TLS or plaintext) with optional Tor, plus dual-server PIR HTTP driven by a constant-rate scheduler with dummy queries. A local bucket index map binds heights to PIR buckets.
Privacy mechanisms PIR with dummy cadence, full-memo ciphertext hydration, constant-cost smoothing (dummy decryptions and min per-block delay), bucket rounding for range endpoints, and Tor-only with isolation for RPC/PIR.
Sync + integrity checks Trial-decrypt Sapling/Orchard with UFVK; when compact headers are present, run branch/header/PoW checks and recompute the Sapling root locally; bind the Orchard treestate root to the validated header hash at checkpoints/final height; detect reorgs, reset to birthday, and checkpoint pools every 1000 blocks when non-empty.
State handling Encrypted seed (Argon2id + AES-GCM) and UFVK persisted, notes/witnesses/block-hash cache/tree checkpoints in SQLite, full-memo cache kept in-memory, ephemeral keeps the DB in-memory, stateless streams notes without DB or witnesses.
Outputs Balances, history, UA/UFVK export, seed export; receive-only, shielded-only; Orchard migration derives UFVK from the seed then resets to birthday to rescan.

Running Rime

Receive-only UA client. Create or import a UFVK, then pick the sync privacy mode you want.

Init new wallet
cargo run -p rime-cli -- init --network testnet --birthday-height 0
Import UFVK (receive-only)
cargo run -p rime-cli -- import-ufvk --ufvk <uview> --birthday-height <h>
Sync (choose your privacy mode)
cargo run -p rime-cli -- sync --endpoint https://testnet.zec.rocks:443
# --sync-mode full-memo (download all memos)
# --sync-mode pir --pir-server <url1> --pir-server <url2> [--pir-bucket-size 1000] [--pir-dummy-interval 60s]
# --const-cost --dummy-decryptions 64 --min-block-delay 50ms (timing smoothing)
# --bucket-size 1000 (round ranges), --batch N (block batch size)
# --tor-only [--tor-isolate] (Tor), --rpc-timeout 20s --rpc-retries 6 (tune RPC)
# --ephemeral --ufvk <uview> --birthday-height <h> (in-memory DB)
# --stateless --ufvk <uview> --birthday-height <h> (stream notes, no DB)
Balance
cargo run -p rime-cli -- balance [--pool sapling|orchard] [--json]
History
cargo run -p rime-cli -- history [--limit N] [--json]
Keys
cargo run -p rime-cli -- keys [address|fvk|sapling|migrate-orchard]
Seed export (receive-only wallet seed)
cargo run -p rime-cli -- seed

For full usage, privacy modes, and advanced options, see the README:

github.com/tanctl/rime →