Skip to main content

Crate and Module Map

1. Why this chapter exists

The crate has no CONTRIBUTING.md, no architecture document, and 38 Rust source files clustered into 9 named modules. A new contributor who clones it cold has to derive the module graph from scratch by reading lib.rs and grepping for re-exports. This chapter does that work once. Every later chapter cross-references it: "the value commitment lives in value::ValueCommitment and is consumed by the Spend circuit in circuit::expose_value_commitment."

Concretely: by the end of this chapter you should know which file to open for any of the six load-bearing concepts (keys, notes, values, commitments, circuits, bundles).

2. Definitions

Definition 1.1 (Sapling bundle). A Sapling bundle is a tuple B=(S,O,vb,auth)B = (\mathbf{S}, \mathbf{O}, v_b, \mathsf{auth}) where S\mathbf{S} is a vector of spend descriptions, O\mathbf{O} is a vector of output descriptions, vbZv_b \in \mathbb{Z} is the net value transferred out of the shielded pool, and auth\mathsf{auth} is the authorisation (binding signature plus, per spend, a spend authorisation signature and a Groth16 proof). In code this is bundle::Bundle<A, V>.

Definition 1.2 (authorisation marker). The phantom type A in Bundle<A, V> tags the state of the authorising data: from EffectsOnly (no proofs, no signatures, used by light clients inspecting effects) through several InProgress<P, S> states inside the builder to Authorized (full proofs and signatures, ready for consensus). The Authorization trait associates a SpendProof, OutputProof, and AuthSig type with each marker.

Invariant 1.3 (one anchor per bundle). Every spend in a single bundle references the same Merkle anchor; the builder rejects heterogeneous anchors at construction time, and the circuit re-derives the anchor from the witnessed authentication path so the verifier re-checks it.

3. The crate root

The library's module set is fixed in lib.rs. Two of them are gated behind the circuit feature, which is on by default but is turned off by no_std consumers that only need to verify or construct effects.

src/lib.rs
loading...

The re-export block at the bottom of lib.rs (pub use ...) is the "true" public surface most users touch: PaymentAddress, Bundle, the key types, Note, Nullifier, Anchor, CommitmentTree, MerklePath, Node, NOTE_COMMITMENT_TREE_DEPTH, plus BatchValidator and SaplingVerificationContext from the verifier module when circuit is on.

4. Module-by-module breakdown

4.1 Primitives (frozen)

These modules implement the primitives the Zcash protocol spec names outright. They are essentially frozen; expect to read them, not to modify them.

4.2 Data types (read constantly, rarely modified)

4.3 ZIP 32 and note encryption

4.4 Circuit and prover (feature-gated)

These three modules are behind feature = "circuit":

4.5 Transaction assembly

4.6 Re-exports at the crate root

src/lib.rs (re-exports)
loading...

Anything not in that re-export block is reachable only via sapling_crypto::module_name::Type. The crate makes that explicit by leaving mod address, mod spec, mod tree, mod verifier private at the crate root.

5. Failure modes

  • Importing a deprecated path. The crate has been moved out of zcash_primitives (see CHANGELOG entry for 0.1.0); some external documentation still refers to the old paths (zcash_primitives::sapling::*). If you see those, treat the external doc as stale. Caught by: nothing automatic in this workspace; downstream crates discover this at compile time.
  • Using circuit::Spend without the circuit feature. The module is gated behind #[cfg(feature = "circuit")]. Without it, none of the Groth16 types compile. Caught by: the build-nostd CI matrix (wasm32-wasip1, thumbv7em-none-eabihf) which builds the synthetic crate --no-default-features.
  • Confusing pedersen_hash (out-of-circuit) with circuit::pedersen_hash (in-circuit gadget). They are named identically but live in different modules and have different signatures. The first takes an iterator of bool; the second takes a bellman::ConstraintSystem and an iterator of boolean::Boolean. Caught by: the type system.

6. Spec pointers

  • Zcash Protocol Specification, sec. 4.1.6 (Sapling) gives the protocol-level reading of every module above. This is the document the in-source // comments most often cite.
  • The crate's own README notes the no_std constraint: downstream no_std consumers must enable lazy_static's spin_no_std feature.

7. Exercises

  1. Identify the smallest entry point. Open src/lib.rs and list every symbol re-exported at the crate root. For each, answer: which module file does it live in, and is it visible without the circuit feature? Compare your list with the re-export block above.
  2. Map module dependencies. Grep the source for use crate:: and use super:: statements. Draw the resulting dependency graph by hand. Hint: keys, note, and value sit at the bottom; builder and pczt sit on top of everything else.
  3. Add a missing accessor. bundle::Bundle has accessors for shielded_spends, shielded_outputs, value_balance, and authorization, but not for the total count of spends or outputs. Add pub fn num_spends(&self) -> usize and pub fn num_outputs(&self) -> usize on Bundle<A, V>. Add a unit test that builds an EffectsOnly bundle and asserts both counts. Run cargo test --all-features and verify the test passes.

Answers in the code. For exercise 1, the re-export list is in src/lib.rs lines 49-68. For exercise 3, the Bundle::shielded_spends and Bundle::shielded_outputs accessors already return slices, so the implementation is self.shielded_spends.len() / self.shielded_outputs.len().