06 - Keys, addresses, ZIP 32, unified addresses
1. Why this chapter exists
Every wallet operation in librustzcash starts from a seed and ends
at an address. The intermediate machinery is ZIP 32 derivation, the
Sapling and Orchard key trees, and the Unified containers (USK, UFVK,
UA) defined by ZIP 316. A contributor who cannot trace a byte from
UnifiedSpendingKey::from_seed in
zcash_keys/src/keys.rs
through to a bech32m Unified Address will mis-handle privacy-critical
encoding and produce wallets that leak or refuse payments. By the
end of this chapter you will be able to follow the path from a 32-byte
seed through ZIP 32 derivation, the Sapling and Orchard key trees,
the F4Jumble padding step in
components/f4jumble/src/lib.rs,
and the Unified Address encoding in
components/zcash_address/src/kind/unified.rs.
For a per-symbol reference of every key in this chapter () plus their concrete types and code locations, keep chapter 23 open in another tab.
2. Definitions
2.1 ZIP 32 derivation
Definition (extended spending key). A pair where is pool-specific spending-key material and is a chain code. Children at index are derived by
Hardened derivation uses the parent private key and prepends a distinguishing tag; non-hardened uses the parent public key and is forbidden for shielded paths.
Definition (Zcash standard path). All Zcash account-level keys are derived along the hardened path . Coin type is for mainnet and for testnet.
2.2 Sapling key tree
Definition (Sapling expanded spending key). From the 32-byte output of ZIP 32 at the account level :
Then and are Jubjub points; are 32-byte strings.
Definition (Sapling incoming viewing key).
Definition (Sapling diversifier). Indexed by ,
Not every produces a valid Jubjub point; returns on failure and the wallet enumerates until a valid one is found.
Definition (Sapling payment address).
encoded as 43 bytes (11-byte diversifier 32-byte
) and bech32m-encoded with HRP zs on mainnet.
2.3 Orchard key tree
Definition (Orchard key components). From :
Unlike Sapling, Orchard's is a Pallas field element, not a curve point. Feeding a scalar straight into Poseidon is much cheaper in-circuit than first decoding a curve point.
Definition (Orchard incoming viewing key). randomises a Sinsemilla commitment that produces .
2.4 Unified containers
Definition (Unified Spending Key, USK).
containing whichever pool keys are configured. The corresponding Unified Full Viewing Key is
Definition (Unified Address encoding, ZIP 316). Given a set of typed receivers ,
- Concatenate per-typecode TLVs of the form .
- Append a 16-byte HMAC tag over the concatenation under a fixed key.
- Apply F4Jumble to the resulting byte string.
- Bech32m-encode with HRP
u(UAs) oruview(UFVKs).
Definition (F4Jumble). A length-preserving unkeyed Feistel that turns any 1-bit input change into a uniformly random output change. Split the message into where . Then
with and built from BLAKE2b with per-round personalisations. Output is . F4Jumble is its own inverse when the round order is reversed.
Invariant (UFVK sensitivity). A UFVK includes for each shielded pool, so leaking a UFVK reveals every diversified address an account has ever used. UFVKs cannot spend, but they are not "public": they are full-view, not no-spend-public.
3. The code
3.1 The big picture
seed (32 B)
|
ZIP 32 path m / purpose' / coin_type' / account'
|
+--------------+--------------------+-----------------+
| | | |
Transparent Sapling Orchard (TZE future)
ExtendedKey ExtendedSpendingKey SpendingKey
| | |
... Sapling FVK, Orchard FVK,
IVK, OVK, dk IVK, OVK, dk, rivk
| |
diversified address diversified address
The Unified Address is a packaged set of per-pool receivers derived from those keys, encoded so that a casual observer cannot tell which pools are available.
3.2 USK from seed
The top-level entry point is UnifiedSpendingKey::from_seed, which
delegates to the per-pool derivation crates (transparent,
sapling-crypto, orchard):
loading...
The Sapling helper sapling::spending_key follows the ZIP 32
hardened path:
loading...
The ZIP 32 derivation logic itself lives in the external zip32
crate; librustzcash exposes only the per-pool glue.
3.3 USK to UFVK
UnifiedSpendingKey::to_unified_full_viewing_key walks each pool
field and computes the corresponding viewing key:
loading...
The unknown field is the forward-compatibility hook: when a UFVK
is parsed that contains a typecode the current binary does not know,
the typecode plus its data is preserved here so that round-trip
encoding remains byte-exact.
3.4 Unified receivers
A Unified Address contains a set of receivers; the receiver enum
keeps the four standard typecodes plus an Unknown catch-all:
loading...
| Typecode | Receiver |
|---|---|
| 0x00 | P2PKH transparent receiver (20-byte ) |
| 0x01 | P2SH transparent receiver (20-byte ) |
| 0x02 | Sapling receiver (43 bytes: 11 + 32) |
| 0x03 | Orchard receiver (43 bytes) |
Padding with null (0xFFFFFFFF) typecode entries aligns to F4Jumble
break points, hiding which receivers were omitted. The wallet
selects the highest-priority pool both sides support (typically
Orchard > Sapling > P2PKH > P2SH).
3.5 F4Jumble inside the encoder
The Feistel state and the four rounds are tiny:
loading...
Two key observations:
- The split point is , so for any message under 256 bytes the left half is the smaller half plus one byte. This asymmetry is deliberate.
- Inversion just reverses the round order. The
Decoderpath in ZIP 316 callsf4jumble_inv_muton the bytes received from bech32m, then verifies the HMAC, then parses TLVs.
The f4jumble-encoded buffer is what bech32m then turns into the
final u1... address string.
3.6 Transparent layer
The transparent layer uses standard Bitcoin-style BIP-44 derivation:
- Path: .
- secp256k1 keys.
- for P2PKH, .
Encoded in
zcash_transparent/src/keys.rs
and consumed by zcash_keys via the transparent-inputs feature.
ZIP 316 also defines transparent receivers inside UAs: just the 20-byte with a typecode (0x00 P2PKH, 0x01 P2SH); these do not specify a derivation path, since the parent UFVK does.
ZIP 48 (in
zcash_transparent/src/zip48.rs)
defines transparent account-level keys for use inside a UFVK.
3.7 Incoming vs outgoing viewing keys
Two viewing-key flavours per shielded pool:
- Incoming viewing key (): decrypts outputs sent to this account.
- Outgoing viewing key (): decrypts outputs sent from this account (so the sender can see their own outgoing payments without keeping per-output state).
The full viewing key includes both. The dk (diversifier key) is
needed to enumerate one's own diversified addresses.
3.8 Address encoding cheat sheet
| Address kind | HRP | Encoding | Length |
|---|---|---|---|
| P2PKH (mainnet) | t1 (prefix) | base58check | 35 chars |
| P2SH | t3 | base58check | 35 |
| Sprout | zc | base58check | 95 |
| Sapling | zs | bech32 | 78 |
| Unified Address | u | bech32m + F4Jumble | variable |
| Unified Full Viewing Key | uview | bech32m + F4Jumble | variable |
HRPs differ on testnet (typical convention: tm, tn, zt,
utest, etc.). See
components/zcash_protocol/src/constants/mainnet.rs
and testnet.rs.
4. Failure modes
- Non-hardened shielded derivation. ZIP 32 originally allowed non-hardened Sapling derivation. ZIP 316 explicitly disallows it inside the unified container. Code paths that allow it produce USKs whose viewing-key descendants can be inverted to spending keys; any change must keep the hardened-only check.
- F4Jumble length-rounding bugs. Forgetting to include the
16-byte HMAC tag in the F4Jumble input is a documented historical
implementation bug. The padding rule must be applied to the
TLV-plus-HMAC concatenation, not the TLV alone, or the encoding
is not the spec's encoding and external wallets reject it. The
test vectors in
components/f4jumble/src/test_vectors.rscover both directions and the length boundaries. - Diversifier-skip side channels. Diversifier enumeration must
skip invalid points without leaking timing information beyond
"this index did not work".
DiversifyHashfailures are uniform in cost across indices. - UFVK leakage equals address graph leakage. A leaked UFVK reveals every diversified address an account has ever used. Wallet UI must treat UFVKs as sensitive even though they cannot spend. This is a policy invariant, not a code bug per se.
- Wrong reduction. reduces a 64-byte string modulo the curve order. Substituting a 32-byte truncation biases the key distribution and silently breaks unlinkability proofs. The same failure mode applies to for Orchard.
- Forgetting the
unknownfield on round trip. A UFVK parsed with an unknown typecode must preserve the bytes in theunknownfield so re-encoding produces the exact same string. Dropping unknown typecodes breaks future-compatible wallets.
Round-trip tests under
zcash_keys/src/keys.rs
cover USK -> UFVK -> string -> UFVK paths.
5. Spec pointers
- ZIP 32 - Shielded Hierarchical Deterministic Wallets:
the derivation algorithm cited in Section 2.1 and implemented in
the
zip32crate. - ZIP 316 - Unified Addresses and Viewing Keys: defines USK, UFVK, UA, the typecode table, the padding and HMAC rules, and references F4Jumble.
- ZIP 48 - Deriving Transparent UFVKs:
the transparent-account-level key inside a UFVK; implemented in
zcash_transparent/src/zip48.rs. - Zcash Protocol Specification, section 4.2: the Sapling and Orchard key trees including the and reductions.
- BIP 32 - Hierarchical Deterministic Wallets: the parent algorithm ZIP 32 extends.
- F4Jumble specification (ZIP 316 appendix): the Feistel round structure and BLAKE2b personalisations.
6. Exercises
- Decode a UA by hand. Take a mainnet Unified Address, run
bech32m decode on it, then call
f4jumble::f4jumble_invon the bytes. Identify the TLVs and the trailing 16-byte HMAC. Cite the typecodes you found. - Find the hardened check. Locate the assertion in the
zip32crate (or inzcash_keys) that prevents non-hardened derivation on shielded paths. State the file and line and explain what the error message tells a caller. - Modify and test (code change). Under
zcash_keys, add a unit test that builds aUnifiedSpendingKeyfrom a fixed seed, converts it to aUnifiedFullViewingKey, re-encodes to a string and back, and asserts byte-exact equality. The test must fail if theunknownfield is dropped during a round trip; verify by temporarily clearing the field and confirming the assertion fires.
Answers in the code
- USK construction:
zcash_keys/src/keys.rs#L236-L256. - USK -> UFVK:
zcash_keys/src/keys.rs#L280-L290. - F4Jumble core:
components/f4jumble/src/lib.rs#L137-L173. - UA receivers:
components/zcash_address/src/kind/unified/address.rs#L8-L39.
7. Further reading
- chapter 07: how Unified keys become inputs to the transaction builder.
- chapter 08: how and are used at decryption time.
- chapter 23: per-symbol reference for every key in this chapter.