ZIP 326 — NU6.3 Consequences for Wallets

Draft

Terminology

The key words "MUST", "MUST NOT", "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 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

The terms "Orchard protocol", "Orchard pool", "Ironwood pool", "Orchard-pool Action", and "version 6 transaction" are to be interpreted as described in 5. NU6.3 and its activation height are as defined in 6.

The terms "expanded receiver" and "cross-address restriction" are to be interpreted as described in ZIP 2006. 7

The terms "quantum spending key" (𝗊𝗌𝗄), the flag 𝗎𝗌𝖾_𝗊𝗌𝗄 that affects derivation of viewing keys and addresses, and the term "recoverable note" are to be interpreted as described in ZIP 2005. 8 (Recall from that ZIP that 𝗎𝗌𝖾_𝗊𝗌𝗄 does not determine whether a note is quantum-recoverable — every Ironwood-pool note is recoverable and no Orchard-pool note is, regardless of 𝗎𝗌𝖾_𝗊𝗌𝗄.)

An "incoming viewing key" (𝗂𝗏𝗄) is as described in § 3.1 ‘Payment Addresses and Keys’. 9 We distinguish the external and internal incoming viewing keys of an account, derived as described in ZIP 32. 10

The "birthday height" of an account is a lower bound, known to the wallet, on the height of the first block in which the account could have received funds. A correctly recorded birthday height is at or below the height at which the account's keys were first usable; it has traditionally been safe for a wallet to use a conservatively early birthday height.

A "light wallet" is a wallet that does not validate the consensus rules itself. It requires the assistance of a light wallet server in order to learn of relevant transactions by trial-decrypting (scanning) "compact ciphertexts". 11

Abstract

The NU6.3 network upgrade 6 introduces the Ironwood pool, an Orchard-protocol successor to the Orchard pool, that ensures quantum recoverability (ZIP 2005 8) and has supply integrity supported from the start by formal verification efforts 12.

The introduction of a new pool requires wallets to take account of the Orchard-to-Ironwood migration strategy specified in 13.

This ZIP specifies the consequences for wallets of several other changes made in NU6.3:

  • ZIP 2005 introduces a 𝗎𝗌𝖾_𝗊𝗌𝗄 key distinction, which affects how viewing keys and addresses are derived 14. In order to more efficiently support scanning for incoming Ironwood-pool funds, we place constraints on how 𝗎𝗌𝖾_𝗊𝗌𝗄 is chosen and describe optimizations to the scanning algorithm.
  • We also explain how scanning for Orchard-pool funds should be handled across the upgrade.
  • We specify how wallets spend from, and rebalance notes within, the Orchard pool under the restrictions of ZIP 2006 7. This includes how they randomize note ciphertexts, in order to maintain privacy against quantum and discrete-log-breaking adversaries.
  • We specify that wallets send payments to Orchard-protocol receivers in the Ironwood pool.

Motivation

For interoperability with existing addresses, Ironwood-pool notes are allowed to use 𝗎𝗌𝖾_𝗊𝗌𝗄=𝖿𝖺𝗅𝗌𝖾 keys as well as 𝗎𝗌𝖾_𝗊𝗌𝗄=𝗍𝗋𝗎𝖾 keys.

In ZIP 2005, the incoming viewing key, the diversifier key, and the outgoing viewing key are derived through 𝗋𝗂𝗏𝗄, which depends on 𝗎𝗌𝖾_𝗊𝗌𝗄. Consequently the addresses an account exposes, and the 𝗂𝗏𝗄 needed to trial-decrypt notes sent to them, also depend on 𝗎𝗌𝖾_𝗊𝗌𝗄. A wallet that did not know which value applied (for example after restoring from seed) would therefore have to trial-decrypt each Ironwood-pool note ciphertext against four incoming viewing keys: the external and the internal, each for 𝗎𝗌𝖾_𝗊𝗌𝗄∈{𝖿𝖺𝗅𝗌𝖾,𝗍𝗋𝗎𝖾}.

A wallet that performed its own key derivation always knows which 𝗎𝗌𝖾_𝗊𝗌𝗄 value is used, but that value is not traditionally stored with wallet recovery information, and requiring the user to record it would worsen the recovery user experience.

This ZIP specifies restrictions on key generation, key exposure, and sending Orchard-pool funds, and the scanning rules that they enable. These keep scanning cost close to its pre-NU6.3 level in the common cases, while remaining correct in the restore-from-seed case.

The recommendations here assume that pools and activations refer to Mainnet, and that a secure wallet either does not support Testnet, or strictly segregates key material between Testnet and Mainnet.

This ZIP also specifies how wallets spend from, and rebalance notes within, the Orchard pool under the restrictions that NU6.3 places on that pool. 7

Specification

Scanning for Ironwood-pool and Orchard-pool notes

A receiver, and the corresponding incoming viewing key, is scoped to the Orchard protocol, not to a pool: the same 𝗂𝗏𝗄 is used to trial-decrypt both Orchard-pool and Ironwood-pool note ciphertexts.

We assume that secure wallets do not expose internal receivers or viewing keys to any party that is not subject to the same restrictions as the wallet itself. A wallet MUST NOT use the fact that it knows the internal addresses of all of its accounts to send funds from one account to an internal address of another.

Restrictions on key generation and exposure

The following restrictions, together with the restrictions on sending, make the scanning optimizations below sound. They concern only how a wallet generates keys and exposes receivers and viewing keys; they are not consensus rules.

  • For each ZIP 32 account, a wallet MUST generate keys with the same value of 𝗎𝗌𝖾_𝗊𝗌𝗄 — either all 𝗎𝗌𝖾_𝗊𝗌𝗄=𝗍𝗋𝗎𝖾 or all 𝗎𝗌𝖾_𝗊𝗌𝗄=𝖿𝖺𝗅𝗌𝖾, never a mixture.
  • Any wallet that generates 𝗎𝗌𝖾_𝗊𝗌𝗄=𝗍𝗋𝗎𝖾 keys MUST NOT expose the corresponding Orchard-protocol receivers or viewing keys before NU6.3 activation. Otherwise Orchard-pool funds could be sent to a 𝗎𝗌𝖾_𝗊𝗌𝗄=𝗍𝗋𝗎𝖾 account, which would incur extra scanning cost or risk loss of funds.

To make these restrictions easier to satisfy, a production wallet MUST NOT generate 𝗎𝗌𝖾_𝗊𝗌𝗄=𝗍𝗋𝗎𝖾 keys at all before NU6.3 has activated on Mainnet. It follows that:

  • the mistake of exposing the corresponding receivers or viewing keys before NU6.3 Mainnet activation is excluded; and
  • any account that creates 𝗎𝗌𝖾_𝗊𝗌𝗄=𝗍𝗋𝗎𝖾 keys will have a birthday height after NU6.3 Mainnet activation.

Rationale for restrictions on key generation and exposure

Rationale

Orchard-pool notes do not benefit in any way from 𝗎𝗌𝖾_𝗊𝗌𝗄=𝗍𝗋𝗎𝖾; they are never recoverable in any case.

If a 𝗎𝗌𝖾_𝗊𝗌𝗄=𝗍𝗋𝗎𝖾 account were able to hold Orchard-pool funds, that case would need to be handled by scanning the Orchard pool with 𝗎𝗌𝖾_𝗊𝗌𝗄=𝗍𝗋𝗎𝖾, or else the funds would be lost. Neither the extra scanning cost nor the possibility of losing funds are at all desirable.

Exposing the address or viewing key is not enough to send Orchard-pool funds to a 𝗎𝗌𝖾_𝗊𝗌𝗄=𝗍𝗋𝗎𝖾 account after NU6.3 activation. At that point a sender would need the spending key, because the Orchard pool's cross-address restriction 7 requires the spend side of an action to be at the same expanded receiver as its output side. Consensus cannot ensure that a 𝗎𝗌𝖾_𝗊𝗌𝗄=𝗍𝗋𝗎𝖾 account receives no Orchard-pool funds: any party that knows the account's spending key can send funds to it regardless of the cross-address restriction, since the spend side of the action can be a dummy. However, control over the spending key is something that wallets can enforce.

Scan ranges

Under the restrictions above and the restrictions on sending, a wallet can reduce its scanning load and complexity:

  • Orchard-pool note ciphertexts SHOULD NOT be scanned with 𝗎𝗌𝖾_𝗊𝗌𝗄=𝗍𝗋𝗎𝖾 incoming viewing keys.

  • For an account with birthday height after NU6.3 activation, the account will not have received Orchard-pool funds, so Orchard-pool note ciphertexts SHOULD NOT be scanned at all, with either the internal or the external 𝗂𝗏𝗄.

  • For an account with birthday height before NU6.3 activation, Orchard-pool note ciphertexts SHOULD NOT be scanned with the external 𝗎𝗌𝖾_𝗊𝗌𝗄=𝖿𝖺𝗅𝗌𝖾 𝗂𝗏𝗄 after NU6.3 activation. (A transaction containing such an output would have to have been created by a non-conformant wallet that sent funds to an external Orchard-pool receiver after NU6.3.)

  • If an account's Orchard-pool balance is known to be zero at a block at or after NU6.3 activation, then Orchard-pool note ciphertexts SHOULD NOT be scanned in any block that descends from that block in the block tree. (After NU6.3, no new value may enter the Orchard pool 6, and only a holder of the account's spending key can move value in that pool to the account's receivers 7. With a zero balance, that value would have to come from another account, which the restrictions on sending and the rule against sending funds to an internal address of another account forbid. So a zero balance there cannot subsequently become non-zero.)

Note that, for an account with birthday height before NU6.3, it might seem as though scanning could be further optimized by not scanning the Ironwood pool with 𝗎𝗌𝖾_𝗊𝗌𝗄=𝗍𝗋𝗎𝖾 incoming viewing keys. However, because a conservatively early birthday height has traditionally been safe, such an optimization would not be safe. A 𝗎𝗌𝖾_𝗊𝗌𝗄=𝗍𝗋𝗎𝖾 account whose recorded birthday height was set too early would have its 𝗎𝗌𝖾_𝗊𝗌𝗄=𝗍𝗋𝗎𝖾 Ironwood-pool notes skipped. So the optimization is not specified.

These rules are summarized in the following table. NU6_3ActivationHeight is the NU6.3 activation height 6; later_of denotes the maximum of two heights.

Pool𝗎𝗌𝖾_𝗊𝗌𝗄𝗂𝗏𝗄 scopeBlock range to scan
Orchard𝖿𝖺𝗅𝗌𝖾internalbirthday to (Orchard-pool balance known-zero after NU6.3)
Orchard𝖿𝖺𝗅𝗌𝖾externalbirthday to NU6_3ActivationHeight − 1
Orchard𝗍𝗋𝗎𝖾eithernothing
Ironwoodeither*either*later_of(birthday, NU6_3ActivationHeight) to tip

"either*" means subject to the first-funds optimization below.

The following additional rule does not fit well into the table:

  • Once a wallet that is scanning sees that it has received funds in either the Orchard pool or the Ironwood pool with a given 𝗎𝗌𝖾_𝗊𝗌𝗄 value, it SHOULD stop scanning with the other 𝗎𝗌𝖾_𝗊𝗌𝗄 value. (Because 𝗎𝗌𝖾_𝗊𝗌𝗄 is uniform per account, observing funds under one value is reliable evidence of the account's 𝗎𝗌𝖾_𝗊𝗌𝗄 choice. This remains true even if the transaction that provided the evidence is later reorged away, since it was consensus-valid on some chain.)

First-funds scanning in the Ironwood pool

Under the rules so far, a wallet that does not yet know its 𝗎𝗌𝖾_𝗊𝗌𝗄 value has to scan the Ironwood pool with up to four incoming viewing keys, until it first receives funds there. We describe an optimization for the Ironwood pool below. (It could be applied separately to the Orchard pool, but that is less helpful, because the other rules almost subsume the benefit.)

While the wallet knows that no Ironwood-pool funds have been received by the account before the block being scanned, it only needs to use the external incoming viewing keys, with one exception: if the transaction's valueBalanceIronwood is negative (value is entering the Ironwood pool), then the transaction has an input to the pool via the value balance. That input might go to an Ironwood-pool note at an internal receiver of this wallet. (No other party can address an internal receiver. So before the account has received Ironwood-pool funds, an internal note for it can only arise from the account's own transaction. That transaction must be funded either by spending existing Ironwood-pool funds —of which there are none yet— or by such an inflow.)

Let initial_use_qsk_set be the set of 𝗎𝗌𝖾_𝗊𝗌𝗄 values that could have been used by the account: either a singleton set, when the wallet knows its 𝗎𝗌𝖾_𝗊𝗌𝗄 value, or {𝖿𝖺𝗅𝗌𝖾,𝗍𝗋𝗎𝖾} when it does not (for example when restoring from seed). The resulting scanning algorithm, applied to each account, is:

let mut use_qsk_set : Set<bool> := initial_use_qsk_set

// Incoming shielded funds cannot be spent in the same block in which they are
// received. The optimization can therefore be applied up to *and including* this
// block (or the NU6.3 activation block, if later).
let mut no_ironwood_until_after : Height = later_of(birthday, NU6_3ActivationHeight)

for height in arbitrary order {
    // Account for reorgs: retain the conclusion that no Ironwood funds have been
    // received only up to the fork height (the highest block still on the
    // canonical chain), floored at the NU6.3 activation block.
    if (a reorg has occurred whose last unchanged block is at fork_height)
       and fork_height < no_ironwood_until_after {
        no_ironwood_until_after := later_of(fork_height, NU6_3ActivationHeight)
    }

    let received_ironwood_in_block : bool := false
    for each Ironwood output in each transaction at height {
        let scope_set :=
            if valueBalanceIronwood < 0 or height > no_ironwood_until_after {
                {internal, external}
            } else {
                {external}
            }
        for (scope in scope_set, use_qsk in use_qsk_set) {
            if the output decrypts using ivk(scope, use_qsk) {
                record the funds
                use_qsk_set := {use_qsk}
                received_ironwood_in_block := true
            }
        }
    }
    if !received_ironwood_in_block and height == no_ironwood_until_after {
        // No Ironwood funds were received in this or any prior block, so the
        // optimization can be applied to the next block.
        no_ironwood_until_after := height + 1
    }
}

A wallet MAY scan in any order. Only scanning forward from the birthday height can take full advantage of the optimization, but it is possible to scan forward and backward in parallel.

As further optimizations (not shown in the pseudocode):

  • A wallet can also track whether the account has received any funds at all. This avoids having to conservatively scan other users' migration transactions with scope_set set to {𝗂𝗇𝗍𝖾𝗋𝗇𝖺𝗅,𝖾𝗑𝗍𝖾𝗋𝗇𝖺𝗅}.

  • If the wallet is scanning for Orchard-pool funds in parallel —which, as specified above, uses only 𝗎𝗌𝖾_𝗊𝗌𝗄=𝖿𝖺𝗅𝗌𝖾— it can narrow use_qsk_set to {𝖿𝖺𝗅𝗌𝖾} whenever it sees any.

Scanning when restoring from seed

The only situation in which a wallet does not know an account's 𝗎𝗌𝖾_𝗊𝗌𝗄 value is restoring from seed, since 𝗎𝗌𝖾_𝗊𝗌𝗄 is not stored with wallet recovery information. In that situation the wallet MUST take initial_use_qsk_set to be {𝖿𝖺𝗅𝗌𝖾,𝗍𝗋𝗎𝖾} and scan with both values until the locking rule narrows it. This must be done even if the wallet implementation always makes a fixed 𝗎𝗌𝖾_𝗊𝗌𝗄 choice itself, because restoration must work across wallet implementations: a seed created by one wallet may be restored into another.

Outgoing-note detection

The outgoing viewing key 𝗈𝗏𝗄 (and its internal counterpart) is likewise derived through 𝗋𝗂𝗏𝗄 and so also depends on 𝗎𝗌𝖾_𝗊𝗌𝗄. A wallet that detects its own outgoing notes via 𝗈𝗏𝗄 uses the 𝗈𝗏𝗄 for the same per-account 𝗎𝗌𝖾_𝗊𝗌𝗄 value, so the considerations above apply unchanged. No additional 𝗎𝗌𝖾_𝗊𝗌𝗄 dimension is introduced beyond the 𝗂𝗏𝗄 scanning described here.

Receiving funds into the Ironwood pool

Because receivers are scoped to the Orchard protocol rather than to a pool (see above), an Orchard-protocol receiver that was exposed before NU6.3 can receive Ironwood-pool funds after NU6.3. A wallet that supports NU6.3 will scan for them as specified above. Once an Orchard-protocol receiver has been exposed, no party can be prevented from sending funds to it in either pool, subject to the consensus rules.

Restrictions on sending

The following restrictions concern where a wallet sends funds:

  • A wallet MUST NOT send funds to any external receiver (including its own) in the Orchard pool after NU6.3 activation.
  • Any wallet that generates 𝗎𝗌𝖾_𝗊𝗌𝗄=𝗍𝗋𝗎𝖾 keys MUST NOT send Orchard-pool funds to them at any point.
  • After NU6.3 activation, a wallet that sends funds to an external Orchard-protocol receiver, such as the Orchard receiver of a unified address 15, MUST send them to the Ironwood pool.

The first two restrictions, together with the rule above against sending funds to an internal address of another account, make the scan ranges above sound. The first also protects privacy. A note that carries value at an external receiver has a real note ciphertext, which anyone holding that receiver's 𝗂𝗏𝗄 can decrypt. That includes a quantum adversary that has recovered the 𝗂𝗏𝗄 from a published address, whereas internal receivers are never published.

The third restriction follows from the first for the wallet's own external receivers. For a receiver of another party, it is needed to comply with the consensus rules: after NU6.3 activation, the cross-address restriction 7 makes an Orchard-pool output carrying funds to that receiver invalid.

Migrating a wallet's own Orchard-pool funds to the Ironwood pool is specified in ZIP 318. 13

Spending from and rebalancing within the Orchard pool

After NU6.3 activation, every Orchard-pool Action is subject to the cross-address restriction: the expanded receiver of its output note must equal the expanded receiver of its spent note. 7 A wallet therefore builds Orchard-pool Actions of two kinds:

  • A spend Action spends a real note, and pairs it with a dummy output note to the spent note's expanded receiver.
  • An output Action creates an output note that carries value, at one of the wallet's own internal receivers, and pairs it with a dummy spent note at the same receiver.

A transaction that spends from the Orchard pool uses spend Actions. A transaction that keeps change in the Orchard pool, or rebalances notes within it (for example as note preparation for migration 13), also uses output Actions.

A wallet SHOULD NOT spend a real note and create an output note that carries value in the same Action. Such an output note has a real note ciphertext, which would let a party holding the receiver's 𝗂𝗏𝗄 link the spend to the receiver, as described for dummy output notes below.

Dummy spent notes

The dummy spent note of an output Action is constructed as in § 4.8.3 ‘Dummy Notes (Orchard)’ 16, except for its key and address. Instead of a full viewing key and address generated from a random spending key, it uses the account's own full viewing key and the diversifier of the output's receiver. Its value is zero, so its Merkle path is not checked. Its spend authorization signature is made with the account's spend authorizing key. So a wallet can only create an output Action at a receiver of its own.

Dummy output notes

For the dummy output note of a spend Action, the wallet MUST fill the note ciphertext enc_ciphertext with random bytes, rather than with a real encryption of the note plaintext to the spent note's receiver.

If enc_ciphertext were instead a real encryption, it would trial-decrypt under the spent note's receiver's 𝗂𝗏𝗄, in the same Action that carries the spend's nullifier. Any party holding that 𝗂𝗏𝗄 could then link the nullifier to the address, and so detect the spend. That includes a quantum adversary, which can recover the 𝗂𝗏𝗄 from the published (diversified) address — although it cannot feasibly recover the nullifier key. Randomizing enc_ciphertext prevents this. The note and its commitment are unchanged.

An output note that carries value keeps a real encryption, so that the wallet can recover the note from the chain, for example after restoring from seed.

In a Partially Created Zcash Transaction (PCZT) 17, the dummy output still carries its explicit recipient, value, and rseed, but no user-supplied address. A signer therefore recovers nothing from the ciphertext, and reads a value of zero. So it classifies the output as a tolerable dummy output, rather than reconstructing an expected ciphertext and rejecting the mismatch.

Value balance and fees

After NU6.3 activation, valueBalanceOrchard MUST be nonnegative 7. So the total value of a transaction's Orchard-pool output notes cannot exceed the total value of the Orchard-pool notes that it spends. A transaction that rebalances notes within the Orchard pool cannot add value to them from another pool; it can pay its fee from the value of the notes that it spends.

Rationale

The restrictions on key generation, key exposure, and sending Orchard-pool funds are chosen so that the only situation requiring both 𝗎𝗌𝖾_𝗊𝗌𝗄 values to be scanned is restoration from seed, and so that no scanning rule relies on the recorded birthday height being accurate as an upper bound. The latter is essential: it has always been safe to set a conservatively early birthday height, and the restore-from-seed case —the one case in which 𝗎𝗌𝖾_𝗊𝗌𝗄 is unknown— is also the case in which the birthday height is least reliable. Pruning 𝗎𝗌𝖾_𝗊𝗌𝗄=𝗍𝗋𝗎𝖾 Ironwood-pool scanning by birthday height would therefore be unsafe in exactly the case it would apply, so it is not specified.

Requiring 𝗎𝗌𝖾_𝗊𝗌𝗄=𝗍𝗋𝗎𝖾 keys not to be generated before NU6.3 Mainnet activation, rather than merely recommending it, keeps the Orchard pool free of 𝗎𝗌𝖾_𝗊𝗌𝗄=𝗍𝗋𝗎𝖾 funds without relying on senders' behaviour, and bounds the birthday height of every 𝗎𝗌𝖾_𝗊𝗌𝗄=𝗍𝗋𝗎𝖾 account.

The first-funds optimization rests on the observation that, before an account has received any Ironwood-pool funds, an internal Ironwood-pool note for it can only be created by the account's own transaction, and only when value enters the pool via the value balance. The "up to and including this block" treatment is sound because incoming shielded funds cannot be spent in the block in which they are received.

The reorg handling retains the "no funds received" conclusion only up to the fork height, not the chain tip. A shallow reorg at the scanning frontier can replace blocks below the watermark, while leaving the chain the same length or longer. So keying the rollback on the tip height would leave a stale watermark, and could cause an internal Ironwood-pool note on the new branch to be missed.

Deployment

This ZIP describes wallet behaviour consequent on the deployment of NU6.3. 6 The restrictions on generating and using 𝗎𝗌𝖾_𝗊𝗌𝗄=𝗍𝗋𝗎𝖾 keys take effect immediately; the scanning rules apply from NU6.3 activation.

References

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" ↩

  2. ZIP 200: Network Upgrade Mechanism ↩

  3. Zcash Protocol Specification, Version 2026.8.0 [NU6.3] or later ↩

  4. Zcash Protocol Specification, Version 2026.8.0 [NU6.3]. Section 3.12: Mainnet and Testnet ↩

  5. ZIP 229: Version 6 Transaction Format ↩

  6. ZIP 258: Deployment of the NU6.3 Network Upgrade ↩ ↩2 ↩3 ↩4 ↩5

  7. ZIP 2006: Restricting Transfers into the Orchard Pool ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8

  8. ZIP 2005: Ironwood Quantum Recoverability ↩ ↩2

  9. Zcash Protocol Specification, Version 2026.8.0 [NU6.3] or later. Section 3.1: Payment Addresses and Keys ↩

  10. ZIP 32: Shielded Hierarchical Deterministic Wallets ↩

  11. ZIP 307: Light Client Protocol for Payment Detection ↩

  12. The Ironwood Book ↩

  13. ZIP 318: Orchard to Ironwood Migration ↩ ↩2 ↩3

  14. ZIP 2005: Ironwood Quantum Recoverability, section "Usage with FROST" ↩

  15. ZIP 316: Unified Addresses and Unified Viewing Keys ↩

  16. Zcash Protocol Specification, Version 2026.8.0 [NU6.3] or later. Section 4.8.3: Dummy Notes (Orchard) ↩

  17. zcash/zips issue #693: Standardize a protocol for creating shielded transactions offline ↩