ZIP: 231
Title: Memo Bundles
Owners: Jack Grigg <[email protected]>
Kris Nuttycombe <[email protected]>
Daira-Emma Hopwood <[email protected]>
Arya Solhi <[email protected]>
Credits: Sean Bowe
Nate Wilcox
Status: Draft
Category: Consensus / Wallet
Created: 2024-04-26
License: MIT
Discussions-To: <https://github.com/zcash/zips/issues/627>
The key words “MUST”, “MUST NOT”, “SHOULD”, 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 term “network upgrade” in this document is to be interpreted as described in ZIP 200. 2
The character § is used when referring to sections of the Zcash Protocol Specification. 3
The terms “Mainnet” and “Testnet” are to be interpreted as described in § 3.12 ‘Mainnet and Testnet’. 4
Currently, the memo associated with a shielded output is fixed to 512 bytes of data.
This ZIP proposes to decouple memo data from outputs by introducing a per-transaction memo bundle. Each shielded output carries a 32-byte memo key rather than an inline 512-byte memo field. The memo key is used to derive a symmetric encryption key that decrypts the output’s memo from the bundle, which consists of individually encrypted 256-byte chunks. This will:
In Zcash transaction versions v2 to v5 inclusive, each Sapling or Orchard shielded output contains a ciphertext comprised of a 52-byte note plaintext and corresponding 512-byte memo field, with a 16-byte authentication tag 5. Recipients can only decrypt the outputs sent to them, and thus can also only observe the memo fields included with the outputs they can decrypt.
An ordinary transaction is one that is padded to 2 inputs and 2 outputs (or 2 Orchard actions), so as to make transfers that spend a note without change indistinguishable from an ordinary transaction with change. Such a transaction will consume 1024 bytes of block space for memo data. An Orchard transfer that spends a larger number of notes will incur a memo data cost of \(512 \times n\) bytes on-chain, where \(n\) is the number of notes being spent as inputs. Ordinarily, most of this data space is wasted, as most wallets support only a single 512-byte memo to be sent to the recipient of the transfer. Virtually all transactions with shielded components consume at least 1024 bytes of block space for memo data, and between half and 3/4 of that data payload is ordinarily waste. Adopting this ZIP will eliminate a significant source of chain bloat.
Beyond reducing chain bloat, decoupling memos from shielded outputs allows for some important use cases to be served. The shielded transaction protocol always hides the sender(s) (that is, the addresses corresponding to the keys used to spend the input notes) from all of the recipients. For certain kinds of transactions, it is desirable to make one or more sender addresses available to one or more recipients (for example, a reply address). In such circumstances it is important to authenticate the sender address(es), to give the recipient a guarantee that the address is controlled by a sender of the transaction; failure to authenticate this address can enable phishing attacks. These Authenticated Reply Addresses require zero-knowledge proofs, and for the Orchard protocol these proofs are too large to fit into the existing 512-byte memo field. This ZIP makes the maximum size of memo data large enough to support the inclusion of such proofs.
Another motivation is that it is desirable, for clients with more stringent bandwidth constraints, to be able to transmit encrypted notes to the client without including the encrypted memo data. In the current light client protocol 6, this is done by truncating the note ciphertext to just the part that encrypts the note plaintext, removing the 512-byte encrypted memo and the 16-byte AEAD authentication tag. Because the authentication tag is not transmitted to the light client, the decryption performed by the client does not meet the standard IND-CCA2 ∧ INT-CTXT security notion for an authenticated encryption scheme 7.
By decoupling memo data from note ciphertexts, this proposal reduces the v6-onward note ciphertext to 100 bytes (excluding any other changes to the note plaintext proposed by other ZIPs). This makes it practical for the light client protocol to transmit complete note ciphertexts, including the AEAD tag, in exchange for a relatively minor bandwidth increase compared to the current truncated ciphertexts. This allows light clients to perform fully authenticated decryption, ensuring security against chosen ciphertext attacks and simplifying the security argument.
Instead of the memo data, this ZIP proposes that it is possible to indicate whether a memo is present for the recipient. When using the light client protocol, a recipient need not download full transaction information if this indication tells them that they have not received any memo in the transaction.
In addition to reducing chain bloat by eliminating wasted memo space, this ZIP defines a mechanism by which validating nodes may reduce their long-term storage requirements by pruning memo data, without impairing their ability to validate the entire chain.
Finally, at present, it is not possible to transmit the same memo data to multiple transaction recipients without redundantly encoding that data, and sending memo data greater than 512 bytes requires sending multiple outputs; the problem is compounded when attempting to send more than 512 bytes to each recipient. By separating memo data from the decryption capability for those memos, it admits a greater variety of applications that utilize memo data, while decreasing the amount of data that needs to be stored on-chain overall.
Prior to the activation of this ZIP, every shielded output has an associated memo field. A chain observer can therefore infer a likely 1:1 correlation between transaction recipients and memo payloads. The maximum number of distinct memos is precisely known, and the bounds on possible memo sizes are very small (between 0 and 512 bytes). It is possible to send additional data to a single recipient by adding (potentially zero-valued) outputs; on-chain this is indistinguishable from sending more memos to more recipients or (in the case of Orchard) spending more input notes.
After the activation of this ZIP, recipient count and memo count are decoupled. It becomes possible to construct transactions for which there are many more recipients than distinct memos, or vice versa. Without mitigation, this could result in more observably distinct patterns relating the number of recipients to the number of possible memos. In particular, a v6-onward transaction with no memo would be distinguishable from one with a memo.
To mitigate this, a v6-onward transaction with any shielded outputs is required to include at least 2 memo chunks in its memo bundle and pad the memo to a multiple of 2 chunks (see Memo bundle padding). This ensures that the common use case of sending a transaction without including explicit memo data is indistinguishable from sending a transaction with a short memo, as is currently supported by all wallets in common use.
If a wallet includes an authenticated reply-to address, however, this may be distinguishable from other ordinary wallet behaviour because the memo data (including the reply-to address) necessarily exceeds 512 bytes. While it would be possible to require transactions be padded to at least 16 chunks to ameliorate this potential distinguisher, this introduces an unreasonable amount of waste in the case of ordinary transactions.
On the flip side, a chain observer now only knows upper bounds on the amount of memo data being conveyed, and the number of possible distinct memos. They have no information about how memo data is split among the recipients. This can provide a privacy improvement in some situations. For example, it is not possible to distinguish between many recipients receiving many small memos, and the same set of recipients receiving one large shared memo.
In summary, when sending large amounts of memo data, the change introduced by this ZIP eliminates a potential distinguisher along one axis in exchange for a potential distinguisher along another.
Since this proposal is defined only for v6 and later transactions, it is not necessary to consider Sprout JoinSplit outputs. The following sections apply to both Sapling and Orchard outputs in v6-onward transactions.
A memo bundle consists of a sequence of 272-byte memo chunks, each encrypting a 256-byte chunk of memo plaintext. These memo chunks represent zero or more encrypted memos.
Each v6-onward transaction may contain a single memo bundle, and a memo bundle may contain at most \(\mathsf{memo\_chunk\_limit}\) = 64 memo chunks. This limits the total amount of memo data that can be conveyed within a single transaction to \(\mathsf{memo\_chunk\_limit}\) × 256 = 16384 bytes, or 16 KiB.
Memo bundles are encoded in transactions in a prunable manner: the entire memo bundle can be replaced by a single digest.
During transaction construction, each output with memo data is assigned a 32-byte memo key \(\mathsf{K^{memo}}\), as follows:
A single memo key MUST NOT be used to encrypt more than one memo within a single transaction. To share a memo among multiple recipients, the same \(\mathsf{K^{memo}}\) value is placed in each recipient’s note plaintext; this does not violate the above constraint because all recipients decrypt the same memo.
In note plaintexts of v6-onward transactions, the 512-byte memo field is replaced by the 32-byte \(\mathsf{K^{memo}}\).
The explicit encodings of the note plaintexts for Sapling and Orchard outputs are specified in ZIP 230 8.
The transaction builder generates a 32-byte salt value \(\mathsf{salt}\) from a CSPRNG. A new salt MUST be generated for each memo bundle.
The symmetric encryption key for a memo is derived from its \(\mathsf{K^{memo}}\) as follows:
\(\hspace{2em}\mathsf{encryption\_key} = \mathsf{PRF^{expand}_{K^{memo}}}([\mathtt{0xE0}] \,||\, \mathsf{salt})\)
The first byte \(\mathtt{0xE0}\) should be added to the documentation of inputs to \(\mathsf{PRF^{expand}}\) in § 4.1.2 ‘Pseudo Random Functions’ 9.
If the generated key \(\mathsf{encryption\_key}\) is 32 \(\mathtt{0xFF}\) bytes, the transaction constructor SHOULD repeat this procedure with a different salt, in order to avoid the recipient misinterpreting the output as having no memo data. Since that has negligible probability, it alternatively could omit this check.
Each memo MUST have a length that is a multiple of 256 bytes. (For UTF-8 text memos, padding is performed at the application level as specified in ZIP 302 10.) The memo is split into 256-byte plaintext chunks. Each plaintext chunk is encrypted with ChaCha20Poly1305 11 as follows:
\(\hspace{2em}\mathsf{IETF\_AEAD\_CHACHA20\_POLY1305}(\mathsf{encryption\_key}, \mathsf{nonce}, \mathsf{memo\_chunk})\)
where \(\mathsf{nonce} = \mathsf{I2BEOSP}_{88}(\mathsf{counter}) \,||\, [\mathsf{is\_final\_chunk}]\).
The resulting 272-byte ciphertext is referred to as a memo chunk:
This is a variant of the STREAM construction 12.
Finally, the encrypted memo chunks for all memos are combined into a single sequence using an order-preserving shuffle. Memo chunks from different memos MAY be interleaved in any order, but memo chunks from the same memo MUST have the same relative order.
Given a set of memos, each broken into a sequence of chunks, the shuffle algorithm SHOULD select each successive chunk by choosing among the sequences with probability weighted by each sequence’s remaining length. That is, given \(N\) lists with remaining lengths \(l_1, l_2, \ldots, l_N\), the next chunk is drawn from list \(i\) with probability \(l_i / \sum_j l_j\). This can be implemented by generating a uniform random index \(r\) in \([0, \sum_j l_j)\) and selecting the list \(i\) such that \(\sum_{j<i} l_j \leq r < \sum_{j \leq i} l_j\).
The shuffle algorithm MUST ensure that a memo recipient learns only (a) the number of chunks in their own memo and (b) the total number of chunks they cannot decrypt, but gains no information about how the undecryptable chunks are distributed among other memos. The above weighting satisfies this property.
The following diagram shows an example shuffle for three memos:
[
(memo_a, 0),
(memo_b, 0),
(memo_a, 1),
(memo_c, 0),
(memo_c, 1),
(memo_a, 2),
]
If the transaction contains any shielded outputs, the memo bundle MUST contain an even number of memo chunks, with a minimum of 2. If the number of memo chunks resulting from the encryption and shuffle steps above does not satisfy this requirement, the transaction builder MUST append padding chunks generated by encrypting random memo data using a random memo key generated using a CSPRNG until the total is even and at least 2, prior to shuffling. Padding chunks generated in this fashion will be indistinguishable from real encrypted memo chunks to an observer who does not hold the memo key for those chunks.
When a recipient decrypts a shielded output, they obtain a memo key \(\mathsf{K^{memo}}\).
If \(\mathsf{K^{memo}}\) consists of 32 \(\mathtt{0xFF}\) bytes, no memo is associated with
this output and memo decryption SHOULD NOT be performed. If the memo bundle has
been pruned (i.e. \(\mathtt{fAllPruned} = 1\)), memo decryption cannot be
attempted, as no memo chunk data is available. Otherwise, the recipient derives
encryption_key as above, and then proceeds as follows:
\(\hspace{1.5em}\) let mutable \(\mathsf{memo} \;{\small ⦂}\; \mathbb{B}^* \leftarrow [] \\\) \(\hspace{1.5em}\) let mutable \(\mathsf{counter} \;{\small ⦂}\; \mathbb{N} \leftarrow 0 \\\) \(\hspace{1.5em}\) let mutable \(\mathsf{potential\_last\_chunk\_index} \;{\small ⦂}\; \mathbb{N} \leftarrow 0 \\\) \(\hspace{1.5em}\) for \(i\) from \(0\) up to \(\mathtt{nMemoChunks}-1\): \(\\\) \(\hspace{3.0em}\) let \(\mathsf{nonce} = \mathsf{I2BEOSP}_{88}(\mathsf{counter}) \,||\, [\mathtt{0x00}] \\\) \(\hspace{3.0em}\) let \(P = \mathsf{IETF\_AEAD\_CHACHA20\_POLY1305.Decrypt}(\mathsf{encryption\_key}, \mathsf{nonce}, \mathtt{vMemoChunks}[i]) \\\) \(\hspace{3.0em}\) if \(P \neq \bot\): \(\\\) \(\hspace{4.5em}\) set \(\mathsf{memo} \leftarrow \mathsf{memo} \,||\, P \\\) \(\hspace{4.5em}\) set \(\mathsf{counter} \leftarrow \mathsf{counter} + 1 \\\) \(\hspace{4.5em}\) set \(\mathsf{potential\_last\_chunk\_index} \leftarrow i + 1 \\\) \(\,\\\) \(\hspace{1.5em}\) let \(\mathsf{final\_nonce} = \mathsf{I2BEOSP}_{88}(\mathsf{counter}) \,||\, [\mathtt{0x01}] \\\) \(\hspace{1.5em}\) let mutable \(\mathsf{success} \leftarrow \kern0.05em\) false \(\\\) \(\hspace{1.5em}\) for \(i\) from \(0\) up to \(\mathtt{nMemoChunks}-1\): \(\\\) \(\hspace{3.0em}\) let \(P = \mathsf{IETF\_AEAD\_CHACHA20\_POLY1305.Decrypt}(\mathsf{encryption\_key}, \mathsf{final\_nonce}, \mathtt{vMemoChunks}[i]) \\\) \(\hspace{3.0em}\) if \(i \geq \mathsf{potential\_last\_chunk\_index}\) and \(P \neq \bot\) and not \(\mathsf{success}\): \(\\\) \(\hspace{4.5em}\) set \(\mathsf{memo} \leftarrow \mathsf{memo} \,||\, P \\\) \(\hspace{4.5em}\) set \(\mathsf{success} \leftarrow \kern0.05em\) true \(\\\) \(\,\\\) \(\hspace{1.5em}\) if \(\mathsf{success}\) then return \(\mathsf{memo}\) else return \(\bot \\\)
This algorithm relies on the order-preserving shuffle invariant from Memo encryption: the final chunk of a memo always appears after all of its non-final chunks. The \(\mathsf{potential\_last\_chunk\_index}\) guard ensures that a chunk is only accepted as the final chunk if it appears at or after the position following the last successfully decrypted non-final chunk.
Wallet implementations SHOULD defer memo bundle decryption to an asynchronous process that is independent of the trial decryption of note ciphertexts.
In pre-v6 transactions performed by full-node wallets, trial decryption of each shielded output performs constant work regardless of whether the output belongs to the wallet: the AEAD decryption of the full 580-byte note ciphertext costs the same whether it succeeds or fails.
With memo bundles, successful trial decryption of a 100-byte v6 note ciphertext yields a memo key \(\mathsf{K^{memo}}\), and if the key is not the no-memo sentinel, the wallet must scan up to \(\mathsf{memo\_chunk\_limit}\) memo chunks to recover the memo. This additional work — potentially up to 64 AEAD operations — creates a timing differential between successful and unsuccessful trial decryptions that could be exploited by an adversary to determine whether a node’s wallet recognized a particular output.
Deferring memo bundle decryption decorrelates the observable timing of block processing from whether any notes were successfully decrypted, making it more difficult for an adversary to attribute memo decryption work to a specific transaction.
| Bytes | Name | Data Type | Description |
|---|---|---|---|
| 1 | \(\mathtt{fAllPruned}\) | \(\mathtt{uint8}\) | 1 if the memo bundle has been pruned, otherwise 0. |
| 32 | \(\mathtt{saltOrHash}\) | \(\mathtt{byte}[32]\) | The salt for deriving memo encryption keys, or the memo bundle digest. |
| † varies | \(\mathtt{nMemoChunks}\) | \(\mathtt{compactSize}\) | The number of memo chunks. |
| † varies | \(\mathtt{vMemoChunks}\) | \(\mathtt{MemoChunk}[\mathtt{nMemoChunks}]\) | A sequence of encrypted memo chunks. |
† These fields are present if and only if \(\mathtt{fAllPruned} = 0\).
If \(\mathtt{fAllPruned} = 0\), then:
If \(\mathtt{fAllPruned} = 1\), then:
\(\mathsf{memo\_chunk\_digest}[i] = H(\mathtt{vMemoChunks}[i]) \\\) \(\mathsf{memo\_bundle\_digest} = H(\mathsf{concat}(\mathsf{memo\_chunk\_digests}))\)
The memo bundle digest is used in place of the full memo bundle when the bundle has been pruned.
TODO: finish this as a modification to ZIP 248 13, which defines the v6 transaction digest structure. Note that \(\mathtt{fAllPruned}\) MUST NOT contribute to the transaction digest, as it is not part of the committed transaction data.
The conventional fee in ZEC is altered such that a memo bundle may contain two
free chunks if there are any shielded outputs in the transaction. Any memo chunk
beyond this requires marginal_fee. See the Fee calculation section of ZIP 317
15 for details.
Nodes MUST reject tx messages having an \(\mathtt{fAllPruned}\) value that is
nonzero. A node that has pruned the memo bundle from a transaction MUST NOT serve
that transaction in response to a getdata message for MSG_TX or MSG_WTX; it SHOULD respond as though
the transaction is not available.
Decoupling memo data from note data and making it independently prunable helps to mitigate the long-term storage costs that memo data imposes on node operators, while preserving the ability of every node to validate the full transaction chain using the memo bundle digest.
Light client servers SHOULD include memo bundle pruning status in the compact transaction format, so that clients can avoid attempting to retrieve memo data from a server that has pruned it.
The changes to support memo bundles that affect the definitions of note plaintexts and note ciphertexts, interact with the addition of an \(\mathsf{asset\_base}\) field to v6-onward Orchard note plaintexts in order to support ZSAs, which must be merged with the changes in this section.
Changes to the algorithms for encryption and decryption are specified below. Note that 16 also updates these algorithms, but the merge is trivial.
In § 3.2.1 ‘Note Plaintexts and Memo Fields’:
Change
Each Sapling or Orchard note plaintext (denoted \(\mathbf{np}\)) consists of
\(\hspace{2em}(\mathsf{leadByte} \;{\small ⦂}\; \mathbb{B}^{{\kern-0.05em\tiny\mathbb{Y}}}, \mathsf{d} \;{\small ⦂}\; \mathbb{B}^{[\ell_{\mathsf{d}}]}, \mathsf{rseed} \;{\small ⦂}\; \mathbb{B}^{{\kern-0.05em\tiny\mathbb{Y}}[32]}, \mathsf{memo} \;{\small ⦂}\; \mathbb{B}^{{\kern-0.05em\tiny\mathbb{Y}}[512]})\)
to
The form of a Sapling or Orchard note plaintext depends on the version of the transaction in which it will be included; specifically whether that version is pre-v6, or v6-onward.
Each pre-v6 Sapling or Orchard note plaintext (denoted \(\mathbf{np}\)) consists of
\(\hspace{2em}(\mathsf{leadByte} \;{\small ⦂}\; \mathbb{B}^{{\kern-0.05em\tiny\mathbb{Y}}}, \mathsf{d} \;{\small ⦂}\; \mathbb{B}^{[\ell_{\mathsf{d}}]}, \mathsf{rseed} \;{\small ⦂}\; \mathbb{B}^{{\kern-0.05em\tiny\mathbb{Y}}[32]}, \mathsf{memo} \;{\small ⦂}\; \mathbb{B}^{{\kern-0.05em\tiny\mathbb{Y}}[512]})\)
Each v6-onward Sapling or Orchard note plaintext (denoted \(\mathbf{np}\)) consists of
\(\hspace{2em}(\mathsf{leadByte} \;{\small ⦂}\; \mathbb{B}^{{\kern-0.05em\tiny\mathbb{Y}}}, \mathsf{d} \;{\small ⦂}\; \mathbb{B}^{[\ell_{\mathsf{d}}]}, \mathsf{rseed} \;⦂\; \mathbb{B}^{{\kern-0.05em\tiny\mathbb{Y}}[32]}, \mathsf{K^{memo}} \;{\small ⦂}\; \mathbb{B}^{{\kern-0.05em\tiny\mathbb{Y}}[32]})\)
In § 5.5 ‘Encodings of Note Plaintexts and Memo Fields’ 17:
Change the paragraph that describes “The encoding of a Sapling or Orchard note plaintext” to refer to “The encoding of a pre-v6 Sapling or Orchard note plaintext”.
Add a new paragraph at the end of the section:
The encoding of a v6-onward Sapling or Orchard note plaintext consists of:
\(\begin{array}{|c|c|c|c|c|} \hline \raisebox{0.6ex}{\mathstrut} \text{8-bit } \mathsf{leadByte} & \text{88-bit } \mathsf{d} & \text{64-bit } \mathsf{v} & \text{256-bit } \mathsf{rseed} & \text{32-byte } \mathsf{K^{memo}} \\\hline \end{array}\)
- A byte 0x03, indicating this version of the encoding of a v6-onward Sapling or Orchard note plaintext.
- 11 bytes specifying \(\mathsf{d}\).
- 8 bytes specifying \(\mathsf{v}\).
- 32 bytes specifying \(\mathsf{rseed}\).
- 32 bytes specifying \(\mathsf{K^{memo}}\).
A value consisting of 32 \(\mathtt{0xFF}\) bytes for \(\mathsf{K^{memo}}\) is used to indicate that there is no memo for this note plaintext.
In § 4.7.2 ‘Sending Notes (Sapling)’ 18 and § 4.7.3 ‘Sending Notes (Orchard)’ 19:
Add a reference to this ZIP specifying the construction of the memo bundle and derivation of \(\mathsf{K^{memo}}\) in the case of a v6-onward note plaintext.
Change
Let \(\mathbf{np} = (\mathsf{leadByte}, \mathsf{d}, \mathsf{v}, \mathsf{rseed}, \mathsf{memo})\).
to
Let \(\mathbf{np}\) be the encoding of a Sapling note plaintext using \(\mathsf{leadByte}\), \(\mathsf{d}\), \(\mathsf{v}\), \(\mathsf{rseed}\), and either \(\mathsf{memo}\) for a pre-v6 note plaintext or \(\mathsf{K^{memo}}\) for a v6-onward note plaintext.
replacing “Sapling” with Orchard in the case of § 4.7.3.
In § 4.20.1 ‘Encryption (Sapling and Orchard)’ 20:
For v6-onward note ciphertexts, the KDF used to derive the note encryption key uses a different BLAKE2b-256 personalization string than for pre-v6 note ciphertexts, to provide domain separation. The personalization string is formed by concatenating a protocol prefix with the 4-byte little-endian encoding of the transaction version group id (\(\mathsf{nVersionGroupId}\)):
For v6-onward Sapling note ciphertexts:
\(\hspace{2em}\mathsf{KDF^{Sapling}}(\mathsf{sharedSecret}, \mathsf{ephemeralKey}) := \mathsf{BLAKE2b\text{-}256}(\text{"Zc_SaplingKD"} \,||\, \mathsf{I2LEOSP}_{32}(\mathsf{nVersionGroupId}), \mathsf{sharedSecret} \,||\, \mathsf{ephemeralKey})\)
For v6-onward Orchard note ciphertexts:
\(\hspace{2em}\mathsf{KDF^{Orchard}}(\mathsf{sharedSecret}, \mathsf{ephemeralKey}) := \mathsf{BLAKE2b\text{-}256}(\text{"Zc_OrchardKD"} \,||\, \mathsf{I2LEOSP}_{32}(\mathsf{nVersionGroupId}), \mathsf{sharedSecret} \,||\, \mathsf{ephemeralKey})\)
This prevents a malicious lightwalletd server from presenting a v6 note ciphertext to a wallet as though it were a partial v5 ciphertext, which could otherwise cause the wallet to decrypt and act on unauthenticated plaintext.
Change
Let \(\mathbf{np} = (\mathsf{leadByte}, \mathsf{d}, \mathsf{v}, \mathsf{rseed}, \mathsf{memo})\) be the Sapling or Orchard note plaintext. \(\mathbf{np}\) is encoded as defined in § 5.5 ‘Encodings of Note Plaintexts and Memo Fields’.
to
Let \(\mathbf{np}\) be the encoding of the Sapling or Orchard note plaintext (which may be pre-v6 or v6-onward), as defined in § 5.5 ‘Encodings of Note Plaintexts and Memo Fields’.
Add another normative note to that section:
- \(\mathsf{C^{enc}}\) will be of length either 580 or 100 bytes, depending on whether \(\mathbf{np}\) is a pre-v6 or v6-onward note plaintext.
In § 4.20.2 ‘Decryption using an Incoming Viewing Key (Sapling and Orchard)’ 21 and § 4.20.3 ‘Decryption using an Outgoing Viewing Key (Sapling and Orchard)’ 22:
All of these changes apply identically to Mainnet and Testnet.
Because memo data can be pruned from stored blocks, nodes that have pruned memo data are not required to serve complete block data for affected transactions. However, to support reliable memo delivery, it would be beneficial for the Zcash community to establish expectations for how long after block confirmation a node is expected to retain complete (unpruned) memo data. Such a retention policy would allow wallet implementations to make reasonable assumptions about memo availability when recovering from backup or performing initial synchronization.
TBD
Restricting the total amount of memo data in a bundle, for example to 16 KiB, limits the rate at which the chain size can grow cheaply (from a computational perspective; memo bundles are much easier to produce than proofs or signatures).
The current behaviour for previous transaction versions (no limit on the number of memos) is not altered by this ZIP, because memos in those transactions are tied to individual shielded outputs (incurring their computational cost), and are not natively aggregatable.
By decoupling memos from shielded outputs, this ZIP creates an opportunity to impose economic disincentives on adding memo data to the chain without affecting the shielded value transfer protocols. Because the fee for memo data is independent of the fee for value transfer, it can be adjusted independently — for example, to impose a superlinear cost curve for large amounts of memo data, or to increase memo fees in response to chain growth, without any impact on the cost of ordinary shielded transfers. More generally, this decoupling makes it straightforward to impose restrictions on memo creation that cannot currently be imposed without also affecting value transfer.
To understand the effect of memo chunk size, we construct a table showing the total amount of data stored on-chain when encoding 16 KiB of memo data to as many recipients as possible.
Each table entry has the format “\(N\) @ \(M\) (\(O\))” where \(N\) is the maximum number of distinct recipients you can have within the memo data limits, \(M\) is the cost in bytes of that memo data plus memo keys and authentication tags when using a 32-byte memo key, and \(O\) is the relative overhead compared to pre-ZIP-231 memos.
Let:
Then
| Chunk size | Memo size ≤ 256 bytes | Memo size = 512 bytes |
|---|---|---|
| Pre-231 | 32 @ 16384 ( 0.00%) | 32 @ 16384 ( 0.00%) |
| 512 | 32 @ 17920 (+ 9.38%) | 32 @ 17920 (+ 9.38%) |
| 256 | 64 @ 19456 (+18.75%) | 32 @ 18432 (+12.50%) |
| 256 32-out | 32 @ 9728 (-40.63%) |
In the “256 32-out” case you have a distinguisher compared to old transactions, in that you can tell the transaction is sending at most 256 bytes per recipient rather than 512 if it is sending the maximum number of memos. But that’s inherently baked into the decision to use a smaller plaintext chunk size (and it is still possible for the chunks to all be a single memo sent to all outputs, or anything in between).
16-byte (128-bit) keys don’t meet Zcash’s target security level of 125 bits, as argued in 23.
However, for the sake of argument, if we used a 16-byte memo key instead of 32 bytes, the transaction size overhead would become:
| Chunk size | Memo size ≤ 256 bytes | Memo size = 512 bytes |
|---|---|---|
| Pre-231 | 32 @ 16384 ( 0.00%) | 32 @ 16384 ( 0.00%) |
| 512 | 32 @ 17408 (+ 6.25%) | 32 @ 17408 (+ 6.25%) |
| 256 | 64 @ 18432 (+12.50%) | 32 @ 17920 (+ 9.38%) |
| 256 32-out | 32 @ 9216 (-43.75%) |
The decrease in overhead is relatively modest in most cases, but more noticeable for small memos with a 256-byte plaintext chunk.
The benefits of 256-bit keys are:
Including a per-transaction \(\mathsf{salt}\) in the derivation of \(\mathsf{encryption_key}\) gives protection against accidental (or intentional) reuse of \(\mathsf{K^{memo}}\) across multiple transactions. We do not protect against \(\mathsf{K^{memo}}\) reuse within a transaction; it is up to the transaction builder to ensure that the same \(\mathsf{K^{memo}}\) is not used to encrypt two different memos (and if they did so, normal clients would either never observe the second memo, or would decrypt parts of each memo and get a nonsensical and potentially insecure “spliced” memo).
We do not include commitments to the shielded outputs in the derivation of \(\mathsf{encryption\_key}\) for two reasons:
V6-onward note ciphertexts use KDF personalization strings that incorporate the transaction version group id (\(\text{"Zc_SaplingKD"} \,||\, \mathsf{nVersionGroupId}\) and \(\text{"Zc_OrchardKD"} \,||\, \mathsf{nVersionGroupId}\)), providing domain separation from pre-v6 note ciphertexts. This prevents a downgrade attack in which a malicious lightwalletd server presents a v6 note ciphertext as a partial v5 note ciphertext. Without domain separation, a wallet that supports the v5 partial-ciphertext optimization (downloading note ciphertexts without the MAC to save bandwidth) could decrypt a v6 ciphertext without verifying its authentication tag, potentially acting on unauthenticated data. Using the version group id rather than a fixed suffix ensures that domain separation extends automatically to future transaction versions.
The separation of memo data from note data, and the new ability to easily store variable-length memo data, opens up an attack vector against node operators for storing arbitrary data. The transaction digest commitments to the memo bundle are structured such that a node operator can prune the entire memo bundle from a stored transaction, replacing it with a single digest, while still enabling the transaction to be validated as part of its corresponding block.
This ZIP is proposed to activate with Network Upgrade 7. 25
TBD
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” ↩︎
Zcash Protocol Specification, Version 2025.6.2 [NU6.1] or later ↩︎
Zcash Protocol Specification, Version 2025.6.2 [NU6.1]. Section 3.12: Mainnet and Testnet ↩︎
Zcash Protocol Specification, Version 2025.6.2 [NU6.1]. Section 3.2.1: Note Plaintexts and Memo Fields ↩︎
Authenticated Encryption: Relations among notions and analysis of the generic composition paradigm ↩︎
ZIP 230: Version 6 Transaction Format — Orchard Note Plaintext ↩︎
Zcash Protocol Specification, Version 2025.6.2 [NU6.1]. Section 4.1.2: Pseudo Random Functions ↩︎
Online Authenticated-Encryption and its Nonce-Reuse Misuse-Resistance ↩︎
ZIP 248: Extensible Transaction Format (PR: zcash/zips#1156) ↩︎
ZIP 317: Proportional Transfer Fee Mechanism - Fee calculation ↩︎
Zcash Protocol Specification, Version 2025.6.2 [NU6.1]. Section 5.5: Encodings of Note Plaintexts and Memo Fields ↩︎
Zcash Protocol Specification, Version 2025.6.2 [NU6.1]. Section 4.7.2: Sending Notes (Sapling) ↩︎
Zcash Protocol Specification, Version 2025.6.2 [NU6.1]. Section 4.7.3: Sending Notes (Orchard) ↩︎
Zcash Protocol Specification, Version 2025.6.2 [NU6.1]. Section 4.20.1: Encryption (Sapling and Orchard) ↩︎
Zcash Protocol Specification, Version 2025.6.2 [NU6.1]. Section 4.20.2: Decryption using an Incoming Viewing Key (Sapling and Orchard) ↩︎
Zcash Protocol Specification, Version 2025.6.2 [NU6.1]. Section 4.20.3: Decryption using an Outgoing Viewing Key (Sapling and Orchard) ↩︎
Zcash Protocol Specification, Version 2025.6.2 [NU6.1]. Section 8.7: In-band secret distribution ↩︎
zcash/zips issue #693: Standardize a protocol for creating shielded transactions offline ↩︎
draft-arya-deploy-nu7: Deployment of the NU7 Network Upgrade ↩︎