Expand description
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:
- 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;
peaveilis a privacy-oriented re-implementation on top ofpeashape. - 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.”
- Metadata resistance via
peashape. Every outboundpeaveilframe is shaped bypeashape’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.
peaveildiscovers 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.peaveildoes 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
pea2peaHandshake).
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:
peaveildoes 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 apea2peaHandshake(e.g. Noise / TLS), by configuringpeashapewith a custompeashape::CoverGeneratorthat produces encrypted-looking cover, or by encrypting the payload before submitting it topeashape. The constant size, constant timing, and per-tick cover thatpeashapeprovides 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
use std::time::Duration;
use peaveil::{CoverStrategy, Node, NodeConfig};
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;Re-exports§
pub use pea2pea;
Modules§
- sim
- Deterministic local simulations for
peaveil.
Structs§
- Connection
- Re-export of the
pea2peatransport primitives thatpeaveilbuilds on.Node::p2preturns the underlyingpea2pea::Node, and theprotocolsmodule re-exports theHandshake,Pea2Pea, andConnectiontypes needed to wire a customHandshakeon top. - Node
- A single peer in a
peaveilnetwork. - Node
Config - The set of parameters that govern a
Node. - Peer
Entry - A single peer in a
PeerSample. - Peer
Info - Read-only metadata about a single peer in a
ViewSnapshot. - Peer
Sample - A list of peers to be exchanged between two nodes.
- View
Snapshot - A read-only view of a node’s view, returned by
crate::Node::view.
Enums§
- Connection
Side - Re-export of the
pea2peatransport primitives thatpeaveilbuilds on.Node::p2preturns the underlyingpea2pea::Node, and theprotocolsmodule re-exports theHandshake,Pea2Pea, andConnectiontypes needed to wire a customHandshakeon top. - Cover
Strategy - How the node generates outbound traffic on top of
peashape. - Decode
Error - All the ways a
peaveilpayload can fail to decode. - Discovery
Event - An observable event in the local discovery loop. Fired by
the explorer and by the peaveil receive task; observed by
callers of
Node::subscribe_events. - Error
- All errors that can be surfaced to the application through the
public
peaveilAPI. - Peer
Category - Which of the four pools a peer currently belongs to.
- Topology
- Re-export of the
pea2peatransport primitives thatpeaveilbuilds on.Node::p2preturns the underlyingpea2pea::Node, and theprotocolsmodule re-exports theHandshake,Pea2Pea, andConnectiontypes needed to wire a customHandshakeon top.
Constants§
- EVENT_
CHANNEL_ CAPACITY - Default capacity of the broadcast channel of
DiscoveryEvents. Late subscribers see only events fired after they subscribed. - PEAVEIL_
MAGIC - On-the-wire magic that prefixes every
peaveilpayload. - PEAVEIL_
VERSION - Current wire-protocol version.
Traits§
- Handshake
- Re-export of the
pea2peatransport primitives thatpeaveilbuilds on.Node::p2preturns the underlyingpea2pea::Node, and theprotocolsmodule re-exports theHandshake,Pea2Pea, andConnectiontypes needed to wire a customHandshakeon top. - Pea2Pea
- Re-export of the
pea2peatransport primitives thatpeaveilbuilds on.Node::p2preturns the underlyingpea2pea::Node, and theprotocolsmodule re-exports theHandshake,Pea2Pea, andConnectiontypes needed to wire a customHandshakeon top.
Functions§
- connect_
nodes - Re-export of the
pea2peatransport primitives thatpeaveilbuilds on.Node::p2preturns the underlyingpea2pea::Node, and theprotocolsmodule re-exports theHandshake,Pea2Pea, andConnectiontypes needed to wire a customHandshakeon top.