Expand description
§nym-swizzle
Application-layer traffic-shape obfuscation for privacy-preserving apps — primarily wallets, or anything that fetches sequential, index-addressed data (blocks, notes, checkpoints) or broadcasts at meaningful moments.
§Threat model
A mixnet (or any transport anonymity layer) hides who is talking; it does not hide what an application’s query pattern says about it:
- Timing correlation. The destination observes wall-clock arrival. A
wallet that broadcasts a transaction immediately after reaching chain tip
is trivially correlatable with its own sync activity. Use
delayto decorrelate observable actions from the events that trigger them. - Index / content correlation. A light client that requests exactly
blocks
4_120_000..4_120_010reveals its resume point, and the start height acts as a linking key across sessions: today’s start is yesterday’s end. Userangeto fetch via overlapping, shuffled chunks, with a randomized start overlap and/or checkpoint snapping so starts stop being exact pointers to previous ends.
§What stays your responsibility
- Transport and destination splitting (sync from one server, broadcast through another; never broadcast over the sync session) — app-level.
- Range widening for interest-masking: this crate never extends the end of a range and cannot know which indexes exist (chain tip, array bounds). If you want to mask which sub-range you care about, widen the requested range yourself; the crate obfuscates coverage of whatever range it is given. The one sanctioned outward extension is the downward start overlap, where earlier indexes always exist.
- Deduplication: overlapping chunks deliberately re-fetch data. Index-addressed data is idempotent; dedup is yours.
§Tuning caveat
Wider overlaps and checkpoint spacing buy a larger anonymity set at the cost of re-downloaded data. There are no settled numbers for this trade-off; the defaults here are conservative percentage-of-range derivations, exposed as knobs, not validated recommendations.
§Examples
Delay a broadcast by a random duration:
use std::time::Duration;
let mut s = nym_swizzle::delay::Delay::uniform(Duration::ZERO, Duration::from_secs(10));
let result = s.run(async move { broadcast_tx().await }).await;Fetch an index range via overlapping, shuffled chunks:
let mut s = nym_swizzle::range::Range::new(0, 1000).plan();
while let Some((start, end)) = s.next() {
get_block(start, end).await;
}§Wasm
Every non-dev dependency compiles for wasm32-unknown-unknown; the crate
is designed to be wrapped, unmodified, by a wasm-pack wrapper crate. On
wasm targets, timing uses [wasmtimer] and randomness reaches the browser
through getrandom’s wasm_js backend (enable it with
RUSTFLAGS='--cfg getrandom_backend="wasm_js"').
Re-exports§
pub use delay::Delay;pub use range::ChunkPlan;pub use range::Range;pub use range::Snap;pub use rng::Sampling;