Skip to main content

nym_swizzle/
lib.rs

1// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
2// SPDX-License-Identifier: Apache-2.0
3
4//! # nym-swizzle
5//!
6//! Application-layer traffic-shape obfuscation for privacy-preserving apps —
7//! primarily wallets, or anything that fetches sequential, index-addressed data
8//! (blocks, notes, checkpoints) or broadcasts at meaningful moments.
9//!
10//! ## Threat model
11//!
12//! A mixnet (or any transport anonymity layer) hides *who* is talking; it does
13//! not hide *what an application's query pattern says about it*:
14//!
15//! - **Timing correlation.** The destination observes wall-clock arrival. A
16//!   wallet that broadcasts a transaction immediately after reaching chain tip
17//!   is trivially correlatable with its own sync activity. Use [`delay`] to
18//!   decorrelate observable actions from the events that trigger them.
19//! - **Index / content correlation.** A light client that requests exactly
20//!   blocks `4_120_000..4_120_010` reveals its resume point, and the start
21//!   height acts as a *linking key across sessions*: today's start is
22//!   yesterday's end. Use [`range`] to fetch via overlapping, shuffled chunks,
23//!   with a randomized start overlap and/or checkpoint snapping so starts stop
24//!   being exact pointers to previous ends.
25//!
26//! ## What stays your responsibility
27//!
28//! - **Transport and destination splitting** (sync from one server, broadcast
29//!   through another; never broadcast over the sync session) — app-level.
30//! - **Range widening for interest-masking**: this crate never extends the
31//!   *end* of a range and cannot know which indexes exist (chain tip, array
32//!   bounds). If you want to mask *which* sub-range you care about, widen the
33//!   requested range yourself; the crate obfuscates coverage of whatever range
34//!   it is given. The one sanctioned outward extension is the *downward* start
35//!   overlap, where earlier indexes always exist.
36//! - **Deduplication**: overlapping chunks deliberately re-fetch data.
37//!   Index-addressed data is idempotent; dedup is yours.
38//!
39//! ## Tuning caveat
40//!
41//! Wider overlaps and checkpoint spacing buy a larger anonymity set at the
42//! cost of re-downloaded data. There are **no settled numbers** for this
43//! trade-off; the defaults here are conservative percentage-of-range
44//! derivations, exposed as knobs, not validated recommendations.
45//!
46//! ## Examples
47//!
48//! Delay a broadcast by a random duration:
49//!
50//! ```no_run
51//! # async fn broadcast_tx() {}
52//! # async fn example() {
53//! use std::time::Duration;
54//!
55//! let mut s = nym_swizzle::delay::Delay::uniform(Duration::ZERO, Duration::from_secs(10));
56//! let result = s.run(async move { broadcast_tx().await }).await;
57//! # }
58//! ```
59//!
60//! Fetch an index range via overlapping, shuffled chunks:
61//!
62//! ```no_run
63//! # async fn get_block(_s: u64, _e: u64) {}
64//! # async fn example() {
65//! let mut s = nym_swizzle::range::Range::new(0, 1000).plan();
66//! while let Some((start, end)) = s.next() {
67//!     get_block(start, end).await;
68//! }
69//! # }
70//! ```
71//!
72//! ## Wasm
73//!
74//! Every non-dev dependency compiles for `wasm32-unknown-unknown`; the crate
75//! is designed to be wrapped, unmodified, by a `wasm-pack` wrapper crate. On
76//! wasm targets, timing uses [`wasmtimer`] and randomness reaches the browser
77//! through `getrandom`'s `wasm_js` backend (enable it with
78//! `RUSTFLAGS='--cfg getrandom_backend="wasm_js"'`).
79
80#![warn(missing_docs)]
81
82pub mod delay;
83pub mod range;
84pub mod rng;
85
86pub(crate) mod timer;
87
88pub use delay::Delay;
89pub use range::{ChunkPlan, Range, Snap};
90pub use rng::Sampling;