09 - Equihash and consensus structures
1. Why this chapter exists
Wallets and node implementations need to agree on the same view of
the chain, even when the wallet does not personally verify proof of
work. The non-shielded but consensus-significant cryptographic
structures live in two small crates:
components/equihash/
implements the proof-of-work verifier, and
zcash_history/
implements the ZIP 221 MMR tree. A contributor who plans to bump the
codebase across a network upgrade (NU) must understand both. By the
end of this chapter you will be able to explain why the Generalised
Birthday Problem chooses the Equihash parameters, identify
the entry point is_valid_solution in
components/equihash/src/verify.rs,
and locate the history-tree append path in
zcash_history/src/tree.rs.
2. Definitions
2.1 Generalised Birthday Problem
Definition (GBP). Given and a parameter , find distinct inputs with
Lemma (Wagner's algorithm). The best known algorithm builds collision lists pairwise and runs in time and memory
Memory is the limiting factor. Equihash chooses so the memory is in the gigabyte range, making custom ASICs less profitable than general-purpose memory-bandwidth hardware.
2.2 Zcash Equihash parameters
Definition (parameters). Mainnet uses , so a solution is a set of distinct 32-bit indices. Testnet uses : much smaller, faster to test.
Definition (the hash function ). Built from BLAKE2b with
personalisation "ZcashPoW",
where is the block header up to the nonce, is the 32-byte nonce, and is a 32-bit index.
Invariant (solution constraints). A 512-index Wagner-style solution must satisfy:
- All 512 indices are distinct.
- The Wagner pairing structure is encoded in the solution order (in pairs, then in pairs-of-pairs, etc., for levels).
- At each level , the two halves of a pair must satisfy a collision condition on a specific 20-bit segment of : the leading bits should match exactly (so the XOR has them cancel).
- At each level the left half has the smaller minimum index, forcing a canonical ordering and preventing permutations of an existing solution into a different one.
2.3 Merkle Mountain Range
Definition (MMR). A Merkle tree variant that supports efficient append: adding a leaf creates one new leaf plus at most new internal nodes. The "mountain range" is the picture of a sequence of perfect Merkle trees of decreasing height that get merged as more leaves arrive.
Lemma (MMR shape). At leaves the MMR has total nodes; the "peaks" are the roots of the perfect subtrees, one per set bit of . The root of the MMR is a hash of the peaks combined.
2.4 Zcash history-tree leaf
Definition (per-leaf data, ZIP 221). For each block:
- The block hash.
- The Sapling commitment-tree root at the block.
- The Orchard commitment-tree root at the block.
- The block's cumulative chain-work.
- Reserved fields for future upgrades.
The hash function combining leaves is BLAKE2b with a per-network-upgrade personalisation; the tree forks at NU boundaries.
2.5 Block-header commitments (NU5)
Definition. Since NU5 the block-header field
hashBlockCommitments is
The chain-history hash binds the MMR root; the auth-data root binds the per-transaction authorising data; the final Sapling root binds the per-block Sapling commitment-tree state.
2.6 Difficulty adjustment
Definition. Zcash uses a Dark Gravity Wave (DGW)-style
retargeting algorithm: the target adjusts based on the average
block time over the last 17 blocks plus a moving exponential
window. The target is encoded as nBits in the block header in
compact form. Expected block interval since Blossom is 75 seconds.
This is implemented in the node, not in librustzcash.
3. The code
3.1 Equihash verification
The public entry point is is_valid_solution:
loading...
It calls is_valid_solution_recursive, which initialises a BLAKE2b
state with the input and nonce, then validates the Wagner pairing
tree:
loading...
Verification is BLAKE2b's worth of work. Finding a solution requires gigabytes of memory and minutes; verifying takes milliseconds.
3.2 Minimal encoding
The solution is encoded as a packed bit-string of the 512 21-bit
indices, so bits bytes. The
"minimal encoding" lives in
components/equihash/src/minimal.rs.
The verifier calls indices_from_minimal to unpack the encoded
bytes into the 512 u32 indices, then validates them.
3.3 Why care, for librustzcash purposes?
You will not implement an Equihash solver in this workspace (the
tromp solver is for testing and is feature-gated). You will use
the verifier to validate block headers if you ever need
consensus-adjacent checks. Wallets do not verify PoW directly; they
trust the node.
3.4 The history tree (ZIP 221)
The history tree is a Merkle Mountain Range committed to in every
block header since Heartwood. The zcash_history crate provides
append and proof verification. The core Tree type:
loading...
append_leaf is what every block adds to the tree. It returns the
new node links (peaks may merge), and the new MMR root is
Tree::root. The per-leaf node data lives in
zcash_history/src/node_data.rs;
the leaf fields are versioned via the Version trait, with V1
(Heartwood) and V2 (Canopy+) variants.
The wallet does not normally use this; full nodes (Zebra, zcashd)
do. We mention it because adding new fields to the history tree
(which has happened at most network upgrades) requires touching
this crate.
3.5 Consensus rules a wallet must respect
We listed these in chapter 02; the consolidated list:
- Anchors must be at least 10 blocks deep (after Sapling/Orchard
activation; specifically
MIN_CONFIRMATIONS = 10). expiry_heightmust be in the future or zero.- The transaction fee must be positive and at least the ZIP 317 minimum (10,000 zatoshi).
- The value balance equation must hold: , with the shielded sides accounted for by the bundle value balances.
- Spends from coinbase outputs must obey coinbase maturity rules ( blocks).
- Coinbase transactions cannot have shielded spends with a value balance other than zero, and cannot have transparent outputs except to a permitted address policy.
- Per-pool nullifier uniqueness within the transaction (caught by the node, not the wallet).
The wallet code in
zcash_client_backend/src/data_api/wallet.rs
and the fee/Proposal pipeline implement these as construction-time
checks.
4. Failure modes
- Skipping the index-distinctness check. Equihash's correctness argument relies on all 512 indices being distinct. Verifier code that omits this check would accept trivially-padded solutions with repeated indices. The recursive verifier walks the indices in order; do not refactor away the distinctness invariant.
- Wrong personalisation for the BLAKE2b state. The
personalisation depends on both and . Hard-coding the
mainnet values in a verifier that is supposed to be
parametric (e.g. for tests) will reject all testnet blocks. The
parameter table is in
components/equihash/src/params.rs. - History-tree leaf-format drift. Each network upgrade may
extend the leaf data. The
Versiontrait enables per-upgrade serialisation; adding a new NU means adding a newVersionimplementation, not mutating the existing one. Cross-version leaves must never be combined under the same root. - Difficulty-adjustment confusion. Wallets that try to estimate
fee timing by reading
nBitsare sensitive to network-upgrade changes in the DGW window. This is not inlibrustzcash; if a contributor adds DGW logic here, it belongs in a node, not a wallet. - MIN_CONFIRMATIONS = 10 invariant. Lowering this constant to accommodate testnet edge cases without isolating the change behind a network parameter is a documented source of consensus drift.
The equihash test vectors in
components/equihash/src/test_vectors/
exercise both valid and known-invalid solutions and run via
components/equihash/src/verify.rs.
5. Spec pointers
- Biryukov, Khovratovich, Equihash: Asymmetric Proof-of-Work Based on the Generalized Birthday Problem, NDSS 2016: the original paper. Section 4 is the algorithm verifier code follows.
- Wagner, A Generalized Birthday Problem, CRYPTO 2002: the underlying combinatorial problem.
- Zcash Protocol Specification, section 7.6.1 (Equihash): the concrete parameter choice and personalisation strings.
- ZIP 221 - FlyClient Proof-of-Work Evidence:
the chain-history MMR
zcash_historyimplements. - ZIP 244 - Transaction Identifier Non-Malleability:
defines
hashAuthDataRootcited in Section 2.5. - ZIP 317 - Proportional Transfer Fee Mechanism: the fee invariant in the wallet-side consensus list.
6. Exercises
- Compute a verification cost. For a mainnet
solution, count how many BLAKE2b compressions
is_valid_solution_recursiveperforms in the worst case. Show the arithmetic. - Identify the validity path. Trace the call chain for a
single-byte mutation of a known-valid solution from
is_valid_solutionto the specificErrorvariant that fires. Cite the file and line. - Modify and test (code change). Under
components/equihash/src/, add a unit test that builds an MMR of leaves usingzcash_history::Tree, computes the root, appends a 9th leaf, and asserts the root changes. The test must run in under one second. - Map the consensus list to code. For each of the seven rules
in Section 3.5, cite the file and function in
zcash_client_backend::data_api::walletthat enforces it (or note "no enforcement; relies on the node").
Answers in the code
- Equihash verifier:
components/equihash/src/verify.rs#L241-L255. - Equihash recursive validator:
components/equihash/src/verify.rs#L221-L239. - History-tree append:
zcash_history/src/tree.rs#L138-L165. - Equihash parameters:
components/equihash/src/params.rs.
7. Further reading
- chapter 10: the wallet pipeline that enforces the wallet-side consensus list.
- chapter 21: in-flight NU7 work that touches the MMR leaf format and consensus rules.
- BCTV-style PoW review: background reading on why ASIC-resistance moved against memory rather than time.