|=[ HOW PRIVATE IS PRIVACY POOLS? :: METHODOLOGY ]=

METHODOLOGY.

How every number in How private is Privacy Pools? was produced: the dataset, the seven heuristics, the maths, and the limits.

DATA · 31.03.2025 → 09.08.2026 · ETHEREUM MAINNET

THE DATASET

Every action on Privacy Pools v1 emits a public event. I re-indexed all of the events from the Entrypoint contract and used the raw logs as my base dataset. The dataset includes 14 mainnet pools: ETH, wBTC, USDC, USDT, DAI, USDS, BOLD, wstETH, USDe, fxUSD, frxUSD, USD1, yUSND, woETH. It captures events from 31.03.2025 to 09.08.2026:

  • 6,333 deposits
  • 7,944 withdrawals
  • 375 ragequits
  • 2,242 ASP root updates

What the public record shows per event:

  • Deposit: the depositing address, the amount, the commitment, the timestamp.
  • Withdrawal: the recipient, the amount, the relayer if one was used, the timestamp.
  • Root update: the moment deposits were approved into the ASP's association set.
  • Association set: the approved deposit lists the ASP publishes to IPFS at each root update; 2,232 of 2,233 snapshots retrieved from public gateways.

THE HYPOTHESIS

The hypothesis: the public record alone is enough to link most withdrawals back to their deposits.

Each heuristic is ranked on two things: how many withdrawals it links, and how certain those links are.

THE HEURISTICS

01 Address reuse.

Compare every withdrawal's recipient against every depositing address. The user withdrew to the same address they deposited from, and the link is certain.

02 Amount fingerprinting.

For each withdrawal $w$, build the candidate set of approved deposits in the same pool that are old enough and match on amount, tolerating the relayer fee $\tau$:

$$S(w) = \{\, d \ \text{approved},\ t(d) \le t(w),\ |a_d - a_w| \le \tau \cdot a_w \,\}$$

The information gained is the entropy drop from the naive crowd $N$ to the candidate set: $I = \log_2 N - \log_2 |S(w)|$. A chance match is ruled out against the pool's empirical amount distribution $f_p$: $P_{\text{chance}} = 1 - (1 - f_p(a))^N$. Round amounts collide constantly, but a high-precision amount like 0.48732951 ETH is effectively unique, so an exact pair is a near-certain link.

03 Funding-source tracing.

Walk one hop back from each depositing address and record who funded it, pulling funding histories from public explorer APIs. Zero-value spam deposits, relayed deposits, and contracts are filtered out first; what remains is fresh wallets. A fresh wallet with a single inbound transfer links the depositor to its funder with certainty. Funders are cross-checked against publicly labeled exchange hot wallets, and the gap between funding and depositing is recorded.

04 Root-update timing.

Withdrawals must prove against the latest ASP root, so a user can only withdraw once their deposit has entered the approved set. For each withdrawal, measure the gap to the most recent root update, $g = t(w) - t(\text{root}_k)$, and compare the observed gap distribution $F_{\text{obs}}$ against the null $F_0$ implied by the root process itself. The excess fraction

$$e(g) = \frac{F_{\text{obs}}(g) - F_0(g)}{F_{\text{obs}}(g)}$$

is the share of withdrawals at gap $< g$ that exist because a root update just released their deposits. Intersecting those withdrawals with the $m$ deposits each update approved gives an expected information gain of $E[IG] \approx b\,(\log_2 N - \log_2 m) - H_2(b)$, where $b$ is the probability a withdrawal belongs to the rush. A control window of the same length, away from any update, gives the false-match rate.

05 Relayer concentration.

Group relayed withdrawals by the relayer that submitted them and measure each relayer's share. There is no onchain link here, but it measures how much offchain metadata (IP addresses, timing, destinations) a single operator could hold.

06 Inter-event timing.

For each candidate deposit-withdrawal pair, record the gap $\Delta t$ between the two events. The gap distribution of known pairs is fit with a mixture of exponentials, $p(\Delta t) = \sum_i \pi_i\, \lambda_i\, e^{-\lambda_i \Delta t}$, and each candidate pair is scored against it. Alone it rarely identifies a user. As a multiplier on other heuristics it discards candidate deposits whose gaps fall outside the observed pattern.

07 Circadian timing.

Build a smoothed hour-of-day kernel per repeat user, $p_u(h) = \dfrac{c_u(h) + \alpha\, q(h)}{n_u + \alpha}$, where $c_u$ is the user's hourly count, $q$ the all-deposits base rate, and $\alpha$ the smoothing weight. Each withdrawal scores a log-likelihood ratio against the population, $LR_u(w) = \log_2 p_u(h_w) - \log_2 p_{\text{pop}}(h_w)$, and the method's separating power is the gap between true and false pairs, $KL(p_u \| p_{\text{pop}}) + KL(p_{\text{pop}} \| p_u)$. Consistent hours leak a small amount of entropy per withdrawal, and the leak compounds across a user's history.

THE MATHS

Leaks are measured in bits: a leak of n bits means the crowd you can hide in shrinks 2ⁿ times. For example, ten bits halves the crowd ten times.

The honest crowd: a withdrawal can only have come from a deposit that was:

  • (a) vetted and approved by the ASP
  • (b) worth at least as much as the withdrawal
  • (c) actually published in the approved list at that moment
  • (d) and not already ragequit:

$$S(w) = \{\, d : \text{approved} \ \wedge\ \text{published} \le t(w) \ \wedge\ v_d \ge a_w \ \wedge\ \neg\,\text{ragequit} \,\}$$

That count, not the naive "every deposit before mine", is the real anonymity set, and the gap between the two is itself a leak: $I = \log_2 \dfrac{N_{\text{naive}}}{|S(w)|}$.

THE LIMITS

  • Except address reuse (#1) and funding source (#3), every link is a probability, not a proof. Overwhelmingly likely is not certain.
  • Everything is published aggregated. No addresses appear anywhere in the essay or here. The point is to measure the system, not to dox its users.
  • The heuristics overlap. A withdrawal caught by two of them is not caught twice, so the percentages cannot be summed.
  • The observer cannot see offchain: relayer logs, exchange internal books, VPN use. Real-world linkage could be better or worse than what is measured here.
  • Exchange hot-wallet labels are best-effort public labels; a mislabeled address moves #3's numbers.