Expand description
Communicate with a fixed set of authenticated peers over encrypted connections.
authenticated provides multiplexed communication between fully-connected peers
identified by a developer-specified cryptographic identity (i.e. BLS, ed25519, etc.).
Peer discovery occurs automatically using ordered bit vectors (sorted by authorized
cryptographic identities) to efficiently communicate knowledge of dialable peers.
§Features
- Configurable Cryptography Scheme for Peer Identities (BLS, ed25519, etc.)
- Automatic Peer Discovery Using Bit Vectors (Also Used as Ping Messages)
- Multiplexing With Configurable Rate Limiting Per Channel and Send Prioritization
- Optional Message Compression (using
zstd)
§Design
§Discovery
Peer discovery operates under the assumption that all peers are aware of and synchronized on
the composition of peer sets at specific, user-provided indices (u64). Each index maps to a
list of authorized PublicKeys ((u64, Vec<PublicKey>)). Based on this shared knowledge, each
peer can construct a sorted bit vector message (BitVec) representing its knowledge of the
dialable addresses SocketAddr for the peers in that set.
The BitVec message contains:
index: Theu64index the bit vector applies to.bits: The bit vector itself, where a ‘1’ signifies knowledge of the corresponding peer’s address in the sorted list for that index.
Warning: If peers are not synchronized on the peer set composition at a given index, discovery messages can be misinterpreted. A peer might associate a bit vector index with the wrong peer or fail to parse the vector if its length doesn’t match the expected set size. The application layer is responsible for ensuring peer set synchronization.
Due to their small size, these BitVec messages are exchanged periodically (configured by
gossip_bit_vec_frequency in the Config) between connected peers. This serves as both a
peer discovery mechanism and a keep-alive “ping” message to maintain the underlying
connection, especially during periods of low application-level traffic. The protocol supports
tracking multiple peer sets concurrently (up to tracked_peer_sets), each identified by its
index. This is useful, for instance, during transitions like distributed key generation
(DKG) where connections to both old and new peer sets are needed simultaneously.
Upon receiving a BitVec message, a peer compares it against its own knowledge for the same
index. If the receiving peer knows addresses that the sender marked as ‘0’ (unknown), it
selects a random subset of these known PeerInfo structures (up to peer_gossip_max_count)
and sends them back in a Payload::Peers message. Each PeerInfo structure verifies a peer’s
address claim and contains:
socket: TheSocketAddrof the peer.timestamp: Au64timestamp indicating when the address was attested.public_key: The peer’s public key.signature: The peer’s cryptographic signature over thesocketandtimestamp.
If the receiver doesn’t know any addresses the sender is unaware of, it sends no
Payload::Peers response; the received BitVec implicitly acts as a “pong”.
If a peer receives a PeerInfo message (either directly or through gossip) containing a more
recent timestamp for a known peer’s address, it updates its local Record. This updated
PeerInfo is also used in future gossip messages. Each peer generates its own signed
PeerInfo upon startup and sends it immediately after establishing a connection (following
the cryptographic handshake). This ensures that if a peer connects using an outdated address
record, it will be corrected promptly by the peer being dialed.
To initiate the discovery process, a peer needs a list of bootstrappers (defined in
Config) - known peer public keys and their corresponding socket addresses. The peer
attempts to dial these bootstrappers, performs the handshake, sends its own PeerInfo, and
then sends a BitVec for the relevant peer set(s) (initially only knowing its own address,
marked as ‘1’). It then waits for responses, learning about other peers through the
Payload::Peers messages received. Bootstrapper information is persisted, and connections to
them are maintained even if they aren’t part of any currently tracked peer sets. Different
peers can have different bootstrapper lists.
Note: If a peer (listener) receives a connection request from another peer (dialer) that
belongs to a registered peer set, the listener will accept the connection, even if the
listener itself hasn’t yet learned about that specific peer set (or has an older version). The
core requirement is that the listener recognizes the dialer’s public key as belonging to
some authorized set it tracks (see actors::tracker::Actor). This mechanism allows peers
with more up-to-date peer set information to connect and propagate that information, enabling
the listener to potentially learn about newer sets it is part of.
§Messages
Application-level data is exchanged using the Payload::Data message type, which wraps an
internal Data structure. This structure contains:
channel: Au32identifier used to route the message to the correct application handler.message: The arbitrary application payload asBytes.
The size of the message bytes (after potential compression) must not exceed the configured
max_message_size. If it does, the sending operation will fail with
Error::MessageTooLarge. Messages can be sent with priority, allowing certain
communications to potentially bypass lower-priority messages waiting in send queues across all
channels. Each registered channel (Sender, Receiver) handles its own message queuing,
rate limiting, and optional zstd compression/decompression.
§Example
use commonware_p2p::{authenticated::{self, Network}, Sender, Recipients};
use commonware_cryptography::{Ed25519, Signer, Verifier};
use commonware_runtime::{tokio, Spawner, Runner, Metrics};
use governor::Quota;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::num::NonZeroU32;
// Configure context
let runtime_cfg = tokio::Config::default();
let runner = tokio::Runner::new(runtime_cfg.clone());
// Generate identity
//
// In production, the signer should be generated from a secure source of entropy.
let signer = Ed25519::from_seed(0);
// Generate peers
//
// In production, peer identities will be provided by some external source of truth
// (like the staking set of a blockchain).
let peer1 = Ed25519::from_seed(1).public_key();
let peer2 = Ed25519::from_seed(2).public_key();
let peer3 = Ed25519::from_seed(3).public_key();
// Configure bootstrappers
//
// In production, it is likely that the address of bootstrappers will be some public address.
let bootstrappers = vec![(peer1.clone(), SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 3001))];
// Configure namespace
//
// In production, use a unique application namespace to prevent cryptographic replay attacks.
let application_namespace = b"my-app-namespace";
// Configure network
//
// In production, use a more conservative configuration like `Config::recommended`.
const MAX_MESSAGE_SIZE: usize = 1_024; // 1KB
let p2p_cfg = authenticated::Config::aggressive(
signer.clone(),
application_namespace,
SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 3000),
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 3000), // Use a specific dialable addr
bootstrappers,
MAX_MESSAGE_SIZE,
);
// Start context
runner.start(|context| async move {
// Initialize network
let (mut network, mut oracle) = Network::new(context.with_label("network"), p2p_cfg);
// Register authorized peers
//
// In production, this would be updated as new peer sets are created (like when
// the composition of a validator set changes).
oracle.register(0, vec![signer.public_key(), peer1, peer2, peer3]).await;
// Register some channel
const MAX_MESSAGE_BACKLOG: usize = 128;
const COMPRESSION_LEVEL: Option<i32> = Some(3);
let (mut sender, receiver) = network.register(
0,
Quota::per_second(NonZeroU32::new(1).unwrap()),
MAX_MESSAGE_BACKLOG,
COMPRESSION_LEVEL,
);
// Run network
let network_handler = network.start();
// Example: Use sender
let _ = sender.send(Recipients::All, bytes::Bytes::from_static(b"hello"), false).await;
// Shutdown network
network_handler.abort();
});Structs§
- Config
- Configuration for the peer-to-peer instance.
- Network
- Implementation of an
authenticatednetwork. - Oracle
- Mechanism to register authorized peers.
- Receiver
- Channel to asynchronously receive messages from a channel.
- Sender
- Sender is the mechanism used to send arbitrary bytes to a set of recipients over a pre-defined channel.
Enums§
- Error
- Errors that can occur when interacting with the network.
Type Aliases§
- Bootstrapper
- Known peer and its accompanying address that will be dialed on startup.