Skip to main content

Module shaping

Module shaping 

Source
Available on crate feature std only.
Expand description

Traffic shaping — anti-fingerprint size padding (WIRE v6, deliverable (c)).

The data-plane datagram size otherwise tracks the application payload size (even with the v6 length-prefix diet, the datagram length is observable). This module hides it by padding each packet up to a size bucket before it is sealed, so an on-path observer sees only a small set of sizes.

Padding lives inside the AEAD plaintext (encrypted + authenticated), so a network attacker can neither see it, strip it, nor forge it — only the bucketed datagram size is observable. A padded packet sets PacketFlags::PADDED; its plaintext gains a trailer ‹pad_n zero bytes› ‖ pad_n:u16be, which the receiver strips after a successful decrypt.

The policy that picks the bucket is PADÉ (Nikitin et al., “PURBs”, 2019): a length is rounded up so its low E−S bits are zero (E = ⌊log2 L⌋, S = ⌊log2 E⌋+1), which caps the overhead at ≈ 1/E (≤ ~12% for small packets, →0 for large) while collapsing the size distribution to O(log) values per magnitude. Far cheaper than pad-to-MTU, far better than fixed buckets near their edges.

Pure + no_std-friendly (no allocation in the size math; append/strip operate on caller buffers). Default policy is PaddingPolicy::None — shaping is fully opt-in (it costs bandwidth).

Enums§

PaddingPolicy
How a packet’s on-wire size is chosen before sealing.

Constants§

MAX_SHAPED_WIRE
Upper bound on the padded on-wire packet size (the PhantomPacket wire image: 15-byte header + ciphertext). Capped below the 1200-byte path MTU with margin for the largest transport envelope (the 8-byte UDP ConnId, plus slack), so a padded datagram never fragments. A packet already larger than this is not padded (it is near-MTU already — low size entropy).
PAD_LEN_FIELD
Size of the in-plaintext padding length field (pad_n: u16be). The minimum trailer a padded packet carries (with pad_n == 0 zero bytes).

Functions§

append_padding
Append a padding trailer of trailer total bytes to plaintext: (trailer − PAD_LEN_FIELD) zero bytes, then the big-endian u16 count of those zero bytes. trailer must be ≥ PAD_LEN_FIELD and ≤ u16::MAX + PAD_LEN_FIELD (callers get trailer from padding_trailer_len, which respects both). A trailer < PAD_LEN_FIELD is a no-op (defensive).
padding_trailer_len
The number of trailer bytes to append to the AEAD plaintext to bring the resulting on-wire packet to a PADÉ bucket. 0 when no padding applies (policy None, or the packet is already too large to pad under MAX_SHAPED_WIRE). When non-zero it is ≥ PAD_LEN_FIELD (the 2-byte length field plus zero fill).
padme
PADÉ: round l up so its low E−S bits are zero, where E = ⌊log2 l⌋ and S = ⌊log2 E⌋ + 1. Returns l unchanged for l ≤ 2 (nothing to round). Monotone non-decreasing and idempotent; overhead (padme(l) − l)/l < 2^−S.
random_jitter
Anti-fingerprint timing jitter (WIRE v6, deliverable (d)): a uniform random delay in [0, max_ms] milliseconds to add before a send, so the inter-packet timing no longer tracks the application’s write pattern. Returns Duration::ZERO when max_ms == 0 (jitter off). Opt-in; it trades up to max_ms of added latency per packet for timing-correlation resistance.
strip_padding
Strip a padding trailer from a decrypted, PADDED-flagged plaintext: read the trailing u16be pad length, then drop that many zero bytes plus the 2-byte field. Returns the inner plaintext slice. A trailer that claims more bytes than are present is WireError::Truncated (an authenticated peer cannot reach this with a malformed trailer — the AEAD already verified — but a buggy peer must not panic the receiver).