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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
//! `peaveil` — a privacy-first peer discovery protocol.
//!
//! # What it does
//!
//! `peaveil` is a continuous, background peer discovery
//! service. It does not answer "find node X"; it answers
//! "give me enough good peers, all the time." The protocol
//! is built on three ideas:
//!
//! 1. **Probabilistic peer sampling.** Every node keeps a
//! small, locally-known view of the network. On every
//! tick the node picks a random peer from its view and
//! ships a uniformly-random subset of the view (including
//! the local self-entry) to that peer. The peer merges
//! the received sample into its own view. The process
//! repeats forever. This is the Newscast / Cyclon / PPS
//! family of protocols; `peaveil` is a privacy-oriented
//! re-implementation on top of `peashape`.
//! 2. **Continuous exploration.** There is no "done" state
//! for discovery. Idle or busy, the node keeps sampling,
//! re-classifying, and rotating its view. The protocol
//! *never* pauses because there is nothing to look up —
//! there is always something to look up, namely "more
//! peers, to keep the view fresh."
//! 3. **Metadata resistance via `peashape`.** Every outbound
//! `peaveil` frame is shaped by `peashape`'s cover-traffic
//! scheduler: constant-size, constant-rate (or
//! Poisson-distributed) emission interleaved with cover
//! frames. An observer of the wire cannot tell when a
//! node is actively exchanging peer samples from when it
//! is doing nothing at all.
//!
//! # What it deliberately is not
//!
//! - Not a DHT. There is no deterministic key lookup, no XOR
//! metric, no finger table, no logarithmic routing. The
//! view is a *probabilistic sample* of the network, not a
//! routing table.
//! - Not a content-discovery protocol. `peaveil` discovers
//! *peers*; if you want content addressing, build a DHT
//! on top.
//! - Not a global-coverage guarantee. The view is bounded
//! and randomly sampled, so the probability of *any
//! particular* peer being in your view at *any particular*
//! moment is a function of the view size and the network
//! size, not a guarantee.
//!
//! # The four pools
//!
//! The view is partitioned into four named pools:
//!
//! - **Bootstrap** — well-known addresses seeded as network
//! entry points. They are sticky (never aged out) and do not
//! count against `view_size`, so the node always remembers
//! where to re-enter. `peaveil` does not dial them — see
//! "Who opens connections?" below.
//! - **Trusted** — peers that have been seen often enough in
//! gossip; the stable core of the view.
//! - **Recent** — peers that have been seen recently but
//! not yet promoted to `Trusted`; the transient part of
//! the view.
//! - **Random** — a tiny set of long-range "exploration"
//! peers. A small fraction of outgoing samples draws from
//! this pool specifically, so the discovery traffic does
//! not collapse onto the trusted core.
//!
//! Re-classification between pools runs on every explorer
//! tick: trusted peers that have not been heard from in a
//! while are demoted to `Recent`; recent peers that have
//! been seen often enough are promoted to `Trusted`; and
//! any pool's entries that exceed the configured age limit
//! are evicted.
//!
//! # Who opens connections?
//!
//! The application does — never `peaveil`. `peaveil`'s scope
//! is *discoverability*: it maintains the view and gossips
//! peer samples over the connections that already exist. It
//! reads the live connection set to know which links it can
//! gossip over, but it never opens or closes one, and it never
//! probes a peer for liveness. Deciding *who* and *when* to
//! connect to — bootstrap entry points and discovered peers
//! alike — is the caller's job, using [`Node::view`] /
//! [`Node::known_peers`] as the input and [`Node::connect`] /
//! [`Node::disconnect`] (re-exported from `peashape`) to act.
//! This is the *pea*-stack philosophy: a library does strictly
//! what the caller cannot do for itself, and opening a socket
//! is something the caller can already do.
//!
//! # Composition
//!
//! `peaveil` is a building block. The most common
//! composition is to wrap it in front of a higher-level
//! protocol: the higher-level layer is in charge of the
//! application semantics (pub/sub, RPC, file chunking), and
//! uses [`Node::known_peers`] / [`Node::view`] to ask
//! `peaveil` for the current best guess of "who else is
//! out there" whenever it needs to dial a new connection.
//!
//! Because every byte `peaveil` puts on the wire goes
//! through `peashape`'s scheduler, the discovery traffic
//! inherits `peashape`'s metadata-privacy property for
//! free: the on-the-wire timing distribution and size
//! distribution are independent of whether the explorer is
//! actively sampling or has nothing to do.
//!
//! # Threat model
//!
//! `peaveil` is designed to defeat a *passive global
//! network observer* who can:
//!
//! - observe every byte sent between every pair of nodes;
//! - observe the timing of every byte;
//! - but cannot break the cryptographic primitives
//! protecting the link (e.g. TLS via a `pea2pea`
//! `Handshake`).
//!
//! Against such an observer, the cover-traffic schedule
//! provided by `peashape` ensures that the *timing
//! distribution* and *size distribution* of a node's
//! outbound traffic are independent of whether the
//! explorer is sampling or idle. The observer learns
//! nothing about the existence, frequency, or destination
//! of `peaveil`'s discovery activity beyond the cover rate
//! the node has been configured for.
//!
//! `peaveil` does **not** attempt to defeat:
//!
//! - an observer that can compromise the node itself;
//! - an observer that controls a non-trivial fraction of
//! the network's nodes and can correlate views across
//! them (the "Sybil" attack against any sampling
//! protocol);
//! - traffic *content* analysis: `peaveil` does not
//! encrypt the contents of a peer sample. A passive
//! observer who can read the wire learns the full list of
//! peers this node has been talking to. End-to-end
//! confidentiality of the sample is the application's
//! responsibility; layer it via a `pea2pea`
//! [`Handshake`] (e.g. Noise / TLS), by configuring
//! `peashape` with a custom [`peashape::CoverGenerator`]
//! that produces encrypted-looking cover, or by
//! encrypting the payload before submitting it to
//! `peashape`. The constant size, constant timing, and
//! per-tick cover that `peashape` provides still defeat
//! the "is this node exchanging samples right now?"
//! question regardless of whether the payload is
//! encrypted.
//!
//! The recommended path for transport-level encryption is
//! to register a custom `Handshake` via
//! [`Node::p2p`], which exposes the underlying
//! `pea2pea::Node` so a `Handshake` can be wired in
//! *before* the listener comes up. See `examples/encrypted.rs`
//! for a minimal pre-shared-key handshake that wraps the
//! TCP stream in ChaCha20-Poly1305.
//!
//! # Measurements
//!
//! Every claim about `peaveil`'s behaviour is measurable in
//! a local simulation. The [`sim::Simulation`] type is a
//! self-contained harness that spawns a configured number
//! of nodes, wires them into a topology, drives the
//! network forward in time, and exposes:
//!
//! - **convergence time** — how long it takes for every
//! node to learn about every other node (or for a
//! configured coverage threshold to be reached);
//! - **peer diversity** — the average number of distinct
//! peers in each view, broken down by pool;
//! - **resilience to churn** — coverage recovery after a
//! fraction of the nodes is killed;
//! - **bootstrap latency** — how long it takes a node with
//! only bootstrap peers to reach a non-trivial view
//! size;
//! - **partition recovery** — coverage recovery after a
//! two-group network partition is healed;
//! - **bandwidth overhead** — total bytes emitted on the
//! wire, dominated by `peashape`'s cover traffic;
//! - **discovery stability** — the variance of view size
//! and category distribution over time once the network
//! has reached steady state.
//!
//! All random choices in the simulation are driven by a
//! seeded RNG, so re-running the simulation with the same
//! seed and configuration produces metrics in the same
//! bands. Exact bit-for-bit equality is not guaranteed
//! because the explorer ticks on real wall-clock time;
//! pin the clock with `tokio::time::pause()` to get
//! bit-exact determinism.
//!
//! # Quick start
//!
//! ```no_run
//! use std::time::Duration;
//! use peaveil::{CoverStrategy, Node, NodeConfig};
//!
//! # async fn run() -> Result<(), Box<dyn std::error::Error>> {
//! let alice = Node::new(NodeConfig {
//! name: Some("alice".into()),
//! listener_addr: Some("127.0.0.1:0".parse()?),
//! bootstrap: vec!["127.0.0.1:9001".parse()?],
//! cover: CoverStrategy::Constant {
//! interval: Duration::from_millis(100),
//! },
//! ..Default::default()
//! });
//! alice.spawn().await?;
//!
//! // ask peaveil what it knows
//! let view = alice.view();
//! for p in view.trusted.iter().chain(view.recent.iter()).chain(view.random.iter()) {
//! println!("{}:{} (seen {} times, last seen {:?} ago)",
//! p.addr.ip(), p.addr.port(), p.seen_count, p.last_seen);
//! }
//!
//! alice.shutdown().await;
//! # Ok(()) }
//! ```
//!
//! [`Node::known_peers`]: crate::Node::known_peers
//! [`Node::view`]: crate::Node::view
pub use crate;
pub use crateError;
pub use crate;
pub use crate;
pub use crate;
/// Re-export of the `pea2pea` transport primitives that
/// `peaveil` builds on. `Node::p2p` returns the underlying
/// `pea2pea::Node`, and the `protocols` module re-exports
/// the [`Handshake`], [`Pea2Pea`], and [`Connection`]
/// types needed to wire a custom `Handshake` on top.
///
/// [`Handshake`]: pea2pea::protocols::Handshake
/// [`Pea2Pea`]: pea2pea::Pea2Pea
/// [`Connection`]: pea2pea::Connection
pub use ;