Skip to main content

Crate peasub

Crate peasub 

Source
Expand description

peasub — a metadata-private gossip protocol built on top of peashape, which in turn is built on pea2pea.

§What it does

peasub provides a gossip substrate in which every node emits outbound traffic at a constant rate (or, optionally, with Poisson-distributed inter-arrival times). Real application messages are interleaved with cover traffic drawn from the same code path, so a passive observer of the wire cannot distinguish a “real” frame from a “cover” frame, nor infer when a node has user activity.

The privacy property is not free: real messages are paced by the cover rate, so a node that wants to send at 10 msg/s must configure a cover rate of at least 10 msg/s, and the bandwidth cost is the same as if it were sending cover frames 100% of the time. The cover rate is the only knob that matters; the application should size its publish rate to the cover rate and not exceed it.

§Quick start

use std::time::Duration;
use peasub::{CoverStrategy, Node, NodeConfig};

let node = Node::new(NodeConfig {
    name: Some("alice".into()),
    listener_addr: Some("127.0.0.1:0".parse()?),
    cover: CoverStrategy::Constant {
        interval: Duration::from_millis(100),
    },
    ..Default::default()
});

let _local_addr = node.spawn().await?;

// subscribe to incoming frames
let mut rx = node.subscribe();

// submit a real message
node.publish(b"hello, peasub")?;

// connect to a peer (any peer running a compatible `Node`)
// node.connect(remote_addr).await?;

// ... process received frames ...
node.shutdown().await;

§Threat model

peasub 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 ensures that the timing distribution and size distribution of a node’s outbound traffic are independent of whether the application is publishing messages or not. The observer learns nothing about the existence, frequency, or destination of user activity beyond the rate the node has been configured for.

peasub does not attempt to defeat:

  • an observer that can compromise the node itself;
  • side channels outside the network (e.g. a screen-snooping adversary, or a process that visibly burns CPU only when the user is active);
  • an observer that controls a non-trivial fraction of the network’s nodes and can correlate across them;
  • traffic content analysis: cover hides when messages are sent, not what they say. End-to-end payload confidentiality is the application’s responsibility (or can be layered on via a pea2pea Handshake).

§Architecture

  • Node wraps a peashape::Node (which in turn wraps a pea2pea::Node) and adds two pieces of bookkeeping: an LRU of recently-seen message IDs (for dedup), and a background task that subscribes to the peashape incoming broadcast and re-enqueues novel frames into peashape’s low-priority lane for fanout. The high-priority lane is used for application-submitted messages (Node::publish); the low-priority lane is used for relays.
  • The length-delimited codec forces every frame on the wire to a single fixed size (configurable via NodeConfig::message_size), so the length of a frame is never a tell.
  • The first ID_SIZE bytes of every frame are a random message identifier; real messages receive a random ID at publication time, cover messages receive a fresh random ID per emission. The receiver’s LRU dedups on this field, which is the only piece of the frame the application could meaningfully leak through.
  • When a peer receives a frame, it is deduplicated against the LRU, delivered to local subscribers, and re-queued into the peashape low-priority lane. The LIFO discipline of the low-priority lane forwards a fresh relay on the very next cover tick.
  • When a tick fires, the peashape scheduler either pulls from the high-priority lane, then the low-priority lane, or generates a cover frame — and ships it to fanout randomly-chosen connected peers. Every tick emits exactly fanout frames of the same on-the-wire size, so the metadata-privacy property is preserved regardless of fanout.

§Choosing the cover rate

The cover rate is the only knob that controls the privacy / bandwidth trade-off. As a rule of thumb:

  • the application should publish no faster than the cover rate, otherwise its messages accumulate in the high-priority lane;
  • relay_outbox_capacity (the low-priority lane capacity) should be sized to fanout * cover_rate * drain_seconds to keep relay backlog from crowding out user data.

§Composition with peashape

Every byte that hits the wire goes through peashape’s scheduler and codec, so peasub inherits the metadata-privacy property for free. Conversely, the peashape library is a generic, application-agnostic substrate; peasub is one specific application (gossip). If you want to build a different application on the same property — private RPC, private pub/sub, private file chunking, private membership — use peashape directly.

Re-exports§

pub use pea2pea;

Structs§

Node
A single peer in a peasub gossip network.
NodeConfig
The set of parameters that govern a Node.

Enums§

CoverStrategy
How the node generates outbound traffic.
Error
All errors that can be surfaced to the application through the public peasub API.
Topology
Re-exported so that callers can wire up a topology in tests without adding pea2pea as a direct dependency. The way in which nodes are connected to each other; used in connect_nodes.

Constants§

ID_SIZE
Re-exported for backward compatibility: the random message identifier is sized by the peashape convention. Size, in bytes, of the message-identifier field that prefixes every frame in protocols built on top of peashape.

Functions§

connect_nodes
Re-exported so that callers can wire up a topology in tests without adding pea2pea as a direct dependency. Connects the provided list of nodes in order to form the given Topology.