I just published my thoughts on Zashi on X and I wanted to go a little bit deeper into some of these subjects:
Wallet syncing times: I noticed that Zashi uses the wallet birthday height as the syncing starting point.
a) Does Zashi speak to a data provider for all the shielded transactions (e.g. a full node / indexer operator)?
b) If so, does Zashi use any light client that verifies that the data provider is including all shielded txs in history, and not withholding any data?
c) is the team working on any other ways to make the syncing faster?
Sapling set vs. Orchard set / Privacy fragmentation: A couple of years ago I made this diagram for another blogpost:
At the time of writing, the Sapling shielded set was significantly larger than the Orchard set. I haven’t checked yet the sizes of Orchard, since the boom in ZEC usage in 2025. By using Zashi I realized that the default is Orchard, so I assume there’s been a substantial growth in Orchard.
a) My question: is anyone working on a way of merging the shielded sets, i.e. a way to migrate security and privately the volume in Sprout and Sapling to Orchard.
Thanks for the X article! Will digest it. Just to quickly chime in on one aspect:
Zashi connects to lightwalletd servers (hosted independently by zec.rocks, but also with the option to connect to a custom one). lightwalletd servers are blind to the transactions that belong to any given wallet, so I don’t see the possibility of maliciously withholding data.
I’m sure someone can opine on this more fully, but you also might appreciate the insight contained in the light wallet threat model here:
This was answered above. The protocol used is specified in ZIP 307 (which needs updating for some of the subsequent updates to the protocol but is still broadly correct), and the current protobuf definitions for the protocol are here:
Zashi verifies that shielded transactions are not being omitted by computing the note commitment tree itself. More specifically:
When created, the wallet initializes its local tree state as of the birthday height, using checkpointed treestates stored in the SDKs (Swift SDK, Android SDK).
During syncing:
The wallet fetches (or updates) the list of depth-16 subtree roots from the lightwalletd server (CompactTxStreamer.GetSubtreeRoots).
The wallet requests ranges of CompactBlocks from the lightwalletd server (CompactTxStreamer.GetBlockRange), along with the tree state as of the start of each range (CompactTxStreamer.GetTreeState).
The fetched tree state at the start of the range is inserted into the wallet’s local tree state. If this is incompatible, an error is returned.
Every note commitment in each CompactBlock is inserted contiguously and in-order starting just after the fetched tree state. If this results in an incompatibility, an error is returned.
During spending:
The wallet selects notes.
The wallet fetches witness paths from its local tree state for the selected notes, and selects a corresponding anchor.
The wallet builds and submits the transaction. If its anchor does not exist in the chain, the submission will fail.
If a lightwalletd server withheld shielded transactions from history, it would result in Zashi inserting leaves into the tree at their incorrect positions (because they are inserted contiguously), producing different hashes at inner nodes and thus a different anchor. If the lightwalletd server did not also fake the subtree roots, this would be detected immediately. If the lightwalletd server did fake the subtree roots, then they are necessarily faking the entire history and would need to do so permanently going forward for this user in order to not trigger errors during scan. However, this kind of attack is useless if the user is expected to still interact with non-attacked others, because transactions created by this user would not be valid on the main chain, and thus either the user would see their tx rejected, or (if the user is under an Eclipse attack and the lightwalletd server pretends their tx submission succeeded) their counterparty would never see a corresponding transaction (unless they were also being attacked and under the Eclipse, but we can presume that would not be the case for e.g. exchanges that run their own full nodes and do not use light wallet servers).
Additionally, it would be possible to include more data in the CompactBlocks themselves to make them self-authenticating (namely, including FlyClient proofs for block validity, full block headers, and either txids of missing transactions or Merkle paths from included transactions to the block header, as well as hashes for missing data subtrees pruned from transactions). This would however significantly increase bandwidth costs (in this issue I calculated the cost of just full block headers as 4.4 GiB, vs 251 MiB for the current pruned headers, and the rest of the data would increase this further).
The main two reasons that Zashi (and all wallets using the Swift or Android SDKs) sync is currently slow are both historic:
Due to design constraints in 2019, the SDKs were built to do all network communications in the native language (Kotlin / Swift). This incurs very significant costs for FFI traversal (for example, we open/close a connection to the wallet’s SQLite database on each FFI call).
We have agreement that these design constraints no longer apply, and we could move the entire sync engine and its network calls over to Rust. Doing so requires non-trivial engineering work, but it’s something I’m personally interested in.
Due to sandblasting, scanning blocks in the height range of approximately 1,700,000 to 2,000,000 is very slow. We try to fetch blocks in larger ranges to both improve download speeds and enable batch scanning, but due to the first point above, the current architecture means that we can potentially block inside an FFI call for very long periods of time if we don’t keep block ranges short in this region (IIRC we process 100 blocks at a time here, vs 1000 blocks at a time elsewhere).
Fixing the first point is the primary thing that would help here.
We’ve talked about inspecting the sandblasting region to develop specific targeted filters that only match known-sandblasting transactions, so we can skip trial-decryption of them (we did not implement basic filters at the time like some other wallets because the proposed filters would have blocked legitimate transactions, and partially re-scanning the chain for missing transactions increases the likelihood of state corruption issues). Note however that we still need to pay the cost of note commitment tree updates for these transactions, which is a significant part of the sync cost.
The main effort for improving scaling and performance beyond the current protocols is Tachyon.
Indeed. This is the current distribution of ZEC across the transparent and shielded pools:
No, because this would break a security property that the turnstile is providing. A new shielded pool has been created each time there has been a significant change in the trust assumptions associated with it, specifically regarding the cryptography securing the pool and enforcing consensus rules. The turnstile between pools (which requires amounts to be revealed, but not senders or recipients) ensures that if the security properties of a particular pool were to be violated (e.g. if the counterfeiting bug in the Sprout pool had ever been exercised), the other pools would be insulated from it (because the value of a given pool cannot go negative, meaning it is not possible to cause inflation of the overall ZEC supply).