|
Download docs/VALIDITY.md from Anish-g/btc-intel: direct link, hf CLI and curl.
- Browser
- Download file 53.9 kB
-
https://huggingface.co/spaces/Anish-g/btc-intel/resolve/main/docs/VALIDITY.md
- Command line
-
hf download hf://spaces/Anish-g/btc-intel/docs/VALIDITY.md
-
curl -L -o VALIDITY.md https://huggingface.co/spaces/Anish-g/btc-intel/resolve/main/docs/VALIDITY.md
53.9 kB
| # Validity layer | |
| **Generated by `python -m eval.report`, except the two sections copied through | |
| verbatim: `Metric revision` (pre-registered before the rerun) and `P6 results | |
| (frozen)`. Do not edit by hand.** The results are also section 10 of | |
| `eval/results.md`, from the same run. | |
| Origin attribution names the peer that announced a transaction first, or most | |
| centrally, to an observer. **Calibration** says how often such a name is right. | |
| The **validity layer** (`analysis/validity.py`) asks two prior questions: can | |
| an attribution be about this transaction at all, and if so, what may it | |
| claim? Every detector runs; every reason that fires is reported; the verdict's | |
| **tier** is the most severe among them. | |
| | tier | effect | | |
| | --- | --- | | |
| | `ABSTAIN` | the answer is withheld, through the existing rule β `low_confidence_origin` in `engines/propagation`, `eval.origin.flagged_at` everywhere else β not a second path | | |
| | `QUALIFIED` | the answer is given with its meaning restricted, and never shaped like an IP attribution | | |
| | `ANNOTATE` | the answer is given unchanged, with the flag and its evidence | | |
| | `PASS` | nothing fired | | |
| `validity.enforce: false` switches the layer off in that one place; | |
| `validity.mode: binary` restores P6's gate, where every non-PASS verdict | |
| abstained. | |
| Every origin the system emits β `engines/propagation`'s estimates, the | |
| origination model's output, the API's `/transactions/{txid}/propagation`, the | |
| red-team origin table, each attribution lead, and the console views of them β | |
| carries a probability, its calibration basis, and a verdict | |
| `{"tier", "reason", "reasons", "confidence", "evidence"}`; where an answer is | |
| given, `answer` says what it may claim: `ip_attribution`, `onion_identity` | |
| (no `ip` field) or `broadcasting_peer` (with `input_ownership: not | |
| attributable`). `tests/test_api.py` asserts the verdict at the API boundary; | |
| `tests/test_validity.py` asserts that no TOR_ONION answer carries an IP and no | |
| COINJOIN answer attributes input ownership. | |
| ## Reason codes | |
| `confidence` is confidence that the *condition* is present, not that the | |
| attribution is wrong. | |
| | code | tier | fires when | reads | catches | does not catch | | |
| | --- | --- | --- | --- | --- | --- | | |
| | `DEGENERATE` | ABSTAIN | 0 or 1 peers announced the transaction | `candidate_count` (the matrix's `degenerate`; a single-observation tree) | a capture with nothing to rank | β it is definitional | | |
| | `NOT_REACHABLE` | ABSTAIN | every candidate is a known public relay | the matrix's `scope_out`; the same rule on a tree | the zero-ceiling case: the sender is not among what this observer can see | a sender absent from the candidates while an ordinary node happens to be among them β that is the unobserved-origin case calibration prices | | |
| | `COINJOIN` | QUALIFIED | `graph.clustering.is_coinjoin`: β₯3 inputs, β₯3 outputs, β₯3 equal-value outputs above dust, no more equal outputs than inputs | the transaction's structure | a mix; the answer becomes "broadcasting peer only", input ownership non-attributable | an equal-value **batch payout** from one owner paying no more equal amounts than it spends inputs (a false positive: structure cannot see input ownership); a CoinJoin with unequal denominations or fewer than three equal outputs (PayJoin, some WabiSabi rounds) | | |
| | `TOR_ONION` | QUALIFIED | the named peer is a `.onion` address | the peer address (onion parsing in `p2p/capture_reader.py`) | a hidden-service peer named as origin; the answer becomes an onion identity, never an IP | a Tor sender whose transaction entered through a mixed-transport node: the observer sees that node's clearnet address, and nothing in one capture marks it | | |
| | `DANDELION_STEM` | ANNOTATE | the gap after the first announcement is β₯ `isolation_ratio` Γ the median gap among the rest, with β₯ `min_candidates` candidates | announcement times | an observer on or next to a stem, which hears one peer, then silence, then the broadcast | a stem that ends away from every observer (the common case); and it false-alarms on sparse captures. A flag only: BIP-156 was never merged into Bitcoin Core, which has no stem phase | | |
| | `V2_PASSIVE_TAP` | ANNOTATE | a pcap capture held port-8333 flows that never decoded | `capture_source`, `unreadable_flows` | a packet capture blind to its BIP-324 v2 peers, whose announcements are then missing from the candidates | which peers those were. It never fires on debug.log or .btcap: the node writes those, and as a session endpoint it decrypts. A pcap on the node's own host is as blind as a span port β tcpdump holds no session keys | | |
| | `NOT_ASSESSED` | β | not a detector: a stored output from before this layer | β | keeps an old lead from being served as PASS | β | | |
| **Taint and leads.** A CoinJoin terminates taint: `graph.entity_graph` counts | |
| the CoinJoin transactions on each entity edge (`mix_count`, from the same | |
| `is_coinjoin`), and `fusion.taint.propagate` does not cross an edge made of | |
| nothing else. A correlation lead says "this IP broadcast for this cluster's | |
| inputs", so none is built from a COINJOIN or TOR_ONION answer or a | |
| NOT_REACHABLE one. DEGENERATE observations are kept β aggregating many | |
| single-candidate observations, each weighted by its low confidence, is what | |
| correlation is for β and ANNOTATE flags ride along; a lead's own verdict is | |
| `PASS` or `ANNOTATE`, naming how many of its observations carried each flag. | |
| The DANDELION_STEM thresholds were fixed in config.yaml before the first P6 | |
| result and have not been tuned since. | |
| ## Metric revision | |
| **Pre-registered.** Written and committed before the evaluation was re-run; | |
| nothing in this section was changed after the first revised result existed. | |
| ### What P6 showed | |
| Enforced as a binary gate, the validity layer lowered the cross-topology cost | |
| score from 0.057 to 0.034. The per-reason table (P6 results, below) shows why, | |
| and each reason failed differently: | |
| * **DANDELION_STEM** withheld 230 answers that were correct_actionable against 23 | |
| wrong. The detector cannot tell an observer that is the sender's *first* stem | |
| hop β where the lone first announcer really is the sender β from one further | |
| down the stem. It also fired on 4.2% of the base corpus, which has no stems. | |
| And the condition it guards against does not occur on Bitcoin's network: | |
| BIP-156 (Dandelion) was never merged into Bitcoin Core, and no Core release | |
| has a stem phase. A gate for a condition the deployed network does not | |
| produce, with 0.43 precision, costs answers and protects nothing. | |
| * **TOR_OR_V2** fired mostly on v2. BIP-324 encrypts a link; it hides neither | |
| the peer's address nor its timing from the node at the other end, and the | |
| simulator models no v2 timing effect at all, so every v2 abstention withheld | |
| an answer the evidence did not question. What v2 does remove is a *passive* | |
| observer's view: a packet capture cannot read a v2 link, so its candidate set | |
| can be missing the v2 peers. That is a statement about the capture, not | |
| about the answer. | |
| * **COINJOIN** withheld 167 right answers against 12 wrong β but the metric | |
| scored "naming the broadcaster of a mix" exactly like naming the sender of | |
| a payment. An unqualified answer about a CoinJoin claims more than the | |
| evidence supports: it reads as "this address sent these coins", and the | |
| inputs belong to 5β12 unrelated participants. P6's cost function had no | |
| outcome for that claim, so it could not price what abstaining avoided. | |
| * **TOR (onion)** answers were already scored correct_infrastructure (+0.3) | |
| after the onion classification fix, not correct_actionable. The onion | |
| clause had precision 1.0; the question is what the answer may *say*, not | |
| whether to give one. | |
| ### Verdict tiers | |
| Every detector still runs, and every reason that fires is reported. The | |
| verdict's tier is the most severe tier among them: | |
| | tier | reasons | what happens to the answer | | |
| | --- | --- | --- | | |
| | **ABSTAIN** | `DEGENERATE`, `NOT_REACHABLE` | withheld through `low_confidence_origin` / `eval.origin.flagged_at`, as before. Already free: both were abstentions by construction in P6. | | |
| | **QUALIFIED** | `TOR_ONION`, `COINJOIN` | given, with its meaning restricted. TOR_ONION: the answer is an onion identity and is never presented as an IP. COINJOIN: the answer is the broadcasting peer only; input ownership is explicitly non-attributable; taint still terminates at the mix. Neither becomes a correlation lead. | | |
| | **ANNOTATE** | `DANDELION_STEM`, `V2_PASSIVE_TAP` | given unchanged, with the flag and its evidence attached. DANDELION_STEM leaves the abstention path because Dandelion is not deployed in Bitcoin Core. V2_PASSIVE_TAP replaces the v2 clause and fires only on a pcap capture in which port-8333 flows could not be decoded β the evidence that v2 peers are missing from the candidate set. A debug.log or .btcap capture is written by the node, a session endpoint that decrypts, so v2 never fires there. (A pcap taken on the node's own host is no better than a span port: tcpdump does not hold the session keys. "Endpoint" here means the node process, not the host.) | | |
| | **PASS** | none | unchanged. | | |
| `validity.enforce: false` stays the off switch. `validity.mode: binary` | |
| reproduces P6's gate (every non-PASS verdict abstains) so P6's policy can be | |
| scored under the new metric. | |
| ### Cost-function outcomes | |
| `eval.origin`'s outcome vocabulary is extended, not forked. Each estimate gets | |
| exactly one outcome, decided in this order; the weights go in | |
| `engines.propagation.origin_filter.cost_weights`. | |
| | order | outcome | when | weight | rationale | | |
| | --- | --- | --- | --- | --- | | |
| | 1 | `abstained` | the policy withholds it (ABSTAIN tier; any non-PASS under `binary`; the cutoff; a relay at the top) | 0.0 | unchanged | | |
| | 2 | `coinjoin_input_misattribution` | the transaction is a CoinJoin by ground truth, it is answered, and the answer is not qualified COINJOIN (layer off, `binary`, or the detector missed it) | **β3.0** | An unqualified answer about a mix asserts ownership of other participants' inputs: a false claim that points follow-up at people it should not. That is the harm `wrong_uninvolved_third_party` prices, so it carries the same weight. It is not scaled by the number of participants: the harm is the claim, and scaling would let the generator's participant-count distribution set the number. It applies whether or not the named broadcaster is right, because the ownership claim is wrong either way. | | |
| | 3 | `qualified_correct` | QUALIFIED tier, answered, the named peer is the true origin | **+0.3** | Equal to `correct_infrastructure`: a right answer that cannot support IP-level follow-up (an onion identity) or supports it only for the broadcaster and never for input ownership (a CoinJoin). | | |
| | 4 | `qualified_wrong` | QUALIFIED tier, answered, the named peer is not the true origin | **β1.0** | Qualification scales the cost of a wrong answer by the same factor it scales the value of a right one: 0.3 / 1.0 for right answers, β1.0 / β3.0 for wrong ones. Not β3: the qualification removes the claim that makes a wrong answer expensive (a wrong onion identity names no IP; a wrong CoinJoin broadcaster is presented without input ownership). Not 0: it still sends an investigator to the wrong node. | | |
| | 5 | `correct_actionable`, `correct_infrastructure`, `wrong_uninvolved_third_party` | everything else, ANNOTATE tier included | +1.0, +0.3, β3.0 | unchanged | | |
| The **P6 metric** is outcomes 1 and 5 only, with P6's weights. Every row of the | |
| revised report shows the P6 metric and the revised metric side by side. | |
| ### Protocol | |
| * Three policies are scored on the same test transactions: **off** | |
| (`enforce: false`), **binary** (P6's gate, current detectors), **tiered** | |
| (the revision). Each is scored under both metrics. | |
| * Each (policy, metric) row's abstention cutoff is chosen on the calibration | |
| captures under that policy and that metric, by `eval.origin.choose_cutoff_for`. | |
| * Base and variant corpora, within- and cross-topology. | |
| * **Decision rule:** the revision beats no-validity if and only if the tiered | |
| policy's cross-topology cost score on the **revised** metric exceeds the off | |
| policy's on the same metric. Reported plainly either way. The weights above | |
| are not changed after the result. | |
| * **Known bias, stated in advance.** Outcome 2 can only lower the off policy's | |
| score: it prices a claim the off policy makes and the tiered policy does not. | |
| It is justified on its own terms above, not by its effect; the P6 metric is | |
| reported beside it on every row so the share of any difference that comes | |
| from the metric rather than from the policy is visible. The tiered policy | |
| must also beat off on the P6 metric for the revision to be called an | |
| improvement *without* qualification; beating it only on the revised metric | |
| will be reported as exactly that. | |
| * Red-team time-to-detect is re-run on an otherwise idle machine to settle | |
| whether P6's 2.22 s β 4.44 s was load. | |
| ## P6 results (frozen) | |
| The binary layer as committed in `3a1752a`: every non-PASS verdict abstained, v2 fired on every v2 link. Kept verbatim; the numbers below are the ones the metric revision responds to, and they are never regenerated. | |
| `condition="simulated"`. **What the simulator omits:** The simulator omits: Bitcoin Core's per-peer Poisson INV trickling (one exponential delay per hop stands in for it), clock skew between observers, inbound/outbound connection asymmetry, peer churn during a capture, re-announcement, wtxid relay, user agents and real GeoIP; senders keep one address and one peer set for a whole capture, and the share of senders connected directly to an observer is a sampled parameter, not a measurement. The validity variant adds, per capture: a Dandelion stem phase in BIP-156's shape (serial single-peer forwarding for a geometrically drawn number of hops, then an ordinary broadcast; the stem successor is drawn fresh per hop rather than from two per-epoch destinations, stem hops use the ordinary per-hop delay, and there is no embargo timer), senders reachable only over Tor (one uniform circuit latency on their first hop, entry only through mixed-transport nodes), BIP-324 v2 links (a label only: the simulated v2 link is timed like v1), and CoinJoin and equal-value batch-payout transaction shapes. Onion traffic between two relays, Tor latency on later hops, and Dandelion++'s per-epoch routing remain unsimulated. | |
| Corpus variant `efc5a443edf54d56`, deterministic from `origination/manifest_validity.json`: the base manifest's captures, each drawing four independent toggles. The model is fitted, calibrated and scored on it exactly as in section 9. Captures with each toggle on: dandelion 1029, onion 1037, v2 1101, coinjoin 1071, of 2160. | |
| #### Ground-truth prevalence | |
| | condition | measure | transactions | share of broadcast | | |
| | --- | --- | --- | --- | | |
| | simulated | dandelion stem | 21599 | 0.248 | | |
| | simulated | dandelion stem through an observer | 2469 | 0.028 | | |
| | simulated | onion origin | 14650 | 0.168 | | |
| | simulated | coinjoin | 6562 | 0.075 | | |
| | simulated | equal value batch payout | 4093 | 0.047 | | |
| | simulated | matrix rows over v2 | 112673 | n/a | | |
| | simulated | matrix rows from onion peers | 1617 | n/a | | |
| *`condition="simulated"`.* The simulator omits: Bitcoin Core's per-peer Poisson INV trickling (one exponential delay per hop stands in for it), clock skew between observers, inbound/outbound connection asymmetry, peer churn during a capture, re-announcement, wtxid relay, user agents and real GeoIP; senders keep one address and one peer set for a whole capture, and the share of senders connected directly to an observer is a sampled parameter, not a measurement. The validity variant adds, per capture: a Dandelion stem phase in BIP-156's shape (serial single-peer forwarding for a geometrically drawn number of hops, then an ordinary broadcast; the stem successor is drawn fresh per hop rather than from two per-epoch destinations, stem hops use the ordinary per-hop delay, and there is no embargo timer), senders reachable only over Tor (one uniform circuit latency on their first hop, entry only through mixed-transport nodes), BIP-324 v2 links (a label only: the simulated v2 link is timed like v1), and CoinJoin and equal-value batch-payout transaction shapes. Onion traffic between two relays, Tor latency on later hops, and Dandelion++'s per-epoch routing remain unsimulated. | |
| #### Detector precision and recall against the generator's labels | |
| Each detector run on its own over every observed test transaction. TOR_OR_V2 depends on which peer is named; the model's choice is used. | |
| | condition | test set | detector | ground truth | transactions | truly present | fired | true positives | precision | precision 95% CI | recall | recall 95% CI | | |
| | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | |
| | simulated | within-topology | DANDELION_STEM | a stem was used | 12223 | 3078 | 737 | 313 | 0.425 | 0.390β0.461 | 0.102 | 0.091β0.113 | | |
| | simulated | within-topology | DANDELION_STEM | the stem passed through an observer | 12223 | 375 | 737 | 219 | 0.297 | 0.265β0.331 | 0.584 | 0.533β0.633 | | |
| | simulated | within-topology | COINJOIN | CoinJoin shape | 12223 | 923 | 1054 | 923 | 0.876 | 0.854β0.894 | 1.000 | 0.996β1.000 | | |
| | simulated | within-topology | TOR_OR_V2 (onion clause) | sender reachable only over Tor | 12223 | 1905 | 249 | 249 | 1.000 | 0.985β1.000 | 0.131 | 0.116β0.147 | | |
| | simulated | within-topology | TOR_OR_V2 (both clauses) | sender reachable only over Tor | 12223 | 1905 | 1370 | 372 | 0.272 | 0.249β0.296 | 0.195 | 0.178β0.214 | | |
| | simulated | cross-topology | DANDELION_STEM | a stem was used | 19250 | 4678 | 1143 | 489 | 0.428 | 0.399β0.457 | 0.105 | 0.096β0.114 | | |
| | simulated | cross-topology | DANDELION_STEM | the stem passed through an observer | 19250 | 635 | 1143 | 335 | 0.293 | 0.267β0.320 | 0.528 | 0.489β0.566 | | |
| | simulated | cross-topology | COINJOIN | CoinJoin shape | 19250 | 1383 | 1597 | 1383 | 0.866 | 0.848β0.882 | 1.000 | 0.997β1.000 | | |
| | simulated | cross-topology | TOR_OR_V2 (onion clause) | sender reachable only over Tor | 19250 | 3471 | 443 | 443 | 1.000 | 0.991β1.000 | 0.128 | 0.117β0.139 | | |
| | simulated | cross-topology | TOR_OR_V2 (both clauses) | sender reachable only over Tor | 19250 | 3471 | 2411 | 720 | 0.299 | 0.281β0.317 | 0.207 | 0.194β0.221 | | |
| *`condition="simulated"`.* The simulator omits: Bitcoin Core's per-peer Poisson INV trickling (one exponential delay per hop stands in for it), clock skew between observers, inbound/outbound connection asymmetry, peer churn during a capture, re-announcement, wtxid relay, user agents and real GeoIP; senders keep one address and one peer set for a whole capture, and the share of senders connected directly to an observer is a sampled parameter, not a measurement. The validity variant adds, per capture: a Dandelion stem phase in BIP-156's shape (serial single-peer forwarding for a geometrically drawn number of hops, then an ordinary broadcast; the stem successor is drawn fresh per hop rather than from two per-epoch destinations, stem hops use the ordinary per-hop delay, and there is no embargo timer), senders reachable only over Tor (one uniform circuit latency on their first hop, entry only through mixed-transport nodes), BIP-324 v2 links (a label only: the simulated v2 link is timed like v1), and CoinJoin and equal-value batch-payout transaction shapes. Onion traffic between two relays, Tor latency on later hops, and Dandelion++'s per-epoch routing remain unsimulated. | |
| On the base corpus `945261fe69e264d6` (cross-topology test set), where no transaction has a stem, DANDELION_STEM fires on 815 of 19414 transactions (0.042): every one a false alarm. | |
| #### The system with and without the validity layer | |
| The supervised model on the same test transactions; each row's cutoff chosen on the calibration captures by `eval.origin.choose_cutoff_for`, with the layer on and off respectively. **The headline is `cost_weighted_score` and `acc if answered`.** | |
| | condition | test set | estimator | n | top1 | top1 95% CI | top3 | top3 95% CI | abstention rate | acc if answered | acc if answered 95% CI | ceiling (origin observed) | cost_weighted_score | wrong_uninvolved_third_party | cutoff (chosen on calibration) | | |
| | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | |
| | simulated | within-topology | supervised_origination (P5), validity off | 12223 | 0.192 | 0.185β0.199 | 0.195 | 0.188β0.202 | 0.887 | 0.919 | 0.903β0.932 | 0.195 | 0.067 | 112 | 0.700 | | |
| | simulated | within-topology | supervised_origination (P5) + validity | 12223 | 0.192 | 0.185β0.199 | 0.195 | 0.188β0.202 | 0.933 | 0.906 | 0.884β0.924 | 0.195 | 0.042 | 77 | 0.700 | | |
| | simulated | cross-topology | supervised_origination (P5), validity off | 19250 | 0.190 | 0.185β0.196 | 0.193 | 0.188β0.199 | 0.885 | 0.900 | 0.886β0.911 | 0.194 | 0.057 | 223 | 0.700 | | |
| | simulated | cross-topology | supervised_origination (P5) + validity | 19250 | 0.190 | 0.185β0.196 | 0.193 | 0.188β0.199 | 0.932 | 0.874 | 0.855β0.891 | 0.194 | 0.034 | 165 | 0.700 | | |
| *`condition="simulated"`.* The simulator omits: Bitcoin Core's per-peer Poisson INV trickling (one exponential delay per hop stands in for it), clock skew between observers, inbound/outbound connection asymmetry, peer churn during a capture, re-announcement, wtxid relay, user agents and real GeoIP; senders keep one address and one peer set for a whole capture, and the share of senders connected directly to an observer is a sampled parameter, not a measurement. The validity variant adds, per capture: a Dandelion stem phase in BIP-156's shape (serial single-peer forwarding for a geometrically drawn number of hops, then an ordinary broadcast; the stem successor is drawn fresh per hop rather than from two per-epoch destinations, stem hops use the ordinary per-hop delay, and there is no embargo timer), senders reachable only over Tor (one uniform circuit latency on their first hop, entry only through mixed-transport nodes), BIP-324 v2 links (a label only: the simulated v2 link is timed like v1), and CoinJoin and equal-value batch-payout transaction shapes. Onion traffic between two relays, Tor latency on later hops, and Dandelion++'s per-epoch routing remain unsimulated. | |
| **Verdict (cross-topology): the validity layer does not improve the cost score** β 0.034 with it against 0.057 without. That means its detectors abstain on transactions the model was getting right more often than on ones it was getting wrong; the ablation shows which. Accuracy-given-answered is 0.874 with the layer and 0.9 without; abstention 0.932 against 0.885. | |
| #### What each reason withholds | |
| At the layer-off cutoff: how many transactions each reason withholds that the layer-off system would have answered, what those answers would have been, and the cost score with only that reason enforced. DEGENERATE and NOT_REACHABLE were already abstentions by construction and cost nothing new. | |
| | condition | test set | reason | withheld | would have been answered | β¦correct_actionable | β¦correct_infrastructure | β¦wrong_uninvolved_third_party | cost score, this reason only | change vs layer off | | |
| | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | |
| | simulated | within-topology | DEGENERATE | 1107 | 0 | 0 | 0 | 0 | 0.067 | 0.000 | | |
| | simulated | within-topology | NOT_REACHABLE | 914 | 0 | 0 | 0 | 0 | 0.067 | 0.000 | | |
| | simulated | within-topology | COINJOIN | 878 | 127 | 95 | 22 | 10 | 0.061 | -0.006 | | |
| | simulated | within-topology | TOR_OR_V2 | 1030 | 229 | 72 | 148 | 9 | 0.059 | -0.008 | | |
| | simulated | within-topology | DANDELION_STEM | 577 | 209 | 193 | 0 | 16 | 0.055 | -0.012 | | |
| | simulated | cross-topology | DEGENERATE | 1482 | 0 | 0 | 0 | 0 | 0.057 | 0.000 | | |
| | simulated | cross-topology | NOT_REACHABLE | 1446 | 0 | 0 | 0 | 0 | 0.057 | 0.000 | | |
| | simulated | cross-topology | COINJOIN | 1340 | 179 | 134 | 33 | 12 | 0.051 | -0.006 | | |
| | simulated | cross-topology | TOR_OR_V2 | 1838 | 482 | 156 | 303 | 23 | 0.048 | -0.009 | | |
| | simulated | cross-topology | DANDELION_STEM | 854 | 254 | 230 | 1 | 23 | 0.048 | -0.009 | | |
| *`condition="simulated"`.* The simulator omits: Bitcoin Core's per-peer Poisson INV trickling (one exponential delay per hop stands in for it), clock skew between observers, inbound/outbound connection asymmetry, peer churn during a capture, re-announcement, wtxid relay, user agents and real GeoIP; senders keep one address and one peer set for a whole capture, and the share of senders connected directly to an observer is a sampled parameter, not a measurement. The validity variant adds, per capture: a Dandelion stem phase in BIP-156's shape (serial single-peer forwarding for a geometrically drawn number of hops, then an ordinary broadcast; the stem successor is drawn fresh per hop rather than from two per-epoch destinations, stem hops use the ordinary per-hop delay, and there is no embargo timer), senders reachable only over Tor (one uniform circuit latency on their first hop, entry only through mixed-transport nodes), BIP-324 v2 links (a label only: the simulated v2 link is timed like v1), and CoinJoin and equal-value batch-payout transaction shapes. Onion traffic between two relays, Tor latency on later hops, and Dandelion++'s per-epoch routing remain unsimulated. | |
| #### Verdicts issued on the test sets | |
| | verdict | within-topology | cross-topology | | |
| | --- | --- | --- | | |
| | PASS | 7717 | 12290 | | |
| | DEGENERATE | 1107 | 1482 | | |
| | TOR_OR_V2 | 1030 | 1838 | | |
| | NOT_REACHABLE | 914 | 1446 | | |
| | COINJOIN | 878 | 1340 | | |
| | DANDELION_STEM | 577 | 854 | | |
| *`condition="simulated"`.* The simulator omits: Bitcoin Core's per-peer Poisson INV trickling (one exponential delay per hop stands in for it), clock skew between observers, inbound/outbound connection asymmetry, peer churn during a capture, re-announcement, wtxid relay, user agents and real GeoIP; senders keep one address and one peer set for a whole capture, and the share of senders connected directly to an observer is a sampled parameter, not a measurement. The validity variant adds, per capture: a Dandelion stem phase in BIP-156's shape (serial single-peer forwarding for a geometrically drawn number of hops, then an ordinary broadcast; the stem successor is drawn fresh per hop rather than from two per-epoch destinations, stem hops use the ordinary per-hop delay, and there is no embargo timer), senders reachable only over Tor (one uniform circuit latency on their first hop, entry only through mixed-transport nodes), BIP-324 v2 links (a label only: the simulated v2 link is timed like v1), and CoinJoin and equal-value batch-payout transaction shapes. Onion traffic between two relays, Tor latency on later hops, and Dandelion++'s per-epoch routing remain unsimulated. | |
| #### The variant's section-9 table (cross-topology), validity enforced | |
| | condition | test set | estimator | n | top1 | top1 95% CI | top3 | top3 95% CI | abstention rate | acc if answered | acc if answered 95% CI | ceiling (origin observed) | cost_weighted_score | wrong_uninvolved_third_party | cutoff (chosen on calibration) | | |
| | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | |
| | simulated | cross-topology | first_spy_baseline | 19250 | 0.173 | 0.168β0.178 | 0.191 | 0.185β0.197 | n/a | 0.173 | n/a β the floor never abstains | 0.194 | -2.327 | 15922 | n/a | | |
| | simulated | cross-topology | first_timestamp | 19250 | 0.176 | 0.171β0.182 | 0.191 | 0.186β0.197 | 0.991 | 0.874 | 0.817β0.915 | 0.194 | 0.004 | 22 | 0.500 | | |
| | simulated | cross-topology | rumor_centrality | 19250 | 0.086 | 0.082β0.090 | 0.122 | 0.118β0.127 | 0.991 | 0.899 | 0.844β0.936 | 0.194 | 0.005 | 17 | 0.500 | | |
| | simulated | cross-topology | timestamp_weighted_centrality | 19250 | 0.176 | 0.171β0.182 | 0.191 | 0.186β0.197 | 0.991 | 0.894 | 0.839β0.932 | 0.194 | 0.005 | 18 | 0.500 | | |
| | simulated | cross-topology | supervised_origination (P5) | 19250 | 0.190 | 0.185β0.196 | 0.193 | 0.188β0.199 | 0.932 | 0.874 | 0.855β0.891 | 0.194 | 0.034 | 165 | 0.700 | | |
| *`condition="simulated"`.* The simulator omits: Bitcoin Core's per-peer Poisson INV trickling (one exponential delay per hop stands in for it), clock skew between observers, inbound/outbound connection asymmetry, peer churn during a capture, re-announcement, wtxid relay, user agents and real GeoIP; senders keep one address and one peer set for a whole capture, and the share of senders connected directly to an observer is a sampled parameter, not a measurement. The validity variant adds, per capture: a Dandelion stem phase in BIP-156's shape (serial single-peer forwarding for a geometrically drawn number of hops, then an ordinary broadcast; the stem successor is drawn fresh per hop rather than from two per-epoch destinations, stem hops use the ordinary per-hop delay, and there is no embargo timer), senders reachable only over Tor (one uniform circuit latency on their first hop, entry only through mixed-transport nodes), BIP-324 v2 links (a label only: the simulated v2 link is timed like v1), and CoinJoin and equal-value batch-payout transaction shapes. Onion traffic between two relays, Tor latency on later hops, and Dandelion++'s per-epoch routing remain unsimulated. | |
| ## Red-team timing (measured) | |
| Hand-written from a dedicated measurement; copied through, not regenerated. | |
| P6's report moved red-team median time-to-detect from 2.22 s to 4.44 s. The | |
| batch (`eval.redteam_batch.run_batch`, shifted dataset at the default rate, 30 | |
| injections) was re-run alone, on an otherwise idle machine (load average | |
| 1.1β1.5), alternating the current code with `a6a0c7c` β the commit before the | |
| validity layer β in a separate worktree: | |
| | code | run 1 | run 2 | batch wall time | | |
| | --- | --- | --- | --- | | |
| | current (tiered validity) | 4.13 s | 4.06 s | 221 s, 220 s | | |
| | `a6a0c7c`, before validity | 3.96 s | 3.90 s | 210 s, 210 s | | |
| **It was not load, and it was not the validity layer.** The layer costs about | |
| 4% (β0.15 s per detection). The pre-validity code itself no longer reproduces | |
| 2.22 s β the figure its own `eval/results.md` recorded at 11:23 the same | |
| morning. The machine is a laptop: the kernel logged the AC adapter on-line at | |
| boot (11:11), and during these runs it was on battery, discharging, with the | |
| `powersave` CPU governor. Power state is the likely cause; that is an | |
| inference, not a measurement β re-running both on AC would settle it. Until | |
| then the timing figures in `eval/results.md` are machine-state dependent and | |
| should not be compared across runs. | |
| ## Results β metric revision | |
| `condition="simulated"`. **What the simulator omits:** The simulator omits: Bitcoin Core's per-peer Poisson INV trickling (one exponential delay per hop stands in for it), clock skew between observers, inbound/outbound connection asymmetry, peer churn during a capture, re-announcement, wtxid relay, user agents and real GeoIP; senders keep one address and one peer set for a whole capture, and the share of senders connected directly to an observer is a sampled parameter, not a measurement. The validity variant adds, per capture: a Dandelion stem phase in BIP-156's shape (serial single-peer forwarding for a geometrically drawn number of hops, then an ordinary broadcast; the stem successor is drawn fresh per hop rather than from two per-epoch destinations, stem hops use the ordinary per-hop delay, and there is no embargo timer), senders reachable only over Tor (one uniform circuit latency on their first hop, entry only through mixed-transport nodes), BIP-324 v2 links (a label only: the simulated v2 link is timed like v1), and CoinJoin and equal-value batch-payout transaction shapes. Onion traffic between two relays, Tor latency on later hops, and Dandelion++'s per-epoch routing remain unsimulated. | |
| Variant corpus `973cc484afc7e7f5` (`origination/manifest_validity.json`) and base corpus `3f2d3bbe9a1f7088` (`origination/manifest.json`). The model is fitted, calibrated and scored on each exactly as in section 9. Variant captures with each toggle on: dandelion 1029, onion 1037, v2 1101, coinjoin 1071, of 2160. | |
| ### The policies under both metrics | |
| Same test transactions for every row. `off`: no validity layer. `binary`: P6's gate (every non-PASS verdict abstains), current detectors. `tiered`: the revision. Each metric's cost is at that (policy, metric)'s own cutoff, chosen on the calibration captures; the other columns are at the revised-metric cutoff. **The headline is the cost score and `acc if answered`.** | |
| #### Variant corpus | |
| | condition | corpus | test set | policy | n | cost, revised metric | cost, P6 metric | abstention rate | acc if answered | acc if answered 95% CI | correct_actionable | correct_infrastructure | wrong_uninvolved_third_party | qualified_correct | qualified_wrong | coinjoin_input_misattribution | cutoff, revised | cutoff, P6 | | |
| | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | |
| | simulated | variant | within-topology | off | 12223 | 0.034 | 0.067 | 0.890 | 0.924 | 0.909β0.937 | 991 | 149 | 93 | 0 | 0 | 112 | 0.800 | 0.700 | | |
| | simulated | variant | within-topology | binary | 12223 | 0.044 | 0.044 | 0.928 | 0.903 | 0.882β0.921 | 800 | 0 | 86 | 0 | 0 | 0 | 0.700 | 0.700 | | |
| | simulated | variant | within-topology | tiered | 12223 | 0.063 | 0.067 | 0.887 | 0.919 | 0.903β0.932 | 1004 | 0 | 102 | 265 | 10 | 0 | 0.700 | 0.700 | | |
| | simulated | variant | cross-topology | off | 19250 | 0.028 | 0.057 | 0.887 | 0.903 | 0.890β0.915 | 1518 | 305 | 200 | 0 | 0 | 154 | 0.800 | 0.700 | | |
| | simulated | variant | cross-topology | binary | 19250 | 0.037 | 0.037 | 0.925 | 0.873 | 0.855β0.889 | 1268 | 0 | 184 | 0 | 0 | 0 | 0.700 | 0.700 | | |
| | simulated | variant | cross-topology | tiered | 19250 | 0.053 | 0.057 | 0.885 | 0.900 | 0.886β0.911 | 1527 | 1 | 211 | 470 | 12 | 0 | 0.700 | 0.700 | | |
| *`condition="simulated"`.* The simulator omits: Bitcoin Core's per-peer Poisson INV trickling (one exponential delay per hop stands in for it), clock skew between observers, inbound/outbound connection asymmetry, peer churn during a capture, re-announcement, wtxid relay, user agents and real GeoIP; senders keep one address and one peer set for a whole capture, and the share of senders connected directly to an observer is a sampled parameter, not a measurement. The validity variant adds, per capture: a Dandelion stem phase in BIP-156's shape (serial single-peer forwarding for a geometrically drawn number of hops, then an ordinary broadcast; the stem successor is drawn fresh per hop rather than from two per-epoch destinations, stem hops use the ordinary per-hop delay, and there is no embargo timer), senders reachable only over Tor (one uniform circuit latency on their first hop, entry only through mixed-transport nodes), BIP-324 v2 links (a label only: the simulated v2 link is timed like v1), and CoinJoin and equal-value batch-payout transaction shapes. Onion traffic between two relays, Tor latency on later hops, and Dandelion++'s per-epoch routing remain unsimulated. | |
| #### Base corpus (no invalidating condition simulated) | |
| | condition | corpus | test set | policy | n | cost, revised metric | cost, P6 metric | abstention rate | acc if answered | acc if answered 95% CI | correct_actionable | correct_infrastructure | wrong_uninvolved_third_party | qualified_correct | qualified_wrong | coinjoin_input_misattribution | cutoff, revised | cutoff, P6 | | |
| | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | |
| | simulated | base | within-topology | off | 12279 | 0.122 | 0.122 | 0.815 | 0.915 | 0.902β0.925 | 2078 | 0 | 194 | 0 | 0 | 0 | 0.750 | 0.750 | | |
| | simulated | base | within-topology | binary | 12279 | 0.110 | 0.110 | 0.825 | 0.907 | 0.894β0.919 | 1944 | 0 | 199 | 0 | 0 | 0 | 0.650 | 0.650 | | |
| | simulated | base | within-topology | tiered | 12279 | 0.122 | 0.122 | 0.815 | 0.915 | 0.902β0.925 | 2078 | 0 | 194 | 0 | 0 | 0 | 0.750 | 0.750 | | |
| | simulated | base | cross-topology | off | 19414 | 0.120 | 0.120 | 0.808 | 0.906 | 0.896β0.915 | 3382 | 1 | 350 | 0 | 0 | 0 | 0.750 | 0.750 | | |
| | simulated | base | cross-topology | binary | 19414 | 0.107 | 0.107 | 0.817 | 0.896 | 0.886β0.906 | 3189 | 1 | 369 | 0 | 0 | 0 | 0.650 | 0.650 | | |
| | simulated | base | cross-topology | tiered | 19414 | 0.120 | 0.120 | 0.808 | 0.906 | 0.896β0.915 | 3382 | 1 | 350 | 0 | 0 | 0 | 0.750 | 0.750 | | |
| *`condition="simulated"`.* The simulator omits: Bitcoin Core's per-peer Poisson INV trickling (one exponential delay per hop stands in for it), clock skew between observers, inbound/outbound connection asymmetry, peer churn during a capture, re-announcement, wtxid relay, user agents and real GeoIP; senders keep one address and one peer set for a whole capture, and the share of senders connected directly to an observer is a sampled parameter, not a measurement. The validity variant adds, per capture: a Dandelion stem phase in BIP-156's shape (serial single-peer forwarding for a geometrically drawn number of hops, then an ordinary broadcast; the stem successor is drawn fresh per hop rather than from two per-epoch destinations, stem hops use the ordinary per-hop delay, and there is no embargo timer), senders reachable only over Tor (one uniform circuit latency on their first hop, entry only through mixed-transport nodes), BIP-324 v2 links (a label only: the simulated v2 link is timed like v1), and CoinJoin and equal-value batch-payout transaction shapes. Onion traffic between two relays, Tor latency on later hops, and Dandelion++'s per-epoch routing remain unsimulated. | |
| **Verdict (variant, cross-topology): the revised layer beats no-validity on the revised metric only** β 0.053 against 0.028; on the P6 metric it scores 0.057 against 0.057. As pre-registered, that is reported as exactly this and not as an unqualified improvement: the difference comes from pricing the CoinJoin ownership claim, not from the policy answering better by P6's measure. P6's binary gate, rescored on the same transactions: revised 0.037, P6 metric 0.037. | |
| ### Why each answer was withheld | |
| Every abstention, by the one reason code `eval.origin.abstention_reasons` gives it β the validity verdict's leading reason when its tier is withheld, then `RELAY_AT_TOP` (a listed public relay ranked first), then `BELOW_CUTOFF` (calibrated confidence under the policy's cutoff). Same frames and revised-metric cutoffs as the table above; the codes sum to `abstained`, so coverage (1 β abstention rate) is explained next to `acc if answered`. | |
| | condition | corpus | test set | policy | n | abstained | abstention rate | acc if answered | DEGENERATE | NOT_REACHABLE | COINJOIN | TOR_ONION | DANDELION_STEM | V2_PASSIVE_TAP | RELAY_AT_TOP | BELOW_CUTOFF | | |
| | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | |
| | simulated | variant | within-topology | off | 12223 | 10878 | 0.890 | 0.924 | 0 | 0 | 0 | 0 | 0 | 0 | 1125 | 9753 | | |
| | simulated | variant | within-topology | binary | 12223 | 11337 | 0.928 | 0.903 | 1107 | 914 | 878 | 153 | 625 | 0 | 93 | 7567 | | |
| | simulated | variant | within-topology | tiered | 12223 | 10842 | 0.887 | 0.919 | 1107 | 914 | 0 | 0 | 0 | 0 | 118 | 8703 | | |
| | simulated | variant | cross-topology | off | 19250 | 17073 | 0.887 | 0.903 | 0 | 0 | 0 | 0 | 0 | 0 | 1768 | 15305 | | |
| | simulated | variant | cross-topology | binary | 19250 | 17798 | 0.925 | 0.873 | 1482 | 1446 | 1340 | 308 | 968 | 0 | 167 | 12087 | | |
| | simulated | variant | cross-topology | tiered | 19250 | 17029 | 0.885 | 0.900 | 1482 | 1446 | 0 | 0 | 0 | 0 | 221 | 13880 | | |
| | simulated | base | within-topology | off | 12279 | 10007 | 0.815 | 0.915 | 0 | 0 | 0 | 0 | 0 | 0 | 987 | 9020 | | |
| | simulated | base | within-topology | binary | 12279 | 10136 | 0.825 | 0.907 | 1305 | 808 | 0 | 0 | 521 | 0 | 86 | 7416 | | |
| | simulated | base | within-topology | tiered | 12279 | 10007 | 0.815 | 0.915 | 1305 | 808 | 0 | 0 | 0 | 0 | 91 | 7803 | | |
| | simulated | base | cross-topology | off | 19414 | 15681 | 0.808 | 0.906 | 0 | 0 | 0 | 0 | 0 | 0 | 1599 | 14082 | | |
| | simulated | base | cross-topology | binary | 19414 | 15855 | 0.817 | 0.896 | 1802 | 1380 | 0 | 0 | 815 | 0 | 120 | 11738 | | |
| | simulated | base | cross-topology | tiered | 19414 | 15681 | 0.808 | 0.906 | 1802 | 1380 | 0 | 0 | 0 | 0 | 129 | 12370 | | |
| *`condition="simulated"`.* The simulator omits: Bitcoin Core's per-peer Poisson INV trickling (one exponential delay per hop stands in for it), clock skew between observers, inbound/outbound connection asymmetry, peer churn during a capture, re-announcement, wtxid relay, user agents and real GeoIP; senders keep one address and one peer set for a whole capture, and the share of senders connected directly to an observer is a sampled parameter, not a measurement. The validity variant adds, per capture: a Dandelion stem phase in BIP-156's shape (serial single-peer forwarding for a geometrically drawn number of hops, then an ordinary broadcast; the stem successor is drawn fresh per hop rather than from two per-epoch destinations, stem hops use the ordinary per-hop delay, and there is no embargo timer), senders reachable only over Tor (one uniform circuit latency on their first hop, entry only through mixed-transport nodes), BIP-324 v2 links (a label only: the simulated v2 link is timed like v1), and CoinJoin and equal-value batch-payout transaction shapes. Onion traffic between two relays, Tor latency on later hops, and Dandelion++'s per-epoch routing remain unsimulated. | |
| ### Verdicts by tier | |
| | condition | corpus | test set | tier | transactions | reasons fired | | |
| | --- | --- | --- | --- | --- | --- | | |
| | simulated | variant | within-topology | PASS | 8546 | β | | |
| | simulated | variant | within-topology | ABSTAIN | 2021 | DEGENERATE 1107, NOT_REACHABLE 1007 | | |
| | simulated | variant | within-topology | QUALIFIED | 1031 | COINJOIN 1054, TOR_ONION 249 | | |
| | simulated | variant | within-topology | ANNOTATE | 625 | DANDELION_STEM 737, V2_PASSIVE_TAP 0 | | |
| | simulated | variant | cross-topology | PASS | 13706 | β | | |
| | simulated | variant | cross-topology | ABSTAIN | 2928 | DEGENERATE 1482, NOT_REACHABLE 1547 | | |
| | simulated | variant | cross-topology | QUALIFIED | 1648 | COINJOIN 1597, TOR_ONION 443 | | |
| | simulated | variant | cross-topology | ANNOTATE | 968 | DANDELION_STEM 1143, V2_PASSIVE_TAP 0 | | |
| | simulated | base | within-topology | PASS | 9645 | β | | |
| | simulated | base | within-topology | ABSTAIN | 2113 | DEGENERATE 1305, NOT_REACHABLE 896 | | |
| | simulated | base | within-topology | QUALIFIED | 0 | COINJOIN 0, TOR_ONION 0 | | |
| | simulated | base | within-topology | ANNOTATE | 521 | DANDELION_STEM 543, V2_PASSIVE_TAP 0 | | |
| | simulated | base | cross-topology | PASS | 15417 | β | | |
| | simulated | base | cross-topology | ABSTAIN | 3182 | DEGENERATE 1802, NOT_REACHABLE 1470 | | |
| | simulated | base | cross-topology | QUALIFIED | 0 | COINJOIN 0, TOR_ONION 0 | | |
| | simulated | base | cross-topology | ANNOTATE | 815 | DANDELION_STEM 857, V2_PASSIVE_TAP 0 | | |
| *`condition="simulated"`.* The simulator omits: Bitcoin Core's per-peer Poisson INV trickling (one exponential delay per hop stands in for it), clock skew between observers, inbound/outbound connection asymmetry, peer churn during a capture, re-announcement, wtxid relay, user agents and real GeoIP; senders keep one address and one peer set for a whole capture, and the share of senders connected directly to an observer is a sampled parameter, not a measurement. The validity variant adds, per capture: a Dandelion stem phase in BIP-156's shape (serial single-peer forwarding for a geometrically drawn number of hops, then an ordinary broadcast; the stem successor is drawn fresh per hop rather than from two per-epoch destinations, stem hops use the ordinary per-hop delay, and there is no embargo timer), senders reachable only over Tor (one uniform circuit latency on their first hop, entry only through mixed-transport nodes), BIP-324 v2 links (a label only: the simulated v2 link is timed like v1), and CoinJoin and equal-value batch-payout transaction shapes. Onion traffic between two relays, Tor latency on later hops, and Dandelion++'s per-epoch routing remain unsimulated. | |
| ### What each reason fires on, and what the answers were (P6's table, again) | |
| At the layer-off policy's P6-metric cutoff, P6's method exactly: the transactions each reason fires on that the layer-off system would have answered, and whether those answers were right. The `tiered policy` column is what the revision now does with them. | |
| | condition | corpus | test set | reason | tier now | tiered policy | fired | would have been answered | β¦correct_actionable | β¦correct_infrastructure | β¦wrong_uninvolved_third_party | | |
| | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | |
| | simulated | variant | within-topology | DEGENERATE | ABSTAIN | withholds | 1107 | 0 | 0 | 0 | 0 | | |
| | simulated | variant | within-topology | NOT_REACHABLE | ABSTAIN | withholds | 1007 | 0 | 0 | 0 | 0 | | |
| | simulated | variant | within-topology | COINJOIN | QUALIFIED | answers, qualified | 1054 | 127 | 95 | 22 | 10 | | |
| | simulated | variant | within-topology | TOR_ONION | QUALIFIED | answers, qualified | 249 | 170 | 0 | 170 | 0 | | |
| | simulated | variant | within-topology | DANDELION_STEM | ANNOTATE | answers, flagged | 737 | 255 | 220 | 19 | 16 | | |
| | simulated | variant | within-topology | V2_PASSIVE_TAP | ANNOTATE | answers, flagged | 0 | 0 | 0 | 0 | 0 | | |
| | simulated | variant | cross-topology | DEGENERATE | ABSTAIN | withholds | 1482 | 0 | 0 | 0 | 0 | | |
| | simulated | variant | cross-topology | NOT_REACHABLE | ABSTAIN | withholds | 1547 | 0 | 0 | 0 | 0 | | |
| | simulated | variant | cross-topology | COINJOIN | QUALIFIED | answers, qualified | 1597 | 179 | 134 | 33 | 12 | | |
| | simulated | variant | cross-topology | TOR_ONION | QUALIFIED | answers, qualified | 443 | 336 | 0 | 336 | 0 | | |
| | simulated | variant | cross-topology | DANDELION_STEM | ANNOTATE | answers, flagged | 1143 | 347 | 281 | 38 | 28 | | |
| | simulated | variant | cross-topology | V2_PASSIVE_TAP | ANNOTATE | answers, flagged | 0 | 0 | 0 | 0 | 0 | | |
| | simulated | base | within-topology | DEGENERATE | ABSTAIN | withholds | 1305 | 0 | 0 | 0 | 0 | | |
| | simulated | base | within-topology | NOT_REACHABLE | ABSTAIN | withholds | 896 | 0 | 0 | 0 | 0 | | |
| | simulated | base | within-topology | COINJOIN | QUALIFIED | answers, qualified | 0 | 0 | 0 | 0 | 0 | | |
| | simulated | base | within-topology | TOR_ONION | QUALIFIED | answers, qualified | 0 | 0 | 0 | 0 | 0 | | |
| | simulated | base | within-topology | DANDELION_STEM | ANNOTATE | answers, flagged | 543 | 201 | 184 | 0 | 17 | | |
| | simulated | base | within-topology | V2_PASSIVE_TAP | ANNOTATE | answers, flagged | 0 | 0 | 0 | 0 | 0 | | |
| | simulated | base | cross-topology | DEGENERATE | ABSTAIN | withholds | 1802 | 0 | 0 | 0 | 0 | | |
| | simulated | base | cross-topology | NOT_REACHABLE | ABSTAIN | withholds | 1470 | 0 | 0 | 0 | 0 | | |
| | simulated | base | cross-topology | COINJOIN | QUALIFIED | answers, qualified | 0 | 0 | 0 | 0 | 0 | | |
| | simulated | base | cross-topology | TOR_ONION | QUALIFIED | answers, qualified | 0 | 0 | 0 | 0 | 0 | | |
| | simulated | base | cross-topology | DANDELION_STEM | ANNOTATE | answers, flagged | 857 | 302 | 282 | 0 | 20 | | |
| | simulated | base | cross-topology | V2_PASSIVE_TAP | ANNOTATE | answers, flagged | 0 | 0 | 0 | 0 | 0 | | |
| *`condition="simulated"`.* The simulator omits: Bitcoin Core's per-peer Poisson INV trickling (one exponential delay per hop stands in for it), clock skew between observers, inbound/outbound connection asymmetry, peer churn during a capture, re-announcement, wtxid relay, user agents and real GeoIP; senders keep one address and one peer set for a whole capture, and the share of senders connected directly to an observer is a sampled parameter, not a measurement. The validity variant adds, per capture: a Dandelion stem phase in BIP-156's shape (serial single-peer forwarding for a geometrically drawn number of hops, then an ordinary broadcast; the stem successor is drawn fresh per hop rather than from two per-epoch destinations, stem hops use the ordinary per-hop delay, and there is no embargo timer), senders reachable only over Tor (one uniform circuit latency on their first hop, entry only through mixed-transport nodes), BIP-324 v2 links (a label only: the simulated v2 link is timed like v1), and CoinJoin and equal-value batch-payout transaction shapes. Onion traffic between two relays, Tor latency on later hops, and Dandelion++'s per-epoch routing remain unsimulated. | |
| ### Detector precision and recall against the generator's labels | |
| Each detector on its own over every observed test transaction of the variant. TOR_ONION depends on which peer is named; the model's choice is used. V2_PASSIVE_TAP cannot fire here: every simulated observer is a node, which writes its own log, so no simulated capture is a pcap. | |
| | condition | test set | detector | tier | ground truth | transactions | truly present | fired | true positives | precision | precision 95% CI | recall | recall 95% CI | | |
| | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | |
| | simulated | within-topology | DANDELION_STEM | ANNOTATE | a stem was used | 12223 | 3078 | 737 | 313 | 0.425 | 0.390β0.461 | 0.102 | 0.091β0.113 | | |
| | simulated | within-topology | DANDELION_STEM | ANNOTATE | the stem passed through an observer | 12223 | 375 | 737 | 219 | 0.297 | 0.265β0.331 | 0.584 | 0.533β0.633 | | |
| | simulated | within-topology | COINJOIN | QUALIFIED | CoinJoin shape | 12223 | 923 | 1054 | 923 | 0.876 | 0.854β0.894 | 1.000 | 0.996β1.000 | | |
| | simulated | within-topology | TOR_ONION | QUALIFIED | sender reachable only over Tor | 12223 | 1905 | 249 | 249 | 1.000 | 0.985β1.000 | 0.131 | 0.116β0.147 | | |
| | simulated | within-topology | V2_PASSIVE_TAP | ANNOTATE | a v2 peer is missing from a pcap capture | 12223 | 0 | 0 | 0 | n/a | n/a | n/a | n/a | | |
| | simulated | cross-topology | DANDELION_STEM | ANNOTATE | a stem was used | 19250 | 4678 | 1143 | 489 | 0.428 | 0.399β0.457 | 0.105 | 0.096β0.114 | | |
| | simulated | cross-topology | DANDELION_STEM | ANNOTATE | the stem passed through an observer | 19250 | 635 | 1143 | 335 | 0.293 | 0.267β0.320 | 0.528 | 0.489β0.566 | | |
| | simulated | cross-topology | COINJOIN | QUALIFIED | CoinJoin shape | 19250 | 1383 | 1597 | 1383 | 0.866 | 0.848β0.882 | 1.000 | 0.997β1.000 | | |
| | simulated | cross-topology | TOR_ONION | QUALIFIED | sender reachable only over Tor | 19250 | 3471 | 443 | 443 | 1.000 | 0.991β1.000 | 0.128 | 0.117β0.139 | | |
| | simulated | cross-topology | V2_PASSIVE_TAP | ANNOTATE | a v2 peer is missing from a pcap capture | 19250 | 0 | 0 | 0 | n/a | n/a | n/a | n/a | | |
| *`condition="simulated"`.* The simulator omits: Bitcoin Core's per-peer Poisson INV trickling (one exponential delay per hop stands in for it), clock skew between observers, inbound/outbound connection asymmetry, peer churn during a capture, re-announcement, wtxid relay, user agents and real GeoIP; senders keep one address and one peer set for a whole capture, and the share of senders connected directly to an observer is a sampled parameter, not a measurement. The validity variant adds, per capture: a Dandelion stem phase in BIP-156's shape (serial single-peer forwarding for a geometrically drawn number of hops, then an ordinary broadcast; the stem successor is drawn fresh per hop rather than from two per-epoch destinations, stem hops use the ordinary per-hop delay, and there is no embargo timer), senders reachable only over Tor (one uniform circuit latency on their first hop, entry only through mixed-transport nodes), BIP-324 v2 links (a label only: the simulated v2 link is timed like v1), and CoinJoin and equal-value batch-payout transaction shapes. Onion traffic between two relays, Tor latency on later hops, and Dandelion++'s per-epoch routing remain unsimulated. | |
| On the base corpus `3f2d3bbe9a1f7088` (cross-topology test set), where no transaction has a stem, DANDELION_STEM fires on 857 of 19414 transactions (0.0441): every one a false alarm. It now only annotates them. | |
| ### Ground-truth prevalence (variant) | |
| | condition | measure | transactions | share of broadcast | | |
| | --- | --- | --- | --- | | |
| | simulated | dandelion stem | 21599 | 0.248 | | |
| | simulated | dandelion stem through an observer | 2469 | 0.028 | | |
| | simulated | onion origin | 14650 | 0.168 | | |
| | simulated | coinjoin | 6562 | 0.075 | | |
| | simulated | equal value batch payout | 4093 | 0.047 | | |
| | simulated | matrix rows over v2 | 112673 | n/a | | |
| | simulated | matrix rows from onion peers | 1617 | n/a | | |
| *`condition="simulated"`.* The simulator omits: Bitcoin Core's per-peer Poisson INV trickling (one exponential delay per hop stands in for it), clock skew between observers, inbound/outbound connection asymmetry, peer churn during a capture, re-announcement, wtxid relay, user agents and real GeoIP; senders keep one address and one peer set for a whole capture, and the share of senders connected directly to an observer is a sampled parameter, not a measurement. The validity variant adds, per capture: a Dandelion stem phase in BIP-156's shape (serial single-peer forwarding for a geometrically drawn number of hops, then an ordinary broadcast; the stem successor is drawn fresh per hop rather than from two per-epoch destinations, stem hops use the ordinary per-hop delay, and there is no embargo timer), senders reachable only over Tor (one uniform circuit latency on their first hop, entry only through mixed-transport nodes), BIP-324 v2 links (a label only: the simulated v2 link is timed like v1), and CoinJoin and equal-value batch-payout transaction shapes. Onion traffic between two relays, Tor latency on later hops, and Dandelion++'s per-epoch routing remain unsimulated. | |