Skip to content

Commit 5de39bd

Browse files
committed
docs(paper): prose-condense pass on sections 3, 6.1, 7.1
Tighten verbose discussion paragraphs that restate adjacent table content. No technical claims changed; section anchors and headline numbers preserved. - 3: collapse 'two more details' + 'fees are used precision-preservingly' block into one paragraph - 6.1: collapse three numbered findings paragraphs into shorter form; drop redundant 'for completeness we re-ran the gate hierarchy on mainnet' restating 6.2 probe results - 7.1: drop 'reduction is bounded' / 'under the corrected residual metric' paragraph pair that restated the headline table
1 parent 68997f7 commit 5de39bd

2 files changed

Lines changed: 78 additions & 123 deletions

File tree

docs/mainnet-deanon.html

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,9 @@ <h2 id="threat-model">2. Threat model</h2>
121121
<h2 id="joinmarket-protocol-primer">3. JoinMarket protocol primer</h2>
122122
<p>Three protocol facts are load-bearing for the clusterer:</p>
123123
<ol><li><strong>Per-CJ slot uniqueness.</strong> Each participant (taker or maker) contributes exactly one slot. A slot may aggregate several UTXOs to cover the offered amount, but it always produces one equal-amount output and at most one change output, all in the same mixdepth.</li><li><strong>Same-mixdepth change (sticky change).</strong> A slot whose inputs come from mixdepth <span class="math inline">$d$</span> lands its change output back in mixdepth <span class="math inline">$d$</span>. This is enforced by the wallet (the change address is derived from the same mixdepth's key tree), so the change UTXO is eligible to be a future input of the <em>same maker</em> whenever the maker next advertises mixdepth <span class="math inline">$d$</span>. The downstream consequence is what makes the change-chain edge so sharp: if some later CJ slot in our corpus has inputs whose ILP-selected combination <em>exactly</em> matches this change UTXO (typically as one of several inputs in a subset-sum decomposition), the two slots are the same wallet by construction.</li><li><strong>Mixdepth-advancing equal output.</strong> The slot's equal output lands in mixdepth <span class="math inline">$d{+}1 \bmod 5$</span>. JoinMarket makers normally advertise from whichever mixdepth currently holds the most coins, which after a successful round is often (but not always) the mixdepth that just received the equal output. Deposits, withdrawals, and consolidations can push the "fattest mixdepth" elsewhere, so the next advertisement is not forced to be <span class="math inline">$d{+}1$</span>. When the maker does come back from mixdepth <span class="math inline">$d{+}1$</span>, the equal output of <span class="math inline">$T$</span> is a natural input for that next slot. This produces an "equal-chain" same-wallet edge that v6 does not use directly, because within one CJ all <span class="math inline">$n_{eq}$</span> equal outputs look identical (any permutation of equal-output owners is consistent with the ILP-recovered fee constraints). Section 5.2 (v7) restores this edge by using the consumer slot's own realized fee as a per-CJ fingerprint: if exactly one slot in the producer CJ would have charged this fee at the producer CJ's amount, we have identified that slot.</li></ol>
124-
<p>Two more JoinMarket details matter for the analysis pipeline but not for the clusterer itself:</p>
125-
<ul><li>A maker offer is <em>either</em> relative or absolute, not both (<code>cjfee_r</code> or <code>cjfee_a</code>, the field name picks the kind). Most makers run a single relative offer.</li><li>The maker's contribution to the on-chain fee (<code>txfee</code>) is 0 sats in the default policy and in practice is 0 across the observed corpus.</li></ul>
126-
<p>The clusterer uses fact 1 as a hard pairwise must-not-link, fact 2 as a definite same-wallet must-link, and fact 3 (via the fee-fingerprint rule of <a href="#v7-fee-fingerprint-equal-output-attribution">§5.2</a>) as a per-CJ disambiguator that sometimes turns the producer-to-consumer equal-output reuse into a definite must-link. Fees are therefore <em>used</em> by the clusterer, but only in a precision-preserving way: we accept a fee match as evidence only when it picks a single producer slot inside a single producer CJ, never as a global fee-band that pools several CJs. Fidelity-bond values, nick patterns, and any other off-chain signal are not used (with the explicit exception of <a href="#v7-3-fidelity-bond-funding-tx-cioh">§5.5</a>, which uses the public orderbook to anchor FB-owner identity to funding-tx inputs). The intentional restriction to protocol-forced or single-CJ-unambiguous evidence is what gives precision = 1.0 by construction (under the gate and corpus stated in <a href="#v7-fee-fingerprint-equal-output-attribution">§5.2</a> and <a href="#simulator-end-to-end-and-the-v7-gate-hierarchy">§6.1</a>; loosening either is the failure mode discussed in <a href="#simulator-end-to-end-and-the-v7-gate-hierarchy">§6.1</a>).</p>
124+
<p>Two more JoinMarket details matter for the analysis pipeline:</p>
125+
<ul><li>A maker offer is <em>either</em> relative or absolute, not both (<code>cjfee_r</code> or <code>cjfee_a</code>). Most makers run a single relative offer.</li><li>The maker's contribution to the on-chain fee (<code>txfee</code>) is 0 sats in the default policy and across the observed corpus.</li></ul>
126+
<p>The clusterer uses fact 1 as a pairwise must-not-link, fact 2 as a same-wallet must-link, and fact 3 (via the fee-fingerprint rule of <a href="#v7-fee-fingerprint-equal-output-attribution">§5.2</a>) as a per-CJ disambiguator. Fees are used only when they pick a single producer slot inside a single producer CJ, never as a global fee-band. Fidelity-bond values, nick patterns and any other off-chain signal are not used (except <a href="#v7-3-fidelity-bond-funding-tx-cioh">§5.5</a>, which uses the public orderbook to anchor FB-owner identity to funding-tx inputs). This restriction to protocol-forced or single-CJ-unambiguous evidence is what gives precision = 1.0 by construction under the gate and corpus stated in <a href="#v7-fee-fingerprint-equal-output-attribution">§5.2</a> and <a href="#simulator-end-to-end-and-the-v7-gate-hierarchy">§6.1</a>.</p>
127127
<h3 id="worked-example">3.1 Worked example</h3>
128128
<p>A two-maker CJ at amount 1,000,000 sats with one taker and makers <span class="math inline">$A$</span>, <span class="math inline">$B$</span>. Each maker charges a CoinJoin fee of 1,000 sats; the miner fee for the whole transaction is 4,000 sats and is paid in full by the taker (default JoinMarket policy: <span class="math inline">$\mathit{txfee} = 0$</span> for each maker offer):</p>
129129
<pre><code>inputs (total 4,480,000):
@@ -217,12 +217,11 @@ <h3 id="simulator-end-to-end-and-the-v7-gate-hierarchy">6.1 Simulator end-to-end
217217
<table><thead><tr><th>mode</th><th>gate</th><th>clusters</th><th>ARI</th><th>recall proxy</th><th>precision (cluster-level)</th><th>precision-violating clusters</th></tr></thead><tbody><tr><td>ground truth</td><td>n/a</td><td>717</td><td>1.000</td><td>1.000</td><td>1.000</td><td>0</td></tr><tr><td>blinded</td><td>any</td><td>717</td><td>1.000</td><td>1.000</td><td>1.000</td><td>0</td></tr><tr><td>torture</td><td>any</td><td>100,000</td><td>0.000</td><td>0.000</td><td>1.000</td><td>0</td></tr></tbody></table>
218218
<p>Cluster-level precision is <span class="math inline">$1 - (\text{precision-violating clusters})/(\text{total clusters})$</span>, i.e. the fraction of clusters that contain slots from at most one simulator wallet. Recall proxy is <span class="math inline">$\max(0, 1 - (n_{\text{clusters}} - n_{\text{owners}}) / (n_{\text{slots}} - n_{\text{owners}}))$</span>, and ARI is the standard adjusted Rand index against the simulator's ownership labels.</p>
219219
<p>The ground-truth row of the varied regime has ARI = 0.879 (not 1.0) because the simulator's <code>wallet_id</code> labels distinguish a wallet's taker role from its maker role; when the same wallet acts in both roles across rounds the chain-edge backbone unions those slots into a single cluster, which is correct operationally but disagrees with the per-role labelling under the ARI metric. Cluster-level precision remains 1.000 because no two distinct wallets share a cluster.</p>
220-
<p>Three findings deserve emphasis.</p>
221-
<p>First, in the <strong>uniform regime</strong> the v7 fee-fingerprint gate never fires: every maker advertises the same offer, so the within-<span class="math inline">$T$</span> fingerprint set is constant and no <span class="math inline">$S_i$</span> is unique. v7 collapses to v6, and v6's chain backbone alone is sufficient to recover identity perfectly because the simulator preserves chain labels. This is a counterfactual on the fee-policy axis, not a recall failure of v7: when fees are uniform, the equal- chain attribution is uninformative by construction, and the clusterer correctly abstains.</p>
222-
<p>Second, in the <strong>varied regime under the per-CJ loose gate</strong> the blinded clusterer produces 136 precision-violating clusters out of 4,620 (2.9%). The violations are all driven by the same mechanism: per-announcement fee jitter occasionally makes a maker's realized fingerprint in producer CJ <span class="math inline">$T$</span> coincide <em>numerically</em> with a different maker's policy, while the true producer of the consumed equal output has drifted off the consumer's fingerprint. The per-CJ univocal test selects the wrong <span class="math inline">$S_i$</span> because the right <span class="math inline">$S_i$</span> is no longer the unique match. The strict gate reduces violations from 136 to 98 (a 28% reduction) by demanding that both interpretations agree on the same slot, but cannot eliminate them: jitter occasionally aligns both abs and rel on the wrong slot.</p>
223-
<p>Third, the <strong>corpus-unique gate eliminates precision violations entirely</strong>, at a recall cost of ~5% (ARI 0.449 to 0.323, recall proxy 0.956 to 0.912). The mechanism is direct: under jitter, a falsely chosen <span class="math inline">$S_i$</span> shares its fingerprint with at least one other slot somewhere else in the corpus (the true producer, or yet another jitter-aligned maker). The corpus-wide doppelganger check intercepts exactly these cases. In the torture regime the corpus-unique gate produces no edges at all because in the absence of chain-edge backbone every fingerprint has many candidate doppelgangers; this is the desired behavior for an adversarial setting in which fees alone do not carry attribution signal.</p>
224-
<p>The simulator therefore separates two questions that mainnet data cannot: (a) is the per-CJ univocal test sound? and (b) does the corpus carry enough fingerprint diversity for soundness to matter? Under sparse, real-world fee distributions both gates report no violations (the corpus-unique gate adds zero corrections on mainnet, see §6.2 cross-nick checks at 0 collisions for the loose gate); under controlled jitter the per-CJ gate breaks while the corpus-unique gate holds. The headline mainnet numbers elsewhere in this paper use the loose gate, which is the right choice when the empirical violation rate is 0 and recall is worth ~5% per gate step; an adversarial-jitter setting should prefer the corpus-unique gate.</p>
225-
<p>For completeness we re-ran the gate hierarchy on the mainnet corpus (110,154 maker slots from 14,639 ILP-decoded CJs). All four gates (loose, strict, corpus-unique, and strict + corpus-unique) produce zero same-CJ violations and zero cross-nick violations on the probe set, and the probe-validator outcome is identical: 35 matched nicks, 40 matched UTXOs, 0 cross-nick collisions, 4 nicks split across 2+ clusters with a maximum of 3 clusters per nick. Moving from loose to corpus-unique shrinks the largest cluster modestly and adds a small fraction of cluster splits, all of them outside the probed nick set. We keep the loose gate as the headline default for the rest of the paper but note that on this corpus the choice does not affect the probe-validated precision result.</p>
220+
<p>Three findings.</p>
221+
<p>In the <strong>uniform regime</strong> v7 never fires (every maker advertises the same offer, so no within-<span class="math inline">$T$</span> <span class="math inline">$S_i$</span> is unique). v7 collapses to v6, and v6's chain backbone recovers identity perfectly because the simulator preserves chain labels. This is a counterfactual: when fees are uniform the equal-chain attribution is uninformative by construction, and the clusterer correctly abstains.</p>
222+
<p>In the <strong>varied regime under the loose gate</strong> the blinded clusterer produces 136 precision-violating clusters out of 4,620 (2.9%), all driven by per-announcement fee jitter aligning a maker's realized fingerprint in producer CJ <span class="math inline">$T$</span> with a different maker's policy. The strict gate (both interpretations agree) reduces violations to 98 but cannot eliminate them.</p>
223+
<p>The <strong>corpus-unique gate eliminates precision violations entirely</strong> at a 5% recall cost (ARI 0.449 to 0.323): under jitter, a falsely chosen <span class="math inline">$S_i$</span> shares its fingerprint with at least one other slot in the corpus, and the doppelganger check intercepts exactly these cases. In the torture regime the corpus-unique gate produces no edges at all, which is the desired behavior when fees alone carry no attribution signal.</p>
224+
<p>The simulator therefore separates (a) is the per-CJ test sound? from (b) does the corpus carry enough fingerprint diversity for soundness to matter? Under real-world fee distributions both gates report zero violations; under controlled jitter the per-CJ gate breaks while the corpus-unique gate holds. Mainnet headline numbers use the loose gate because the empirical violation rate is zero (cross-nick collisions = 0 across all four gates on the probe set; see <a href="#active-probing-of-real-maker-wallets">§6.2</a>) and the recall delta is non-trivial; an adversarial-jitter setting should prefer corpus-unique.</p>
226225
<h3 id="active-probing-of-real-maker-wallets">6.2 Active probing of real maker wallets</h3>
227226
<p>In late April 2026 we ran three probing rounds against the live JoinMarket mainnet orderbook, one per CJ amount (100k / 150k / 200k sats), totalling 72 distinct maker nicks that authenticated with a real PoDLE commitment. For each nick the probe records the set of UTXOs the maker offered to spend (<code>offered_utxos</code>); two UTXOs offered by the same nick are guaranteed to belong to the same wallet because the same fidelity-bond key authenticates both negotiations.</p>
228227
<p>A maker advertises only one mixdepth at a time, so the probe-side invariant is stronger than just "same wallet": two UTXOs from the same nick in the same probe round come from the <strong>same mixdepth of the same wallet</strong>. This is the property <a href="#active-probing-of-real-maker-wallets">§6.2</a> uses both to confirm precision and to look for missed edges.</p>
@@ -246,11 +245,11 @@ <h3 id="headline">7.1 Headline</h3>
246245
<p>Across the 10,368 mainnet CJs analyzed in the 1y window (6,315 fully ILP-decoded plus 4,053 with partial maker slots recovered from the analyzer's greedy preprocessing pass; see <a href="#partial-ilp-slot-recovery">§7.4</a>). The v7.1, v7.2 and v7.3 increments add small numbers of cross-CJ cluster edges (60, 79 and 13 unions respectively) but do not move the mean residual at three decimal places: their contribution is qualitative (widening the wallet boundary on clusters that were already certified by v7), not quantitative. The headline table therefore collapses v7.1-v7.3 into a single column:</p>
247246
<table><thead><tr><th>metric</th><th>v6 (change-chain only)</th><th>v7 (Path A)</th><th>v7.3 (final)</th></tr></thead><tbody><tr><td>mean published <span class="math inline">$n_{eq}$</span></td><td>7.61</td><td>7.61</td><td>7.61</td></tr><tr><td>mean certified makers per CJ</td><td>0.01</td><td>0.75</td><td><strong>0.75</strong></td></tr><tr><td>mean residual anonymity set</td><td>7.60</td><td>6.86</td><td><strong>6.86</strong></td></tr><tr><td>share of CJs with at least one certified maker</td><td>1.4%</td><td>51.5%</td><td><strong>51.5%</strong></td></tr><tr><td>median residual anonymity set</td><td>8</td><td>7</td><td>7</td></tr><tr><td>share of CJs reaching residual = 1 (taker alone)</td><td>0.0%</td><td>0.22%</td><td><strong>0.22%</strong></td></tr><tr><td>attribution edges (Path A, outpoints)</td><td>0</td><td>7,474</td><td><strong>7,474</strong></td></tr><tr><td>Path-B-only credits</td><td>150</td><td>609</td><td><strong>609</strong></td></tr></tbody></table>
248247
<p>The v6 column is the residual under change-chain alone; with no Path A edges, only 150 Path B credits arise from incidental same-cluster anchors. The v7 column adds Path A (fee fingerprint), attributing 7,474 outpoints univocally and lifting the share of certified-maker CJs from 1.4% to 51.5%. The v7.3 column is identical to v7 to three decimal places on this corpus.</p>
249-
<p>The reduction is bounded: the fee fingerprint univocally identifies 7,474 of the 32,801 cross-CJ equal-output reuses in the window (22.8%). The change-chain alone clusters maker slots extensively but those clusters cannot, on their own, identify which equal output came from which slot. The residual drops from 7.61 to 6.86 on average (a 9.8% reduction), 51.5% of CJs leak at least one maker, and 23 CJs (0.22%) collapse fully to the taker.</p>
248+
<p>The fee fingerprint univocally identifies 7,474 of the 32,801 cross-CJ equal-output reuses in the window (22.8%). The change-chain clusters maker slots extensively but cannot, on its own, identify which equal output came from which slot. The residual drops from 7.61 to 6.86 on average (a 9.8% reduction), 51.5% of CJs leak at least one maker, and 23 CJs (0.22%) collapse fully to the taker.</p>
250249
<p><img src="figures/anonset_reduction_hist.svg" alt="residual anonymity set histogram (v7.3, 1y window)" loading="lazy"></p>
251-
<p>The overlay across all five iterations makes the per-iteration contribution visible:</p>
250+
<p>The per-iteration overlay:</p>
252251
<p><img src="figures/v6_vs_v7_anonset_overlay.svg" alt="v6 through v7.3 anonset overlay" loading="lazy"></p>
253-
<p>Under the corrected residual metric (Path A plus univocal Path B), the bulk of the reduction comes from the v6 -&gt; v7 step, where the fee-fingerprint attribution edges (Path A) become available: mean residual drops from 7.60 (v6, Path B only via cluster co-spend) to 6.86 (v7). The later iterations v7.1, v7.2 and v7.3 add cross-CJ cluster edges via CIOH, round-trip hops and fidelity-bond funding respectively; each adds a small number of additional univocal Path B certifications, but the mean residual moves by less than 0.001 because most newly clustered maker slots are not paired with a same-cluster change anchor at a downstream consumer CJ. The taker-facing implication is that the fee fingerprint, not the cluster graph, drives most of the anonymity-set reduction.</p>
252+
<p>The bulk of the reduction comes from the v6 to v7 step, where fee-fingerprint attribution (Path A) becomes available. The later iterations v7.1, v7.2 and v7.3 each add a small number of Path B certifications via CIOH, round-trip hops and FB-funding edges, but the mean residual moves by less than 0.001 because most newly clustered maker slots are not paired with a same-cluster change anchor at a downstream consumer CJ. The fee fingerprint, not the cluster graph, drives most of the reduction.</p>
254253
<h3 id="per-n-eq-breakdown">7.2 Per-<span class="math inline">$n_{eq}$</span> breakdown</h3>
255254
<p>The reduction holds across every round size in the corpus:</p>
256255
<p><img src="figures/anonset_per_n_eq.svg" alt="mean anonset before and after, by n_eq" loading="lazy"></p>

0 commit comments

Comments
 (0)