enlace
Encrypted mailbox and latest-value slot fan-out for Rust applications.
enlace can send the same encrypted payload over multiple transports and accept
the first valid delivery. It currently includes HTTP relay, DHT, pkarr, and iroh
transports.
Crates
enlace: library API
enlace-agent: local daemon for non-Rust clients
enlace-relay: HTTP relay server
enlace-testkit: in-memory, lossy, and delayed test transports
Features
Default features enable all built-in transports. For smaller builds, disable
defaults and select only what you use:
enlace = { version = "0.1", default-features = false, features = ["http"] }
enlace = { version = "0.1", default-features = false, features = ["dht"] }
enlace = { version = "0.1", default-features = false, features = ["pkarr"] }
enlace = { version = "0.1", default-features = false, features = ["iroh"] }
enlace = { version = "0.1", default-features = false, features = ["http", "iroh"] }
Modes
- Shared-seed mode uses
Namespace::open(&seed, config). Use it when every peer
can share one 32-byte secret and should have equal access to the same mailbox
and slot names.
- Public-key mode uses
PeerNamespace::open(identity, config). Use it when peers
pair by exchanging public PeerCard values and need per-peer trust, revocation,
pairwise messages, or caller-managed group keys.
HTTP and iroh support mailboxes. HTTP, DHT, pkarr, and iroh support slots. DHT and
pkarr are latest-record slot transports; they do not support mailbox delivery.
Shared-Seed Example
use std::sync::Arc;
use enlace::{Config, ConfiguredTransport, Namespace, TransportKind};
use enlace_testkit::InMemoryTransport;
const SEED: [u8; 32] = [7; 32];
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let transport = InMemoryTransport::new();
let config = |transport: InMemoryTransport| Config {
transports: vec![ConfiguredTransport::new(
TransportKind::Http,
Arc::new(transport),
)],
..Config::default()
};
let sender = Namespace::open(&SEED, config(transport.clone())).await?;
let receiver = Namespace::open(&SEED, config(transport)).await?;
sender.mailbox("chat")?.send(b"hello").await?;
let message = receiver.mailbox("chat")?.recv().await?;
assert_eq!(message.payload, b"hello");
receiver.slot("status")?.put(b"online").await?;
let status = sender.slot("status")?.get().await?.expect("slot value");
assert_eq!(status.payload, b"online");
Ok(())
}
Public-Key Example
use std::sync::Arc;
use enlace::{
ConfiguredTransport, PeerConfig, PeerIdentity, PeerNamespace, TransportKind, TrustedPeer,
};
use enlace_testkit::InMemoryTransport;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let transport = InMemoryTransport::new();
let sender_identity = PeerIdentity::generate();
let receiver_identity = PeerIdentity::generate();
let sender_card = sender_identity.card();
let receiver_card = receiver_identity.card();
let sender = PeerNamespace::open(
sender_identity,
peer_config(
transport.clone(),
vec![TrustedPeer::try_from_card(receiver_card.clone())?],
),
)
.await?;
let receiver = PeerNamespace::open(
receiver_identity,
peer_config(transport, vec![TrustedPeer::try_from_card(sender_card)?]),
)
.await?;
sender
.mailbox("chat")?
.send_to_peers(std::slice::from_ref(&receiver_card), b"hello")
.await?;
let message = receiver.mailbox("chat")?.recv().await?;
assert_eq!(message.sender, sender.peer_id());
assert_eq!(message.payload, b"hello");
Ok(())
}
fn peer_config(transport: InMemoryTransport, trusted_peers: Vec<TrustedPeer>) -> PeerConfig {
PeerConfig {
trusted_peers,
transports: vec![ConfiguredTransport::new(
TransportKind::Http,
Arc::new(transport),
)],
..PeerConfig::default()
}
}
Agent Daemon
enlace-agent exposes the public-key peer API to local clients over WebSocket
and Unix socket JSON.
Build a release binary:
cargo build -p enlace-agent --release --all-features --locked
Example host A config at /etc/enlace/agent.toml:
seed_file = "/etc/enlace/seed"
token_file = "/etc/enlace/token"
listen_ws = "127.0.0.1:3000"
listen_unix = "/run/enlace/agent.sock"
data_dir = "/var/lib/enlace-agent"
transports = ["http"]
relay = "https://relay.example.com"
Host B uses its own seed and a different local port:
seed_file = "/etc/enlace/seed"
token_file = "/etc/enlace/token"
listen_ws = "127.0.0.1:3001"
data_dir = "/var/lib/enlace-agent"
transports = ["http"]
relay = "https://relay.example.com"
Start each host:
enlace-agent --config /etc/enlace/agent.toml
Pair hosts by exporting each local card and adding it on the other host:
{"id":"card","type":"export_card"}
{"id":"add","type":"add_peer","card":"<peer-card-from-other-host>"}
Client messages stay opaque base64 payloads:
{"id":"sub","type":"subscribe","channel":"default"}
{"id":"send","type":"send","to":"<peer-id>","channel":"default","payload":"aGVsbG8="}
The systemd unit template is in packaging/systemd/enlace-agent.service.
Relay
Run the HTTP relay locally:
cargo run -p enlace-relay -- --listen 127.0.0.1:7777
With basic auth:
ENLACE_RELAY_AUTH=user:pass cargo run -p enlace-relay -- --listen 127.0.0.1:7777
Verification
cargo test --workspace --locked
cargo check -p enlace --no-default-features --locked
cargo check -p enlace --no-default-features --features http --locked
cargo run -p enlace-testkit --example two_peers --locked
License
MIT