Skip to main content

23 - The complete key catalog

1. Why this chapter exists

Every chapter of this course names keying-material symbols (ask,nsk,ak,nk,ivk,ovk,dk,esk,epk,rcm,rcv,α\mathsf{ask}, \mathsf{nsk}, \mathsf{ak}, \mathsf{nk}, \mathsf{ivk}, \mathsf{ovk}, \mathsf{dk}, \mathsf{esk}, \mathsf{epk}, \mathsf{rcm}, \mathsf{rcv}, \alpha, and so on). A reader who needs the exact derivation, type, or code location for any of these should not have to recover it from prose. This chapter is the authoritative reference: every Zcash key is listed with its domain, derivation formula, role, and the source file that defines its Rust type. It also points at the related catalog of circuit clauses in chapter 24 and at chapter 06 for HD derivation.

2. Definitions

This chapter assumes the cryptographic primitives defined in chapter 03 (groups, fields, PRFs, commitments, signatures). Specific to this chapter:

Definition (Field notation). Fq\mathbb{F}_q denotes a prime field of order qq. We use J\ell_J for the Jubjub subgroup order, rr for the BLS12-381 scalar field (which equals the Jubjub base field), qPq_P for the Pallas scalar field, pPp_P for the Pallas base field. The Pallas and Vesta cofactors are 11; the Jubjub cofactor is 88.

Definition (Generator notation). GG with a superscript (GakG^{\mathsf{ak}}, etc.) denotes a fixed generator of the relevant prime-order subgroup, derived deterministically from a personalisation string and a hash-to-curve construction.

Definition (Scalar mul). [x]P[x]P denotes scalar multiplication of point PP by scalar xx.

Definition (Canonical encoding). reprT()\mathsf{repr}_T(\cdot) means "canonical encoding into TT", e.g. reprFr\mathsf{repr}_{\mathbb{F}_r} of a Jubjub point is its uu-coordinate encoded as a little-endian field element.

Definition (To-scalar / to-base reduction). ToScalar(s)\mathsf{ToScalar}(s) for a 64-byte string ss means "interpret ss as a little-endian integer and reduce modulo the relevant scalar field order". ToBase(s)\mathsf{ToBase}(s) is analogous for the base field.

Definition (Expand-from-seed PRF). The Sapling and Orchard expansions use

PRFskexpand(t)  =  BLAKE2b-512(pers="Zcash_ExpandSeed",skt),\mathsf{PRF}^{\text{expand}}_{\mathsf{sk}}(t) \;=\; \mathsf{BLAKE2b\text{-}512}\bigl( \text{pers}=\text{"Zcash\_ExpandSeed"},\, \mathsf{sk} \,\|\, t\bigr),

where tt is one or more bytes that distinguish derivation purposes.

Per-key catalog entry. Each entry below records:

  • Symbol and short name.
  • Type: field element, curve point, or byte string.
  • Domain: which field or group.
  • Derivation.
  • Role: who knows it and what it enables.
  • Code: where the type lives in this workspace (or in the external sapling-crypto / orchard crates that this workspace depends on).

3. The code

The catalog is organised by pool. Within each pool, keys are listed in derivation order: spending key first, then derived material, then per-transaction ephemerals.

3.1 Sprout (legacy)

Sprout is closed for new outputs but historical notes still exist.

SymbolTypeDerivationRole
aska_{\mathsf{sk}}{0,1}252\{0,1\}^{252}uniformSprout spending key
apka_{\mathsf{pk}}{0,1}256\{0,1\}^{256}PRFaskaddr(0)\mathsf{PRF}^{\mathsf{addr}}_{a_{\mathsf{sk}}}(0)Sprout paying key (public)
skenc\mathsf{sk}_{\text{enc}}{0,1}256\{0,1\}^{256}PRFaskaddr(1)\mathsf{PRF}^{\mathsf{addr}}_{a_{\mathsf{sk}}}(1) derivedCurve25519 secret for in-band encryption
pkenc\mathsf{pk}_{\text{enc}}Curve25519 point[skenc]GC25519[\mathsf{sk}_{\text{enc}}]G_{C25519}Curve25519 public, part of the address
ρ\rho{0,1}256\{0,1\}^{256}per-note uniqueNullifier seed in note
rr (Sprout){0,1}256\{0,1\}^{256}per-note randomCommitment randomness
ϕ\phi{0,1}252\{0,1\}^{252}per-JoinSplit randomUsed to derive new ρ\rho values
hsigh_{\mathsf{sig}}{0,1}256\{0,1\}^{256}hash of tx context + JoinSplit pubkeyBinds JoinSplit to the signature

Nullifier: nf=PRFasknf(ρ)\mathsf{nf} = \mathsf{PRF}^{\mathsf{nf}}_{a_{\mathsf{sk}}}(\rho).

Sprout PRFs are SHA-256 with 4-bit tag prefixes; exact tags are in protocol spec section 5.4.2.

The Sprout circuit definition lives in zcash_proofs/src/circuit/sprout/mod.rs:

zcash_proofs/src/circuit/sprout/mod.rs
loading...

3.2 Sapling

The Sapling key tree implementation lives in the external sapling-crypto crate; this workspace consumes it via zcash_keys/src/keys.rs.

Root spending key

Symbolsk\mathsf{sk}
Type32-byte string
Domain{0,1}256\{0,1\}^{256}
DerivationZIP 32 hardened path from seed, e.g. m/32/133/acctm / 32' / 133' / \text{acct}'
RoleHolder can do everything: spend, view, derive
Codesapling-crypto::keys::SpendingKey

Sometimes called the "expanded spending key" or, with a chain code, the "extended spending key" (ZIP 32, Extended).

Spend-authorisation private key

ask  =  ToScalar(PRFskexpand(0x00)).\mathsf{ask} \;=\; \mathsf{ToScalar}\bigl( \mathsf{PRF}^{\text{expand}}_{\mathsf{sk}}(0\text{x}00)\bigr).
Symbolask\mathsf{ask}
Typescalar
DomainFJ\mathbb{F}_{\ell_J}
RoleSigns Sapling spend-auth signatures (after re-randomisation)
Codesapling-crypto::keys::ExpandedSpendingKey::ask

Nullifier private key

nsk  =  ToScalar(PRFskexpand(0x01)).\mathsf{nsk} \;=\; \mathsf{ToScalar}\bigl( \mathsf{PRF}^{\text{expand}}_{\mathsf{sk}}(0\text{x}01)\bigr).
Symbolnsk\mathsf{nsk}
Typescalar
DomainFJ\mathbb{F}_{\ell_J}
RoleDerives nk\mathsf{nk} and (inside the circuit) the nullifier
Codesapling-crypto::keys::ExpandedSpendingKey::nsk

Outgoing viewing key

ovk  =  truncate32(PRFskexpand(0x02)).\mathsf{ovk} \;=\; \mathsf{truncate}_{32}\bigl( \mathsf{PRF}^{\text{expand}}_{\mathsf{sk}}(0\text{x}02)\bigr).
Symbolovk\mathsf{ovk}
Type32-byte string
Domain{0,1}256\{0,1\}^{256}
RoleDecrypts outputs sent by the holder (via CoutC^{\text{out}})
Codesapling-crypto::keys::ExpandedSpendingKey::ovk

The truncation is the first 32 bytes of the 64-byte BLAKE2b output.

Diversifier key

dk  =  truncate32(PRFskexpand(0x10)).\mathsf{dk} \;=\; \mathsf{truncate}_{32}\bigl( \mathsf{PRF}^{\text{expand}}_{\mathsf{sk}}(0\text{x}10)\bigr).
Symboldk\mathsf{dk}
Type32-byte AES-128/256 key (used as FF1 key)
RoleEnumerates this account's diversifiers via FF1 format-preserving encryption
Codesapling-crypto::zip32::DiversifierKey

Spend-authorisation public key

ak  =  [ask]GSapak,\mathsf{ak} \;=\; [\mathsf{ask}]\,G^{\mathsf{ak}}_{\text{Sap}},

with GSapakG^{\mathsf{ak}}_{\text{Sap}} a fixed Jubjub generator (SpendAuthSig.BasePoint) in the prime-order subgroup.

Symbolak\mathsf{ak}
Typecurve point
DomainEJubjubE^{\circ}_{\text{Jubjub}}
RolePublic spend-authority key; published (after re-randomisation) as rk\mathsf{rk}
Codesapling-crypto::keys::ProofGenerationKey::ak

Nullifier deriving key

nk  =  [nsk]GSapnk,\mathsf{nk} \;=\; [\mathsf{nsk}]\,G^{\mathsf{nk}}_{\text{Sap}},

with GSapnkG^{\mathsf{nk}}_{\text{Sap}} a fixed Jubjub generator (ProvingPublicKey.BasePoint) in the prime-order subgroup.

Symbolnk\mathsf{nk}
Typecurve point
DomainEJubjubE^{\circ}_{\text{Jubjub}}
RoleKeys the nullifier PRF; part of the full viewing key
Codesapling-crypto::keys::ProofGenerationKey::nk

Incoming viewing key

ivk  =  [BLAKE2s-256(pers="Zcashivk",reprFr(ak)reprFr(nk))]modJ,\mathsf{ivk} \;=\; \bigl[\mathsf{BLAKE2s\text{-}256}\bigl(\text{pers}=\text{"Zcashivk"}, \mathsf{repr}_{\mathbb{F}_r}(\mathsf{ak}) \,\|\, \mathsf{repr}_{\mathbb{F}_r}(\mathsf{nk})\bigr)\bigr] \bmod \ell_J,

extracting each point's uu-coordinate as the 255-bit little-endian field-element encoding; the top bit is cleared before reducing modulo J\ell_J to ensure uniform reduction.

Symbolivk\mathsf{ivk}
Typescalar
DomainFJ\mathbb{F}_{\ell_J}
RoleDecrypts outputs received by this account
Codesapling-crypto::keys::SaplingIvk

Full viewing key

fvk  =  (ak,nk,ovk).\mathsf{fvk} \;=\; (\mathsf{ak}, \mathsf{nk}, \mathsf{ovk}).

With the diversifier key included (per ZIP 316): efvk=(ak,nk,ovk,dk)\mathsf{efvk} = (\mathsf{ak}, \mathsf{nk}, \mathsf{ovk}, \mathsf{dk}).

The full viewing key can: compute ivk\mathsf{ivk} and decrypt incoming notes; decrypt outgoing notes via ovk\mathsf{ovk}; enumerate diversified addresses via dk\mathsf{dk}. It cannot spend or authorise (no ask\mathsf{ask}).

| Code | sapling-crypto::keys::FullViewingKey |

Proof generation key

pgk  =  (ak,nsk).\mathsf{pgk} \;=\; (\mathsf{ak}, \mathsf{nsk}).

The witness the prover supplies for the Sapling Spend circuit: public ak\mathsf{ak} and private nsk\mathsf{nsk}. A hardware-wallet flow that delegates proving hands this pair (and the per-spend α\alpha) to the prover without sending ask\mathsf{ask} (which signs only).

| Code | sapling-crypto::keys::ProofGenerationKey |

Diversifier

d  =  FF1-AESdk(Encode(i)){0,1}88,d \;=\; \mathsf{FF1\text{-}AES}_{\mathsf{dk}}(\mathsf{Encode}(i)) \in \{0,1\}^{88},

for index i[0,288)i \in [0, 2^{88}). Eleven bytes. Not every dd produces a valid Jubjub generator (probability roughly 1/2 per dd); invalid ones are skipped.

| Code | sapling-crypto::keys::Diversifier, DiversifierIndex |

Diversified base

gd  =  DiversifyHash(d),g_d \;=\; \mathsf{DiversifyHash}(d),

where DiversifyHash\mathsf{DiversifyHash} is a try-and-increment hash-to-curve into EJubjubE^{\circ}_{\text{Jubjub}} using BLAKE2s with personalisation "Zcash_gd". The output is multiplied by the cofactor (88) to land in the prime-order subgroup.

Symbolgdg_d
Typecurve point
DomainEJubjubE^{\circ}_{\text{Jubjub}}

Diversified transmission key

pkd  =  [ivk]gd.\mathsf{pk}_d \;=\; [\mathsf{ivk}]\,g_d.
Symbolpkd\mathsf{pk}_d
Typecurve point
DomainEJubjubE^{\circ}_{\text{Jubjub}}
RolePublic key tied to diversifier dd; combined to form a payment address

Sapling payment address

addrSap=(d,pkd),\mathsf{addr}_{\text{Sap}} = (d, \mathsf{pk}_d),

encoded as 11+32=4311 + 32 = 43 bytes, then bech32 with HRP zs (mainnet) or ztestsapling (testnet).

Note plaintext

A Sapling note is

note=(gd,pkd,v,rseed),\mathsf{note} = (g_d, \mathsf{pk}_d, v, \mathsf{rseed}),

where rseed\mathsf{rseed} is a 32-byte seed (post-ZIP 212) from which rcm\mathsf{rcm} and esk\mathsf{esk} are derived. Pre-Canopy notes used rcm\mathsf{rcm} directly.

Commitment randomness

Post-ZIP 212:

rcm  =  ToScalar(PRFrseedexpand(0x04)).\mathsf{rcm} \;=\; \mathsf{ToScalar}\bigl( \mathsf{PRF}^{\text{expand}}_{\mathsf{rseed}}(0\text{x}04)\bigr).
Symbolrcm\mathsf{rcm}
Typescalar
DomainFJ\mathbb{F}_{\ell_J}
RoleHides the note in NoteCommit\mathsf{NoteCommit}

Ephemeral secret key

Post-ZIP 212:

esk  =  ToScalar(PRFrseedexpand(0x05)).\mathsf{esk} \;=\; \mathsf{ToScalar}\bigl( \mathsf{PRF}^{\text{expand}}_{\mathsf{rseed}}(0\text{x}05)\bigr).
Symbolesk\mathsf{esk}
Typescalar
DomainFJ\mathbb{F}_{\ell_J}
RoleSender's secret for ECDH note encryption
CodeLocal to builder; not part of any persisted key type

Ephemeral public key

epk  =  [esk]gd.\mathsf{epk} \;=\; [\mathsf{esk}]\,g_d.

Published in the OutputDescription / Action.

| Type | curve point in EJubjubE^{\circ}_{\text{Jubjub}} |

Note commitment

cm  =  NoteCommitrcm(gd,pkd,v)  =  PedersenHashDnc(1011vLE,64reprFr(gd)reprFr(pkd))  +  [rcm]Rnc.\mathsf{cm} \;=\; \mathsf{NoteCommit}^{\mathsf{rcm}}(g_d, \mathsf{pk}_d, v) \;=\; \mathsf{PedersenHash}_{D_{\text{nc}}}\bigl( 1011 \,\|\, v_{\text{LE},64} \,\|\, \mathsf{repr}_{\mathbb{F}_r}(g_d) \,\|\, \mathsf{repr}_{\mathbb{F}_r}(\mathsf{pk}_d) \bigr) \;+\; [\mathsf{rcm}]\,R_{\text{nc}}.

Then cmu=extract(cm)\mathsf{cm}^u = \mathsf{extract}(\mathsf{cm}) is what is published.

| Code | sapling-crypto::primitives::NoteCommitment |

Value commitment randomness and value commitment

rcvFJ\mathsf{rcv} \in \mathbb{F}_{\ell_J}, uniform per spend/output.

cv  =  [v]VSap  +  [rcv]RSap,\mathsf{cv} \;=\; [v]\,V_{\text{Sap}} \;+\; [\mathsf{rcv}]\,R_{\text{Sap}},

with VSap,RSapV_{\text{Sap}}, R_{\text{Sap}} fixed Jubjub generators (ValueCommitValueBase, ValueCommitRandomnessBase).

Symbolcv\mathsf{cv}
Typecurve point in EJubjubE^{\circ}_{\text{Jubjub}}

Position and rho

For a Sapling note at position pos\mathsf{pos} in the commitment tree:

ρ  =  MixingPedersenHash(cm,pos)  =  cm  +  [pos]Gρ.\rho \;=\; \mathsf{MixingPedersenHash}(\mathsf{cm}, \mathsf{pos}) \;=\; \mathsf{cm} \;+\; [\mathsf{pos}]\,G_\rho.

| Type | curve point in EJubjubE^{\circ}_{\text{Jubjub}} |

Nullifier

nf  =  PRFnknfSapling(ρ)  =  BLAKE2s-256(pers="Zcash_nf",reprFr(nk)reprFr(ρ)).\mathsf{nf} \;=\; \mathsf{PRF}^{\mathsf{nfSapling}}_{\mathsf{nk}}(\rho) \;=\; \mathsf{BLAKE2s\text{-}256}\bigl(\text{pers}=\text{"Zcash\_nf"}, \mathsf{repr}_{\mathbb{F}_r}(\mathsf{nk}) \,\|\, \mathsf{repr}_{\mathbb{F}_r}(\rho)\bigr).

Published in the SpendDescription.

Re-randomisation

αFJ\alpha \in \mathbb{F}_{\ell_J}, uniform per spend.

rsk  =  ask  +  α(modJ),rk  =  ak  +  [α]GSapak.\mathsf{rsk} \;=\; \mathsf{ask} \;+\; \alpha \pmod{\ell_J}, \qquad \mathsf{rk} \;=\; \mathsf{ak} \;+\; [\alpha]\,G^{\mathsf{ak}}_{\text{Sap}}.

rsk\mathsf{rsk} stays private; rk\mathsf{rk} is published.

Spend-authorisation signature

A RedJubjub signature under rsk\mathsf{rsk} over the sighash:

σspendAuth  =  RedJubjub.Signrsk(sighash).\sigma_{\text{spendAuth}} \;=\; \mathsf{RedJubjub.Sign}_{\mathsf{rsk}}(\mathsf{sighash}).

Verified under rk\mathsf{rk}.

Outgoing cipher key

ock  =  BLAKE2b-256(pers="Zcash_Derive_ock",ovkrepr(cv)repr(cmu)repr(epk)).\mathsf{ock} \;=\; \mathsf{BLAKE2b\text{-}256}\bigl( \text{pers}=\text{"Zcash\_Derive\_ock"}, \mathsf{ovk} \,\|\, \mathsf{repr}(\mathsf{cv}) \,\|\, \mathsf{repr}(\mathsf{cm}^u) \,\|\, \mathsf{repr}(\mathsf{epk})\bigr).
Symbolock\mathsf{ock} (also KoutK_{\text{out}} in chapter 08)
Type32-byte symmetric key
RoleAEAD key for CoutC^{\text{out}}; recovers (pkd,esk)(\mathsf{pk}_d, \mathsf{esk}) from ovk\mathsf{ovk}

Note encryption key

Kenc  =  BLAKE2b-256(pers="Zcash_SaplingKDF",repr(shared)repr(epk)),K_{\text{enc}} \;=\; \mathsf{BLAKE2b\text{-}256}\bigl( \text{pers}=\text{"Zcash\_SaplingKDF"}, \mathsf{repr}(\mathsf{shared}) \,\|\, \mathsf{repr}(\mathsf{epk})\bigr),

where shared=[esk]pkd=[ivk]epk\mathsf{shared} = [\mathsf{esk}]\,\mathsf{pk}_d = [\mathsf{ivk}]\,\mathsf{epk}.

| Type | 32-byte AEAD key | | Role | Encrypts the note plaintext to the recipient |

Binding signature keys

bsk  =  iinrcvi    joutrcvj(modJ),\mathsf{bsk} \;=\; \sum_{i \in \text{in}} \mathsf{rcv}_i \;-\; \sum_{j \in \text{out}} \mathsf{rcv}_j \pmod{\ell_J}, bvk  =  iincviin    joutcvjout    [vbalanceSap]VSap.\mathsf{bvk} \;=\; \sum_{i \in \text{in}} \mathsf{cv}_i^{\text{in}} \;-\; \sum_{j \in \text{out}} \mathsf{cv}_j^{\text{out}} \;-\; [v_{\text{balance}}^{\text{Sap}}]\,V_{\text{Sap}}.

If everything balances, bvk=[bsk]RSap\mathsf{bvk} = [\mathsf{bsk}]\,R_{\text{Sap}}; the spender holds bsk\mathsf{bsk} and signs the sighash under it.

| Code | sapling-crypto::bundle::Bundle::binding_signature |

Internal vs external addresses

ZIP 316 introduces an "internal" full-viewing-key tree for change addresses, distinct from user-facing external addresses. The internal sub-tree has its own ovk\mathsf{ovk}, dk\mathsf{dk}, and diversifier index space. An external party that sees change outputs cannot link them to user-visible addresses. The internal keys derive from the external ones via further hardened ZIP-32 children with index 11.

3.3 Orchard

The Orchard key tree is implemented in the external orchard crate; this workspace consumes it via zcash_keys/src/keys.rs. Structurally parallel to Sapling with several simplifications.

Root spending key

SymbolskO\mathsf{sk}_{\text{O}}
Type32-byte string
Codeorchard::keys::SpendingKey

Spend-authorisation private key

ask  =  ToScalar(PRFskOexpand(0x06))FqP.\mathsf{ask} \;=\; \mathsf{ToScalar}\bigl( \mathsf{PRF}^{\text{expand}}_{\mathsf{sk}_{\text{O}}}(0\text{x}06)\bigr) \in \mathbb{F}_{q_P}.

Nullifier deriving key

nk  =  ToBase(PRFskOexpand(0x07))FpP.\mathsf{nk} \;=\; \mathsf{ToBase}\bigl( \mathsf{PRF}^{\text{expand}}_{\mathsf{sk}_{\text{O}}}(0\text{x}07)\bigr) \in \mathbb{F}_{p_P}.

Unlike Sapling, Orchard's nk\mathsf{nk} is a field element, not a curve point. This is a key Orchard simplification: the nullifier PRF feeds nk\mathsf{nk} directly into a Poseidon hash inside the circuit, avoiding the cost of decoding a point.

Randomiser for ivk commitment

rivk  =  ToScalar(PRFskOexpand(0x08))FqP.\mathsf{rivk} \;=\; \mathsf{ToScalar}\bigl( \mathsf{PRF}^{\text{expand}}_{\mathsf{sk}_{\text{O}}}(0\text{x}08)\bigr) \in \mathbb{F}_{q_P}.

Used as the randomness in the Sinsemilla-based CommitIvk\mathsf{CommitIvk}.

Outgoing viewing key and diversifier key

ovkdk  =  PRFskOexpand(0x82,reprFpP(ak),reprFpP(nk)).\mathsf{ovk} \,\|\, \mathsf{dk} \;=\; \mathsf{PRF}^{\text{expand}}_{\mathsf{sk}_{\text{O}}}\bigl( 0\text{x}82,\, \mathsf{repr}_{\mathbb{F}_{p_P}}(\mathsf{ak}),\, \mathsf{repr}_{\mathbb{F}_{p_P}}(\mathsf{nk})\bigr).

The 64-byte output is split: first 32 bytes are ovk\mathsf{ovk}, next 32 are dk\mathsf{dk}. The dependence on ak\mathsf{ak} and nk\mathsf{nk} binds these to the rest of the tree.

Spend-authorisation public key

ak  =  [ask]GOrchakEPallas.\mathsf{ak} \;=\; [\mathsf{ask}]\,G^{\mathsf{ak}}_{\text{Orch}} \in E^{\circ}_{\text{Pallas}}.

Incoming viewing key

ivk  =  ExtractFqP(SinsemillaCommitDcvrivk(repr(ak)repr(nk))),\mathsf{ivk} \;=\; \mathsf{Extract}_{\mathbb{F}_{q_P}}\bigl( \mathsf{SinsemillaCommit}^{\mathsf{rivk}}_{D_{\text{cv}}}\bigl( \mathsf{repr}(\mathsf{ak}) \,\|\, \mathsf{repr}(\mathsf{nk})\bigr)\bigr),

with Dcv=D_{\text{cv}} = "z.cash:Orchard-CommitIvk". SinsemillaCommit is a randomised commitment: Sinsemilla hash plus a randomness term.

Note this differs from Sapling: Sapling's ivk\mathsf{ivk} is a hash; Orchard's is a commitment with a fresh randomiser. The commitment lets the circuit prove derivation more efficiently.

Full viewing key

fvkO  =  (ak,nk,rivk).\mathsf{fvk}_{\text{O}} \;=\; (\mathsf{ak}, \mathsf{nk}, \mathsf{rivk}).

ovk,dk\mathsf{ovk}, \mathsf{dk} are derived from fvkO\mathsf{fvk}_{\text{O}} deterministically.

Diversifier, diversified base, transmission key

gd=DiversifyHash(d),pkd=[ivk]gd,g_d = \mathsf{DiversifyHash}(d), \qquad \mathsf{pk}_d = [\mathsf{ivk}]\,g_d,

with d{0,1}88d \in \{0,1\}^{88} derived from dk\mathsf{dk} as in Sapling. gdEPallasg_d \in E^{\circ}_{\text{Pallas}}.

Orchard payment address

addrO=(d,pkd).\mathsf{addr}_{\text{O}} = (d, \mathsf{pk}_d).

Encoded in 43 bytes, then packaged inside a Unified Address (no standalone bech32 form).

Note plaintext

An Orchard note is

noteO  =  (ρ,ψ,gd,pkd,v,rseed).\mathsf{note}_{\text{O}} \;=\; (\rho, \psi, g_d, \mathsf{pk}_d, v, \mathsf{rseed}).

The additional fields ρ\rho and ψ\psi are uniqueness nonces that chain across the bundle: each new note's ρ\rho is the nullifier of the spent note in the same action.

ψ\psi is derived from rseed\mathsf{rseed} and ρ\rho:

ψ  =  ToBase(PRFrseedexpand(0x09,ρ)).\psi \;=\; \mathsf{ToBase}\bigl( \mathsf{PRF}^{\text{expand}}_{\mathsf{rseed}}(0\text{x}09, \rho)\bigr).

Commitment randomness

rcm  =  ToScalar(PRFrseedexpand(0x05,ρ)).\mathsf{rcm} \;=\; \mathsf{ToScalar}\bigl( \mathsf{PRF}^{\text{expand}}_{\mathsf{rseed}}(0\text{x}05, \rho)\bigr).

Note commitment

cm  =  SinsemillaDncrcm(repr(gd)repr(pkd)vLE,64repr(ρ)repr(ψ)).\mathsf{cm} \;=\; \mathsf{Sinsemilla}^{\mathsf{rcm}}_{D_{\text{nc}}}\bigl( \mathsf{repr}(g_d) \,\|\, \mathsf{repr}(\mathsf{pk}_d) \,\|\, v_{\text{LE}, 64} \,\|\, \mathsf{repr}(\rho) \,\|\, \mathsf{repr}(\psi)\bigr).

Then cmx=extract(cm)\mathsf{cmx} = \mathsf{extract}(\mathsf{cm}) is published.

Ephemeral keys for note encryption

esk  =  ToScalar(PRFrseedexpand(0x04,ρ)),epk  =  [esk]gd.\mathsf{esk} \;=\; \mathsf{ToScalar}\bigl( \mathsf{PRF}^{\text{expand}}_{\mathsf{rseed}}(0\text{x}04, \rho)\bigr), \qquad \mathsf{epk} \;=\; [\mathsf{esk}]\,g_d.

Value commitment

cvnet  =  [vnet]VOrch  +  [rcv]ROrch,\mathsf{cv}^{\text{net}} \;=\; [v^{\text{net}}]\,V_{\text{Orch}} \;+\; [\mathsf{rcv}]\,R_{\text{Orch}},

with vnet=voldvnewv^{\text{net}} = v_{\text{old}} - v_{\text{new}} the net value of the Action (positive if the old note was larger than the new one). The bundle balance equation aggregates these.

Nullifier

nf  =  ExtractFpP([PRFnknfOrchard(ρ)+ψ]KOrch  +  cm),\mathsf{nf} \;=\; \mathsf{Extract}_{\mathbb{F}_{p_P}}\bigl( [\mathsf{PRF}^{\mathsf{nfOrchard}}_{\mathsf{nk}}(\rho) + \psi]\, K_{\text{Orch}} \;+\; \mathsf{cm}\bigr),

with KOrchK_{\text{Orch}} a fixed Pallas generator and PRFnknfOrchard\mathsf{PRF}^{\mathsf{nfOrchard}}_{\mathsf{nk}} a Poseidon-based PRF keyed by nk\mathsf{nk}.

Re-randomisation, binding sig, OCK, K_enc

Parallel to Sapling:

rsk=ask+α,rk=ak+[α]GOrchak,\mathsf{rsk} = \mathsf{ask} + \alpha, \qquad \mathsf{rk} = \mathsf{ak} + [\alpha]\,G^{\mathsf{ak}}_{\text{Orch}}, ock=BLAKE2b-256(pers="Zcash_Orchardock",ovk),\mathsf{ock} = \mathsf{BLAKE2b\text{-}256}( \text{pers}=\text{"Zcash\_Orchardock"},\, \mathsf{ovk} \,\|\, \cdot), bvk=icvinet[vbalanceOrch]VOrch.\mathsf{bvk} = \sum_i \mathsf{cv}_i^{\text{net}} - [v_{\text{balance}}^{\text{Orch}}]\,V_{\text{Orch}}.

The binding signature is RedPallas; ZIP 224 spells out the constants.

Issuance keys (ZSA, NU7-track)

Future-only:

SymbolRole
IssuanceKey\mathsf{IssuanceKey}Issuer's spending-authority root for issuance
ik\mathsf{ik}Public issuance key
AssetId\mathsf{AssetId}64-byte digest binding an asset to its issuer

See chapter 21 for ZSA context.

3.4 Transparent

Standard BIP-32 / SLIP-10 over secp256k1. Path m/44/133/acct/change/indexm / 44' / 133' / \text{acct}' / \text{change} / \text{index}.

SymbolType
xprv\mathsf{xprv}Extended private key (32-byte priv + 32-byte chain code)
xpub\mathsf{xpub}Extended public key
skT\mathsf{sk}_Tsecp256k1 scalar
pkT\mathsf{pk}_Tsecp256k1 point
hash160\mathsf{hash160}RIPEMD160(SHA256(pubkey)), the address payload

The transparent key implementation:

zcash_transparent/src/keys.rs
loading...

ZIP 48 account-level keys live alongside in zcash_transparent/src/zip48.rs.

3.5 Unified

Defined by ZIP 316. Encoded via F4Jumble in components/f4jumble/src/lib.rs and bech32m in components/zcash_address/src.

Unified spending key

USK  =  (xprvT,eskSap,skO),\mathsf{USK} \;=\; (\mathsf{xprv}_T, \mathsf{esk}_{\text{Sap}}, \mathsf{sk}_{\text{O}}),

with components present per the account's policy.

zcash_keys/src/keys.rs
loading...

Unified full viewing key

UFVK  =  (xpubT,efvkSap,fvkO).\mathsf{UFVK} \;=\; (\mathsf{xpub}_T, \mathsf{efvk}_{\text{Sap}}, \mathsf{fvk}_{\text{O}}).

efvkSap\mathsf{efvk}_{\text{Sap}} includes the diversifier key dkSap\mathsf{dk}_{\text{Sap}}.

Unified incoming viewing key

UIVK  =  (xpubTexternal,ivkSap,ivkO),\mathsf{UIVK} \;=\; (\mathsf{xpub}_T^{\text{external}}, \mathsf{ivk}_{\text{Sap}}, \mathsf{ivk}_{\text{O}}),

decrypts incoming but not outgoing notes; a weaker capability than UFVK, suitable for read-only services.

Unified address

A bundle of receivers:

UA  =  {TypecodeiReceiveri}.\mathsf{UA} \;=\; \{\text{Typecode}_i \to \mathsf{Receiver}_i\}.

Typecodes per ZIP 316. Encoded as F4Jumble(TLV concat || HMAC), then bech32m with HRP u.

3.6 Note encryption keys at a glance

For both Sapling and Orchard:

KeySender knowsRecipient knowsPurpose
esk\mathsf{esk}yesnoECDH secret
epk\mathsf{epk}yes (publishes)yes (sees on-chain)ECDH public
shared\mathsf{shared}[esk]pkd[\mathsf{esk}]\,\mathsf{pk}_d[ivk]epk[\mathsf{ivk}]\,\mathsf{epk}DH output
KencK_{\text{enc}}yesyesAEAD key (recipient side)
ock\mathsf{ock}yes (via ovk\mathsf{ovk})noAEAD key (sender side)

3.7 Cross-pool relationships

Every Zcash account in this codebase has:

  • One transparent extended key per account (ZIP 48).
  • One Sapling extended spending key per account.
  • One Orchard spending key per account.

These are independent: knowing one does not reveal the others. The wallet stitches them together via the Unified containers. The same seed deterministically produces all three (via different ZIP 32 paths). Backing up the seed phrase backs up the full account.

3.8 Privacy hierarchy

Per pool, the capability ladder (top: most powerful):

  1. sk\mathsf{sk} - can spend.
  2. ask\mathsf{ask}, nsk\mathsf{nsk} - jointly authorise and prove a spend; cannot derive the address (need ivk\mathsf{ivk}).
  3. fvk=(ak,nk,ovk,dk)\mathsf{fvk} = (\mathsf{ak}, \mathsf{nk}, \mathsf{ovk}, \mathsf{dk}) - view incoming and outgoing; enumerate addresses; cannot spend.
  4. ivk\mathsf{ivk} - decrypt incoming; cannot view outgoing.
  5. (d,pkd)(d, \mathsf{pk}_d) - public receiver; only sendable-to.

The PCZT design respects this hierarchy: each role gets the minimum capability it needs.

3.9 Lifetime: where each key lives

PhaseStoredNotes
At restSeed (encrypted), USK and UFVK in wallet DBUSK should be encrypted in DB or held in a hardware signer
ScanningUIVK, nullifier setNo spending capability needed
Building (single key)USK, proving parameters, Merkle pathsLocal proving
PCZT constructorRead-only wallet stateNo private keys
PCZT proverpgk\mathsf{pgk} + per-spend α\alphaNo ask\mathsf{ask}
PCZT signerask\mathsf{ask} + sighashNo proving

3.10 Code reference table

TypeCrate :: Module
SpendingKey (Sapling)sapling-crypto::keys::SpendingKey
ExpandedSpendingKey (Sapling)sapling-crypto::keys::ExpandedSpendingKey
ProofGenerationKey (Sapling)sapling-crypto::keys::ProofGenerationKey
FullViewingKey (Sapling)sapling-crypto::keys::FullViewingKey
SaplingIvksapling-crypto::keys::SaplingIvk
OutgoingViewingKey (Sapling)sapling-crypto::keys::OutgoingViewingKey
DiversifierKey (Sapling)sapling-crypto::zip32::DiversifierKey
Diversifier (Sapling)sapling-crypto::keys::Diversifier
PaymentAddress (Sapling)sapling-crypto::PaymentAddress
SpendingKey (Orchard)orchard::keys::SpendingKey
FullViewingKey (Orchard)orchard::keys::FullViewingKey
IncomingViewingKey (Orchard)orchard::keys::IncomingViewingKey
DiversifierKey (Orchard)orchard::keys::DiversifierKey
Address (Orchard)orchard::Address
Transparent extended keyzcash_transparent::keys
UnifiedSpendingKeyzcash_keys::keys::UnifiedSpendingKey
UnifiedFullViewingKeyzcash_keys::keys::UnifiedFullViewingKey
UnifiedAddressRequestzcash_keys::keys
UnifiedAddresszcash_keys::address::UnifiedAddress

3.11 Derivation graphs

Sapling derivation:

seed (32 B)
| ZIP 32: m / 32' / 133' / acct'
v
sk (Sapling, 32 B)
|
|---- PRF^expand(.,0x00) -> ask (F_l)
|---- PRF^expand(.,0x01) -> nsk (F_l)
|---- PRF^expand(.,0x02) -> ovk (32 B)
|---- PRF^expand(.,0x10) -> dk (32 B)
|
v v
ak = [ask]G^ak nk = [nsk]G^nk
\_____________ ______/
\/
ivk = CRH^ivk(ak, nk) mod l
|
dk -- FF1 --> d (per index)
|
g_d = DiversifyHash(d)
|
pk_d = [ivk] g_d
|
address = (d, pk_d)

Orchard derivation:

sk_O
|
|---- PRF^expand(.,0x06) -> ask (F_q)
|---- PRF^expand(.,0x07) -> nk (F_p, field elt!)
|---- PRF^expand(.,0x08) -> rivk (F_q)
|
v v
ak = [ask]G^ak_O (nk is a scalar; no point op)
\_____________ ______/
\/
ivk = Extract(SinsemillaCommit^rivk(ak, nk))
|
ovk || dk = PRF^expand(., 0x82, ak, nk)
|
dk -- FF1 --> d
|
g_d = DiversifyHash(d)
|
pk_d = [ivk] g_d
|
receiver = (d, pk_d)

Per-transaction ephemerals (both pools):

rseed rcv alpha
| | |
v v v
rcm, esk (used in rsk = ask + alpha
| cv = [v]V |
v + [rcv]R) rk = ak + [alpha] G^ak
epk = [esk]g_d |
sigma_spendAuth =
RedSig.Sign_rsk(sighash)

5. Spec pointers

  • Zcash Protocol Specification, section 4: high-level definitions for every key in this catalog.
  • Zcash Protocol Specification, section 5: concrete formulas for PRFexpand\mathsf{PRF}^{\text{expand}}, CRHivk\mathsf{CRH}^{\mathsf{ivk}}, DiversifyHash\mathsf{DiversifyHash}, and the nullifier PRFs.
  • ZIP 32: HD derivation paths feeding the per-pool root spending key.
  • ZIP 212: the rseed-derived rcm\mathsf{rcm} and esk\mathsf{esk} that this catalog records.
  • ZIP 224: Orchard key tree.
  • ZIP 316: Unified Addresses, full viewing keys, and the internal/external split for change addresses.
  • ZIP 48: transparent account- level keys included in a UFVK.

6. Exercises

  1. Look up a symbol. A PR comments references rivk\mathsf{rivk} without context. Use this catalog to identify which pool the key belongs to, its derivation, and the line in zcash_keys/src/keys.rs or the external orchard crate where the type is defined.
  2. Diversifier count. Compute the maximum number of diversifiers a Sapling account can produce and the expected fraction of valid ones (where gdg_d lies in the prime-order subgroup). Cite the protocol spec section that gives the numbers.
  3. Trace a derivation in code. Open zcash_keys/src/keys.rs and follow the call path from a seed to a UnifiedSpendingKey to a UnifiedAddress. Cite the file and line of each intermediate type.
  4. Cross-pool capability. A user shares their UFVK with a read-only auditor. Which keys in this catalog does the auditor gain access to? Which do they not? Answer per pool.

Answers in the code

7. Further reading

  • chapter 06: the HD derivation layer that produces the root spending keys this catalog itemises.
  • chapter 24: the circuits that consume these keys as witnesses.
  • Hopwood, Bowe, Hornby, Wilcox, Sapling design notes: the original treatment of the Sapling key tree.