zebra-chain: The Data Model
Why This Chapter Exists
Every other crate depends on zebra-chain. If a type here is wrong, every downstream invariant is suspect. The chapter is also the friendliest entry point because it is purely synchronous: no Tokio, no Tower, no I/O. By the end you should be able to read a raw transaction byte string and tell which version, which Sapling/Orchard bundles, and which transparent inputs and outputs it contains.
zebra-chain is the sync-only crate that defines every consensus-
critical data type Zebra manipulates. No async, no Tokio, no Tower.
Everything here is either:
- a Zcash data structure (block, transaction, address, note, key, commitment, tree, etc.),
- a serialization concern (the Zcash-flavored Bitcoin format),
- a parameter table (network, upgrade, subsidy, checkpoint), or
- a numeric type with consensus-relevant invariants (amount, height, value balance, work, difficulty).
Start with zebra-chain/src/lib.rs for the module map. The crate is
declared with recursion_limit = "256" because of bitvec macros.
Module Tour
Each item below maps to a directory under zebra-chain/src/.
Serialization
serialization/ defines the Zcash binary wire format. Zcash inherits
Bitcoin's CompactSize varints, little-endian fixed-width integers,
length-prefixed vectors, and SHA-256d hashes. Read this module
first; almost every type implements ZcashSerialize/
ZcashDeserialize from here.
Important: any deserialization that consumes attacker-controlled bytes
must use TrustedPreallocate (also defined in this module) to bound
allocation size. This is a security invariant called out in
AGENTS.md.
Parameters
parameters/ defines Network (Mainnet, Testnet variants), the
NetworkUpgrade enum (Genesis, BeforeOverwinter, Overwinter,
Sapling, Blossom, Heartwood, Canopy, Nu5, Nu6, Nu6_1,
Nu7, plus a gated ZFuture), activation height tables for mainnet
and testnet, consensus branch ids, subsidy parameters, checkpoint
data, and the Testnet configuration parameters used by Regtest.
network_upgrade.rs is the source of truth for the chain of network
upgrades. Variants must be ordered by activation height; the trait
implementations rely on Ord.
The testnet.rs submodule lets you build a custom testnet
configuration (used for Regtest and private testnets), so it is the
right place to learn what parameters are actually tunable per
network.
Block
block/ defines Block, Header, Hash, Height, the Merkle root
helper, the genesis hash table, and block commitment. The
Commitment type encodes which block header commitment scheme is in
use for a given height (pre-Sapling has none, then Sapling tree root,
then chain history root, then a hash of treestate-and-history per
NU5).
The serialize.rs here is critical: it defines the block header on-
disk and on-wire layout, including the 1344-byte Equihash solution.
Transaction
transaction/ defines the union type Transaction with variants for
versions 1, 2 (Sprout), 3 (Overwinter), 4 (Sapling), and 5 (NU5+).
v6/NU7 lives behind the nu7 cfg flag. Each variant carries its own
mix of transparent inputs/outputs, Sprout JoinSplits, Sapling Spends
and Outputs, and Orchard Actions.
Read in this order:
transaction.rsfor the type itself.serialize.rsfor the on-wire format. v5 uses a different layout from v4, including the per-pool separation of authorization and non-authorizing data.txid.rsandauth_digest.rsfor ZIP-244 transaction id and authorizing data digest computation (NU5+).sighash.rsfor the signature hash computation. v4 uses a v4 sighash; v5 uses the ZIP-244 sighash, which is also exposed for transparent signers via theSigHashertype.hash.rsfor the boundHashtype and Display order conventions.lock_time.rs,memo.rs,joinsplit.rs,builder.rs,unmined/for the rest.
Transparent
transparent/ defines the Bitcoin-style transparent pool: P2PKH and
P2SH addresses (address.rs), the script bytes wrapper
(script.rs), opcodes (opcodes.rs), and the UTXO type
(utxo.rs). Actual script execution is delegated to zebra- script (FFI to libzcash_script). This module only models the bytes
and addresses.
Sprout, Sapling, Orchard
Each pool gets its own module with the same shape:
keys.rs: spending key, viewing key, payment address types.note.rs: the note type (value + recipient + randomness).commitment.rs: note commitment (Pedersen for Sprout/Sapling, Sinsemilla for Orchard).tree.rs: the commitment tree (Sprout uses incremental SHA-256; Sapling uses incremental Pedersen; Orchard uses incremental Sinsemilla). Wrapsincrementalmerkletree.- pool-specific transfer types:
joinsplit.rsfor Sprout,spend.rs/output.rsfor Sapling,action.rsfor Orchard. shielded_data.rs: the per-tx pool bundle as it appears in a Sapling v4 or v5 transaction, or an Orchard v5 transaction.
sinsemilla.rs under orchard/ exposes the Sinsemilla hash. It is
the only Sinsemilla call site inside zebra-chain; everything else
calls into the orchard crate (ECC).
The internals of the proofs themselves (Groth16 for Sapling, Halo2 for Orchard) are not built here; only the witness types and verifying inputs are.
Primitives
primitives/ is the cross-pool primitives module. It contains:
address.rs: unified types and routing.byte_array.rs: helpers.proofs/: opaque proof byte types (Groth16, Halo2). The verifier for these lives inzebra-consensus/src/primitives/.zcash_history/: chain history tree (ZIP-221).zcash_note_encryption.rs: note encryption wrapper around thezcash_note_encryptioncrate.zcash_primitives.rs: conversions to/from thezcash_primitivesrepresentation, used wherever Zebra hands a transaction to ECC code (for example to compute a sighash via ZIP-244 inlibrustzcash).
Work and Difficulty
work/ defines:
difficulty/: the compactnBitsrepresentation, theWorkscalar, andExpandedDifficulty. The crate-level constants for PoWMedianBlockSpan and friends are here too.equihash.rs: a thin wrapper around theequihashcrate, with Zcash's(n=200, k=9)parameters. Solution length is 1344 bytes.u256.rs: a 256-bit unsigned big-endian integer used in work comparisons.
History_tree
history_tree/ is the chain history MMR introduced in Heartwood
(ZIP-221). Each block commits to the root of this tree, so the
history tree is computed and stored as part of the state.
Value_balance, Amount
amount.rs and value_balance.rs enforce that values are within
constructed bounds. Amount<C> is parameterized by a
Constraint type so that pool inputs are typed differently from
pool outputs (NonNegative vs NegativeAllowed). Addition and
subtraction return Result so overflow is propagated explicitly.
This is one of the most important examples of "use the type system
to encode consensus invariants" in Zebra. Read amount.rs and the
corresponding tests carefully.
Chain_tip and Chain_sync_status
chain_tip.rs defines the ChainTip trait that lets components
above the state crate observe the latest tip without coupling to it.
Various tip observers (latest tip block, tip change watcher) are
implemented here.
chain_sync_status.rs exposes a separate trait for sync status
(close-to-tip vs far-from-tip). Used by the inbound and mempool
services to decide whether to participate in gossip.
Things to Internalize From zebra-chain
- the type-system encoding of consensus invariants (Amount, ValueBalance, Height, Hash with byte-order display, Constraint newtypes).
- the wire format vs display order convention. Hashes, txids, and block hashes are stored in internal byte order, displayed in reverse. Bugs hide in this gap.
- the per-network-upgrade variant pattern in
NetworkUpgradeandTransaction. - the proptest infrastructure: every consensus-relevant type
implements
Arbitrary(gated onproptest-impl). This is what makes property tests across other crates possible.
Suggested Exercises
- find the activation height of every network upgrade on mainnet and
on the default testnet without reading the comments. Hint: the
tables are in
parameters/constants.rs. - given a serialized block hex string, sketch how you would parse it
into a
Block. What module decides whichTransactionvariant to construct? - find the place that wraps Sinsemilla and the place that wraps
Pedersen. Why does one live in
orchard/and the other insapling/? - open
transaction/sighash.rsand trace the v5 sighash computation back intozcash_primitives. Where exactly does Zebra hand control to ECC code?
Spec Pointers
- Zcash protocol spec sections 7.1 (transaction format) and 7.6 (block format).
- ZIP 225 (transaction format v5).
- BIP 144 (witness serialization), referenced by Zcash transparent inputs.
Exercises
- Find the
ZcashSerializeimpl forTransactionand trace which fields are written for each version. Cite the file and line. - Build a v5 transaction with one Sapling spend and one Orchard action and serialize it round-trip. Where do the bundle digests live?
- Add a property test that round-trips a randomly generated
transparent::Inputand confirms equality. Run it withcargo test -p zebra-chain.