PR #1202proposed changes toZIP 307

[ZIP 307] Update out-of-date light client protocol definition.

Draftby nuttycom · updated 2026-04-07zips/zip-0307.rst @ 75f1a84fc4 · upstream 4699a8d518view on GitHub ↗

Unofficial preview — the proposed text below is rendered from the pull request head and is not part of the canonical ZIP set.

Terminology

The key words "MUST", "SHOULD", "SHOULD NOT", and "MAY" in this document are to be interpreted as described in BCP 14 [1] when, and only when, they appear in all capitals.

The character § is used when referring to sections of the Zcash Protocol Specification. [^protocol]

The terms "Mainnet" and "Testnet" are to be interpreted as described in § 3.12 ‘Mainnet and Testnet’. [^protocol-networks]

The terms below are to be interpreted as follows:

Light client

A client that is not a full participant in the network of Zcash peers. It can receive and (if it holds the required spending keys) send payments, but does not store or validate a full copy of the block chain.

Abstract

This ZIP defines a protocol for Zcash light clients that enables detection of incoming payments, identification of spent notes, and maintenance of note commitment tree witnesses and nullifier sets for Sapling [13] and Orchard [15] shielded transactions, as well as transparent input and output tracing.

Motivation

Prior to the introduction of this protocol, a client that wished to send or receive shielded payments had to be a full node participating in the Zcash network. This required an amount of available bandwidth, space, and processing power that was unsuitable for some use cases. This light client protocol addresses that need, and is appropriate for low-power, bandwidth-conscious, or otherwise limited machines (such as mobile phones).

High-Level Design

There are three logical components to a Zcash light client system:

  • Zcash full node that provides chain state and serves as a root of trust for the system.

  • Proxy server that extracts block chain data from a Zcash full node to store and serve it in a lower-bandwidth format.

  • Light client that subscribes to the stream from a proxy server and uses that data to update its own view of the chain state. The light client MAY be attached to a wallet backend that will track particular shielded notes.

../rendered/assets/images/zip-0307-arch.png

Outline of the light wallet architecture

Security Model

In this model, we propose payment detection privacy as our main security goal. That is, the proxy should not learn which transactions (received from the block chain) are addressed to a given light wallet. If we further assume network privacy (via Tor or similar), the proxy should not be able to link different connections or queries as deriving from the same wallet.

In particular, the underlying Zcash node / proxy combination is assumed to be "honest but curious" and is trusted to provide a correct view of the current best chain state and to faithfully transmit queries and responses.

This ZIP does not address how to spend notes privately.

Compact Stream Format

A key observation in this protocol is that the ciphertext in each shielded output is several hundred bytes long, due to the inclusion of a transaction memo. The need to download the entire ciphertext imposes a substantial bandwidth cost on each light wallet, which may be a limited mobile device on a restricted-bandwidth plan. The memo field can be ignored during payment detection, as can any information that is not directly relevant to shielded transaction processing.

A compact block is a packaging of ONLY the data from a block needed to:

  1. Detect a payment to your shielded address (Sapling or Orchard).

  2. Detect a spend of your shielded notes.

  3. Update note commitment tree witnesses.

Compact blocks and their component compact transactions are encoded on the wire using Protocol Buffers [17]. The normative definitions of the compact block format are specified in the compact_formats.proto file of the Zcash Light Client Protocol [16].

The protocol has evolved through several major versions:

  • v0.3.x (2021–2023): Added Orchard shielded protocol support via CompactOrchardAction and note commitment tree state queries (GetTreeState, GetLatestTreeState) and subtree root retrieval (GetSubtreeRoots) to support efficient witness construction.

  • v0.4.x (2025): Added transparent transaction data (CompactTxIn, TxOut) to compact blocks, enabling transparent outputs to be discovered without revealing the client's addresses to the light wallet server. The update also added PoolType-based filtering for selective retrieval by shielded protocol.

The following sections describe the rationale for the compact format design.

Output Compression

In the Zcash protocol, each shielded output contains an AEAD-encrypted note plaintext [9]. For both Sapling and Orchard, this ciphertext (encCiphertext) is 580 bytes long and includes the memo field and an authentication tag.

A recipient detects their transactions by trial-decrypting this ciphertext. On a full node that has the entire block chain, the primary cost is computational. For light clients however, there is an additional bandwidth cost: every ciphertext on the block chain must be received from the server (or network node) the light client is connected to.

However, payment detection does not require the full ciphertext. The first 52 bytes of encCiphertext contain the contents and opening of the note commitment, which is sufficient to detect, verify, and spend the note. The compact representation therefore includes only these 52 bytes along with the extracted note commitment and ephemeral public key.

For Sapling, a CompactSaplingOutput contains the 32-byte extracted note commitment ( 𝚌 𝚖 𝚞 ), the 32-byte ephemeral public key, and the first 52 bytes of the ciphertext, for a total of 116 bytes per output. A full Sapling Output description [11] is 948 bytes in v4 transactions (756 bytes in v5), so the compact representation provides an 85–88% reduction in bandwidth.

For Orchard, a CompactOrchardAction contains the 32-byte nullifier, the 32-byte extracted note commitment ( 𝚌 𝚖 𝚡 ), the 32-byte ephemeral public key, and the first 52 bytes of the ciphertext, for a total of 148 bytes per action. A full Orchard Action description [12] is 820 bytes, so the compact representation provides an 82% reduction in bandwidth. Note that the Orchard compact representation includes the nullifier because the Orchard Action structure combines a spend and an output into a single description.

Since the compact representation omits the authentication tag, the integrity of the decrypted note plaintext is instead validated by recalculating the note commitment from the decrypted plaintext and checking it against the note commitment sent outside the ciphertext, which is authenticated by the binding signature over the entire transaction.

Spend Compression

For Sapling, a full Spend description is 384 bytes long [10]. The only part necessary for spend detection is the 32-byte nullifier, which allows a light client to detect when one of its own notes has been spent. A CompactSaplingSpend therefore contains only the nullifier, for a 92% reduction in bandwidth.

For Orchard, spend detection is handled by checking the nullifier included in each CompactOrchardAction (as described in Output Compression above), since the Orchard Action structure combines a spend and an output.

Proxy operation

The proxy's purpose is to provide a scalable and bandwidth-efficient interface between a Zcash node and any number of light clients. It accomplishes this by parsing a blockwise stream of transactions from the node and converting them into the compact format described above.

The proxy offers a gRPC API to clients, defined by the service.proto file of the Zcash Light Client Protocol [16]. The CompactTxStreamer service provides methods for retrieving compact blocks, submitting transactions, querying note commitment tree state, and querying for transparent outputs by address.

Client operation

Light clients obtain compact blocks from one or more proxy servers, which they then process locally to update their view of the block chain. We consider only a single proxy server here without loss of generality.

Local processing

Given a CompactBlock at block height 𝗁 𝖾 𝗂 𝗀 𝗁 𝗍 received from a proxy server, a light client processes it in the following ways:

Scanning for relevant transactions

For every CompactSaplingOutput and CompactOrchardAction in the CompactBlock, the light client attempts trial decryption against its set of incoming viewing keys. The following subsection describes the compact trial decryption procedure for Sapling and Orchard.

Sapling and Orchard compact trial decryption

For each Sapling or Orchard compact output, the light client trial-decrypts with each incoming viewing key 𝗂 𝗏 𝗄 for that output's shielded protocol.

Let ℓ 𝖽 be as defined in § 5.3 ‘Constants’.

Let 𝖢 𝗁 𝖺 𝖢 𝗁 𝖺 𝟤𝟢 . 𝖣 𝖾 𝖼 𝗋 𝗒 𝗉 𝗍 𝖪 ( 𝖢 ) be the unauthenticated decryption of ciphertext \mathsf{C} \;{\small ⦂}\; \mathbb{B}^{{\kern-0.05em\tiny\mathbb{Y}}[\mathbb{N}]} with 256-bit key 𝖪 , using the stream cipher ChaCha20 as defined in RFC 7539 [2] with the initial counter set to 1 (as defined for the encryption component of AEAD_CHACHA20_POLY1305, but without the authentication component). It returns a byte sequence of the same length as 𝖢 and is infallible.

The procedure to trial-decrypt a compact output takes as input 𝚌 𝚖 * (the 𝚌 𝚖 𝚞 or 𝚌 𝚖 𝚡 field of the Output description or Action description respectively), 𝚎 𝚙 𝚑 𝚎 𝚖 𝚎 𝚛 𝚊 𝚕 𝙺 𝚎 𝚢 , the truncated encCiphertext \mathsf{C^{compact}} \;{\small ⦂}\; \mathbb{B}^{{\kern-0.05em\tiny\mathbb{Y}}[52]}, the shielded protocol ( 𝖲 𝖺 𝗉 𝗅 𝗂 𝗇 𝗀 or 𝖮 𝗋 𝖼 𝗁 𝖺 𝗋 𝖽 ), block height, and transaction version number, and an incoming viewing key 𝗂 𝗏 𝗄 , and outputs a note for that shielded protocol.

This procedure deviates from the standard note decryption using an Incoming Viewing Key, as specified in § 4.20.2 ‘Decryption using an Incoming Viewing Key (Sapling and Orchard)’ [7], only in how 𝐧 𝐩 is obtained:

  • let 𝖯 𝖼 𝗈 𝗆 𝗉 𝖺 𝖼 𝗍 = 𝖢 𝗁 𝖺 𝖢 𝗁 𝖺 𝟤𝟢 . 𝖣 𝖾 𝖼 𝗋 𝗒 𝗉 𝗍 𝖪 𝖾 𝗇 𝖼 ( 𝖢 𝖼 𝗈 𝗆 𝗉 𝖺 𝖼 𝗍 )

  • extract $mathbf{np} = (mathsf{leadByte} ;{small ⦂}; mathbb{B}^{kern-0.05emtinymathbb{Y}}, mathsf{d} ;{small ⦂}; mathbb{B}^{[ell_{mathsf{d}}]}, mathsf{v} ;{small ⦂}; { 0 .. 2^{ell_{mathsf{value}}}!-!1 }, mathsf{rseed} ;{small ⦂}; mathbb{B}^{{kern-0.05emtinymathbb{Y}}[32]})$ from 𝖯 𝖼 𝗈 𝗆 𝗉 𝖺 𝖼 𝗍

and in the fact that the last step returns only 𝐧 , rather than ( 𝐧 , 𝗆 𝖾 𝗆 𝗈 ) .

Non-normative note: Previous versions of this ZIP reiterated the compact output decryption procedure (for Sapling) rather than specifying the differences from § 4.20.2. This led to the following unintended differences:

  • The procedure described in this ZIP did not support pre-ZIP-212 Sapling outputs.

  • The swapping of domain separators $[4]$ and $[5]$ in the 𝖯 𝖱 𝖥 𝖾 𝗑 𝗉 𝖺 𝗇 𝖽 inputs used to derive 𝗋 𝖼 𝗆 and 𝖾 𝗌 𝗄 for Sapling that was fixed in version 2023.4.0 of the protocol specification, had not been fixed in this ZIP.

  • The type error that was fixed in version 2025.6.2 of the protocol specification, had not been fixed in this ZIP.

  • The refactoring to use 𝖺 𝗅 𝗅 𝗈 𝗐 𝖾 𝖽 𝖫 𝖾 𝖺 𝖽 𝖡 𝗒 𝗍 𝖾 𝗌 also made in version 2025.6.2 had not been applied.

  • The last step incorrectly returned 𝐧 𝐩 rather than the note.

Maintaining note commitment tree witnesses

In order to spend a shielded note, the spender must produce a Merkle path (witness) from the note's commitment to an anchor (a root of the note commitment tree at some block height). [4] [6] A light client MUST therefore maintain sufficient note commitment tree state to produce witnesses for its unspent notes.

The note commitments in each CompactBlock (the 𝖼 𝗆 𝗎 values from CompactSaplingOutput entries and the 𝖼 𝗆 𝗑 values from CompactOrchardAction entries) are inserted into the respective Sapling or Orchard note commitment trees in the order they appear in the block.

A light client that has scanned a contiguous range of blocks can construct witnesses from the note commitments it has observed. However, a light client need not have scanned the entire chain in order to construct witnesses: the GetSubtreeRoots RPC method [16] provides roots of completed subtrees of the note commitment trees, which can be combined with locally-scanned commitments to produce valid witnesses even when portions of the chain have not been scanned.

Detecting spends

The CompactSaplingSpend nullifier entries and the nullifier fields of CompactOrchardAction entries can be checked against the nullifiers of notes known to the client to determine when those notes have been spent.

Block header validation

This section describes a proposed enhancement that has been only partially implemented: currently only prevHash is checked.

If the CompactBlock for height X contains a block header, the light client can validate it in a similar way to SPV clients [18] by performing the following checks:

  • version >= MIN_BLOCK_VERSION

  • prevHash == prevBlock.id.blockHash where prevBlock is the previous CompactBlock received (at height X-1).

  • finalSaplingRoot is equal to the root of the Sapling note commitment tree after appending every cmu in the CompactBlock in-order.

  • The Equihash solution is valid.

  • targetFromBits(bits) != 0 && targetFromBits(bits) <= powLimit.

  • If the last 27 CompactBlocks all have block headers, bits is set correctly according to the difficulty adjustment algorithm.

  • toLittleEndian(blockHash) <= targetFromBits(bits).

A CompactBlock that fails any of these checks MUST be discarded. If it was received as part of a GetBlockRange call, the call MUST be aborted.

Block header validation provides light clients with some assurance that the compact outputs being sent to them are indeed from valid blocks that have been mined. The strongest-possible assurance is achieved when all block headers are synchronised; this comes at the cost of bandwidth and storage.

By default, CompactBlocks only contain CompactTxs for transactions that contain shielded spends or outputs. Thus they do not contain sufficient information to validate that the received transaction IDs correspond to the transaction tree root in the block header. This does not have a significant effect on light client security: light clients only directly depend on compact outputs, which can be authenticated via block header validation. If a txid is used in a GetTransaction call, the returned transaction SHOULD be checked against the corresponding compact outputs, in addition to verifying the transaction signatures.

Client-server interaction

A light client MUST perform the following operations as part of synchronisation:

  • Trial decryption: For each CompactSaplingOutput and CompactOrchardAction in the scanned CompactBlocks, the light client attempts trial decryption against its set of incoming viewing keys. Successfully decrypted outputs represent received notes. See Scanning for relevant transactions for details.

  • Nullifier matching: For each CompactSaplingSpend nullifier and CompactOrchardAction nullifier in the scanned CompactBlocks, the light client checks against its set of known nullifiers for unspent notes. A match indicates that the corresponding note has been spent.

  • Note commitment tree maintenance: The light client updates its local note commitment tree state as described in Maintaining note commitment tree witnesses.

Retrieving full transactions

The compact block stream provides sufficient information for payment detection and balance tracking, but does not include memo fields or other transaction details. When a light client needs to access the memo field of a received note, the outgoing ciphertexts for spend recovery, or other transaction components, it retrieves the full transaction via the GetTransaction RPC method.

Since a GetTransaction request reveals to the proxy server that the light client is interested in a specific transaction, full transaction retrieval SHOULD be performed over a privacy-preserving network connection such as Tor [19] or the Nym mixnet [20].

In addition, even when using a privacy-preserving network connection for these operations, a client SHOULD NOT make transaction data requests to the proxy server in a fashion that links the client's interest in that transaction to other operations that the client may be performing in the clear. For example, if a client uses an unprotected connection for compact block download, that client SHOULD NOT retrieve full transaction data immediately upon transaction discovery, but SHOULD defer it until a later time when the download will not be identifiably related to the download of the compact block range in which the transaction was discovered.

Importing a pre-existing key

The light client's scanning start height (often called the "wallet birthday") determines how far back the light client must scan to detect all relevant transactions. When a light client is initialized with a newly generated key, it can use the current chain tip as its wallet birthday, since no prior transactions can be relevant.

When initialized with a pre-existing key —whether a seed, a full viewing key, or an incoming viewing key— the light client MUST scan from the key's birthday height, which is the earliest block height at which the key could have been used. If the birthday height is unknown and cannot be estimated by the user, the light client SHOULD scan from the activation height of the earliest shielded protocol supported by the key (Sapling activation at block 419200 on Mainnet, or Orchard activation at block 1687104 on Mainnet).

Block privacy via bucketing

This section describes a proposed enhancement that has not been implemented.

When a light client requests a range of compact blocks via GetBlockRange, the start height of the request reveals to the proxy server the block height to which the light client had previously synchronised. This is an information leak under our security model (assuming network privacy). The information leakage can be reduced by "bucketing" the start point of each synchronisation request.

Let ⌊X⌋ = X - (X % N) be the value of X rounded down to some multiple of the bucket size N. Instead of requesting blocks starting from the light client's last synchronised height X, the light client requests blocks starting from ⌊X⌋. Blocks between ⌊X⌋ and X are checked against the light client's cached chain view for consistency and then discarded without further processing. This also helps detect chain reorganisations that occurred within the bucket window.

Transaction privacy

The compact block stream gives the light client sufficient information to determine accurate address balances, show when funds were received or spent, and spend any unspent notes. Since synchronisation happens via a broadcast medium (the compact block stream), it leaks no information about which transactions the light client is interested in.

However, other API methods reveal more specific interests to the proxy server:

  • GetTransaction reveals interest in a specific transaction. See Retrieving full transactions for mitigation recommendations.

  • SendTransaction reveals that the light client is the sender of a transaction. Transaction submission SHOULD be performed over a privacy-preserving network connection such as Tor [19] or the Nym mixnet [20].

  • Transparent address queries (GetTaddressTransactions, GetTaddressBalance, GetAddressUtxos) reveal the transparent addresses held by the light client. These queries SHOULD be performed over a privacy-preserving network connection such as Tor [19] or the Nym mixnet [20].

  • If a client queries for transactions related to multiple transparent addresses in a single request, this will leak the fact that those addresses are controlled by the same client. If the GetTaddressTransactions, GetTaddressBalance or GetAddressUtxos methods are used, even over a privacy-preserving network connect, the client SHOULD NOT request data for more than one address at a time and SHOULD make such requests in a fashion that is not temporally linkable to other operation of the wallet.

  • GetMempoolTx and GetMempoolStream reveal the light client's interest in unconfirmed transactions.

Reference Implementation

References

Changes

zips/zip-0307.rst +292 −449
@@ -15,43 +15,54 @@ Terminology =========== -The key words "MUST", "SHOULD", and "MAY" in this document are to be interpreted as -described in BCP 14 [#BCP14]_ when, and only when, they appear in all capitals. +The key words "MUST", "SHOULD", "SHOULD NOT", and "MAY" in this document are to be +interpreted as described in BCP 14 [#BCP14]_ when, and only when, they appear in all +capitals. + +The character § is used when referring to sections of the Zcash Protocol Specification. +[^protocol] + +The terms "Mainnet" and "Testnet" are to be interpreted as described in § 3.12 +‘Mainnet and Testnet’. [^protocol-networks] The terms below are to be interpreted as follows: Light client - A client that is not a full participant in the network of Zcash peers. It can send and - receive payments, but does not store or validate a copy of the block chain. + A client that is not a full participant in the network of Zcash peers. It can receive + and (if it holds the required spending keys) send payments, but does not store or + validate a full copy of the block chain. Abstract ======== -This proposal defines a protocol for a Zcash light client supporting Sapling shielded -transactions. +This ZIP defines a protocol for Zcash light clients that enables detection of incoming +payments, identification of spent notes, and maintenance of note commitment tree witnesses +and nullifier sets for Sapling [#zip-0205]_ and Orchard [#zip-0224]_ shielded transactions, +as well as transparent input and output tracing. Motivation ========== -Currently a client that wishes to send or receive shielded payments must be a full node -participating in the Zcash network. This requires an amount of available bandwidth, -space, and processing power that may be unsuitable for some classes of user. This light -client protocol addresses that need, and is appropriate for low-power, -bandwidth-conscious, or otherwise limited machines (such as mobile phones). +Prior to the introduction of this protocol, a client that wished to send or +receive shielded payments had to be a full node participating in the Zcash +network. This required an amount of available bandwidth, space, and processing +power that was unsuitable for some use cases. This light client protocol +addresses that need, and is appropriate for low-power, bandwidth-conscious, or +otherwise limited machines (such as mobile phones). High-Level Design ================= There are three logical components to a Zcash light client system: -- **Zcash node** that provides chain state and serves as a root of trust for the system. +- **Zcash full node** that provides chain state and serves as a root of trust for the system. -- **Proxy server** that extracts block chain data from zcashd to store and serve it in a - lower-bandwidth format. +- **Proxy server** that extracts block chain data from a Zcash full node to store and serve it + in a lower-bandwidth format. - **Light client** that subscribes to the stream from a proxy server and uses that data to update its own view of the chain state. The light client MAY be attached to a wallet - backend that will track particular Sapling notes. + backend that will track particular shielded notes. .. figure:: ../rendered/assets/images/zip-0307-arch.png :align: center @@ -66,7 +77,7 @@ In this model, we propose **payment detection privacy** as our main security goa is, the proxy should not learn which transactions (received from the block chain) are addressed to a given light wallet. If we further assume network privacy (via Tor or similar), the proxy should not be able to link different connections or queries as -deriving from the the same wallet. +deriving from the same wallet. In particular, the underlying Zcash node / proxy combination is assumed to be "honest but curious" and is trusted to provide a correct view of the current best chain state and to @@ -77,122 +88,92 @@ This ZIP does not address how to spend notes privately. Compact Stream Format ===================== -A key observation in this protocol is that the current zcashd encrypted field is several -hundred bytes long, due to the inclusion of a transaction “memo”. The need to download -this entire field imposes a substantial bandwidth cost on each light wallet, which may be -a limited mobile device on a restricted-bandwidth plan. While more efficient techniques -can be developed in the future, for the moment we propose ignoring the memo field during -payment detection. Futhermore, we can also ignore any information that is not directly -relevant to a Sapling shielded transaction. +A key observation in this protocol is that the ciphertext in each shielded output +is several hundred bytes long, due to the inclusion of a transaction memo. The need to +download the entire ciphertext imposes a substantial bandwidth cost on each light wallet, +which may be a limited mobile device on a restricted-bandwidth plan. The memo field can be +ignored during payment detection, as can any information that is not directly relevant to +shielded transaction processing. A **compact block** is a packaging of ONLY the data from a block needed to: -1. Detect a payment to your shielded Sapling address -2. Detect a spend of your shielded Sapling notes -3. Update your witnesses to generate new Sapling spend proofs. - -A compact block and its component compact transactions are encoded on the wire using the -following Protocol Buffers [#protocolbuffers]_ format: +1. Detect a payment to your shielded address (Sapling or Orchard). +2. Detect a spend of your shielded notes. +3. Update note commitment tree witnesses. -.. code:: proto +Compact blocks and their component compact transactions are encoded on the wire using +Protocol Buffers [#protocolbuffers]_. The normative definitions of the compact block format +are specified in the ``compact_formats.proto`` file of the Zcash Light Client Protocol +[#lightwallet-protocol]_. - message BlockID { - uint64 blockHeight = 1; - bytes blockHash = 2; - } +The protocol has evolved through several major versions: - message CompactBlock { - BlockID id = 1; - repeated CompactTx vtx = 3; - } +- **v0.3.x** (2021–2023): Added Orchard shielded protocol support via + ``CompactOrchardAction`` and note commitment tree state queries + (``GetTreeState``, ``GetLatestTreeState``) and subtree root retrieval + (``GetSubtreeRoots``) to support efficient witness construction. - message CompactTx { - uint64 txIndex = 1; - bytes txHash = 2; +- **v0.4.x** (2025): Added transparent transaction data (``CompactTxIn``, ``TxOut``) to + compact blocks, enabling transparent outputs to be discovered without revealing + the client's addresses to the light wallet server. The update also added + ``PoolType``-based filtering for selective retrieval by shielded protocol. - repeated CompactSpend spends = 3; - repeated CompactOutput outputs = 4; - } - - message CompactSpend { - bytes nf = 1; - } - - message CompactOutput { - bytes cmu = 1; - bytes epk = 2; - bytes ciphertext = 3; - } - -Encoding Details ----------------- - -``blockHash``, ``txHash``, ``nf``, ``cmu``, and ``epk`` are encoded as -specified in the Zcash Protocol Spec. - -The output and spend descriptions are handled differently, as described in the following -sections. +The following sections describe the rationale for the compact format design. Output Compression ------------------ -In the normal Zcash protocol, the output ciphertext consists of the AEAD-encrypted form of -a *note plaintext* [#protocol-notept]_: - -+------------+----------+----------+---------------+-----------------------------------+ -| 8-bit 0x01 | 88-bit d | 64-bit v | 256-bit rseed | memo (512 bytes) + tag (16 bytes) | -+------------+----------+----------+---------------+-----------------------------------+ - -A recipient detects their transactions by trial-decrypting this ciphertext. On a full node -that has the entire block chain, the primary cost is computational. For light clients -however, there is an additional bandwidth cost: every ciphertext on the block chain must -be received from the server (or network node) the light client is connected to. This -results in a total of 580 bytes per output that must be streamed to the client (in addition -to the 32-byte ephemeral public key). - -However, we don't need all of that just to detect payments. The first 52 bytes of the -ciphertext contain the contents and opening of the note commitment, which is all of the -data needed to spend the note and to verify that the note is spendable. If we ignore the -memo and the authentication tag, we're left with a 32-byte ephemeral public key, the 32-byte -note commitment, and only the first 52 bytes of the ciphertext for each output needed to -decrypt, verify, and spend a note. This totals to 116 bytes per output, for an 80% -reduction in bandwidth use. - -However, skipping the full ciphertext means that we can no longer calculate the -authentication tag for the entire ciphertext and will need to do something else to -validate the integrity of the decrypted note plaintext. - -Since the note commitment is sent outside the ciphertext and is authenticated by the -binding signature over the entire transaction, it serves as an adequate check on the -validity of the decrypted plaintext (assuming you trust the entity assembling -transactions). We therefore recalculate the note commitment from the decrypted plaintext. -If the recalculated commitment matches the one in the output, we accept the note as valid -and spendable. +In the Zcash protocol, each shielded output contains an AEAD-encrypted *note +plaintext* [#protocol-notept]_. For both Sapling and Orchard, this ciphertext +(``encCiphertext``) is 580 bytes long and includes the memo field and an +authentication tag. + +A recipient detects their transactions by trial-decrypting this ciphertext. On +a full node that has the entire block chain, the primary cost is computational. +For light clients however, there is an additional bandwidth cost: every +ciphertext on the block chain must be received from the server (or network +node) the light client is connected to. + +However, payment detection does not require the full ciphertext. The first 52 +bytes of ``encCiphertext`` contain the contents and opening of the note +commitment, which is sufficient to detect, verify, and spend the note. The +compact representation therefore includes only these 52 bytes along with the +extracted note commitment and ephemeral public key. + +For **Sapling**, a ``CompactSaplingOutput`` contains the 32-byte extracted note +commitment ($\mathtt{cmu}$), the 32-byte ephemeral public key, and the first 52 +bytes of the ciphertext, for a total of 116 bytes per output. A full Sapling +Output description [#protocol-outputencodingandconsensus]_ is 948 bytes in v4 +transactions (756 bytes in v5), so the compact representation provides an 85–88% +reduction in bandwidth. + +For **Orchard**, a ``CompactOrchardAction`` contains the 32-byte nullifier, the +32-byte extracted note commitment ($\mathtt{cmx}$), the 32-byte ephemeral public +key, and the first 52 bytes of the ciphertext, for a total of 148 bytes per +action. A full Orchard Action description [#protocol-actionencodingandconsensus]_ +is 820 bytes, so the compact representation provides an 82% reduction in +bandwidth. Note that the Orchard compact representation includes the nullifier +because the Orchard Action structure combines a spend and an output into a +single description. + +Since the compact representation omits the authentication tag, the integrity of +the decrypted note plaintext is instead validated by recalculating the note +commitment from the decrypted plaintext and checking it against the note +commitment sent outside the ciphertext, which is authenticated by the binding +signature over the entire transaction. Spend Compression ----------------- -Recall that a full Sapling Spend description is 384 bytes long [#protocol-spendencodingandconsensus]_: - -+-------+--------------+-----------+ -| Bytes | Name | Type | -+=======+==============+===========+ -| 32 | cv | char[32] | -+-------+--------------+-----------+ -| 32 | anchor | char[32] | -+-------+--------------+-----------+ -| 32 | nullifier | char[32] | -+-------+--------------+-----------+ -| 32 | rk | char[32] | -+-------+--------------+-----------+ -| 192 | zkproof | char[192] | -+-------+--------------+-----------+ -| 64 | spendAuthSig | char[64] | -+-------+--------------+-----------+ - -The only part necessary for detection is the nullifier, which allows a light client to -detect when one of its own notes has been spent. This means we only need to take 32 bytes -of each Spend, for a 90% improvement in bandwidth use. +For **Sapling**, a full Spend description is 384 bytes long +[#protocol-spendencodingandconsensus]_. The only part necessary for spend +detection is the 32-byte nullifier, which allows a light client to detect when +one of its own notes has been spent. A ``CompactSaplingSpend`` therefore +contains only the nullifier, for a 92% reduction in bandwidth. + +For **Orchard**, spend detection is handled by checking the nullifier included +in each ``CompactOrchardAction`` (as described in `Output Compression`_ above), +since the Orchard Action structure combines a spend and an output. Proxy operation =============== @@ -202,46 +183,10 @@ Zcash node and any number of light clients. It accomplishes this by parsing a bl stream of transactions from the node and converting them into the compact format described above. -*The details of the API described below may differ from the implementation.* - -The proxy offers the following API to clients: - -.. code:: proto - - service CompactTxStreamer { - rpc GetLatestBlock(ChainSpec) returns (BlockID) {} - rpc GetBlock(BlockID) returns (CompactBlock) {} - rpc GetBlockRange(RangeFilter) returns (stream CompactBlock) {} - rpc GetTransaction(TxFilter) returns (FullTransaction) {} - } - - // Remember that proto3 fields are all optional. - - // Someday we may want to specify e.g. a particular chain fork. - message ChainSpec {} - - - // A BlockID message contains identifiers to select a block: either a - // height or a hash. - message BlockID { - uint64 blockHeight = 1; - bytes blockHash = 2; - } - - - message RangeFilter { - BlockID start = 1; - BlockID end = 2; - } - - // A TxFilter contains the information needed to identify a particular - // transaction: either a block and an index, or a direct transaction hash. - message TxFilter { - BlockID blockID = 1; - uint64 txIndex = 2; - bytes txHash = 3; - } - +The proxy offers a gRPC API to clients, defined by the ``service.proto`` file of the +Zcash Light Client Protocol [#lightwallet-protocol]_. The ``CompactTxStreamer`` service +provides methods for retrieving compact blocks, submitting transactions, querying note +commitment tree state, and querying for transparent outputs by address. Client operation ================ @@ -253,77 +198,95 @@ server here without loss of generality. Local processing ---------------- -Given a ``CompactBlock`` at block height $\mathsf{height}$ received in height-sequential -order from a proxy server, a light client can process it in four ways: +Given a ``CompactBlock`` at block height $\mathsf{height}$ received from a proxy server, a +light client processes it in the following ways: Scanning for relevant transactions `````````````````````````````````` -For every ``CompactOutput`` in the ``CompactBlock``, the light client can trial-decrypt it -against a set of Sapling incoming viewing keys. The procedure for trial-decrypting a -``CompactOutput`` $(\mathtt{cmu}, \mathtt{ephemeralKey}, \mathsf{ciphertext})$ with an incoming -viewing key $\mathsf{ivk}$ is a slight deviation from the standard decryption process -[#protocol-saplingdecryptivk]_ (all constants and algorithms are as defined there): - -- let $\mathsf{epk} = \mathsf{abst}_{\mathbb{J}}(\mathtt{ephemeralKey})$ -- if $\mathsf{epk} = \bot$, return $\bot$ -- let $\mathsf{sharedSecret} = \mathsf{KA^{Sapling}.Agree}(\mathsf{ivk}, \mathsf{epk})$ -- let $K^{\mathsf{enc}} = \mathsf{KDF^{Sapling}}(\mathsf{sharedSecret}, \mathtt{ephemeralKey})$ -- let $P^{\mathsf{enc}} = \mathsf{ChaCha20.Decrypt}_{K^{\mathsf{enc}}}(\mathsf{ciphertext})$ -- extract $\mathbf{np} = (\mathsf{leadByte}, \mathsf{d}, \mathsf{v}, \mathsf{rseed})$ from $P^{\mathsf{enc}}$ -- [Pre-Canopy] if $\mathsf{leadByte} \neq 0x01$, return $\bot$ -- [Pre-Canopy] let $\mathsf{\underline{rcm}} = \mathsf{rseed}$ -- [Canopy onward] if $\mathsf{height} < \mathsf{CanopyActivationHeight} + \mathsf{ZIP212GracePeriod}$ and $\mathsf{leadByte} \not\in \{ \mathtt{0x01}, \mathtt{0x02} \}$, return $\bot$ -- [Canopy onward] if $\mathsf{height} < \mathsf{CanopyActivationHeight} + \mathsf{ZIP212GracePeriod}$ and $\mathsf{leadByte} \neq \mathtt{0x02}$, return $\bot$ -- [Canopy onward] let $\mathsf{\underline{rcm}} = \begin{cases}\mathsf{rseed}, &\text{if } \mathsf{leadByte} = \mathtt{0x01} \\ \mathsf{ToScalar}(\mathsf{PRF^{expand}_{rseed}}([5])), &\text{otherwise}\end{cases}$ -- let $\mathsf{rcm} = \mathsf{LEOS2IP}_{256}(\mathsf{\underline{rcm}})$ and $\mathsf{g_d} = \mathsf{DiversifyHash}(\mathsf{d})$ -- if $\mathsf{rcm} \geq r_{\mathbb{J}}$ or $\mathsf{g_d} = \bot$, return $\bot$ -- [Canopy onward] if $\mathsf{leadByte} \neq \mathtt{0x01}$: - - * $\mathsf{esk} = \mathsf{ToScalar}(\mathsf{PRF^{expand}_{rseed}}([4]))$ - * if $\mathsf{repr}_{\mathbb{J}}(\mathsf{KA^{Sapling}.DerivePublic}(\mathsf{esk}, \mathsf{g_d})) \neq \mathtt{ephemeralKey}$, return $\bot$ - -- let $\mathsf{pk_d} = \mathsf{KA^{Sapling}.DerivePublic}(\mathsf{ivk}, \mathsf{g_d})$ -- let $\mathsf{cm}_u' = \mathsf{Extract}_{\mathbb{J}^{(r)}}(\mathsf{NoteCommit^{Sapling}_{rcm}}(\mathsf{repr}_{\mathbb{J}}(\mathsf{g_d}), \mathsf{repr}_{\mathbb{J}}(\mathsf{pk_d}), \mathsf{v}))$. -- if $\mathsf{LEBS2OSP}_{256}(\mathsf{cm}_u') \neq \mathtt{cmu}$, return $\bot$ -- return $\mathbf{np}$. - -Creating and updating note witnesses -```````````````````````````````````` -As ``CompactBlocks`` are received in height order, and the transactions within them have -their order preserved, the *cmu* values in each ``CompactOutput`` can be sequentially -appended to an incremental Merkle tree of depth 32 in order to maintain a local copy of -the Sapling note commitment tree. [#protocol-merkletree]_ This can then be used to -create incremental witnesses for each unspent note the light client is tracking. -[#incremental-witness]_ An incremental witness updated to height ``X`` corresponds to a -Merkle path from the note to the Sapling commitment tree anchor for block ``X``. -[#protocol-merklepath]_ - -Let ``tree`` be the Sapling note commitment tree at height ``X-1``, and ``note_witnesses`` -be the incremental witnesses for unspent notes detected up to height ``X-1``. When the -``CompactBlock`` at height ``X`` is received: - -- For each ``CompactTx`` in ``CompactBlock``: - - - For each ``CompactOutput`` (*cmu*, *epk*, *ciphertext*) in ``CompactBlock``: - - - Append ``cmu`` to ``tree``. - - For ``witness`` in ``note_witnesses``: - - - Append ``cmu`` to ``witness``. - - - If ``ciphertext`` contains a relevant note, create an incremental witness from - ``tree`` and append it to ``note_witnesses``. - -Incremental Merkle trees cannot be rewound, so the light client should cache both the -Sapling note commitment tree and per-note incremental witnesses for recent block heights. -Cache management is implementation-dependent, but a cache size of 100 is reasonable, as no -full Zcash node will roll back the chain by more than 100 blocks. + +For every ``CompactSaplingOutput`` and ``CompactOrchardAction`` in the ``CompactBlock``, +the light client attempts trial decryption against its set of incoming viewing keys. The +following subsection describes the compact trial decryption procedure for Sapling and +Orchard. + +Sapling and Orchard compact trial decryption +'''''''''''''''''''''''''''''''''''''''''''' + +For each Sapling or Orchard compact output, the light client trial-decrypts with each +incoming viewing key $\mathsf{ivk}$ for that output's shielded protocol. + +Let $\ell_{\mathsf{d}}$ be as defined in § 5.3 ‘Constants’. + +Let $\mathsf{ChaCha20.Decrypt_K}(\mathsf{C})$ be the unauthenticated decryption of +ciphertext $\mathsf{C} \;{\small ⦂}\; \mathbb{B}^{{\kern-0.05em\tiny\mathbb{Y}}[\mathbb{N}]}$ +with 256-bit key $\mathsf{K}$, using the stream cipher ChaCha20 as defined in +RFC 7539 [#RFC7539-ChaCha20]_ with the initial counter set to 1 (as defined for +the encryption component of AEAD_CHACHA20_POLY1305, but without the authentication +component). It returns a byte sequence of the same length as $\mathsf{C}$ and is +infallible. + +The procedure to trial-decrypt a compact output takes as input $\mathtt{cm*}$ (the +$\mathtt{cmu}$ or $\mathtt{cmx}$ field of the Output description or Action description +respectively), $\mathtt{ephemeralKey}$, the truncated ``encCiphertext`` +$\mathsf{C^{compact}} \;{\small ⦂}\; \mathbb{B}^{{\kern-0.05em\tiny\mathbb{Y}}[52]}$, +the shielded protocol ($\mathsf{Sapling}$ or $\mathsf{Orchard}$), block height, and +transaction version number, and an incoming viewing key $\mathsf{ivk}$, and outputs a +note for that shielded protocol. + +This procedure deviates from the standard note decryption using an Incoming Viewing Key, as specified in +§ 4.20.2 ‘Decryption using an Incoming Viewing Key (Sapling and Orchard)’ [#protocol-decryptivk]_, +only in how $\mathbf{np}$ is obtained: + +- let $\mathsf{P^{compact}} = \mathsf{ChaCha20.Decrypt_{K^{enc}}}(\mathsf{C^{compact}})$ +- extract $\mathbf{np} = (\mathsf{leadByte} \;{\small ⦂}\; \mathbb{B}^{\kern-0.05em\tiny\mathbb{Y}}, \mathsf{d} \;{\small ⦂}\; \mathbb{B}^{[\ell_{\mathsf{d}}]}, \mathsf{v} \;{\small ⦂}\; \{ 0 .. 2^{\ell_{\mathsf{value}}}\!-\!1 \}, \mathsf{rseed} \;{\small ⦂}\; \mathbb{B}^{{\kern-0.05em\tiny\mathbb{Y}}[32]})$ + from $\mathsf{P^{compact}}$ + +and in the fact that the last step returns only $\mathbf{n}$, rather than +$(\mathbf{n}, \mathsf{memo})$. + +Non-normative note: Previous versions of this ZIP reiterated the compact output +decryption procedure (for Sapling) rather than specifying the differences from +§ 4.20.2. This led to the following unintended differences: + +- The procedure described in this ZIP did not support pre-ZIP-212 Sapling outputs. +- The swapping of domain separators $[4]$ and $[5]$ in the $\mathsf{PRF^{expand}}$ + inputs used to derive $\mathsf{rcm}$ and $\mathsf{esk}$ for Sapling that was fixed in + `version 2023.4.0 of the protocol specification <https://zips.z.cash/protocol/protocol.pdf#2023.4.0>`_, + had not been fixed in this ZIP. +- The type error that was fixed in + `version 2025.6.2 of the protocol specification <https://zips.z.cash/protocol/protocol.pdf#2025.6.2>`_, + had not been fixed in this ZIP. +- The refactoring to use $\mathsf{allowedLeadBytes}$ also made in version 2025.6.2 + had not been applied. +- The last step incorrectly returned $\mathbf{np}$ rather than the note. + +Maintaining note commitment tree witnesses +`````````````````````````````````````````` +In order to spend a shielded note, the spender must produce a Merkle path +(witness) from the note's commitment to an anchor (a root of the note commitment +tree at some block height). [#protocol-merkletree]_ [#protocol-merklepath]_ +A light client MUST therefore maintain sufficient note commitment tree state to +produce witnesses for its unspent notes. + +The note commitments in each ``CompactBlock`` (the $\mathsf{cmu}$ values from +``CompactSaplingOutput`` entries and the $\mathsf{cmx}$ values from +``CompactOrchardAction`` entries) are inserted into the respective Sapling or +Orchard note commitment trees in the order they appear in the block. + +A light client that has scanned a contiguous range of blocks can construct +witnesses from the note commitments it has observed. However, a light client +need not have scanned the entire chain in order to construct witnesses: the +``GetSubtreeRoots`` RPC method [#lightwallet-protocol]_ provides roots of +completed subtrees of the note commitment trees, which can be combined with +locally-scanned commitments to produce valid witnesses even when portions of +the chain have not been scanned. Detecting spends ```````````````` -The ``CompactSpend`` entries can be checked against known local nullifiers, to for example -ensure that a transaction has been received by the network and mined. +The ``CompactSaplingSpend`` nullifier entries and the nullifier fields of +``CompactOrchardAction`` entries can be checked against the nullifiers +of notes known to the client to determine when those notes have been spent. Block header validation ``````````````````````` @@ -350,272 +313,152 @@ A ``CompactBlock`` that fails any of these checks MUST be discarded. If it was r part of a ``GetBlockRange`` call, the call MUST be aborted. Block header validation provides light clients with some assurance that the -``CompactOutputs`` being sent to them are indeed from valid blocks that have been mined. +compact outputs being sent to them are indeed from valid blocks that have been mined. The strongest-possible assurance is achieved when all block headers are synchronised; this comes at the cost of bandwidth and storage. By default, ``CompactBlocks`` only contain ``CompactTxs`` for transactions that contain -Sapling spends or outputs. Thus they do not contain sufficient information to validate +shielded spends or outputs. Thus they do not contain sufficient information to validate that the received transaction IDs correspond to the transaction tree root in the block header. This does not have a significant effect on light client security: light clients -only directly depend on ``CompactOutputs``, which can be authenticated via block header +only directly depend on compact outputs, which can be authenticated via block header validation. If a txid is used in a ``GetTransaction`` call, the returned transaction -SHOULD be checked against the corresponding ``CompactOutputs``, in addition to verifying +SHOULD be checked against the corresponding compact outputs, in addition to verifying the transaction signatures. -Potential extensions -```````````````````` - -A trivial extension (with corresponding bandwidth cost) would be to transmit empty -``CompactTxs`` corresponding to transactions that do not contain Sapling spends or -outputs. A more complex extension would send the inner nodes within the transaction -trees corresponding to non-Sapling-relevant subtrees; this would require strictly less -bandwidth that the trivial extension. These extensions are not currently defined. - - Client-server interaction ------------------------- -We can divide the typical client-server interaction into four distinct phases: - -.. code:: text - - Phase Client Server - ===== ============================ - A GetLatestBlock ------------> - - <---------------- BlockID(X) - - GetBlock(X) ---------------> - - <----------- CompactBlock(X) - - === - - B GetLatestBlock ------------> - - <---------------- BlockID(Y) - - GetBlockRange(X, Y) -------> - - <--------- CompactBlock(X) - <--------- CompactBlock(X+1) - <--------- CompactBlock(X+2) - ... - <--------- CompactBlock(Y-1) - <--------- CompactBlock(Y) - - === - - C GetTransaction(X+4, 7) ----> - - <--- FullTransaction(X+4, 7) - - GetTransaction(X+9, 2) ----> - - <--- FullTransaction(X+9, 2) - - === - - D GetLatestBlock ------------> - - <---------------- BlockID(Z) - - GetBlockRange(Y, Z) -------> - - <--------- CompactBlock(Y) - <--------- CompactBlock(Y+1) - <--------- CompactBlock(Y+2) - ... - <--------- CompactBlock(Z-1) - <--------- CompactBlock(Z) - -**Phase A:** The light client starts up for the first time. - -- The light client queries the server to fetch the most recent block ``X``. -- The light client queries the commitment tree state for block ``X``. - - - Or, it has to set ``X`` to the block height at which Sapling activated, so as to be - sent the entire commitment tree. [TODO: Decide which to specify.] - -- Shielded addresses created by the light client will not have any relevant transactions - in this or any prior block. - -**Phase B:** The light client updates its local chain view for the first time. - -- The light client queries the server to fetch the most recent block ``Y``. -- It then executes a block range query to fetch every block between ``X`` (inclusive) and - ``Y`` (inclusive). -- The block at height ``X`` is checked to ensure the received ``blockHash`` matches the - light client's cached copy, and then discards it without further processing. - - - An inconsistency would imply that block ``X`` was orphaned during a chain reorg. - -- As each subsequent ``CompactBlock`` arrives, the light client: - - - Validates the block header if it is present. - - Scans the ``CompactBlock`` to find any relevant transactions for addresses generated - since ``X`` was fetched (likely the first transactions involving those addresses). If - notes are detected, it: - - - Generates incremental witnesses for the notes, and updates them going forward. - - Scans for their nullifiers from that block onwards. - -**Phase C:** The light client has detected some notes and displayed them. User interaction -has indicated that the corresponding full transactions should be fetched. - -- The light client queries the server for each transaction it wishes to fetch. - -**Phase D:** The user has spent some notes. The light client updates its local chain view -some time later. - -- The light client queries the server to fetch the most recent block ``Z``. -- It then executes a block range query to fetch every block between ``Y`` (inclusive) and - ``Z`` (inclusive). -- The block at height ``Y`` is checked to ensure the received ``blockHash`` matches the - light client's cached copy, and then discards it without further processing. - - - An inconsistency would imply that block ``Y`` was orphaned during a chain reorg. - -- As each subsequent ``CompactBlock`` arrives, the light client: - - - Validates the block header if it is present. - - Updates the incremental witnesses for known notes. - - Scans for any known nullifiers. The corresponding notes are marked as spent at that - height, and excluded from further witness updates. - - Scans for any relevant transactions for addresses generated since ``Y`` was fetched. - These are handled as in phase B. - -Importing a pre-existing seed +A light client MUST perform the following operations as part of synchronisation: + +- **Trial decryption**: For each ``CompactSaplingOutput`` and ``CompactOrchardAction`` in + the scanned ``CompactBlocks``, the light client attempts trial decryption against its + set of incoming viewing keys. Successfully decrypted outputs represent received notes. + See `Scanning for relevant transactions`_ for details. + +- **Nullifier matching**: For each ``CompactSaplingSpend`` nullifier and + ``CompactOrchardAction`` nullifier in the scanned ``CompactBlocks``, the light client + checks against its set of known nullifiers for unspent notes. A match indicates that the + corresponding note has been spent. + +- **Note commitment tree maintenance**: The light client updates its local note commitment + tree state as described in `Maintaining note commitment tree witnesses`_. + +Retrieving full transactions +```````````````````````````` + +The compact block stream provides sufficient information for payment detection +and balance tracking, but does not include memo fields or other transaction +details. When a light client needs to access the memo field of a received note, +the outgoing ciphertexts for spend recovery, or other transaction components, +it retrieves the full transaction via the ``GetTransaction`` RPC method. + +Since a ``GetTransaction`` request reveals to the proxy server that the light +client is interested in a specific transaction, full transaction retrieval +SHOULD be performed over a privacy-preserving network connection such as Tor +[#tor]_ or the Nym mixnet [#nym]_. + +In addition, even when using a privacy-preserving network connection for these +operations, a client SHOULD NOT make transaction data requests to the proxy +server in a fashion that links the client's interest in that transaction to +other operations that the client may be performing in the clear. For example, +if a client uses an unprotected connection for compact block download, that +client SHOULD NOT retrieve full transaction data immediately upon transaction +discovery, but SHOULD defer it until a later time when the download will +not be identifiably related to the download of the compact block range in +which the transaction was discovered. + +Importing a pre-existing key ````````````````````````````` -Phase A of the interaction assumes that shielded addresses created by the light client -will have never been used before. This is not a valid assumption if the light client is -being initialised with a seed that it did not generate (e.g. a previously backed-up seed). -In this case, phase A is modified as follows: - -**Phase A:** The light client starts up for the first time. - -- The light client sets ``X`` to the block height at which Sapling activated. - - - Shielded addresses created by any light client cannot have any relevant transactions - prior to Sapling activation. +The light client's scanning start height (often called the "wallet birthday") +determines how far back the light client must scan to detect all relevant +transactions. When a light client is initialized with a newly generated key, it +can use the current chain tip as its wallet birthday, since no prior +transactions can be relevant. + +When initialized with a pre-existing key —whether a seed, a full viewing key, +or an incoming viewing key— the light client MUST scan from the key's +birthday height, which is the earliest block height at which the key could have +been used. If the birthday height is unknown and cannot be estimated by the +user, the light client SHOULD scan from the activation height of the earliest +shielded protocol supported by the key (Sapling activation at block 419200 on +Mainnet, or Orchard activation at block 1687104 on Mainnet). Block privacy via bucketing --------------------------- *This section describes a proposed enhancement that has not been implemented.* -The above interaction reveals to the server at the start of each synchronisation phase (B -and D) the block height which the light client had previously synchronised to. This is an -information leak under our security model (assuming network privacy). We can reduce the -information leakage by "bucketing" the start point of each synchronisation. Doing so also -enables us to handle most chain reorgs simultaneously. +When a light client requests a range of compact blocks via ``GetBlockRange``, the start +height of the request reveals to the proxy server the block height to which the light +client had previously synchronised. This is an information leak under our security model +(assuming network privacy). The information leakage can be reduced by "bucketing" the +start point of each synchronisation request. Let ``⌊X⌋ = X - (X % N)`` be the value of ``X`` rounded down to some multiple of the -bucket size ``N``. The synchronisation phases from the above interaction are modified as -follows: - -.. code:: text - - Phase Client Server - ===== ============================ - B GetLatestBlock ------------> - - <---------------- BlockID(Y) - - GetBlockRange(⌊X⌋, Y) -----> - - <-------- CompactBlock(⌊X⌋) - <-------- CompactBlock(⌊X⌋+1) - <-------- CompactBlock(⌊X⌋+2) - ... - <-------- CompactBlock(Y-1) - <-------- CompactBlock(Y) - - === - - D GetLatestBlock ------------> - - <---------------- BlockID(Z) - - GetBlockRange(⌊Y⌋, Z) -----> - - <-------- CompactBlock(⌊Y⌋) - <-------- CompactBlock(⌊Y⌋+1) - ... - <-------- CompactBlock(Z-1) - <-------- CompactBlock(Z) - -**Phase B:** The light client updates its local chain view for the first time. - -- The light client queries the server to fetch the most recent block ``Y``. -- It then executes a block range query to fetch every block between ``⌊X⌋`` (inclusive) - and ``Y`` (inclusive). -- Blocks between ``⌊X⌋`` and ``X`` are checked to ensure that the received ``blockHash`` - matches the light client's chain view for each height, and are then discarded without - further processing. - - - If an inconsistency is detected at height ``Q``, the light client sets ``X = Q-1``, - discards all local blocks with height ``>= Q``, and rolls back the state of all local - transactions to height ``Q-1`` (un-mining them as necessary). - -- Blocks between ``X+1`` and ``Y`` are processed as before. - -**Phase D:** The user has spent some notes. The light client updates its local chain view -some time later. - -- The light client queries the server to fetch the most recent block ``Z``. -- It then executes a block range query to fetch every block between ``⌊Y⌋`` (inclusive) - and ``Z`` (inclusive). -- Blocks between ``⌊Y⌋`` and ``Y`` are checked to ensure that the received ``blockHash`` - matches the light client's chain view for each height, and are then discarded without - further processing. - - - If an inconsistency is detected at height ``R``, the light client sets ``Y = R-1``, - discards all local blocks with height ``>= R``, and rolls back the following local - state to height ``R-1``: - - - All local transactions (un-mining them as necessary). - - All tracked nullifiers (unspending or discarding as necessary). - - All incremental witnesses (caching strategies are not covered in this ZIP). - -- Blocks between ``Y+1`` and ``Z`` are processed as before. +bucket size ``N``. Instead of requesting blocks starting from the light client's last +synchronised height ``X``, the light client requests blocks starting from ``⌊X⌋``. Blocks +between ``⌊X⌋`` and ``X`` are checked against the light client's cached chain view for +consistency and then discarded without further processing. This also helps detect chain +reorganisations that occurred within the bucket window. Transaction privacy ------------------- -The synchronisation phases give the light client sufficient information to determine +The compact block stream gives the light client sufficient information to determine accurate address balances, show when funds were received or spent, and spend any unspent -notes. As synchronisation happens via a broadcast medium, it leaks no information about -which transactions the light client is interested in. - -If, however, the light client needs access to other components of a transaction (such as -the memo fields for received notes, or the outgoing ciphertexts in order to recover spend -information when importing a wallet seed), it will need to download the full transaction. -The light client SHOULD obscure the exact transactions of interest by downloading numerous -uninteresting transactions as well, and SHOULD download all transactions in any block from -which a single full transaction is fetched (interesting or otherwise). It MUST convey to -the user that fetching full transactions will reduce their privacy. +notes. Since synchronisation happens via a broadcast medium (the compact block stream), it +leaks no information about which transactions the light client is interested in. + +However, other API methods reveal more specific interests to the proxy server: + +- ``GetTransaction`` reveals interest in a specific transaction. See + `Retrieving full transactions`_ for mitigation recommendations. +- ``SendTransaction`` reveals that the light client is the sender of a transaction. + Transaction submission SHOULD be performed over a privacy-preserving network connection + such as Tor [#tor]_ or the Nym mixnet [#nym]_. +- Transparent address queries (``GetTaddressTransactions``, ``GetTaddressBalance``, + ``GetAddressUtxos``) reveal the transparent addresses held by the light client. These + queries SHOULD be performed over a privacy-preserving network connection such as + Tor [#tor]_ or the Nym mixnet [#nym]_. +- If a client queries for transactions related to multiple transparent addresses in + a single request, this will leak the fact that those addresses are controlled by + the same client. If the ``GetTaddressTransactions``, ``GetTaddressBalance`` + or ``GetAddressUtxos`` methods are used, even over a privacy-preserving network + connect, the client SHOULD NOT request data for more than one address at a time + and SHOULD make such requests in a fashion that is not temporally linkable to + other operation of the wallet. +- ``GetMempoolTx`` and ``GetMempoolStream`` reveal the light client's interest in + unconfirmed transactions. Reference Implementation ======================== -This proposal is supported by a set of libraries and reference code made available by the -Electric Coin Company. +- `lightwalletd <https://github.com/zcash/lightwalletd>`_ (proxy server) +- `zcash_client_backend <https://github.com/zcash/librustzcash/tree/main/zcash_client_backend>`_ (light client scanning library) References ========== .. [#BCP14] `Information on BCP 14 — "RFC 2119: Key words for use in RFCs to Indicate Requirement Levels" and "RFC 8174: Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words" <https://www.rfc-editor.org/info/bcp14>`_ -.. [#protocol-merkletree] `Zcash Protocol Specification, Version 2025.6.1 [NU6.1]. Section 3.8: Note Commitment Trees <protocol/protocol.pdf#merkletree>`_ -.. [#protocol-merklepath] `Zcash Protocol Specification, Version 2025.6.1 [NU6.1]. Section 4.9: Merkle Path Validity <protocol/protocol.pdf#merklepath>`_ -.. [#protocol-saplingdecryptivk] `Zcash Protocol Specification, Version 2025.6.1 [NU6.1]. Section 4.20.2: Decryption using an Incoming Viewing Key (Sapling) <protocol/protocol.pdf#saplingdecryptivk>`_ -.. [#protocol-notept] `Zcash Protocol Specification, Version 2025.6.1 [NU6.1]. Section 5.5: Encodings of Note Plaintexts and Memo Fields <protocol/protocol.pdf#notept>`_ -.. [#protocol-spendencodingandconsensus] `Zcash Protocol Specification, Version 2025.6.1 [NU6.1]. Section 7.3: Spend Description Encoding and Consensus <protocol/protocol.pdf#spendencodingandconsensus>`_ -.. [#protocol-outputencodingandconsensus] `Zcash Protocol Specification, Version 2025.6.1 [NU6.1]. Section 7.4: Output Description Encoding and Consensus <protocol/protocol.pdf#outputencodingandconsensus>`_ +.. [#RFC7539-ChaCha20] `RFC 7539: ChaCha20 and Poly1305 for IETF Protocols. Sections 2.1 to 2.4 inclusive. <https://www.rfc-editor.org/rfc/rfc7539.html#section-2.1>`_ +.. [#protocol] `Zcash Protocol Specification, Version 2025.6.3 [NU6.1] or later <protocol/protocol.pdf>`_ +.. [#protocol-merkletree] `Zcash Protocol Specification, Version 2025.6.3 [NU6.1]. Section 3.8: Note Commitment Trees <protocol/protocol.pdf#merkletree>`_ +.. [#protocol-networks] `Zcash Protocol Specification, Version 2025.6.3 [NU6.1]. Section 3.12: Mainnet and Testnet <protocol/protocol.pdf#networks>`_ +.. [#protocol-merklepath] `Zcash Protocol Specification, Version 2025.6.3 [NU6.1]. Section 4.9: Merkle Path Validity <protocol/protocol.pdf#merklepath>`_ +.. [#protocol-decryptivk] `Zcash Protocol Specification, Version 2025.6.3 [NU6.1]. Section 4.20.2: Decryption using an Incoming Viewing Key (Sapling and Orchard) <protocol/protocol.pdf#decryptivk>`_ +.. [#protocol-concreteorchardkeyagreement] `Zcash Protocol Specification, Version 2025.6.3 [NU6.1]. Section 5.4.6.4: Orchard Key Agreement <protocol/protocol.pdf#concreteorchardkeyagreement>`_ +.. [#protocol-notept] `Zcash Protocol Specification, Version 2025.6.3 [NU6.1]. Section 5.5: Encodings of Note Plaintexts and Memo Fields <protocol/protocol.pdf#notept>`_ +.. [#protocol-spendencodingandconsensus] `Zcash Protocol Specification, Version 2025.6.3 [NU6.1]. Section 7.3: Spend Description Encoding and Consensus <protocol/protocol.pdf#spendencodingandconsensus>`_ +.. [#protocol-outputencodingandconsensus] `Zcash Protocol Specification, Version 2025.6.3 [NU6.1]. Section 7.4: Output Description Encoding and Consensus <protocol/protocol.pdf#outputencodingandconsensus>`_ +.. [#protocol-actionencodingandconsensus] `Zcash Protocol Specification, Version 2025.6.3 [NU6.1]. Section 7.5: Action Description Encoding and Consensus <protocol/protocol.pdf#actionencodingandconsensus>`_ +.. [#zip-0205] `ZIP 205: Deployment of the Sapling Network Upgrade <zip-0205.rst>`_ .. [#zip-0212] `ZIP 212: Allow Recipient to Derive Sapling Ephemeral Secret from Note Plaintext <zip-0212.rst>`_ +.. [#zip-0224] `ZIP 224: Orchard Shielded Protocol <zip-0224.rst>`_ +.. [#lightwallet-protocol] `Zcash Light Client Protocol <https://github.com/zcash/lightwallet-protocol>`_ .. [#protocolbuffers] `Protocol Buffers documentation <https://developers.google.com/protocol-buffers/>`_ -.. [#incremental-witness] `zcash_primitives Rust crate — merkle_tree.rs <https://github.com/zcash/librustzcash/blob/master/zcash_primitives/src/merkle_tree.rs>`_ .. [#spv-clients] `Bitcoin Wiki: Scalability — Simplified payment verification <https://en.bitcoin.it/wiki/Scalability#Simplified_payment_verification>`_ +.. [#tor] `Tor Project <https://www.torproject.org/>`_ +.. [#nym] `Nym mixnet <https://nymtech.net/>`_