peaveil 0.2.0

A privacy-first peer discovery protocol based on probabilistic peer sampling, gossip, and metadata-resistant discovery.
Documentation
# peaveil

[![Crates.io](https://img.shields.io/crates/v/peaveil.svg)](https://crates.io/crates/peaveil)
[![Documentation](https://docs.rs/peaveil/badge.svg)](https://docs.rs/peaveil)
[![dependency status](https://deps.rs/repo/github/ljedrz/peaveil/status.svg)](https://deps.rs/repo/github/ljedrz/peaveil)

A privacy-first peer-discovery protocol based on
probabilistic peer sampling, gossip, and metadata-resistant
discovery.

`peaveil` does not answer *"find node X"*. It answers
*"give me enough good peers, all the time."* Discovery is
not an operation; it is a permanent background activity.

---

### 📖 Table of Contents

- [What it is not]#what-it-is-not
- [How it works]#how-it-works
- [Who opens connections?]#who-opens-connections
- [Composition with `peashape` and `peasub`]#composition-with-peashape-and-peasub
- [Threat model]#threat-model
- [Quick start]#quick-start
- [Measurements]#measurements
- [Run the demo]#run-the-demo
- [Run the tests]#run-the-tests
- [Architecture]#architecture
- [License]#license
- [Peapod]#-peapod

---

## What it is not

- Not a DHT. There is no deterministic key lookup, no XOR
  metric, no finger table, no logarithmic routing.
- Not a content-discovery protocol. `peaveil` discovers
  *peers*; layer a DHT on top if you need content
  addressing.
- 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.

## How it works

`peaveil` is a Newscast / Cyclon / PPS-style
probabilistic-peer-sampling protocol with a four-pool
view. Every node keeps a small, locally-known view of the
network partitioned into:

- **Bootstrap** — well-known addresses seeded as network
  entry points; sticky and excluded from the view cap.
  `peaveil` does not dial them (see 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,
  sampled with a small bias to keep the discovery traffic
  from collapsing onto the trusted core.

On every tick the explorer:

1. Re-classifies and evicts (emitting `PeerEvicted`).
2. Picks a uniformly-random peer from the *live connection
   set* — `peaveil` gossips only over links you have already
   opened.
3. Draws a random subset of the view (including the
   self-entry, so the receiver learns about the sender).
4. Hands the subset to `peashape`'s cover-traffic scheduler
   for shaping and transmission.

On receive, the sample is decoded (or, if it is a
random cover frame, dropped), merged into the view, and the
explorer is poked to consider responding on its next tick.

## Who opens connections?

You do — never `peaveil`. Its scope is *discoverability*: it
maintains the view and gossips over the connections you have
opened, and it surfaces what it learns via `Node::view` /
`Node::known_peers` and the `DiscoveryEvent` stream. Deciding
*who* and *when* to connect to — bootstrap entry points and
discovered peers alike — is yours, via `Node::connect` /
`Node::disconnect`. That is the *pea*-stack philosophy: a
library does strictly what you cannot do for yourself, and
opening a socket is something you can already do.

## Composition with `peashape` and `peasub`

`peaveil` is a building block in the *pea* stack:

```
+---------------------------------------------------+
|                application                         |
+---------------------------------------------------+
|       peaveil           peasub (pub/sub)           |   <- discovery + pub/sub
+---------------------------------------------------+
|                 peashape (cover + shaping)         |   <- constant-size, constant-rate
+---------------------------------------------------+
|                  pea2pea (transport)               |   <- TCP
+---------------------------------------------------+
```

Every byte `peaveil` puts on the wire goes through
`peashape`'s scheduler, so the on-the-wire timing
distribution and size distribution are independent of
whether the explorer is actively sampling or idle. An
observer cannot tell *"this node is exchanging peer
samples right now"* from *"this node is doing nothing at
all."*

`peasub` is the gossip / pub-sub layer of the same
family. It runs on the same `peashape` substrate but
spreads application-level messages rather than peer
samples; the two can be used side-by-side, sharing the
connection set and the cover-traffic budget.

## 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), or
  encrypt 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.

`peaveil` does **not** ship its own encryption, by design
— that is the *pea*verse philosophy: a library does
strictly only what it is designed to do, and any
additional property (encryption, authentication, etc.) is
the caller's responsibility to layer on. The recommended
path is to register a custom `pea2pea` `Handshake` on the
underlying transport via `Node::p2p()` — see
`examples/encrypted.rs` for a minimal pre-shared-key
handshake that wraps the TCP stream in ChaCha20-Poly1305.

## Quick start

```rust,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(()) }
```

## Measurements

Every claim about `peaveil`'s behaviour is measurable in a
local simulation. The `peaveil::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:

| Metric                      | How to measure                                              |
| --------------------------- | ----------------------------------------------------------- |
| **convergence time**        | wall-clock seconds to a target coverage threshold           |
| **peer diversity**          | the per-category distribution of view sizes                 |
| **resilience to churn**     | coverage recovery after `Simulation::kill` of a fraction    |
| **bootstrap latency**        | wall-clock seconds for a cold start to reach view_size/2    |
| **partition recovery**       | coverage after `Simulation::heal_partition`                 |
| **bandwidth overhead**       | `frame_size * cover_rate` per peer-pair                     |
| **discovery stability**      | view-size variance over time once steady state is reached  |

All random choices (sample target, bootstrap order,
churn victim selection) 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 across runs is *not* guaranteed
because the explorer ticks on real wall-clock time; the
OS scheduler is allowed to interleave the per-node
background tasks slightly differently across runs.
Pin the explorer's clock with `tokio::time::pause()` (and
swap `tokio::time::interval` for a virtual-clock ticker)
to get bit-exact determinism.

```rust,no_run
use peaveil::sim::{sim_config, Simulation};
use std::time::Duration;

# async fn run() -> Result<(), Box<dyn std::error::Error>> {
let mut sim = Simulation::new(20, 0xC0FFEE, sim_config()).await;
sim.connect_ring().await;
// seed views with neighbours, etc.

for _ in 0..10 {
    sim.step(Duration::from_secs(1)).await;
    let m = sim.metrics();
    println!("alive={} avg_view={:.2} coverage={:.2}",
        m.alive, m.avg_view_size, m.coverage);
}
# Ok(()) }
```

## License

CC0-1.0 OR MIT.

## 🫛 Peapod

This library is part of the Peapod: a collection of small, composable Rust libraries for building robust peer-to-peer systems.

| Library | Purpose |
| ------- | ------- |
| `pea2pea` | Lightweight P2P networking primitive |
| `peashape` | Traffic shaping |
| `peaveil` | Privacy-oriented peer discovery |
| `peasub` | Metadata-private dissemination |
| `peaplex` | Optional stream multiplexing |
| `peaboard` | Reference application |

Each library does one thing well and composes naturally with the others.