Crypto NewsSecurity

We ran nine methods against Tornado Cash. The mixer held, and the money was traceable anyway

A methods paper from a live investigation. Nine techniques run against a real Tornado Cash cash-out, what each one produced, and why the one that broke the case open did not touch the mixer at all.

Editorial hero on off-white halftone newsprint with torn red corners: one heavy circular steel bank-vault door photographed straight on in high-contrast black and white, closed and sealed with its locking bolts engaged and its surface completely undamaged. A trail of red boot prints enters at the lower left, curves around the base of the vault without touching it, and continues off the right edge. Headed TORNADO CASH in square monospaced extrabold with the subtitle NINE METHODS, THE MIXER HELD. The Tornado Cash logo on a torn card at the upper left, a torn ruled sheet at the upper right bearing a tally of nine pencil strokes, and a paper evidence tag at the lower right reading 83 NOTES.

This is a companion to our forensic record of the Venus and Cronos attacks, which sets out what we found. This one sets out how, including the parts that did not work, because a methods paper that only lists its successes is not much use to anyone.

The headline finding is a negative one, and it should be stated first. We did not demix Tornado Cash. Nothing here breaks it. A withdrawal proves, in zero knowledge, that the withdrawer knows the secret behind one of the pool’s deposits. The nullifier hash it publishes derives from that same note material, so it is not literally unrelated to the commitment; the point is that it cannot be linked to a particular commitment without the note secret. No computation over the chain recovers which deposit a withdrawal spent: not from the Merkle root, not from the nullifier, not from gas, not from timing. That is the design, and in this case the design worked.

What follows is what you can do instead.


The subject

Every method below was run against one real cash-out: a wallet that took 83 withdrawals of 100 ETH from the Tornado 100 ETH pool between June 2025 and June 2026, and later put 2,575.5 ETH back in. The full record of what that wallet turned out to be doing is in the companion record.

0x7a79969a0B9D51D922C4810D2950560360F6f234

Etherscan overview page for wallet 0x7a79969a, showing it was first funded by Tornado.Cash 0.1 ETH one year and 86 days before capture, with 526 transactions sent.
The subject. Etherscan names its first funder as the Tornado 0.1 ETH pool, which is where every method below starts. Source: Etherscan, captured 1 September 2026.
Etherscan screenshot of the wallet's oldest internal transfers: 0.0974 ETH arriving from the Tornado.Cash 0.1 ETH pool, followed by repeated 100 ETH arrivals from the Tornado.Cash 100 ETH pool.
The 83 arrivals, and the direction is legible on the page: these are withdrawals into the wallet, not payments into the pool. Source: Etherscan, captured 1 September 2026.

The eight that did not break it

1. Note counting, and why it is worthless without a control

Count deposits and withdrawals of a fixed denomination inside a window and look for a count that appears exactly once on each side. In April 2026 that worked: exactly one address deposited 23 notes and exactly one withdrew 23, in a window where the pool saw five depositors and eight withdrawers.

The control is the whole method. We ran the same test against the wallet’s other batches. Three matched on count alone, and all three failed the next test, which was following the funding backwards. A count match on its own is a coincidence generator.

Grade: counted, with a negative control. Strong in one window, and worth nothing without the control.

Etherscan screenshot of wallet 0xa212417f: a run of 100 ETH deposits to the Tornado Router on one day, 0.926 ETH returned to the funding wallet, and address-poisoning dust below.
The window the method worked in: 23 deposits of 100 ETH in a single day, then the change returned and the wallet abandoned. Source: Etherscan, captured 1 September 2026.

2. Measuring the anonymity set, which usually ends the conversation

Before spending effort, measure what you are up against. Divide the pool contract’s balance by the denomination.

At Unspent notes
First withdrawal, 6 June 2025 1,254
Second batch, December 2025 about 2,945
Third batch, February 2026 about 2,008
Fourth batch, June 2026 about 2,012
Etherscan page for the Tornado.Cash 100 ETH pool contract showing a balance of 245,100 ETH, which is 2,451 unspent notes.
How the measurement is made: the pool contract's own balance, divided by the denomination. Source: Etherscan, captured 1 September 2026.
Three-panel chart showing unspent 100 ETH notes in the Tornado pool from January 2025 to July 2026, rising from about 1,254 at the first batch to 2,945 and settling near 2,000, above weekly deposit traffic and the wallet's own withdrawals grouped into four batches.
The anonymity set never falls below about 1,250 notes across the whole period, which is why no counting argument survives.

The 1,254 figure is confirmed against the contract’s own balance of 125,400 ETH at that block. With over a thousand unspent notes and 224 depositors in the prior month, no counting argument survives. This measurement is cheap and it is the first thing to run, because it tells you whether anything else is worth doing.

3. Merkle root staleness, which is the one genuinely new thing here

Every withdraw call carries the Merkle root the client had synced when it built the proof. Decode that field across a run of withdrawals and compare.

Across the 83 withdrawals there are 24 distinct roots, and the root changes at 23 of the 82 transitions, advancing whenever deposits landed in between.

Be precise about what that bounds. Fifty-nine of the 82 consecutive pairs share a root with the one before, and the longest unbroken run is ten withdrawals. A shared root is exactly what a batch built in one sitting looks like.

So this establishes at least 24 separate occasions on which a client re-synced the tree, not 83 live moments, and it says nothing about whether a person or a script was driving. A proof can be built from cached data, or well before it is broadcast.

What it does rule out is one single batch: a single sitting would have left one root across all 83.

The way to sharpen this, which we have not done. Compare each withdrawal’s root against the block at which that root first existed on chain. A withdrawal that repeatedly spends a root only minutes old puts an upper bound on how stale the proof could be. That would be a real measurement of proof age. Absent it, the honest claim is two dozen syncs and 83 broadcasts.

Either way it gives an investigator something to ask for: any RPC or front-end provider used at those times may hold timestamped network telemetry. A local node, an indexer, rotating providers or a VPN would each blunt that.

Grade: decoded, and bounded. Stated at that size, still the most useful thing on this list for anyone holding a subpoena.

Etherscan screenshot of the first 100 ETH withdrawal: sent by the wallet itself to the Tornado Router with no relayer, 100 ETH arriving from Tornado.Cash 100 ETH, at a gas price of 3.847 gwei.
The call every method here was run against, all 83 times. No relayer, no fee, and the Merkle root sits inside this transaction's own calldata. Source: Etherscan, captured 1 September 2026.

4. Reading the calldata rather than the rendering

The router call is withdraw(address,bytes,bytes32,bytes32,address,address,uint256,uint256). Decoding all 83 gives the relayer field as the zero address, the fee zero and the refund zero, every time. That alone establishes only that no relayer was named. Self-relaying needs one more check, and we ran it: on all 83, the transaction’s own sender is the withdrawing wallet. Relayer field empty and tx.from equal to the recipient is what makes it self-relayed, read from the bytes rather than inferred from an absent fee.

It is also where the method’s limits show. Across the same period 2,951 of the pool’s withdrawals, 39 per cent, were self-relayed. Self-relaying is common. It is a filter, not a fingerprint.

5. Proof cadence

Time the gaps. Sixty-one of the gaps between consecutive withdrawals fall inside half an hour, with a median of 108 seconds and a floor of 60. That is proof generation plus a human confirming a dialogue, repeated. For contrast the same operator’s later deposits went in 12 to 24 seconds apart, because deposits need no proof: that is a script.

Grade this as inference, and weak inference. A 60 to 120 second interval is compatible with a person clicking, and equally compatible with a script that waits. Cadence narrows the range of explanations a little. It does not pick one, and it certainly does not separate one person from another.

Etherscan screenshot of the wallet's final transactions in June 2026: a rapid run of deposits to the Tornado Router in 100, 10, 1 and 0.1 ETH denominations, followed by two bridge calls.
The other end of the scale. These deposits go in 12 to 24 seconds apart because a deposit needs no proof: that is a script, not a person. Source: Etherscan, captured 1 September 2026.

6. Working hours, measured on days rather than events

The obvious mistake here is to count events. Eighty-three withdrawals across eighteen days is eighteen observations, not eighty-three, and the arithmetic changes completely depending on which you use.

On days, across the cluster: 23 of 26 are Monday to Friday, and none is a Sunday. Under a uniform assumption that is roughly one in 27.

Two bar charts of activity by hour of day in UTC: the operator cluster's 163 working sessions spread broadly across the day, above a comparison set concentrated sharply in the early morning.
Sessions by hour. The spread is the finding: it does not localise a time zone, which is a null result worth publishing.

Hour of day gave nothing. Activity spans 02:00 to 23:00 UTC with dead hours scattered through it and does not localise a time zone. We report that as a null result because a method that returns nothing is still information.

7. The gas-note graph

Withdrawal wallets need gas before they can self-relay, and it often comes from a smaller pool whose anonymity set is far thinner. Chaining “small-pool withdrawal, fresh address, self-relayed withdrawals” across eighteen months maps a whole operation rather than one wallet. It is how the second wallet in this cluster was found.

Etherscan overview page for wallet 0x5770c25e showing it was funded by Tornado.Cash 10 ETH and holds approximately 9.2 million dollars of token holdings.
What the gas-note graph found: a second wallet, funded from a different pool five months earlier, running the same tooling. Source: Etherscan, captured 1 September 2026.

It also nearly produced a false positive, which is the part worth publishing. On 14 May 2025 one address deposited nine 10 ETH, four 1 ETH and seven 0.1 ETH notes, and another withdrew exactly those counts minutes later. Identical bundles minutes apart in thin pools is near-certain linkage, and it briefly looked like it belonged to a labelled exchange thief who had deposited nine 10 ETH notes ninety minutes earlier. The deposit timeline showed it was somebody else. The lesson is that the strongest-looking match in a thin pool is exactly where you should slow down.

8. Volume feasibility, which eliminates instead of suggesting

Most heuristics nominate suspects. This one removes them. A sole source must have deposited more notes than the target had withdrawn, at every date.

Chart of daily deposits into the Tornado 100 ETH pool from 1 May to 20 July 2025, with labelled depositors coloured against a grey majority of unlabelled ones, above the wallet's own withdrawals.
Ten weeks of the pool's deposit traffic. Grey is everyone unlabelled, and grey is most of it, which is the constraint every counting method runs into.

Because the cluster’s second wallet was already withdrawing on 25 January 2025, every theft that happened later in 2025 is ruled out as a sole source, however well the timing fits. That single constraint did more to narrow this case than any positive match, and it took an afternoon.

Two further checks that returned nothing

These sit outside the nine. They are recorded because a stated dead end saves the next person the afternoon.

Tornado’s optional note-account feature publishes encrypted notes as events from the depositor’s own address, which would link deposits to a key. The contract recorded no transactions at all across the whole period. Nobody used it.

Whether the withdrawals bypassed the public mempool would be a rare, strong fingerprint, and the relay operator would hold the addresses. Public explorers carry no marker for it, so it stays open.


The one that worked, and it never touched the mixer

After all of the above, the case broke on something much simpler.

Stop trying to see backwards through the mixer. Follow the money forwards.

From the first withdrawal onward, every step is a plain transfer, a labelled contract call, or a bridge. Bridges are the opposite of a mixer: Symbiosis, Relay, Stargate, Across and LI.FI all publish per-transaction status keyed by the source hash. You look it up.

Doing that resolved the whole trail: about $9m to BNB Smart Chain, where the same private keys had been buying a thin token in roughly 30,000 small purchases for nine months, which ended in an attack on a lending protocol, whose proceeds came back to the same wallet, and went back into the mixer. It is a loop, and the mixer is only one arc of it.

Flow-of-funds diagram across two chains: Tornado Cash notes to the wallet, the wallet to Aave, borrowed stablecoins across five bridges to BNB Chain at the same keys, months of token accumulation, an attack on a lending protocol, and the proceeds returning through Relay to Ethereum and back into Tornado Cash.
The loop the forward trail resolved. Not one step of it required looking backwards through the mixer.
BscScan screenshot of a Relay bridge transfer sent from address 0x1a35bD28 through the Relay approval proxy on BNB Smart Chain.
Why it resolves: a bridge publishes its own record of every transfer, keyed by the source hash. You look it up. Source: BscScan, captured 1 September 2026.

One private key produces the same address on every EVM chain. That is not a heuristic, it is the derivation. An address active on a second chain is the same key control, and that needs no argument. It is not the same as the same person: a key can be automated, shared, delegated, or held by a service on someone’s behalf.


What we would tell anyone doing this

Measure the anonymity set first. It is one call and it tells you whether the rest is worth attempting.

Never publish a count match without a control. Ours matched three times and was wrong three times.

Count observations, not events. Eighteen days is eighteen data points however many transactions sit inside them.

Publish the null results. The note-account check and the hour-of-day profile both returned nothing, and saying so is what lets a reader judge the rest.

A mixer is a wall across one step, not around the whole path. The most productive hours of this investigation were spent looking the other way.

The full case these methods were run against, with every address, hash and figure, is in the forensic record.

Sources

  1. Tornado Cash 100 ETH pool contract, Etherscanetherscan.io
  2. Tornado Cash 0.1 ETH pool contract, Etherscanetherscan.io
  3. The Tornado Cash router, Etherscanetherscan.io
  4. The subject wallet 0x7a79969a, Etherscanetherscan.io
  5. Venus Protocol, THE market incident post-mortemcommunity.venus.io
  6. BlockSec, Venus Thena (THE) incident analysisblocksec.com