1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
//! # 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 [`delay`] to
//! decorrelate observable actions from the events that trigger them.
//! - **Index / content correlation.** A light client that requests exactly
//! blocks `4_120_000..4_120_010` reveals its resume point, and the start
//! height acts as a *linking key across sessions*: today's start is
//! yesterday's end. Use [`range`] to 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:
//!
//! ```no_run
//! # async fn broadcast_tx() {}
//! # async fn example() {
//! 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:
//!
//! ```no_run
//! # async fn get_block(_s: u64, _e: u64) {}
//! # async fn example() {
//! 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"'`).
pub
pub use Delay;
pub use ;
pub use Sampling;