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
A newer version of the Gradio SDK is available: 6.30.0
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.