# ZIP 231: Memo Bundles

Status: Draft

Owners: Jack Grigg, Kris Nuttycombe, Daira-Emma Hopwood, Arya Solhi

Snapshot: a34cfc091434275120ff017c92c3b50d3cef623a

Source date: 2026-10-16T02:56:04+01:00

[Source revision](https://github.com/zcash/zips/commit/a34cfc091434275120ff017c92c3b50d3cef623a)

[Official source](https://zips.z.cash/zip-0231) · [Source file](https://github.com/zcash/zips/blob/a34cfc091434275120ff017c92c3b50d3cef623a/zips/zip-0231.md) · [Discussion](https://github.com/zcash/zips/issues/627)

## Document parts

[Part 1](https://zip-tools-web.harpocrates.workers.dev/zip/231) · [Part 2](https://zip-tools-web.harpocrates.workers.dev/zip/231?part=2)

Reading part 2 of 2. Follow all parts for the complete proposal.

- For V7-onward note ciphertexts, the KDF used to derive the note encryption key
  uses a different BLAKE2b-256 personalization string than for pre-V7 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 V7-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 V7-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 V7 note ciphertext
  to a wallet as though it were a partial v5 or v6 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-V7 or V7-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-V7 or V7-onward note plaintext.

In § 4.20.2 ‘Decryption using an Incoming Viewing Key (Sapling and Orchard)’ <a id="user-content-fnref-protocol-decryptivk"></a>
[21](#user-content-fn-protocol-decryptivk)
and § 4.20.3 ‘Decryption using an Outgoing Viewing Key (Sapling and Orchard)’ <a id="user-content-fnref-protocol-decryptovk"></a>
[22](#user-content-fn-protocol-decryptovk):

- Replace $\mathsf{memo} \;{\small ⦂}\; \mathbb{B}^{{\kern-0.05em\tiny\mathbb{Y}}[512]}$ with
  $\mathsf{memoOrKey}$.

- Specify that the type of $\mathsf{memoOrKey}$ is $\mathbb{B}^{{\kern-0.05em\tiny\mathbb{Y}}[512]}$
  when decrypting a pre-V7 note ciphertext, or $\mathbb{B}^{{\kern-0.05em\tiny\mathbb{Y}}[32]}$ when
  decrypting a V7-onward note ciphertext. In the latter case, it is used as $\mathsf{K^{memo}}$
  to decrypt the memo bundle as described in [Memo bundle].

<a id="applicability"></a>

### Applicability

All of these changes apply identically to Mainnet and Testnet.

<a id="open-issues"></a>

## Open issues

<a id="memo-data-retention"></a>

### Memo data retention

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.

<a id="interaction-with-zip-302-11"></a>

### Interaction with ZIP 302 <a id="user-content-fnref-zip-0302-2"></a>
[11](#user-content-fn-zip-0302)

TBD

<a id="note-plaintext-lead-byte-assignment"></a>

### Note plaintext lead byte assignment

The lead byte to be used for this proposal, denoted as {{MBLEADBYTE}} above, has
not yet been assigned.

<a id="rationale"></a>

## Rationale

<a id="memo-bundle-size-restriction"></a>

### Memo bundle size restriction

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.

<a id="independent-fee-policy-for-memo-data"></a>

### Independent fee policy for memo data

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.

<a id="memo-chunk-size"></a>

### Memo chunk size

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.

Calculation details

Let:

- $D$ be the limit on total memo data (16384 bytes);

- $C$ be the plaintext chunk size (256 or 512 bytes);

- $S$ be the maximum length of an individual memo (256 or 512 bytes);

- $K$ be the key size (32 bytes);

- $A$ be the authentication tag size (16 bytes);

- $L$ be the limit on the number of chunks (32 or $\infty$).

Then

- $N = \mathsf{min}(L, \mathsf{ceiling}(D / \mathsf{max}(C, S))$;

- $M = N × (S+K+A)$; and

- $O = (M / D - 1) × 100\%$.

| 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).

<a id="memo-key-size"></a>

### Memo key size

16-byte (128-bit) keys don't meet Zcash's target security level of 125 bits,
as argued in <a id="user-content-fnref-protocol-inbandrationale"></a>
[23](#user-content-fn-protocol-inbandrationale).

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:

- They incur only a small transaction size overhead above the minimum key size
  that *would* meet the target security level.

- This key length matches what we already use elsewhere for symmetric keys.

<a id="encryption-format"></a>

### Encryption format

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:

- It would force the transaction builder to fully define all shielded outputs
  before encrypting the memos, which might prevent potential use cases of PCZTs <a id="user-content-fnref-pczt"></a>
  [24](#user-content-fn-pczt).

- We don't want to unnecessarily prevent the ability to create a transaction
  with a memo bundle and no shielded outputs, as there may be use cases for,
  e.g. a fully-transparent transaction with encrypted memo, or a ZSA issuance
  transaction with exposed memo data using a well-known $\mathsf{K^{memo}}$.

<a id="domain-separation-of-note-ciphertexts"></a>

### Domain separation of note ciphertexts

V7-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-V7 note ciphertexts. This prevents a downgrade attack in
which a malicious lightwalletd server presents a V7 note ciphertext as a partial
v5 or v6 note ciphertext. Without domain separation, a wallet that supports the
v5/v6 partial-ciphertext optimization (downloading note ciphertexts without the
MAC to save bandwidth) could decrypt a V7 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.

<a id="pruned-encoding"></a>

### Pruned encoding

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.

<a id="deployment"></a>

## Deployment

This ZIP is not deployed in Network Upgrade 7. <a id="user-content-fnref-zip-0259"></a>
[25](#user-content-fn-zip-0259) It is proposed to
activate in a later network upgrade.

<a id="reference-implementation"></a>

## Reference implementation

TBD

<a id="alternatives"></a>

## Alternatives

- An alternative chunk size could be selected. The 256-byte chunk size was
  chosen because the 16-byte AEAD tag for each chunk introduces a 6.25%
  per-chunk overhead; smaller chunk sizes would require a higher percentage
  of the memo bundle data to be devoted to encryption overhead, while
  larger chunk sizes would tend to bloat the chain.

- The 64-chunk limit could be modified to be either smaller or larger. However,
  as the smallest Orchard authenticated reply-to address proofs are
  approximately 4 kilobytes in size, the limit needs to be sufficient to
  permit this use case.

- The fee algorithm could be modified to be a superlinear function of the
  number of memo chunks, in order to economically discourage the use of
  large amounts of memo data.

- An alternative approach to memo pruning that permitted pruning at the
  per-chunk level was proposed, but was judged to provide insufficient benefit
  relative to its complexity cost. Per-chunk pruning would have allowed
  selective removal of individual memos, but this added complexity to the
  encoding and also introduced the risk that a maliciously pruned memo could
  have a different interpretation when decrypted relative to the original
  unpruned encoding of the memo. Jack Grigg discovered this attack.

<a id="references"></a>

## References

<a id="footnote-label"></a>

### Footnotes

1. [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) [↩](/zip/231#user-content-fnref-bcp14)

2. [ZIP 200: Network Upgrade Mechanism](/zip/200) [↩](/zip/231#user-content-fnref-zip-0200)

3. [Zcash Protocol Specification, Version 2025.6.2 [NU6.1] or later](protocol/protocol.pdf) [↩](/zip/231#user-content-fnref-protocol)

4. [Zcash Protocol Specification, Version 2025.6.2 [NU6.1]. Section 3.12: Mainnet and Testnet](protocol/protocol.pdf#networks) [↩](/zip/231#user-content-fnref-protocol-networks)

5. [ZIP 248: Extensible Transaction Format](/zip/248) [↩](/zip/231#user-content-fnref-zip-0248) [↩2](/zip/231#user-content-fnref-zip-0248-2) [↩3](/zip/231#user-content-fnref-zip-0248-3) [↩4](/zip/231#user-content-fnref-zip-0248-4) [↩5](/zip/231#user-content-fnref-zip-0248-5) [↩6](/zip/231#user-content-fnref-zip-0248-6) [↩7](/zip/231#user-content-fnref-zip-0248-7) [↩8](/zip/231#user-content-fnref-zip-0248-8) [↩9](/zip/231#user-content-fnref-zip-0248-9) [↩10](/zip/231#user-content-fnref-zip-0248-10) [↩11](/zip/231#user-content-fnref-zip-0248-11) [↩12](/zip/231#user-content-fnref-zip-0248-12)

6. [ZIP 229: Version 6 Transaction Format](/zip/229) [↩](/zip/231#user-content-fnref-zip-0229)

7. [Zcash Protocol Specification, Version 2025.6.2 [NU6.1]. Section 3.2.1: Note Plaintexts and Memo Fields](protocol/protocol.pdf#noteptconcept) [↩](/zip/231#user-content-fnref-protocol-noteptconcept)

8. [ZIP 307: Light Client Protocol for Payment Detection](/zip/307) [↩](/zip/231#user-content-fnref-zip-0307)

9. [Authenticated Encryption: Relations among notions and analysis of the generic composition paradigm](https://eprint.iacr.org/2000/025) [↩](/zip/231#user-content-fnref-bn2000)

10. [Zcash Protocol Specification, Version 2025.6.2 [NU6.1]. Section 4.1.2: Pseudo Random Functions](protocol/protocol.pdf#abstractprfs) [↩](/zip/231#user-content-fnref-protocol-abstractprfs)

11. [ZIP 302: Standardized Memo Field Format](/zip/302) [↩](/zip/231#user-content-fnref-zip-0302) [↩2](#user-content-fnref-zip-0302-2)

12. [RFC 8439: ChaCha20 and Poly1305 for IETF Protocols](https://www.rfc-editor.org/rfc/rfc8439.html) [↩](/zip/231#user-content-fnref-rfc-8439)

13. [Online Authenticated-Encryption and its Nonce-Reuse Misuse-Resistance](https://eprint.iacr.org/2015/189) [↩](/zip/231#user-content-fnref-stream)

14. [ZIP 317: Proportional Transfer Fee Mechanism](/zip/317) [↩](/zip/231#user-content-fnref-zip-0317)

15. [ZIP 317: Proportional Transfer Fee Mechanism - Fee calculation](/zip/317#fee-calculation) [↩](/zip/231#user-content-fnref-zip-0317-fee-calculation)

16. [ZIP 2005: Quantum Recoverability](/zip/2005) [↩](/zip/231#user-content-fnref-zip-2005)

17. [Zcash Protocol Specification, Version 2025.6.2 [NU6.1]. Section 5.5: Encodings of Note Plaintexts and Memo Fields](protocol/protocol.pdf#noteptencoding) [↩](/zip/231#user-content-fnref-protocol-noteptencoding)

18. [Zcash Protocol Specification, Version 2025.6.2 [NU6.1]. Section 4.7.2: Sending Notes (Sapling)](protocol/protocol.pdf#saplingsend) [↩](/zip/231#user-content-fnref-protocol-saplingsend)

19. [Zcash Protocol Specification, Version 2025.6.2 [NU6.1]. Section 4.7.3: Sending Notes (Orchard)](protocol/protocol.pdf#orchardsend) [↩](/zip/231#user-content-fnref-protocol-orchardsend)

20. [Zcash Protocol Specification, Version 2025.6.2 [NU6.1]. Section 4.20.1: Encryption (Sapling and Orchard)](protocol/protocol.pdf#saplingandorchardinband) [↩](/zip/231#user-content-fnref-protocol-saplingandorchardinband)

21. [Zcash Protocol Specification, Version 2025.6.2 [NU6.1]. Section 4.20.2: Decryption using an Incoming Viewing Key (Sapling and Orchard)](protocol/protocol.pdf#decryptivk) [↩](#user-content-fnref-protocol-decryptivk)

22. [Zcash Protocol Specification, Version 2025.6.2 [NU6.1]. Section 4.20.3: Decryption using an Outgoing Viewing Key (Sapling and Orchard)](protocol/protocol.pdf#decryptovk) [↩](#user-content-fnref-protocol-decryptovk)

23. [Zcash Protocol Specification, Version 2025.6.2 [NU6.1]. Section 8.7: In-band secret distribution](protocol/protocol.pdf#inbandrationale) [↩](#user-content-fnref-protocol-inbandrationale)

24. [zcash/zips issue #693: Standardize a protocol for creating shielded transactions offline](https://github.com/zcash/zips/issues/693) [↩](#user-content-fnref-pczt)

25. [ZIP 259: Deployment of the NU7 Network Upgrade](/zip/259) [↩](#user-content-fnref-zip-0259)

## Citation relationships

Cites: [ZIP 200](https://zip-tools-web.harpocrates.workers.dev/zip/200), [ZIP 229](https://zip-tools-web.harpocrates.workers.dev/zip/229), [ZIP 248](https://zip-tools-web.harpocrates.workers.dev/zip/248), [ZIP 259](https://zip-tools-web.harpocrates.workers.dev/zip/259), [ZIP 302](https://zip-tools-web.harpocrates.workers.dev/zip/302), [ZIP 307](https://zip-tools-web.harpocrates.workers.dev/zip/307), [ZIP 317](https://zip-tools-web.harpocrates.workers.dev/zip/317), [ZIP 2005](https://zip-tools-web.harpocrates.workers.dev/zip/2005)

Cited by: [ZIP 230](https://zip-tools-web.harpocrates.workers.dev/zip/230), [ZIP 248](https://zip-tools-web.harpocrates.workers.dev/zip/248), [ZIP 317](https://zip-tools-web.harpocrates.workers.dev/zip/317), [ZIP 374](https://zip-tools-web.harpocrates.workers.dev/zip/374), [ZIP 2005](https://zip-tools-web.harpocrates.workers.dev/zip/2005)