mirror of
https://github.com/logos-blockchain/research.git
synced 2026-08-07 03:33:33 +00:00
blend: align section 3.11 with its committed evidence, and expose every study via make
The 3.11 table carried numbers from the ad-hoc analysis that preceded the sweep. Replaced with the values the checked-in run actually produces (MAP success 0.993/0.905/0.683 clock, 0.989/0.832/0.550 jitter), so every figure in the report is traceable to data/. The minimum-interval control likewise now quotes the committed 10.14s vs 10.22s and 0.858 vs 0.860. cover-traffic was the only study without a make target, and correlated-churn, cover-traffic and timing were missing from the simulator quick-start. Added. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
fb49a790fc
commit
36c16a0a98
@ -165,7 +165,7 @@ So **the `full_deanon` figures above overstate confident origination by up to fi
|
||||
|
||||
**`confidence = 1 / (1 + (1 − f_adv)^L)`** for `L` upstream hops — the local model being exactly `L = 1`.
|
||||
|
||||
`L` is not a free parameter; the graph fixes it. A forwarder sits about halfway along a route, and the peer graph is low-diameter: mean hop distance is **5.15 at degree 8** (3.87 at degree 16), so `L ≈ 2.6`. That gives:
|
||||
`L` is not a free parameter; the graph fixes it. A forwarder sits about halfway along a route, and the peer graph is low-diameter: mean hop distance is **5.15 at degree 8** (3.87 at degree 16), so `L ≈ 2.6` — measured by `blend.adversary.mean_upstream_hops`, which the deanon table now records per run as `upstream_hops` alongside the resulting `neighbourhood_conf`. That gives:
|
||||
|
||||
| `f_adv` | local (`L=1`) | neighbourhood (`L≈2.6`) | `L` needed for 0.9 |
|
||||
|---|---|---|---|
|
||||
@ -408,20 +408,20 @@ The measure is the **effective anonymity set** of a release: the perplexity of t
|
||||
|
||||
| rate (msg/s) | design | mean hold | effective set | linked | **MAP success** |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | clock | 10.4 s | 1.02 | 0.985 | **0.992** |
|
||||
| 1 | jitter | 9.8 s | 1.05 | 0.958 | **0.983** |
|
||||
| 16 | clock | 10.2 s | 1.23 | 0.811 | 0.900 |
|
||||
| 16 | jitter | 10.2 s | 1.57 | 0.554 | 0.830 |
|
||||
| 64 | clock | 10.2 s | 1.97 | 0.468 | 0.678 |
|
||||
| 64 | jitter | 10.1 s | 3.27 | 0.097 | **0.553** |
|
||||
| 1 | clock | 10.3 s | 1.02 | 0.986 | **0.993** |
|
||||
| 1 | jitter | 10.8 s | 1.03 | 0.969 | **0.989** |
|
||||
| 16 | clock | 10.1 s | 1.21 | 0.820 | 0.905 |
|
||||
| 16 | jitter | 10.1 s | 1.57 | 0.559 | 0.832 |
|
||||
| 64 | clock | 10.2 s | 1.96 | 0.476 | 0.683 |
|
||||
| 64 | jitter | 10.2 s | 3.28 | 0.098 | **0.550** |
|
||||
|
||||
**At the specified cover rate, timing linkage is essentially total under either design** — the adversary's best guess is right 98–99 % of the time. That is the direct consequence of §3.10's result that a relay holds 0.0014 messages: with one message per relay there is nothing to confuse it with, and no amount of delay manufactures ambiguity out of a single message. **Traffic buys timing protection; delay does not.**
|
||||
|
||||
Between the two designs, **the independent draw wins at equal cost**, contrary to the intuition that batching at a tick should be safer. The clock's tick is a *hard boundary*: the observer can exclude everything before the previous tick, so the candidate set is exactly one inter-tick batch. An exponential has unbounded support and never excludes anything outright. But the margin is much smaller than the set size suggests — at 64 msg/s jitter's effective set is **68 %** larger while its advantage in defeating the best guess is only **19 %**. The heavy tail keeps old arrivals nominally possible while contributing almost nothing, so **perplexity alone would have overstated the case for Poisson-style mixing by roughly a factor of three.**
|
||||
Between the two designs, **the independent draw wins at equal cost**, contrary to the intuition that batching at a tick should be safer. The clock's tick is a *hard boundary*: the observer can exclude everything before the previous tick, so the candidate set is exactly one inter-tick batch. An exponential has unbounded support and never excludes anything outright. But the margin is much smaller than the set size suggests — at 64 msg/s jitter's effective set is **67 %** larger while its advantage in defeating the best guess is only **19 %**. The heavy tail keeps old arrivals nominally possible while contributing almost nothing, so **perplexity alone would have overstated the case for Poisson-style mixing by roughly a factor of three.**
|
||||
|
||||
One objection needs disposing of: the clock figures above hand the observer the *tick schedule*, which is the conservative assumption but also the harsher one, since a real observer sees only ticks that released something. Measured both ways, it makes **no difference at all** — and provably so. A silent tick implies nothing was pending at it, and any arrival older than the previous release has already left, so the window bounded by the true previous tick and the window bounded by the last observed release contain the same arrivals. The clock design is not being handicapped by a generous adversary.
|
||||
|
||||
**A minimum release interval changes nothing.** Forbidding zero-length intervals (`min_blend_delay = 1`) leaves the mean hold at 10.1 s against 10.4 s, and the effective set, linkage and MAP success are identical to three decimals. This is structural rather than a null measurement: a zero-length gap is instantaneous, so it can never cover a message arrival and is never drawn by the residual or by the size-biased interval. Excluding it removes probability mass that was never sampled. What it *does* change is `E[S]`, the spacing of release opportunities — so a minimum would matter under per-message jitter, where a zero draw is a genuinely unmixed forward, but under a free-running clock it buys nothing.
|
||||
**A minimum release interval changes nothing.** Forbidding zero-length intervals (`min_blend_delay = 1`) leaves the mean hold at 10.14 s against 10.22 s, and MAP success at 0.858 against 0.860 — identical within noise. This is structural rather than a null measurement: a zero-length gap is instantaneous, so it can never cover a message arrival and is never drawn by the residual or by the size-biased interval. Excluding it removes probability mass that was never sampled. What it *does* change is `E[S]`, the spacing of release opportunities — so a minimum would matter under per-message jitter, where a zero draw is a genuinely unmixed forward, but under a free-running clock it buys nothing.
|
||||
|
||||
---
|
||||
|
||||
@ -458,7 +458,7 @@ One objection needs disposing of: the clock figures above hand the observer the
|
||||
<a id="s6"></a>
|
||||
## 6. Reproducibility
|
||||
|
||||
The simulator, configs, and analytic checks live in [`tools/simulators/blend`](../../tools/simulators/blend). From that directory: `make install`, then `make sweep` runs the main grid (`configs/default.yaml`: N up to 10⁵, degree 3–16, 1–5 blend hops, `f_adv` up to 0.5, unresponsive fractions to 0.5, all three placement modes, 8 topology seeds) into `runs/<timestamp>_default/`, writing three tables — `propagation.parquet`, `adversary.parquet`, and `deanon.parquet` — and rendering the figures. `make sweep-fullscale` extends the exact metrics to 10⁶ nodes. The messaging-redundancy study (§3.8) and the linkability figures come from `configs/redundancy.yaml` (`python -m blend.sweep --config configs/redundancy.yaml`), which sweeps `redundancy` ∈ {1, 2, 3, 4} alongside the churn and adversary grids; the churn-threshold study (§3.5, Fig 20) comes from `configs/percolation.yaml`, which walks the unresponsive fraction to 0.9 so each degree's collapse can be located against `u_c = 1 − 1/(degree − 1)`; the release-design study (§3.11) from `configs/timing.yaml` (`make timing`), which sweeps the two designs and the minimum interval against the cover rate; and the correlated-outage study (§3.9, Fig 22) from `configs/correlated-churn.yaml` (`make correlated-churn`), which partitions the network into failure domains and runs both churn modes on the same topologies. `make sweep-fullscale` produces the 10⁶ scaling check described in §5; and the cover-traffic study (§3.10, Figs 23–24) comes from `configs/cover-traffic.yaml`, which sweeps the emission rate over three decades against three release delays and pairs each timeline with the epoch-scale emission budget. Round counts in all three configs are set for statistical resolution, not speed — see the sampling-error note in §5. `make verify` runs the analytic anchors (d-regularity; `observed ≈ 1 − (1 − f)^degree`; `eclipsed ≈ f^degree`; delivery `≈ (1 − u)^blend_hops`; both deanonymization rates against a direct Monte-Carlo of the same draw; and — check 6 — `deanon_R` / `delivery_R = 1 − (1 − x)^R` for R independent cascades and the time-to-link geometric law), and `make test` the unit suite (`test_linkability.py` covers the time-to-link and stake formulae). The time-to-link and stake-inference curves are computed by `blend.linkability` from these exact rates.
|
||||
The simulator, configs, and analytic checks live in [`tools/simulators/blend`](../../tools/simulators/blend). From that directory: `make install`, then `make sweep` runs the main grid (`configs/default.yaml`: N up to 10⁵, degree 3–16, 1–5 blend hops, `f_adv` up to 0.5, unresponsive fractions to 0.5, all three placement modes, 8 topology seeds) into `runs/<timestamp>_default/`, writing three tables — `propagation.parquet`, `adversary.parquet`, and `deanon.parquet` — and rendering the figures. `make sweep-fullscale` extends the exact metrics to 10⁶ nodes. The messaging-redundancy study (§3.8) and the linkability figures come from `configs/redundancy.yaml` (`python -m blend.sweep --config configs/redundancy.yaml`), which sweeps `redundancy` ∈ {1, 2, 3, 4} alongside the churn and adversary grids; the churn-threshold study (§3.5, Fig 20) comes from `configs/percolation.yaml`, which walks the unresponsive fraction to 0.9 so each degree's collapse can be located against `u_c = 1 − 1/(degree − 1)`; the release-design study (§3.11) from `configs/timing.yaml` (`make timing`), which sweeps the two designs and the minimum interval against the cover rate; and the correlated-outage study (§3.9, Fig 22) from `configs/correlated-churn.yaml` (`make correlated-churn`), which partitions the network into failure domains and runs both churn modes on the same topologies. `make sweep-fullscale` produces the 10⁶ scaling check described in §5; and the cover-traffic study (§3.10, Figs 23–24) comes from `configs/cover-traffic.yaml` (`make cover-traffic`), which sweeps the emission rate over three decades against three release delays and pairs each timeline with the epoch-scale emission budget. Round counts in all three configs are set for statistical resolution, not speed — see the sampling-error note in §5. `make verify` runs the analytic anchors (d-regularity; `observed ≈ 1 − (1 − f)^degree`; `eclipsed ≈ f^degree`; delivery `≈ (1 − u)^blend_hops`; both deanonymization rates against a direct Monte-Carlo of the same draw; and — check 6 — `deanon_R` / `delivery_R = 1 − (1 − x)^R` for R independent cascades and the time-to-link geometric law), and `make test` the unit suite (`test_linkability.py` covers the time-to-link and stake formulae). The time-to-link and stake-inference curves are computed by `blend.linkability` from these exact rates.
|
||||
|
||||
The figures of record for this report are the copies checked in under [`report-figures/`](report-figures); the simulator does not commit its own generated figures. To regenerate: run the sweeps above, then copy `runs/<…>/figures/*.png` into `report-figures/`.
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ export OPENBLAS_NUM_THREADS := 1
|
||||
export MKL_NUM_THREADS := 1
|
||||
export NUMEXPR_NUM_THREADS := 1
|
||||
|
||||
.PHONY: install smoke sweep sweep-fullscale redundancy percolation correlated-churn timing figures verify test lint clean
|
||||
.PHONY: install smoke sweep sweep-fullscale redundancy percolation correlated-churn cover-traffic timing figures verify test lint clean
|
||||
|
||||
# The stamp is the real install; targets below depend on it so `make sweep` (etc.) auto-installs
|
||||
# on a fresh checkout and re-installs whenever pyproject.toml changes.
|
||||
@ -35,6 +35,9 @@ redundancy: $(STAMP) ## messaging redundancy R=1..4 (delivery vs deanonymization
|
||||
percolation: $(STAMP) ## churn threshold: coverage collapse at u_c = 1 - 1/(degree-1)
|
||||
$(PY) -m blend.sweep --config configs/percolation.yaml
|
||||
|
||||
cover-traffic: $(STAMP) ## cover traffic: blending, mixing, and the emission-quota ceiling
|
||||
$(PY) -m blend.sweep --config configs/cover-traffic.yaml
|
||||
|
||||
timing: $(STAMP) ## release designs under a timing attack (jitter vs clock tick)
|
||||
$(PY) -m blend.sweep --config configs/timing.yaml
|
||||
|
||||
|
||||
@ -80,6 +80,9 @@ make sweep # configs/default.yaml (N up to 1e5, both adversary
|
||||
make sweep-fullscale # configs/fullscale.yaml (N up to 1e6, random-mode exact)
|
||||
make redundancy # configs/redundancy.yaml (R=1..4: delivery vs deanonymization)
|
||||
make percolation # configs/percolation.yaml (churn threshold u_c = 1-1/(degree-1))
|
||||
make correlated-churn # configs/correlated-churn.yaml (AS/region outages vs uniform churn)
|
||||
make cover-traffic # configs/cover-traffic.yaml (blending, mixing, the stake ceiling)
|
||||
make timing # configs/timing.yaml (jitter vs clock-tick release under attack)
|
||||
make figures RUN=runs/<dir>
|
||||
```
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user