A minimal libp2p implementation in Rust, built with Sans-I/O architecture, no_std-friendly core crates, and clear developer experience.
The project is built around four non-negotiables:
- Sans-I/O architecture for protocol and transport state machines.
no_std-friendly core crates (alloc-based where needed).- Top-notch DX with clear defaults, actionable errors, and easy local bring-up.
- FFI-friendly APIs so Rust boundaries can later map cleanly to WASM/TypeScript hosts.
Sans-I/O core crates (no_std + alloc):
crates/identity(minip2p-identity): peer identity primitives, Ed25519 keys, varint helpers.crates/core(minip2p-core): transport-agnostic types (Multiaddr,PeerAddr,Protocol,PeerIdre-export).crates/transport(minip2p-transport): transport contract, shared lifecycle types (trait + data types only).crates/tls(minip2p-tls): libp2p TLS certificate generation and peer verification, transport-agnostic.crates/multistream-select(minip2p-multistream-select):/multistream/1.0.0negotiation state machine.crates/ping(minip2p-ping):/ipfs/ping/1.0.0state machine with RTT measurement.crates/identify(minip2p-identify):/ipfs/id/1.0.0state machine for protocol and address exchange.crates/relay(minip2p-relay): Circuit Relay v2 client-side state machines (HopReservation,HopConnect,StopResponder).crates/autonat(minip2p-autonat): AutoNAT reachability probe state machines.crates/dcutr(minip2p-dcutr): DCUtR hole-punch coordination state machines (DcutrInitiator,DcutrResponder).crates/swarm(minip2p-swarm):SwarmCoreSans-I/O orchestrator that composes the protocol state machines, tracks connections and streams, drives multistream-select, and emits actions/events for the driver.crates/nat(minip2p-nat):NatAgentSans-I/O NAT-traversal orchestrator — races direct dials against a relayed circuit, hole-punches with DCUtR over the bridge, and reports explicit path establish/upgrade/fallback events.
Runtime adapters (std):
crates/minip2p(minip2p): app-facing facade that glues identity, QUIC, and the std swarm driver into anEndpointAPI. The opt-innatcargo feature wires theminip2p-nattraversal agent intoEndpoint(connect/wait_path/take_nat_events, relay reservations, AutoNAT probing) with zero changes to the feature-off API.transports/quic(minip2p-quic): QUIC transport adapter built onquiche, with libp2p TLS baked in.crates/swarm(also ships a thinstddriverSwarm<T: Transport>behind thestdfeature).
Current validated behavior:
- Two local peers connect over QUIC in integration tests, with mutual libp2p TLS peer authentication.
- Bidirectional stream data exchange with half-close propagation.
- Multistream-select negotiation with spec-compliant varint framing.
- Ping protocol round-trips with RTT measurement over negotiated streams.
- Identify protocol exchange with observed-address plumbing from the transport.
- Transport contract with documented lifecycle guarantees and conformance tests.
- End-to-end stack via
minip2p::Endpoint: QUIC transport + multistream-select + identify + ping through one app-facing facade. - Swarm DX events for application readiness (
PeerReady) and typed runtime errors. - Pure-state-machine integration test covering Circuit Relay v2 + DCUtR (reservation, connect, stop, hole-punch coordination).
- AutoNAT reachability probe wire logic and state machines in
minip2p-autonat. - Manual cross-network test against a rust-libp2p relay validates HOP reservation, STOP circuit establishment, DCUtR coordination, IPv6 hole punching, direct ping, and relay-ping fallback.
- NAT-traversal orchestration (
minip2p-nat): scripted agent tests for the dialer race, housekeeping, and responder side; a two-agent relay-emulator integration test; and a loopback QUIC e2e through theminip2pfacade'snatfeature.
- Core crates listed above are designed to remain
no_std + alloc. Protocol state machines (ping,identify,relay,autonat,dcutr,multistream-select) never depend on sockets, async runtimes, or wall clocks. - Runtime networking concerns (UDP/TCP sockets, DNS resolution, timers) belong in transport adapter crates.
- Transport-specific address validation belongs in transport adapters, not
crates/core. crates/swarmsplits into a Sans-I/OSwarmCore(no_std) and astd-gated driver that owns a concreteTransportand reads the wall clock.
The minimal default swarm intentionally composes only Identify, Ping, and
registered application protocols. Relay, AutoNAT, and DCUtR remain independent
Sans-I/O machines driven over generic streams; their dialing, retry, and
fallback policy belongs to the host. This keeps the base library small and
avoids hiding network policy in a monolithic swarm while still allowing
declarative protocol registration through SwarmBuilder::protocol and
EndpointBuilder::protocol.
transports/quic is the optional synchronous socket adapter. It owns UDP and
DNS I/O, but all libp2p protocol orchestration beneath it remains input/output
driven. Hosts that own their I/O loop can use SwarmCore directly; the adapter
exposes QUIC deadlines rather than creating an async runtime or timer thread.
Prerequisites:
- Rust stable toolchain
- Cargo
Build and run tests:
cargo testBuild an app endpoint with the top-level facade:
use minip2p::{Deadline, Endpoint, Event};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut node = Endpoint::builder()
.agent_version("my-app/0.1.0")
.bind_quic_dual_stack()?;
let addrs = node.listen_all()?;
println!("listening on {addrs:?}");
// `next_event` also accepts an `Instant` or a relative `Duration`
// when a wait needs a timeout.
while let Some(event) = node.next_event(Deadline::NEVER)? {
println!("{event:?}");
if matches!(event, Event::ConnectionEstablished { .. }) {
// The endpoint remains entirely caller-driven: keep polling,
// or integrate `poll()` and transport deadlines in your host loop.
}
}
Ok(())
}Endpoint is the batteries-included synchronous facade. Custom runtimes can
drive SwarmCore and the protocol crates directly with inputs, outputs, and
explicit timestamps; none of those layers performs I/O, reads a clock, blocks,
or requires an async executor.
Common contributor workflows are also available through just:
just test
just check-nostd
just bench
just fuzz 30Check no_std builds for the core crates:
cargo check --no-default-features -p minip2p-core -p minip2p-identity \
-p minip2p-transport -p minip2p-tls -p minip2p-identify \
-p minip2p-multistream-select -p minip2p-ping -p minip2p-relay \
-p minip2p-autonat -p minip2p-dcutr -p minip2p-swarm -p minip2p-natEvery crate has a README and rustdoc on all public APIs. Internal methods and types are commented for contributor onboarding.
Generate the full API docs with:
cargo doc --workspace --no-deps --open- Local QUIC connectivity and integration coverage.
- Multistream-select with spec-compliant varint framing.
- Ping protocol with RTT measurement and timeout handling.
- End-to-end protocol stack tests (QUIC + multistream + ping).
- Rustdoc and internal comments across all crates.
- Transport contract hardening with documented guarantees and conformance tests.
- libp2p TLS peer authentication (automatic PeerId verification after handshake on the dialer side).
- Identify protocol (
/ipfs/id/1.0.0). - Swarm / connection management layer with builder DX.
- Top-level
minip2p::Endpointfacade for app authors. - Circuit Relay v2 client state machines.
- DCUtR hole-punching state machines.
- Runnable hole-punch CLI against a real relay.
- Mutual TLS on the QUIC transport so the listener learns the client PeerId at handshake time.
- Additional transport adapters (TCP, WebSocket, WebRTC).
See plan.md for the detailed execution plan and longer-term milestones.