research
protecting content is the easy part. what this work measures is how much an observer who sees only timings and volumes can still learn, and what it costs to take that away.
- flows
- 10
- relays
- 3
- suite
- c25519
- runs per configuration
- 5 x 30 s
- statistic
- median
the observer only sees when frames cross.
it watches the entry link and the last link between relays, counts frames per time window for every flow, and correlates every entry flow with every exit flow. the window is its choice, so both 10 ms and 100 ms are scored. chance is auc 0.5 and top-1 10%.

| protection | bandwidth between relays | median round trip | auc, 10 ms | auc, 100 ms | top-1, 10 ms |
|---|---|---|---|---|---|
| none | x1.00 | 0.146 ms | 1.000 | 1.000 | 100% |
| cover on top, +2x | x2.99 | 0.131 ms | 1.000 | 1.000 | 100% |
| constant rate at the client, 70 ms | x2.85 | 48 ms | 0.951 | 0.951 | 70% |
| constant rate at the client, 35 ms | x5.69 | 20 ms | 0.962 | 0.937 | 70% |
| relays on their own clocks, 66.5 ms | x3.00 | 187 ms | 0.562 | 0.463 | 5% |
| relays on their own clocks, 33.25 ms | x6.00 | 88 ms | 0.554 | 0.487 | 15% |
| both, client 70 ms, relays 66.5 ms | x3.00 | 230 ms | 0.462 | 0.442 | 3% |
| both, client 35 ms, relays 33.25 ms | x5.99 | 108 ms | 0.673 | 0.472 | 20% |
what the series shows.
cover on top does not help.
cover traffic added on top of real messages does not help at all: even at three times the bandwidth every flow is linked.
a constant rate at the client does not hide a flow.
each client ticks with its own phase, and the phase crosses a chain of relays that forward at once. with a 10 ms window the attack links flows at every period: the auc medians lie between 0.93 and 0.99. at 100 ms the auc falls to between 0.50 and 0.55 only where the period divides the window, at 25, 50 and 100 ms, and stays at 0.77 to 0.95 elsewhere: with a dividing period nearly all scores tie, which says nothing about protection.
relays on their own clocks lower this attack.
the attack counts cells only: the moments the connections of a circuit open and close match along the chain and are not part of it yet. its auc medians lie between 0.44 and 0.67, against 0.94 to 1.00 in the other rows, but these runs cannot show that it is at chance: four of the eight medians lie outside 0.45 to 0.55, and such a claim needs an equivalence interval inside that band, with the quantity and margin fixed before the series, which five runs cannot give. the 95% interval of a single run includes 0.5 in 33 of 40 cases; of the other seven, three lie above and four below. five runs of ten flows cannot tell a small remaining leak from none; the full series is to settle it. the price is a constant stream on every link a relay sends on and about 2.7 to 2.8 node periods added to a round trip: 187 ms at 66.5 ms, 88 ms at 33.25 ms. in the rows with both measures, the relay period is 5% shorter than the client's, so a missed tick is caught up; the rows with relay clocks alone use the same relay periods with a client that sends at once.
the entry node is not modelled.
the observer watches two links and does not model the entry node, which sees the client's cells directly. whether a constant client rate hides from it the moments the client sends is not measured.

what is measured, and what is planned.
- key material
- measured: exact contiguous 32-byte occurrences per selected client cell key in exported go Object payloads. at active/closed-now/closed-1s: c25519 none 3/3/3 and all 2/2/2; gost none 3/3/3 and all 1/0/0. every key in every child had that count. each condition planned and evaluated 30 fresh processes; all 360 evaluated, no exclusions. revision eddddb2, optimized go 1.27.3, linux/amd64. c25519 all remained observable after close; gost zeros do not prove erasure. external acquisition, disk/swap/history and other secrets remain unmeasured beyond v0.4.0.
- forward secrecy
- unit tests check that a setup cell stops opening once the node releases the onion key it was built for. opening setup cells recorded by a neighbour with keys taken from node memory, by recording age, remains unmeasured and deferred beyond v0.4.0. the cooperative client assay never decrypts recorded traffic.
- metadata
- a traffic correlation attack links the flows on the entry link to the flows on the last link between relays from cell timings and counts alone. auc, top-1 accuracy and tpr at fpr 0.01 are reported against cover traffic rate, a constant client rate and relays on their own clocks. node delay levels, message size classes and the topology of a conversation, two entries and the mailbox, are planned.
- timing watermark
- historical c25519 series f0f1885, optimized go 1.27.3: 960 completed executions in 40 repeat blocks. correct declaration with original-circuit responsiveness was 40/40 without relay pacing in each client mode and 0/40 at each tested positive period. at 25, 47.5, 50 and 52.5 ms the target stayed responsive in 40/40 of each active/sham condition; at 75 ms all six circuits closed in every one of 160 executions, availability loss. seven wrong active and seven sham declarations remain; immediate unpaced sham had 4/40. every denominator belongs to one mode, period and arm. the passive table keeps its separate revision.
- partial compromise
- how often a randomly drawn chain meets rogue nodes is computed and sampled with the client's own choice. a node's route knowledge is derived from the protocol, and a real-node lab test checks the trust boundary. the remaining full measurements of what nodes see and link are required for v0.4.0, in design and not implemented.
- cost
- round-trip latency through the chain, the bandwidth multiplier of cover and padding, and benchmarks of key agreement, signing and sealing a cell, gost versus x25519. node throughput, goodput per client and the cost of memory locking are planned.