ε-tx

Privacy analysis for cryptocurrency transactions. Started as a maths dissertation on Bitcoin cryptography. The question was simple: given a Bitcoin address, how much does an adversary learn? The answer depends on who the adversary is.

TypeScriptDempster-ShaferSpectral LaplacianDFTBoltzmann entropyDifferential privacyInverse-OSPEADBlockstream APIMonero RPC
Live demo Source 8 attack surfaces · 364 tests · 18 papers cited

The adversary model problem

A privacy score without an adversary model is meaningless. The same address scores 1.2 bits against a casual observer and 4.8 bits against law enforcement. The difference is capability: law enforcement has ISP subpoena power (they can correlate timing with IP), exchange KYC data, and specialised ML classifiers.

Four adversary profiles: casual, exchange, law enforcement, nation-state. Each has per-surface capability weights. A casual observer can't exploit network-level timing even if it leaks 2 bits — their weight for network is 0. Law enforcement gets ISP subpoena power (weight 0.6). Nation-state gets backbone surveillance (0.9). Effective leakage = raw bits times adversary weight.

Eight attack surfaces

Each surface is a separate module with its own mathematical framework. UTXO clustering uses spectral analysis via normalized Laplacian eigendecomposition — the Fiedler vector identifies sub-clusters within a wallet cluster (Meiklejohn 2013, Fiedler 1973). Wallet fingerprinting extracts 8 heuristics and scores against 7 known profiles using log-likelihood (Ishaana Misra 2024). Timing uses DFT for periodicity detection and Kolmogorov-Smirnov tests against exponential to detect scheduling (Biryukov 2014).

CoinJoin analysis uses Boltzmann entropy (LaurentMT 2016). Monero ring analysis uses likelihood ratios against the OSPEAD distribution. Lightning routing privacy is modelled as sender anonymity with Pareto frontier (Romiti 2020). Cross-chain privacy applies differential privacy composition theorems (Dwork 2006) to HTLC linking across bridges.

All scores in bits. 170 tests verify the mathematical properties: H ≥ 0, I(X;Y) ≥ 0, Bel ≤ Pl, eigenvalue non-negativity, composition monotonicity.

Figure 1 — Effective leakage (bits) by attack surface × adversary class8 × 4 matrix
casualΣ = 2.45 bitsexchangeΣ = 6.85 bitslaw enforcementΣ = 11.75 bitsnation-stateΣ = 16.25 bitsUTXO clusteringWallet fingerprintInter-tx timingNetwork / IPCoinJoin entropyMonero ringLightning routingCross-chain (HTLC)0.901.802.403.000.501.252.002.500.200.601.001.800.000.802.403.600.501.001.502.000.150.300.751.050.000.300.500.700.200.801.201.60Σ=8.10Σ=6.25Σ=3.60Σ=6.80Σ=5.00Σ=2.25Σ=1.50Σ=3.80row Σ2.45bits total6.85bits total11.75bits total16.25bits totaleffective leakage (bits) — cell shading0.01.02.03.03.6
Each cell is raw_bits(surface) × capability_weight(surface, adversary). Raw bits per surface follow the cited literature (Meiklejohn 2013, Misra 2024, Biryukov 2014, OSPEAD, Romiti 2020); capability weights model what each adversary actually has access to — a casual observer has weight 0 on network, a nation-state has 0.9. Column sums show why the privacy claim has to name the adversary: the same on-chain address goes from 2.4 bits visible to a casual observer up to 16.3 bits to a nation-state. Illustrative for a vanilla on-chain address with no mixer participation.

Dempster-Shafer evidence fusion

The weighted average in threadr's entity resolver kept returning 0.7 confidence when two sources disagreed. Same problem here: clustering says "exposed" and timing says "private." Naive addition sums both leakages. Dempster's combination rule detects the conflict (mass K) and normalises around it. The result says "high conflict, low certainty" instead of a misleading sum.

Each heuristic produces a mass function over{EXPOSED, PRIVATE}with a reliability weight from published accuracy data. 16 tests verify commutativity, associativity, vacuous identity, total conflict, and Bel ≤ Pl — per Shafer (1976) section 3.2.

Inverse-OSPEAD

OSPEAD (Monero Research, 2025) showed that the default gamma decoy distribution can be separated from real spends — 80% of real spends are the newest ring member. I read the paper backwards. If you can separate the distributions, what if you select decoys that make separation impossible?

The answer is indistinguishability ages: output ages where P_spend(age)/P_decoy(age) = 1. At those ages, the likelihood ratio is exactly 1 and the adversary can't tell real from decoy. Expected entropy improvement of 1-2 bits over default selection. 13 tests verify entropy bounds, probability sums, optimal > default, and decoy spread.

From the source

Dempster's combination rule — conflict mass K normalises the resultpackages/core/src/entropy/evidence.ts
function combine(m1: MassFunction, m2: MassFunction): MassFunction {
  const ee = m1.exposed * m2.exposed
  const eu = m1.exposed * m2.uncertain
  const ue = m1.uncertain * m2.exposed
  const pp = m1.private_ * m2.private_
  const pu = m1.private_ * m2.uncertain
  const up = m1.uncertain * m2.private_
  const uu = m1.uncertain * m2.uncertain

  const K = m1.exposed * m2.private_ + m1.private_ * m2.exposed
  const norm = 1 - K
  if (norm <= 0) return { exposed: 0, private_: 0, uncertain: 1 }

  return {
    exposed: (ee + eu + ue) / norm,
    private_: (pp + pu + up) / norm,
    uncertain: uu / norm,
  }
}
Cross-chain privacy composition — basic and advanced theoremspackages/core/src/crosschain/composition.ts
// Basic sequential composition: ε_total ≤ Σ ε_i
const basicComposition = epsilons.reduce((s, e) => s + e, 0)

// Advanced composition: √(2k·ln(1/δ))·max(ε) + k·max(ε)²
const maxEps = Math.max(...epsilons)
const advancedComposition =
  Math.sqrt(2 * k * Math.log(1 / delta)) * maxEps
  + k * maxEps * maxEps

// Use basic for small k (tighter), advanced for large k
const recommended = k < 10 ? basic : Math.min(basic, advanced)

What it does not prove

  • The surrogate classifier uses rules, not a trained ML model. A real Chainalysis classifier trained on labelled data would be more accurate. The rules capture ~80% of what simple features identify.
  • Monero ring member ages are estimated from output indices, not exact block heights. Age estimation has ±10% error.
  • Lightning route analysis only handles direct channels. Multi-hop pathfinding would need a full gossip graph traversal.
  • Cross-chain bridge APIs are rate-limited and incomplete. Wormhole and LayerZero are integrated. Smaller bridges (Hop, Across, Connext) are not.
  • The tool analyses privacy. It doesn't create privacy. It doesn't move money, mix coins, or construct transactions. It computes scores.

Stack

TypeScript. 22 modules, zero dependencies in core. Dempster-Shafer evidence fusion, spectral Laplacian clustering, DFT timing analysis, Boltzmann entropy, inverse-OSPEAD, differential privacy composition. Blockstream API for Bitcoin, daemon RPC for Monero, mempool.space for Lightning.

Authorised use

Blockchain transaction data is, by design, world-readable. The analyser does not identify any specific person; outputs are bounded leakage estimates in bits, not attribution. Output is informational, not financial advice and not evidence of any offence. Methodology at /scope.