Review finding: the timing study and the neighbourhood-confidence numbers were
produced by ad-hoc analysis, not by the simulator. timing_linkability,
neighbourhood_confidence and mean_upstream_hops had no callers outside their own
modules; min_blend_delay and release_mode were declared on SweepConfig, validated
and keyed, but never read by sweep.py, so a YAML setting them was silently
ignored; and propagation.py called mix_wait without the minimum, leaving the knob
inert on the delay tables of 3.1-3.2. Section 6 promised every number was
reproducible and data/README claimed to hold the evidence behind every number --
both were false for 3.11.
Now wired end to end: release_designs() is a real sweep axis, the engine measures
the timing attack per design and records it in traffic.parquet, and the deanon
table carries the full attribution bracket (local confidence, attributable
fractions, upstream hops, neighbourhood confidence). Added configs/timing.yaml
and a make target.
The committed sweep reproduces 3.11: MAP success 0.993/0.905/0.683 for clock and
0.989/0.832/0.550 for jitter across the swept rates, and the minimum interval
changes nothing (0.993 vs 0.993). Evidence checked in under data/timing.
Three regression tests pin the wiring so a measure cannot go back to living only
in analysis.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The jitter-vs-clock verdict rested on handing the clock adversary the tick
schedule, which is the conservative choice but also the harsher one -- a real
observer sees only ticks that released something. Rather than assume, both are
now measurable, and they are provably equivalent.
A silent tick implies nothing was pending at it, and any arrival older than the
previous release has demonstrably already left. So the candidate window bounded by
the true previous tick and the one bounded by the previous observed release hold
the same arrivals. Confirmed identical to 1e-9 in both the sparse regime (one hold
per relay) and the dense one (56 holds per relay), where the two coincide for
different reasons.
That removes the objection to the comparison: jitter beats the clock at a matched
delay budget on merit, not because the clock was given a stronger opponent.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two release designs at a MATCHED delay budget, so they differ only in how they
delay: clock (batch at free-running ticks, the existing model) and jitter (each
message waits its own exponential draw, mean set equal to the clock residual).
Plus min_blend_delay, which forbids intervals shorter than it.
Minimum interval -- a negative result, and provably so. A zero-length gap is
instantaneous, so it never covers an arrival and is never sampled by the residual
or by the size-biased interval. Excluding it therefore leaves the mean hold
exactly unchanged, and with it blending and linkability; what it does change is
E[S], the gap between release opportunities. Confirmed analytically and in
simulation: 1.168s vs 1.167s at M=3.
Timing attack -- the effective anonymity set of a release (perplexity of the
observer posterior over which arrival produced it), plus MAP success, the chance
its single best guess is right. The second matters because perplexity flatters a
heavy tail: an exponential never fully excludes an old arrival, so it can look
unlinkable while still being guessed correctly.
At the baseline rate BOTH designs fail almost completely -- MAP success 0.98-0.99,
effective set ~1. A relay handles so little traffic that in->out matching is
trivial, which follows directly from the mixing~0 result. Traffic, not delay, is
what buys timing protection.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>