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 (Used as Ping/Pongs)
  • Multiplexing With Configurable Rate Limiting Per Channel and Send Prioritization
  • Optional Message Compression (using zstd)

§Design

§Discovery

Peer discovery relies heavily on the assumption that all peers are known and synchronized at each index (a user-provided tuple of (u64, Vec<PublicKey>)). Using this assumption, we can construct a sorted bit vector that represents our knowledge of peer IPs (where 1 == we know, 0 == we don’t know). This means we can represent our knowledge of 1000 peers in only 125 bytes!

If peers at a given index are not synchronized, peers may signal their knowledge of peer IPs that another peer may incorrectly respond to (associating a given index with a different peer) or fail to respond to (if the bit vector representation of the set is smaller/larger than expected). It is up to the application to ensure sets are synchronized.

Because this representation is so efficient/small, peers send bit vectors to each other periodically as a “ping” to keep the connection alive. Because it may be useful to be connected to multiple indexes of peers at a given time (i.e. to perform a DKG with a new set of peers), it is possible to configure this crate to maintain connections to multiple indexes (and pings are a random index we are trying to connect to).

message BitVec {
    uint64 index = 1;
    bytes bits = 2;
}

Upon receiving a bit vector, a peer will select a random collection of peers (under a configured max) that it knows about that the sender does not. If the sender knows about all peers that we know about, the receiver does nothing (and relies on its bit vector to serve as a pong to keep the connection alive).

message Peers {
    repeated Peer peers = 1;
}

If a peer learns about an updated address for a peer, it will update the record it has stored (for itself and for future gossip). This record is created during instantiation and is sent immediately after a connection is established (right after the handshake). This means that a peer that learned about an outdated record for a peer will update it immediately upon being dialed.

message Peer {
    bytes socket = 1;
    uint64 timestamp = 2;
    Signature signature = 3;
}

To get all of this started, a peer must first be bootstrapped with a list of known peers/addresses. The peer will dial these other peers, send its own record, send a bit vector (with all 0’s except its own position in the sorted list), and then wait for the other peer to respond with some set of unknown peers. Different peers do not need to agree on who this list of bootstrapping peers is (this list is configurable). Knowledge of bootstrappers and connections to them are never dropped, even if the bootstrapper is not in any known peer set.

If a peer is not in any registered peer set (to its knowledge) but is dialed by a peer that is, it will accept the connection. This allows peers that have a more up-to-date version of the peer set to connect, exchange application-level information, and for the said peer to potentially learn of an updated peer set (of which it is part).

§Messages

Messages are sent using the Data message type. This message type is used to send arbitrary bytes to a given channel. The message must be smaller (in bytes) than the configured maximum message size. If the message is larger, an error will be returned. It is possible for a sender to prioritize messages over others.

message Data {
    uint32 channel = 1;
    bytes message = 2;
}

§Example

use commonware_p2p::authenticated::{self, Network};
use commonware_cryptography::{Ed25519, Scheme};
use commonware_runtime::{tokio::{self, Executor}, 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 (executor, context) = Executor::init(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::UNSPECIFIED), 3000),
    bootstrappers,
    MAX_MESSAGE_SIZE,
);

// Start context
executor.start(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]);

    // Register some channel
    const MAX_MESSAGE_BACKLOG: usize = 128;
    const COMPRESSION_LEVEL: Option<i32> = Some(3);
    let (sender, receiver) = network.register(
        0,
        Quota::per_second(NonZeroU32::new(1).unwrap()),
        MAX_MESSAGE_BACKLOG,
        COMPRESSION_LEVEL,
    );

    // Run network
    let network_handler = network.start();

    // ... Use sender and receiver ...

    // 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.