btc-intel / docs /VALIDITY.md
Anish-g's picture
Deploy btc-intel on free ZeroGPU Gradio Space
824aa03 verified
|
Raw History Blame Contribute Delete
53.9 kB

A newer version of the Gradio SDK is available: 6.30.0

Upgrade

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.