Network privacy for the Orchard to Ironwood migration: what Nym provides, and under which conditions
1. The issue, briefly
The Ironwood network upgrade seals the Orchard pool. Holders of shielded ZEC must migrate their funds through a turnstile that publicly reveals the amount of every migration transaction. This is deliberate, so that the public can audit how much ZEC leaves the pool. The combination of forced participation and public amounts means that, absent countermeasures, every user's starting balance surfaces on the public chain in whatever decomposition their wallet chooses. The security question is not whether amounts appear (they do), but who can attribute them to a person (Invariant A below), and who can reassemble them into balances and link them to other known transactions (Invariant B).
The Shielded Labs write-up provides the threat model and the two invariants, and recommends network-level privacy as the primary defense and mixing of concurrent migrations as a secondary one. We agree with that framing and with the priority ordering. What this document adds is precision on the network-level side: which property is actually required, against which adversary, and which Nym configuration provides it under which conditions. Our method is to decompose the problem into a small number of simple properties and evaluate each configuration against them.
2. Threat model and invariants, restated
Level 1. Adversary L1 only knows what is publicly visible on the chain. We do not focus on this adversary in this document.
Level 2. Adversary L2 controls or has compromised the lightwalletd used by the wallet (or, for full-node wallets, a peer). It observes the source IP of incoming connections, the fine-grained arrival timing of every request, and the complete request contents: which block ranges are fetched (GetBlockRange), which transactions are retrieved (GetTransaction), and which transactions are broadcast (SendTransaction). This is the primary adversary: it is cheap to instantiate (run a popular lightwalletd) and it sits at a privileged vantage point.
Level 3. Adversaries L3 observe or compromise network infrastructure. The Shielded Labs write-up treats this as one adversary; we distinguish:
- L3L, a local network observer on the client's access network (Wi-Fi, ISP): knows the user's IP, sees where their traffic goes and its timing and volume, but not TLS-protected contents. Capable of inferring timing and type of activity via traffic fingerprinting.
- L3G, a global network observer that simultaneously observes both ends of the communication, as well as all the intermediate hops. This adversary can attempt flow correlation (timing, volume, and size of a sequence of packets) at the input and output of every hop, or alternatively end-to-end. It cannot read TLS-protected contents.
Adversaries at every level may hold auxiliary information: stolen KYC records, records from compromised exchanges, previously deanonymized transactions.
Invariant A (linkage between identity and approximate balance). The adversary cannot link a user identifier (e.g., client IP address) to a balance, even approximately. Since migration amounts are public, the invariant survives only as long as no migration transaction, and no group of them, is attributable to the user. Attribution can be direct (a broadcast arriving from the user's home IP) or transitive (a broadcast linkable to activity that is itself attributable, such as a sync session).
Invariant B (linkage between transactions). During migration this reduces to: the adversary must not be able to group the migration transactions of a single wallet. Grouped amounts sum to the starting balance and feed subset-sum linkage against known amounts.
3. What is actually needed: two properties, three vectors
3.1 Two unlinkability properties
P1 (request-identity unlinkability): the adversary cannot attribute a given request to the user (e.g., via the client IP address).
P2 (request-request unlinkability): the adversary cannot determine that two requests originate from the same client. A "request" is any protocol interaction: a block-range fetch, a transaction fetch, a broadcast.
The two properties are asymmetric. If requests are attributable to an identity, they are thereby linkable to each other, since the identity acts as a linking key; so a P1 failure across requests implies a P2 failure. The converse does not hold: a fully linkable but anonymous request stream is a pseudonymous profile. Pseudonymous profiles are the intermediate state most configurations land in, and they are fragile in a specific way: one attributed request anywhere in the profile retroactively attributes the whole profile.
3.2 Three linkage vectors and their countermeasures
V1, session state: everything that groups requests as a technical side effect of transport: the source IP observed by the destination, TCP/TLS connection state, tunnel session, application-layer session identifiers. These components differ in kind. Connections, tunnel sessions and session identifiers group requests deterministically. If the client keeps connections short-lived and rotates identifiers, then they contribute nothing. The source IP of an exit proxy is the component that survives those countermeasures, and it is probabilistic rather than deterministic, due to two effects:
- crowding: many clients using the same exit IP, thus creating an anonymity set;
- splitting: one client spreading its requests across many exit IPs, denying the adversary a complete profile even where per-IP grouping succeeds.
Crowding is a property of the ecosystem, it depends on how many others share the exits; splitting is a property of the client and can be engineered unilaterally.
V2, timing: at the destination this includes the arrival times of requests; on network links it is the timing, size and volume of packets.
V3, content: requested heights and ranges, fetched txids, queried t-addresses, broadcast payloads. Content is both a linkage vector (contiguous heights chain sessions) and, with auxiliary information, an identification vector (a birthday matching a known KYC withdrawal date; a migration amount matching a known balance). The Shielded Labs write-up already prescribes countermeasures: issue queries only over protected paths, at randomized times.
| Vector | Consists of | Observable from | Countermeasures |
|---|---|---|---|
| V1 session state | source IP; TCP/TLS connection; tunnel session; application identifiers | destination (L2) | relaying (hides client IP); short-lived connections (new connection per request); exit rotation per connection (in dVPN and mixnet alike) |
| V2 timing | arrival times of requests at the destination; flow timing and volume on links | destination (L2) is end point of flows; network adversaries (L3L, L3G) see flows in transit | client side (against destination): randomized request times (Sect 5); In transit (against network adversaries): mixing delays, Poisson sending, cover traffic |
| V3 content | heights and ranges; txids; t-address and UTXO queries; broadcast payloads | destination (L2) | start-height obfuscation: randomized overlap, checkpoint snapping (Sect 5) |
A common category error is crediting in-transit V2 mixing as protection against the destination (L2). Mixing delays and cover traffic change what a network observer can infer or correlate; the destination sees only what arrives and when it arrives, and is indifferent to how the traffic was shaped in transit. No amount of mixing protects a user against the server they are talking to; what protects them is the absence of V1 state, plus V2/V3 discipline on the requests themselves.
4. Configurations and the properties they provide
4.0 Baselines: the direct connection, centralized VPN, Tor
On a direct connection the destination (adversary L2) receives identity and contents together: the client IP, every request, every broadcast. Both invariants fail with no adversarial effort, and the network vantage points collapse into a single observer: both L3L and L3G see the endpoints and the timing and volume of the exchanged flows. This is the baseline that network protection must improve on.
A centralized VPN hides the client IP from the destination (P1 via V1) and nothing else: within a session, requests remain grouped by connection state; across sessions, the exit IP in combination with resume heights re-identify the wallet; and the relay operator is a single party that sees both ends and can effectively function as global observer L3G.
Tor hides the client IP from the destination (P1 via V1). It does not provide P2 via V1 when used in a default configuration, as long-lived circuits from the same exit IP are used for sending multiple requests that are then linkable by L2 (via V1). Tor can be used with a fresh circuit per unlinkability domain, as the Shielded Labs write-up correctly prescribes, to provide P2 via V1 against an adversarial destination L2. The anonymity set for a broadcast is then given by all the clients who reach that lightwalletd via Tor. In contrast to centralized VPNs, the three hops ensure that no single relay sees both ends of the connection. Neither Tor nor a VPN provides in-transit V2 protection: L3G may correlate flows end-to-end, and L3L may fingerprint user activity.
4.1 Nym dVPN, 2-hop, single exit
Diagram: dVPN · single exit. Open the full scenario →
The wallet's traffic is sent via a WireGuard tunnel that goes through an entry and an exit gateway (this configuration is called NymVPN in the Shielded Labs write-up).
Against the destination (L2): P1 holds via V1; the destination sees the exit's IP. P2 fails within a session: the tunnel delivers one NATed flow, and with gRPC the whole sync typically uses one long-lived HTTP/2 connection. Across sessions, linkage depends on crowding at the exit IP: the anonymity set is given by the clients who use that specific exit.
Against network adversaries (L3L, L3G): the protection is similar to Tor's. WireGuard adds no cover and preserves packet timing, so the fingerprint of a sync remains visible at L3L. End-to-end flow correlation can be successful when deployed by a global observer L3G. Given the 2-hop route, a single malicious gateway cannot link the user to their lightwalletd traffic: the entry knows the client IP but not the destination, and the exit knows the destination but not the client IP. Both gateways must collude to link the client to the destination. Although Tor uses an additional relay (three instead of two hops), a colluding guard+exit pair in Tor is also able to correlate the connection. Thus the added latency caused by having an additional hop does not translate to more protection.
4.2 Nym dVPN, 2-hop, multiple exits
One client connected to one entry gateway; batches take dVPN routes to different exit gateways, all reaching a single lightwalletd. Most traffic is downloads (blocks: lightwalletd → exit → entry → client) with some uploads (client → entry → exit → lightwalletd).
Diagram: dVPN · multi-exit. Open the full scenario →
Registering distinct WireGuard peers at several exits splits the sync and transaction broadcasts across exit IPs, resulting in unlinkability domains similar to those obtained by using Tor with fresh circuits. Compared to single exit (section 4.1), this improves P2 towards the destination (L2), provided the Section 5 countermeasures are also applied (request timing and start of sync height may enable linkability). Compared to the single exit configuration, the anonymity set broadens to all the clients who reach that lightwalletd via Nym.
The protections towards network adversaries (L3L, L3G) are the same as in the single-exit case (section 4.1).
4.3 Mixnet via exit proxy (IP packet router)
One client and entry gateway; block chunks are requested from several exit gateways over the mixnet, all reaching one lightwalletd. Responses return through varied mixnet paths (via different exit gateways).
Diagram: Mixnet · rotating IPR. Open the full scenario →
Sphinx packets of fixed size, three mix layers with independent per-hop delays, Poisson sending with loop cover, then an exit-side IP packet router (IPR) that forwards the client's packets to the open internet (NymMix in the Shielded Labs write-up). Two views must be kept apart.
Against the destination (L2), this configuration provides the same protection as the dVPN (4.1 for a fixed IPR, 4.2 under rotation). The wallet's TCP connection to lightwalletd is an ordinary end-to-end connection arriving from the exit's IP. P2 within a session behaves exactly as in the dVPN case. Per-packet unlinkability in transit does not translate into request unlinkability at the destination; that must still be engineered client-side: short-lived connections, exit rotation, and randomized request times.
Against network observers (L3L, L3G) the difference in protection is significant. L3L sees constant-size packets at a Poisson rate with cover traffic, and learns that the client uses the Nym mixnet, but little that is reliable about destination, volume or activity. L3G is the adversary this design targets: per-packet timing correlation is hampered by mixing delays and cover traffic. The extent to which bulk transfers consisting of many packets can be correlated over time by L3G is an open question. The mixnet is strongest for small, independent messages and weakest for bulk sync. That is one of two reasons bulk sync does not belong on the mixnet; the other is throughput, bounded by the fixed ~2 KB payload, the Poisson sending rate and per-hop delays: fine for daily catch-up, hours to days for a year-scale restore, impractical for a birthday-less restore.
4.4 Mixnet to a service-provider-integrated lightwalletd
Forward: client → entry → 3 mix layers → exit → the SP-integrated lightwalletd. It replies via SURBs (pre-computed return paths back through the mixnet), so it never learns the client address; packets arrive from all last-layer mix nodes.
Diagram: Mixnet · SP-integrated. Open the full scenario →
A forked lightwalletd embeds a Nym service provider and receives requests as mixnet (Sphinx) messages, replying through SURBs (pre-computed reply routes that reveal no address). There is no client IP available at the destination, as packets arrive from all the last layer mix nodes. All the users accessing this mixnet-integrated service provider are part of the same anonymity set (perfect crowding and splitting).
This configuration does not automatically eliminate application-layer session state in today's implementation. The fix is small and should be stated as a requirement of the configuration: a fresh sender tag per request, each provisioned with its own SURBs. With the fix in place, this is the only configuration in which destination-side P2 holds at the transport level by construction, rather than through client-side rotation.
With respect to network adversaries, the protection against L3L is the same as when using the mixnet with IP packet router (previous section). However, against L3G this is the strongest option: the actual flow of packets is never seen by the adversary, given cover traffic and multiplexing in all the mixnet links, which include traffic arriving to the integrated lightwalletd.
4.5 The hybrid: sync over dVPN, broadcast over the mixnet
One client and entry gateway. Block sync (dVPN) is mostly downloads — blocks flowing lightwalletd → exit → entry → client — with ~10% uploads. Tx broadcast (mixnet) is upload only: client → entry → 3 mix layers → exit → lightwalletd. Both reach a single lightwalletd.
Diagram: Recommended hybrid. Open the full scenario →
Sync is bulk, bandwidth-bound, while a broadcast is tiny and potentially more sensitive. The hybrid runs sync over dVPN and broadcasts over the mixnet, ideally to an SP-integrated destination with per-request tags, from the same wallet process. t-address and transparent UTXO queries follow the broadcast side and go over the mixnet at randomized times, as the Shielded Labs write-up prescribes, and never over the sync tunnel.
The hybrid provides unlinkability by default between the sync profile and the broadcasts. The two streams share no V1: different transports, different IPs, no common session. The sync side remains a pseudonymous profile at the destination (the height chain), and broadcasts could still be correlated to it through arrival timing if the wallet does nothing else: a migration transaction arriving seconds after a sync reaches the tip can be a strong join under low concurrency. The broadcast-side scheduling of Section 5 addresses this.
Conditions for Invariant A against adversarial destinations (L2) under the hybrid: (i) the sync tunnel hides the client IP from the destination; (ii) broadcasts are unlinkable to the sync profile and to each other (per-request tags, no shared tunnel, decorrelated timing); (iii) broadcast contents do not identify the user through auxiliary information (Invariant B).
Against network adversaries: the sync flow is fingerprintable (L3L) and correlatable (L3G), but what these adversaries learn from sync alone is that "this client syncs Zcash", not amounts and not broadcasts, which are routed separately via the mixnet. We consider this residual acceptable: the hybrid does not make sync invisible to a network adversary; it makes sync uninformative.
4.6 Summary
The table summarizes each configuration against the properties of Section 3. The first two columns concern an adversarial destination (L2), which observes individual requests: P1 via V1 asks whether the client IP is hidden from it, and P2 via V1 whether the requests it receives are unlinkable to each other at the transport level; timing (V2) and content (V3) linkage remain in every configuration and are treated in Section 5. The two network-adversary columns concern P1: L3L and L3G do not observe individual requests, so what they can attempt is attributing Zcash activity, or whole flows, to the client via traffic timing and volume (V2). The last column states what each configuration requires to be deployed.
| Configuration | P1 via V1 at L2 | P2 via V1 at L2 | P1 via V2 at L3L (local) | P1 via V2 at L3G (global) | Requires |
|---|---|---|---|---|---|
| dVPN 2-hop, single exit | yes | not provided within session; weak across sessions (depends on exit crowding) | activity fingerprintable | can correlate flows end-to-end | nothing special |
| dVPN, multiple exits | yes | yes, given per-request rotation | as above | as above | automate exit rotation per request |
| Mixnet via IPR | yes | yes, given per-request IPR rotation | protected by constant-size packets, Poisson rate, cover | resists per-packet correlation; long flows weaken it | ensure IPR rotation per request |
| Mixnet to SP-integrated lightwalletd | yes | yes, given per-request tag and SURBs | as above | strongest protection of all options | forked lightwalletd; ensure per-request tags and SURBs |
| Hybrid: dVPN sync + mixnet broadcast | yes | broadcasts unlinkable to sync and to each other, given per-request IPR / tag rotation | sync activity fingerprintable; broadcasts unobservable | sync correlatable but uninformative; broadcasts protected | both dVPN and mixnet stacks in one wallet process |
5. Residual leakage: start-height obfuscation and request scheduling
Assume an ideal transport. The destination still receives, for every request, its content and its arrival time. A light wallet resumes each sync from the height where it last stopped, so consecutive sessions chain: each starts exactly where the previous one ended. A join is ambiguous only when other wallets finished a sync within the same block interval (~75 seconds); on lightly used servers joins can be unique. Of a session's two endpoints, only the start is fingerprintable: the end equals the chain tip at sync time, shared by every client syncing at that moment.
The Shielded Labs write-up already prescribes randomized timing for operations intended to be unlinkable, fresh circuits per unlinkability domain, a randomized timer for migration broadcasts (their Defense B). Destination splitting, syncing from one lightwalletd and broadcasting through another, is suggested in the review comments on that write-up and fits the same pattern. These measures randomize when and where requests appear, but not what they contain: the requested ranges themselves.
5.1 Start-height obfuscation
Since the linking key is the start height (V3), it is enough to obfuscate the start. Two possibilities:
- Randomized overlap: start a random number of blocks before the true resume point, re-fetching blocks already held, so that starts stop being exact pointers to previous ends and chaining degrades to approximate, deniable joins.
- Checkpoint snapping: round the start down to a standard checkpoint (an absolute height), so that every client whose previous sync ended anywhere in the same checkpoint interval emits an identical start. The collision window grows from one block interval to the checkpoint spacing.
Both approaches cost re-downloaded blocks and nothing else, and both work in every configuration of Section 4. How to set the overlap distribution or the checkpoint spacing, and what each buys quantitatively in terms of anonymity sets, is an open question.
5.2 Broadcast hygiene and scheduling
Never send a broadcast over the sync transport or session, and decorrelate broadcast times (V2) from sync milestones; in particular, do not broadcast immediately upon reaching the tip. The Shielded Labs write-up's randomized timer provides this for migration rounds; wallets should extend the same discipline to every broadcast.
6. Recommendation → Recommended architecture
The configurations of Section 4 are not alternatives of equal standing; they differ in what they provide and in what they require.
The dVPN with short-lived connections and exit rotation is the minimum: deployable today with no changes to lightwalletd, it provides P1 and, provided exits are rotated with every request, transport-level (V1) P2; what remains, request timing (V2) and content (V3), is addressed by the countermeasures of Section 5.
The mixnet configurations add protection against local and global network adversaries; the SP-integrated lightwalletd is the strongest configuration, since no exit IP exists at all and all clients of the service form a single anonymity set, but it requires a forked lightwalletd and per-request sender tags. Mixnet solutions are likely too slow for large bulk syncs.
The most practical target is the hybrid, deployable now: bulk sync over the dVPN, ideally with exit rotation; broadcasts, t-address queries and other small sensitive requests over the mixnet with an IPR session per request, or to an SP-integrated lightwalletd where available; taking into account the scheduling discussed in Section 5. This is the configuration we recommend implementing for the migration.