Module authenticated

Source
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: The u64 index 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: The SocketAddr of the peer.
  • timestamp: A u64 timestamp indicating when the address was attested.
  • public_key: The peer’s public key.
  • signature: The peer’s cryptographic signature over the socket and timestamp.

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: A u32 identifier used to route the message to the correct application handler.
  • message: The arbitrary application payload as Bytes.

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 authenticated network.
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.