# ZIP 318: Orchard to Ironwood Migration

Status: Draft

Owners: Schell Carl Scivally, Pacu Gindre, Kris Nuttycombe

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-0318) · [Source file](https://github.com/zcash/zips/blob/a34cfc091434275120ff017c92c3b50d3cef623a/zips/zip-0318.md) · [Discussion](https://github.com/zcash/zips/issues/1315)

## Document parts

[Part 1](https://zips.tools/zip/318) · [Part 2](https://zips.tools/zip/318?part=2)

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

- The wallet MUST offer Tor <a id="user-content-fnref-tor"></a>
  [13](#user-content-fn-tor) and (if available) Nym <a id="user-content-fnref-nym"></a>
  [14](#user-content-fn-nym) network
  options. When enabled, all migration transaction broadcasts MUST be routed
  through the selected network.

- If Tor and Nym are unavailable on the user's network or region, the wallet
  SHOULD suggest using a trusted VPN before proceeding.

- The user MAY proceed without Tor, Nym, or a VPN; the choice is the user's.

- The wallet MUST display a disclaimer explaining the IP-correlation risk:
  because pool-crossing transfers are visible on-chain, without network-level
  privacy a server or network operator can correlate the broadcasting IP
  address with the on-chain event; if IPs are reused, this may leak the
  total Orchard balance of the wallet to the server operator.

- Broadcasting migration transactions to a different server than the one used for
  synchronization MAY further reduce correlation, and a wallet MAY do so.

<a id="platform-considerations"></a>

### Platform considerations

This section is non-normative except where it restates requirements from
[Background scheduling](#backgroundscheduling) and
[Decoupling synchronization from broadcast](#decouplingsynchronizationfrombroadcast).
The flow applies to both iOS and Android; differences are noted where they affect
implementation.

#### iOS

- Background scheduling SHOULD use *BGTaskScheduler* with a background-processing
  task type suitable for longer work.

- A single task execution MUST either synchronize (updating anchors and
  proofs) or broadcast, never both.

- If the task does not run, the next application open MUST surface the fallback
  prompt.

#### Android

- Background scheduling SHOULD use *WorkManager* with a flex interval aligned to
  the scheduled broadcast heights.

- A single Worker execution MUST either synchronize (updating anchors and
  proofs) or broadcast, never both.

- OEM battery-optimization behaviour (for example on MIUI or One UI) may suppress
  tasks even when permissions are granted; this is acknowledged as a known
  limitation. During onboarding the wallet SHOULD request a battery-optimization
  exemption to improve reliability, and MUST continue to function (relying on the
  on-launch fallback) if the exemption is denied.

- On every application launch, the wallet MUST reconcile whether a pending
  transfer is overdue and surface the fallback prompt if needed.

- WorkManager task persistence across reboots SHOULD be verified by the
  implementation; if a boot receiver is used to re-enqueue tasks, the relevant
  platform permission must be declared.

<a id="status-notifications"></a>

### Status notifications

- On a successful transfer, the wallet MAY show a notification.

- On a failed transfer, the wallet MUST surface the failure with the ability to
  retry. The retry affordance SHOULD lead to a self-contained screen that does not
  start a synchronization.

- Notification delivery SHOULD NOT be relied upon as the primary status mechanism;
  on-launch reconciliation is primary (see
  [Fallback on application open](#fallbackonapplicationopen)).

- On platforms where notifications can be cleared by a reboot or suppressed by the
  OS or OEM managers (notably Android), the wallet MUST disclose that notification
  delivery is not guaranteed and MUST direct the user to return to the application
  to check progress.

<a id="progress-visibility"></a>

### Progress visibility

The wallet SHOULD report migration progress.

<a id="user-facing-copy-requirements"></a>

### User-facing copy requirements

Because background scheduling is best-effort, product copy SHOULD NOT describe the
migration as automatic or guaranteed. The fallback of prompting on the next
application open MUST be presented as a normal part of the flow, not as an error
state.

<a id="error-handling"></a>

### Error handling

The wallet MUST handle at least the following conditions.

- **Transient broadcast failure (network unavailable).** The stored pre-signed
  transaction MUST NOT be discarded. The task SHOULD retry within its window and
  rely on background backoff for the next window. If broadcast continues to fail,
  the on-launch fallback MUST offer the user the choice to send now or retry in
  the background.

- **Invalid transaction (input note already spent).** If the user spends
  *Orchard-pool* funds outside the migration, a queued migration transaction may
  become invalid. The wallet MUST detect this (the detection condition is that
  the confirmed-spendable *Orchard-pool* balance is greater than zero while the
  next scheduled migration transaction is missing or invalid), MUST notify the
  user of the failure, and MUST construct a revised schedule for the remaining
  unspent balance using a fresh anchor. The screen that does so MUST be
  self-contained and MUST NOT trigger a synchronization. A rebuilt transaction
  MAY produce change in the *Orchard pool*: the canonical shape hides whether
  change is present, so this carries no privacy penalty, only the operational
  cost of a later synchronization to recover the change.

- **Expired transaction.** A pre-signed transaction that reaches its expiry height
  <a id="user-content-fnref-zip-0203"></a>
  [15](#user-content-fn-zip-0203) before being broadcast MUST be detected on the next application open
  and handled in the same way as an invalid transaction: a new transaction with a
  fresh anchor and expiry is constructed for the affected part, with its
  denomination unchanged.

- **Canonical fee change.** Exact-value funding notes bake the canonical fee
  into note values at preparation time. If the canonical fee changes between
  preparation and broadcast, the affected pre-signed parts no longer match the
  canonical structure and SHOULD be rebuilt (producing or consuming the difference
  as *Orchard-pool* change) or the remaining balance re-prepared. This carries
  no privacy penalty, but costs an additional confirmation round.

- **Negligible remaining balance.** If, after the last scheduled transfer, only an
  amount below a significance threshold remains (for example, due to fee
  rounding), the wallet MAY treat the migration as complete and inform the user
  that a negligible amount remains. If the remaining amount is above
  `MAX_RESIDUAL_VALUE`, the wallet MAY add a final transfer. Wallets SHOULD
  apply the ZIP 317 marginal-fee considerations <a id="user-content-fnref-zip-0317-3"></a>
  [12](#user-content-fn-zip-0317) when deciding
  whether a residual note is economic to migrate.

- **Note-preparation transaction unconfirmed or failed.** If the application is
  closed before a note-preparation transaction confirms, on re-open the wallet
  MUST report that it is waiting for confirmation and MUST proceed to scheduling
  once the preparation transactions confirm. If a preparation transaction
  expired, the wallet MUST offer to retry it. Where preparation was partitioned
  across multiple transactions, partial confirmation MUST be handled: parts
  funded by confirmed preparation outputs MAY proceed on schedule while the
  remainder waits.

- **Application reinstall or restore on a new device.** On detecting unspent
  *Orchard-pool* funds with no local migration state, the wallet MAY treat the
  situation as requiring a fresh migration (already-confirmed transfers remain
  on-chain; only the remaining balance is rescheduled). See
  [Non-requirements](/zip/318#non-requirements).

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

## Rationale

<a id="rationale-for-the-scheduled-background-approach"></a>

### Rationale for the scheduled background approach

The chosen approach broadcasts migration transactions from best-effort background
tasks at pre-determined, randomly drawn times that are de-correlated from the
user's interaction with the application. An alternative considered was a guided
"on-open" approach, in which each transfer is created at the moment the user
opens the application.

The on-open approach was dismissed primarily because user-triggered sends leak too
much information: the scan ranges a wallet requests from a light wallet server
already link its notes to one user, and if each pool-crossing send also coincides
with an application open, an observer can monitor the wallet across the entire
migration. It also provides no temporal de-correlation, because every transfer is
anchored to the moment of user interaction.

The scheduled approach achieves temporal de-correlation in the common case. Its
worst case (when no background task ever runs and the user sends everything on a
single application open) is no worse than the on-open approach, while its best
case is significantly better. Pre-signed transactions can be stored using existing
transaction-resubmission infrastructure.

<a id="rationale-for-poisson-transfer-scheduling"></a>

### Rationale for Poisson transfer scheduling

This subsection is non-normative.

Drawing inter-transfer delays from an exponential distribution makes the
schedule memoryless: observing one of a wallet's broadcasts gives an observer
no information about when its next broadcast will occur, so there is no
per-wallet timing signature to recover. Because the superposition of
independent Poisson processes is itself a Poisson process, the aggregate
traffic of all wallets following this schedule blends into a single pooled
process from which no individual wallet's contribution is separable by timing
analysis. Shuffling the parts before scheduling extends the same property to
values: the sequence of denominations a wallet emits is not a predictable
function of its balance.

The mean delay must not be too short relative to the anchor-bucket interval. A
schedule whose delays are much shorter than the bucket interval (for example,
minutes) would compress a wallet's entire migration into a tight, predictable
window, and the burst itself would be a clustering signal. With
`MEAN_DELAY` = 66 blocks and the bucket modulus $M$ = 144, a wallet broadcasts
about two parts per bucket on average while the exponential tail continues to
spread its transfers across several cohorts. This roughly halves the expected
inter-transfer latency compared with a 144-block mean. Keeping `MAX_DELAY` at
576 blocks, more than eight mean delays, preserves nearly all of the
exponential distribution's variance while bounding the worst-case gap so that
migration completes in bounded time and canonical expiry windows remain
practical.

Delays are denominated in blocks rather than wall-clock time because bucket
boundaries, anchors, and expiry heights are all block-denominated, and block
height is what an on-chain observer measures.

Routine background synchronization does not undermine the schedule's
de-correlation properties: a synchronization reveals to the light wallet
server only that some wallet has synchronized past a given height, while a
migration transaction's anchor is the shared boundary anchor common to its
entire cohort. Provided synchronization and broadcast never share a
background processing window, the server learns nothing that correlates a
specific synchronizing wallet with a specific migration transaction.

The same reasoning applies, at a smaller scale, to the note-preparation
broadcasts. Their values and direction are fully shielded, but a burst of
identically shaped padded transactions appearing at a single instant is a
shape-and-timing cluster attributable to one wallet, and such a burst also
telegraphs that a migration schedule will follow it. Spacing the preparation
broadcasts with a smaller-mean exponential (`PREP_MEAN_DELAY`) prevents the
cluster from forming without materially delaying the migration: preparation
is unconstrained by anchor buckets — its transactions anchor at the wallet's
ordinary near-tip depth — so its spacing serves only to break up the burst,
not to place transactions into shared anchor cohorts.

<a id="rationale-for-the-stateful-design"></a>

### Rationale for the stateful design

An attractive alternative is a
**stateless, chain-recoverable** design: persist no scheduler state at all, and
instead recover migration progress from the chain using the wallet's viewing keys
(identifying migration transactions by the canonical predicate) plus a
deterministic re-computation of the quantization of the remaining balance. Because
the quantization is a pure function of the balance, it recomputes identically on
every activation and after restore-from-seed, which makes that design robust to
restart and to multi-device restore.

This unified proposal nonetheless adopts a **stateful** design (a persisted
schedule plus pre-signed transactions) and treats statelessness and
multi-device/recovery continuity as non-requirements (see
[Non-requirements](/zip/318#non-requirements)). The reasons are:

- Persisted, pre-signed transactions make best-effort background broadcast
  practical, and make temporal de-correlation of broadcast from synchronization
  and from application opens straightforward to implement.

- Statelessness was judged to make the desired temporal de-correlation harder, not
  easier, while the robustness it buys (restart and restore continuity) is not
  required for this flow.

The chain-recoverability property is not wasted: the canonical
migration-transaction predicate of
[Canonical migration transaction structure](#canonicalmigrationtransactionstructure)
still lets a wallet, using its viewing keys, reconstruct which denominations have
confirmed, which is how
the reinstall/restore case is handled as a fresh migration. The full stateless
design is recorded as a rejected alternative (see
[Alternatives](#alternatives)).

<a id="rationale-for-canonical-amount-selection"></a>

### Rationale for canonical amount selection

Privacy for value-transparent transfers comes from **value collision** (many
wallets emitting the same amount) and **ambiguity of grouping** (an observer
cannot tell which amounts belong together), not from unpredictability of the
amount. A high-entropy random amount collides with no other wallet and is a
near-unique fingerprint; a random partition into high-entropy pieces additionally
exposes the balance to subset-sum reconstruction. Canonical
denominations are therefore preferred precisely because they are *not* distinctive:
many wallets emit identical denominations, so no single transfer is identifiable by
its value, and a wallet MAY safely place several transfers in one cohort.
Randomizing the *choice among canonical denominations* (see
[Amount selection, canonical quantization](#amountselectioncanonicalquantization))
is categorically different from randomizing amounts: every emitted part still
collides with the population, while the multiset emitted for a given balance
is no longer predictable, which improves balance hiding and further weakens
targeted subset-sum. The detailed privacy argument is in
[Why value collision provides privacy](#whyvaluecollisionprovidesprivacy) and the
sections that follow it; the discarded random-amount strategies are listed in
[Alternatives](#alternatives).

<a id="rationale-for-the-canonical-transaction-shape"></a>

### Rationale for the canonical transaction shape

The canonical migration transaction spends exactly one *Orchard-pool* note,
pads the Orchard bundle to exactly two actions, and produces exactly one
*Ironwood* output. Under NU6.3, Orchard bundles pair every spend and every
output with a fabricated counterpart, so a bundle's action count is the sum
of its spends and outputs; values and the fabricated/real distinction are
hidden. The canonical two-action Orchard bundle therefore consists of one
spend action and one output action, and does not reveal whether the output
action carries a change output or a zero-valued dummy. This has two
consequences:

- **Indistinguishability from manual spends.** An automated migration
  transaction is structurally identical to a manually created
  *Orchard*→*Ironwood* spend, which takes the same shape. The anonymity set for
  a migration transaction is therefore the whole population of conforming
  cross-pool spends, not merely the transactions generated by migrating
  wallets. Because NU6.3 disables ordinary payments within the old pool
  <a id="user-content-fnref-zip-2006-3"></a>
  [10](#user-content-fn-zip-2006), that population constitutes essentially all post-upgrade
  *Orchard-pool* activity; this strengthens the ZIP 308 <a id="user-content-fnref-zip-0308-2"></a>
  [11](#user-content-fn-zip-0308) stance
  that migration transactions need be hidden only among themselves.

- **Change carries no privacy penalty.** A change-bearing migration transaction
  (for example, one rebuilt during error recovery, or one sent by a wallet that
  skipped note preparation) is indistinguishable from a change-free one.
  Producing change has only an operational cost: the change cannot be spent
  until a later synchronization witnesses it, and a part that would spend it
  cannot be pre-signed until then.

The *Ironwood* bundle is a single action, not padded to the two-action
minimum. Unlike the Orchard side, where padding hides whether a change output
is present, the Ironwood side of a canonical transfer has exactly one output
and nothing left to hide: its shape is already fixed by the
[canonical structure](#canonicalmigrationtransactionstructure). A padding
dummy would therefore buy no indistinguishability, while costing an extra
action's proof — a real burden when a hardware device generates the proofs,
since each action's proving data must cross the device's
bandwidth-constrained link. What matters is that **one** action count is
pinned as canonical: if padding were left optional, conforming wallets would split
into distinguishable padded and unpadded populations, partitioning the very
anonymity set the canonical shape exists to unify. (The indistinguishability
argument above extends to manually created *Orchard*→*Ironwood* spends to the
extent that they are built to this same single-action shape, which wallet
builders are encouraged to use for this transfer form.)

A corollary is that note preparation and pre-signing are motivated
operationally rather than by privacy: the privacy properties come from the
canonical shape, the canonical values, and the scheduling, while exact-value
funding notes are what allow the entire schedule to be pre-signed at once and
broadcast in any order.

The same hiding applies to note preparation: because fabricated components
are indistinguishable from real ones, an $N$-action preparation transaction
reveals nothing about how its actions divide between spends and outputs —
whether it divides notes, combines them, or both. Only its action count and
timing are observable.

<a id="rationale-for-decoupling-synchronization-from-broadcast"></a>

### Rationale for decoupling synchronization from broadcast

Linking synchronization and broadcast in the same background session would let an
observer correlate the two events and thereby link the broadcast to the wallet.
Keeping them separated in time preserves the de-correlation that the scheduling is
designed to provide. The principal place a synchronization is unavoidable is after the
note-preparation transactions confirm, since the wallet must witness the prepared
notes before the first scheduled send; the exceptional case of a migration
transaction that outputs change back to the *Orchard pool* is handled the same
way, by scheduling the synchronization at a different time from the next
broadcast.

<a id="rationale-for-anchor-height-bucketing"></a>

### Rationale for anchor-height bucketing

Drawing anchors from shared, network-wide boundaries means a transaction's
anchor reveals only that the creator had scanned beyond that boundary at some
time before broadcast, not the wallet's precise sync height or sync schedule,
and allows many users' transactions to share anchors. This mirrors the
goal of ZIP 308 <a id="user-content-fnref-zip-0308-3"></a>
[11](#user-content-fn-zip-0308) (hiding individual migration transactions among those
of all migrating users), adapted to anchor selection in the light-client setting,
and complements the anchor-selection guidance of ZIP 315. <a id="user-content-fnref-zip-0315-5"></a>
[6](#user-content-fn-zip-0315)

<a id="rationale-for-anchor-selection"></a>

### Rationale for anchor selection

This subsection is non-normative.

A "sign ahead, broadcast later" workflow can leak information through anchor
selection: if the anchor were fixed at signing time — as it is under the
version 5 transaction-digest rules <a id="user-content-fnref-zip-0244"></a>
[16](#user-content-fn-zip-0244), where the anchor is committed
by the signature hash — then at broadcast time the gap between the anchor's
age and the broadcast height would reveal when the transaction was prepared.

Two design choices eliminate this leak. First, the version 6 transaction
format <a id="user-content-fnref-zip-0229-5"></a>
[2](#user-content-fn-zip-0229) commits the anchor as authorizing data — a public input to
the proof — rather than in the signature hash, so the prover can choose the
anchor *after* signing, and the wallet can update a pre-signed transaction's
anchor and proof at broadcast time without re-signing (see
[Pre-signed transaction storage](#pre-signedtransactionstorage)). Second, the
anchor is drawn at random from the shared candidate set of boundaries rather
than pinned to the most recent boundary.

Pinning to the most recent boundary would create a bottleneck at each
boundary: the anonymity set for a transaction anchored at boundary $B$ is the
set of wallets that had scanned beyond $B$ by proving time — a set that an
adversary who compromises a light wallet server can approximate by observing
which clients have fetched blocks past $B$ — and shortly after each boundary
that set is small. Drawing the anchor at random from all candidate boundaries
means an observed anchor implies membership only in the set of wallets that
scanned beyond that boundary at *some* time before broadcast, a set that has
had time to grow. This is also why no privacy-preserving block-download
mechanism is required for these invariants: the light wallet server learns
which wallets have scanned past a given height, but that knowledge narrows
the sender of a migration transaction no further than the anchor itself
already does.

The randomized draw carries a tension: an earlier anchor requires the
transaction's funding note to have existed at that boundary, so the
population able to emit early anchors is bounded by the wallets whose note
preparation had confirmed by then — potentially a smaller global anonymity
set. This is mitigated by the fact that note preparation can be performed
before the network upgrade activates, so the funding notes of many wallets
already exist at the earliest post-activation boundaries. The candidate set
is nonetheless always restricted to boundaries above the NU6.3 activation
height, so that wallets that prepared before the upgrade do not form a
distinguishable subpopulation emitting pre-activation anchors.

The draw is recency-weighted rather than uniform over the candidate set, for
four reasons. First, uniformity across wallets: under a uniform draw over
each wallet's own window, the anchor-age distribution a wallet emits is a
function of when it prepared — early preparers emit anchors spread over a
wide window, late preparers only recent ones — so anchor age becomes a
statistical fingerprint of preparation date. Under a fixed, capped
recency-weighted distribution, every wallet whose window is at least
`ANCHOR_AGE_CAP` deep draws from the identical distribution, and wallets with
shallower windows are forced toward recent anchors, where the common
distribution already concentrates. Second, the cap bounds the inevitable
preparation-time leak: an anchor always places the creation of the funding
note before it, but no anchor can reveal more than "prepared at least
`ANCHOR_AGE_CAP` boundaries ago" — little more than the act of broadcasting
already reveals. Third, concentration: the same reasoning that prefers
canonical denominations to high-entropy amounts prefers few, heavily-used
anchors to many thinly-used ones; recency weighting produces large per-anchor
cohorts. Fourth, alignment with manual spends: a conforming wallet making a
one-off *Orchard*→*Ironwood* spend will naturally use a recent boundary, and
keeping the migration flow's anchor ages similar prevents anchor age from
partitioning automated and manual traffic. The cap also bounds the tree
states a wallet must retain for proving to the last `ANCHOR_AGE_CAP`
boundaries.

The cost of recency weighting is a sharper posterior on proving time (a
recent anchor implies proving shortly after it); this is blunted by the
decoupling of proving from broadcast and by the exponential scheduling delay
between them, which together turn "shortly after the anchor" into a
multi-boundary window. The most recent boundary is excluded entirely because
transactions anchored to it could only have been produced by wallets that
synchronized within the current boundary interval — the bottleneck in
miniature. In the first days after activation, every wallet's window is
shallow and all distributions collapse onto the few boundaries that exist;
privacy in that regime rests on cohort size (all migrating wallets share
those anchors), not on anchor diversity.

<a id="privacy-implications"></a>

## Privacy Implications

The migration necessarily reveals information; the requirements in this ZIP are
intended to minimize it and to ensure the user consents to what remains. The
principal sources of leakage are:

- **Migrated amounts.** Each migration transaction reveals on-chain the value it
  moves from the *Orchard pool* to the *Ironwood pool*. <a id="user-content-fnref-zip-0209-3"></a>
  [7](#user-content-fn-zip-0209) Quantizing the
  balance into canonical denominations means no single transaction reveals the
  user's whole *Orchard-pool* balance, and each amount collides with those of many
  other wallets.

- **Linkage between a wallet's own transfers.** If multiple migration transactions
  could be grouped by a shared distinctive value, anchor, expiry, or broadcast
  time, an observer might cluster them as belonging to one wallet. Canonical
  amounts, shared network-wide boundary anchors, bucketed expiries, and
  de-correlated broadcast times are intended to defeat this clustering.

- **Synchronization timing.** The anchor height of a transaction reveals that
  its creator had scanned beyond that height at some time before broadcast.
  Drawing anchors at random from the shared boundary set prevents an observer
  from inferring when a specific wallet synchronized, and from narrowing the
  sender to the initially small set of wallets that had scanned past the most
  recent boundary. Triggering a wallet synchronization in the same background
  session as a broadcast would let an observer correlate the two;
  synchronization is therefore kept decoupled in time from broadcast.

- **Correlation with user behaviour.** If transfers are broadcast at the moment the
  user opens the application, an observer (in particular a light wallet server that
  also sees the user's scan-range requests) can monitor the wallet across the whole
  migration. De-correlating broadcasts from application opens is the primary
  privacy goal of the chosen approach.

- **Network-layer metadata.** Without network-level privacy, the server or network
  operator that receives a broadcast can correlate the broadcasting IP address with
  the on-chain pool-crossing event. This ZIP requires offering Tor as an opt-in and
  disclosing the residual IP-correlation risk.

- **Change back to the Orchard pool.** A migration transaction that produces change
  in the *Orchard pool* requires the wallet to synchronize before that change can
  be spent, creating a point where synchronization and migration interact and must
  be carefully separated in time. Because the canonical shape pads the Orchard
  bundle to two actions, the presence or absence of change is not itself visible
  on-chain; the concern is operational, not a distinguisher.

- **Note-preparation transactions.** A note-preparation transaction is fully
  shielded: its values, and whether it divides notes, combines them, or both, are
  hidden. Its only observable features are its action count and its timing, which
  reveal at most a loose bound on the number of notes involved.

The remaining subsections of this section are non-normative; they analyze the
privacy properties of the procedure specified above.

<a id="threat-model"></a>

### Threat model

This subsection is non-normative.

The adversary observes all on-chain migration transactions: for each, the net
Orchard-out and Ironwood-in amounts and the block (hence time). The adversary does
**not** see addresses. The adversary's goal is **clustering**: partitioning the
global stream of migration transactions into per-wallet sets. Successful clustering
reconstructs a wallet's migration (and hence its balance and behavior) without any
address.

We additionally consider a **targeted** adversary who augments chain
observation with off-chain knowledge of a victim's incoming note values — in
the strongest case having deliberately sent the victim notes of
attacker-chosen values ("tagging"). This adversary's goal is to determine
whether, when, or in what amounts the victim has migrated (see
[On targeted note tagging](#ontargetednotetagging)).

<a id="why-value-collision-provides-privacy"></a>

### Why value collision provides privacy

This subsection is non-normative.

Privacy for value-transparent transfers comes from **value collision** (many
wallets emitting the *same* amount) and **ambiguity of grouping** (an observer
cannot tell which amounts belong together), not from unpredictability of the
amount. Randomizing amounts is counterproductive: a high-entropy amount collides
with no other wallet and is a near-unique fingerprint.

Every part in this design is a canonical denomination drawn from a small
shared set (`…, 100, 50, 20, 10, 5, 2, 1, 0.5, …`). Many wallets emit identical
denominations, so no single part is distinctive by its value.

<a id="on-multiplicity-and-cohorts"></a>

### On multiplicity and cohorts

This subsection is non-normative.

A wallet MAY place several parts into the same cohort. This does **not** let the
adversary attribute those parts to one wallet, because:

- **Canonical values are not self-identifying.** Several `100`-ZEC parts in a
  cohort cannot be attributed to one contributor versus several; the adversary
  cannot tell whether three co-located `100`s came from one wallet or three. The
  grouping is ambiguous *by construction*, and there is no rare value to anchor a
  reconstruction (the design has no high-entropy remainder).

- **Parts are not linked by inputs.** Each part is a separate transaction spending
  an *Orchard-pool* note, which publishes only an unlinkable nullifier; shielded
  inputs do not visibly link a wallet's transactions. The mandated absence of
  transparent components, the shared boundary anchors, the canonical fee, and
  the uniform two-action, single-output structure mean nothing on-chain ties one
  wallet's co-located parts together.

This is precisely why multiplicity is safe **only** under canonical amounts.

What a cohort *does* reveal is only the **aggregate throughput** of the entire
migrating population (the total value of all parts sharing that anchor), which
is population-level information, not any individual's magnitude.

<a id="on-subset-sum"></a>

### On subset-sum

This subsection is non-normative.

A worry with canonical decompositions is **targeted** subset-sum (find a subset
summing to a *known* target). This specification accepts the limited residual
exposure on the following grounds:

- Canonical values compose ambiguously: many unrelated parts sum to clean totals,
  so the constraint "this subset sums to a round number" is weak and yields
  exponentially many spurious solutions for an **untargeted** adversary (one
  without a known total).

- If an adversary already knows a wallet's total balance from off-chain data,
  learning that the wallet migrated that balance reveals no *new* information; the
  only further gain would be transaction-level linkage, which remains hard due to
  canonical collision, dispersion, and the lack of intra-cohort linkage.

- Therefore the dominant practical risk is **rarity** (below), which the
  `DENOM_CAP` bounds.

<a id="on-targeted-note-tagging"></a>

### On targeted note tagging

This subsection is non-normative.

An adversary may target a victim by sending notes of attacker-chosen,
distinctive values, hoping to recognize those values (or their sum) when the
victim's funds cross the pool boundary. Three mechanisms defeat this:

- **Quantization severs received values from migrated values.** Migrated
  amounts are drawn from the canonical denomination set regardless of the
  values of the notes that fund them; a distinctive received value never
  appears as a pool-crossing amount, and any sub-`MAX_RESIDUAL_VALUE` tail it
  contributes is withheld as the residual rather than revealed.

- **Note preparation absorbs tagged notes invisibly.** The preparation
  transaction is fully shielded, so consuming a tagged note and re-minting
  canonical funding notes reveals nothing about the tagged note's value or
  whether it participated at all.

- **Randomized decomposition hides the multiset.** Even an adversary who knows
  the victim's entire balance (for example, having funded it) cannot predict
  the emitted denomination multiset when the wallet randomizes its
  decomposition, and every denomination the adversary might look for collides
  with those of concurrently migrating wallets.

What the tagging adversary retains is population-level information: that some
wallets migrated during a given period. Determining that a specific victim's
funds have — or have not — migrated requires clustering the victim's
transactions out of that population, which the mechanisms above are designed
to prevent.

<a id="accepted-residual-leaks"></a>

### Accepted residual leaks

This subsection is non-normative.

- **Rarity bounded by the cap.** No single part exceeds the maximum
  denomination of 10000 ZEC, so no transfer is extremely large/rare. Larger
  denominations (e.g. 10000 ZEC) are rarer than smaller ones, but every value
  is shared by many wallets in a large migrating population.

- **Aggregate cohort throughput.** A cohort reveals the population's total migrated
  value at that boundary (not individual magnitudes).

- **Total part count and migration duration** for very large balances loosely
  indicate *bucketed* magnitude (a whale emits many parts over a longer
  period), which is far weaker than a unique value.

- **Whale self-selection (deliberate).** Large holders who want fast, clean UX may
  choose to migrate in a single transfer instead of using this procedure. This is
  accepted: forcing whales into a long, manually-driven many-transaction process
  would harm adoption, and lower adoption shrinks the anonymity set for *everyone*.

<a id="whale-handling"></a>

### Whale handling

This subsection is non-normative.

For large balances:

- The number of parts grows with the balance, and with it the expected
  duration of the migration (roughly `MEAN_DELAY` blocks per part). A very
  large balance therefore takes proportionally longer to migrate.

- Dividing a very large note into its funding notes may require layered
  preparation (see
  [Note preparation transactions](#notepreparationtransactions)), adding each
  layer's scheduled broadcast spread plus its mining latency to the
  preparation phase, and additional signing sessions if the layers are not
  pre-signed as a chain. This is exactly the
  situation of a wallet consolidating very many small notes, and the two are
  indistinguishable on-chain.

- $k$ per cohort is bounded by `K_MAX` (enforced by redrawing anchor ages) to
  limit the per-cohort count signal, keeping per-wallet multiplicity within a
  range that the (canonical, ambiguous) cohort can absorb.

- Whales remain weakly protected and may self-select to a single transfer, as
  above.

<a id="a-note-on-cohort-size-vs-per-wallet-multiplicity"></a>

### A note on cohort size vs. per-wallet multiplicity

This subsection is non-normative.

Two different quantities should not be conflated:

- **Cohort size** (parts from *all* wallets at a boundary): larger is always better
  for privacy (bigger crowd). Coordinated boundaries exist to maximize it.

- **Per-wallet multiplicity $k$**: the number of a *single* wallet's parts in one
  cohort. Under canonical-only amounts, $k > 1$ is safe (parts are unattributable),
  but `K_MAX` (enforced by redrawing anchor ages) keeps $k$ modest relative to
  expected cohort size so that per-wallet multiplicity stays well within the
  crowd.

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

## Deployment

This is a Wallet-category ZIP and does not itself introduce consensus rules. It
applies to wallets once the *Ironwood pool* is activated by its network upgrade
<a id="user-content-fnref-zip-0258-3"></a>
[8](#user-content-fn-zip-0258) and the migration becomes relevant. The note-preparation phase MAY be
performed before that activation (see
[Phase 1, Note preparation](#phase1notepreparation)).

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

## Reference implementation

TODO: link to the SDK and wallet pull requests implementing this flow. Phase 2 is
intended to be implemented in a Rust backend exposed through a public SDK
interface, so that wallets can integrate against a stable interface (initially with
mocked data) and later switch to the production implementation.

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

## Alternatives

The following alternative designs were considered and rejected.

- **Migrating the raw balance in a single transfer** publicly reveals the
  exact balance, and the transfer is trivially linkable to the wallet's other
  activity.

- **Random arbitrary amounts per transfer** produce high-entropy values that
  collide with no other wallet; each transfer is a near-unique fingerprint.

- **Random partition into high-entropy pieces**: the pieces sum to the
  balance, exposing it to subset-sum reconstruction, and each piece is
  self-fingerprinting.

- **Whole-ZEC bucketed parts with a high-entropy sub-1-ZEC remainder**: the
  rare remainder is a discriminator that makes co-locating parts in a cohort
  dangerous, and is itself near-unique. Superseded by canonical-only
  quantization, which has no remainder output.

- **Randomized per-wallet multiplicity $k$** adds behavioural entropy — itself
  a fingerprint — without dispersing co-located parts; it is dominated by a
  deterministic, cohort-size-governed $k$.

- **Guided on-open sending**: creating each transfer at the moment the user
  opens the application makes every broadcast coincide with application opens
  and light-wallet-server scan-range requests, letting an observer track the
  wallet across the whole migration; it provides no temporal de-correlation
  (see
  [Rationale for the scheduled background approach](#rationaleforthescheduledbackgroundapproach)).

- **Short-interval memoryless timing** (inter-transfer delays of minutes
  rather than hours): delays much shorter than the anchor-bucket interval
  compress a wallet's entire migration into one or two buckets, producing a
  tight, predictable burst that is itself a clustering signal, and such
  schedules are incompatible with shared boundary anchors (see
  [Rationale for Poisson transfer scheduling](#rationaleforpoissontransferscheduling)).

- **Stateless, chain-recoverable migration** (no persisted schedule): robust
  to restart and restore-from-seed, but statelessness is a non-requirement
  here, and persisted state makes temporal de-correlation and pre-signed
  background scheduling simpler (see
  [Rationale for the stateful design](#rationaleforthestatefuldesign)).

- **A privacy/speed slider exposed to the user**: the decision and the UI are
  too complex for typical users; the wallet picks sensible defaults instead.

<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/318#user-content-fnref-bcp14)

2. [ZIP 229: Version 6 Transaction Format](/zip/229) [↩](/zip/318#user-content-fnref-zip-0229) [↩2](/zip/318#user-content-fnref-zip-0229-2) [↩3](/zip/318#user-content-fnref-zip-0229-3) [↩4](/zip/318#user-content-fnref-zip-0229-4) [↩5](#user-content-fnref-zip-0229-5)

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

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

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

6. [ZIP 315: Best Practices for Wallet Implementations](/zip/315) [↩](/zip/318#user-content-fnref-zip-0315) [↩2](/zip/318#user-content-fnref-zip-0315-2) [↩3](/zip/318#user-content-fnref-zip-0315-3) [↩4](/zip/318#user-content-fnref-zip-0315-4) [↩5](#user-content-fnref-zip-0315-5)

7. [ZIP 209: Prohibit Negative Shielded Chain Value Pool Balances](/zip/209) [↩](/zip/318#user-content-fnref-zip-0209) [↩2](/zip/318#user-content-fnref-zip-0209-2) [↩3](#user-content-fnref-zip-0209-3)

8. [ZIP 258: Deployment of the NU6.3 Network Upgrade](/zip/258) [↩](/zip/318#user-content-fnref-zip-0258) [↩2](/zip/318#user-content-fnref-zip-0258-2) [↩3](#user-content-fnref-zip-0258-3)

9. [NU6.2 and NU6.3 updates to the Orchard Circuit (draft)](/draft/draft-zodl-valargroup-action-circuit-update) [↩](/zip/318#user-content-fnref-draft-zodl-valargroup-action-circuit-update)

10. [ZIP 2006: Restricting Transfers into the Orchard Pool](/zip/2006) [↩](/zip/318#user-content-fnref-zip-2006) [↩2](/zip/318#user-content-fnref-zip-2006-2) [↩3](#user-content-fnref-zip-2006-3)

11. [ZIP 308: Sprout to Sapling Migration](/zip/308) [↩](/zip/318#user-content-fnref-zip-0308) [↩2](#user-content-fnref-zip-0308-2) [↩3](#user-content-fnref-zip-0308-3)

12. [ZIP 317: Proportional Transfer Fee Mechanism](/zip/317) [↩](/zip/318#user-content-fnref-zip-0317) [↩2](/zip/318#user-content-fnref-zip-0317-2) [↩3](#user-content-fnref-zip-0317-3)

13. [Tor Project](https://www.torproject.org/) [↩](#user-content-fnref-tor)

14. [Nym](https://nymtech.net/) [↩](#user-content-fnref-nym)

15. [ZIP 203: Transaction Expiry](/zip/203) [↩](#user-content-fnref-zip-0203)

16. [ZIP 244: Transaction Identifier Non-Malleability](/zip/244) [↩](#user-content-fnref-zip-0244)

## Citation relationships

Cites: [ZIP 200](https://zips.tools/zip/200), [ZIP 203](https://zips.tools/zip/203), [ZIP 209](https://zips.tools/zip/209), [ZIP 229](https://zips.tools/zip/229), [ZIP 244](https://zips.tools/zip/244), [ZIP 258](https://zips.tools/zip/258), [ZIP 308](https://zips.tools/zip/308), [ZIP 315](https://zips.tools/zip/315), [ZIP 317](https://zips.tools/zip/317), [ZIP 2006](https://zips.tools/zip/2006)

Cited by: [ZIP 229](https://zips.tools/zip/229), [ZIP 258](https://zips.tools/zip/258), [ZIP 326](https://zips.tools/zip/326)