Skip to content

GenericJam/peer_net

Repository files navigation

PeerNet

Default-deny peer-to-peer messaging for Elixir.

PeerNet gives you BEAM-distribution-shaped ergonomics — expose a named handler on one node, call it from another — between mutually-suspicious peers, with cryptographic identity, walkie-talkie delivery semantics, and no servers.

It is not a reimplementation of Erlang distribution. It coexists with disterl on the same node, on different ports, with a fundamentally different trust model: every handle is closed by default, and peers are addressed by their public key rather than their (host, port).

Status

Pre-release. All core layers are landed and tested:

  • Identity, Trust, Handlers, Frame
  • Noise XX handshake + ChaCha20-Poly1305 AEAD transport
  • Acceptor, Connection, per-instance supervision
  • Registry with auto-connect on discovery + auto-reconnect on disconnect
  • Discovery: behaviour + Manual + UDP reference impls
  • Liveness (app-level heartbeat)
  • BeamDist (asymmetric-trust RPC convenience)

See PLAN.md for the milestone history, CHANGELOG.md for the change log, and guides/protocol.md for the wire format specification. guides/cookbook.md has working patterns for common use cases.

Remaining before a Hex 0.1.0 release: documentation polish + a NetworkMonitor for IP-change events on mobile (the desktop / Nerves use cases work without it).

Why

The Erlang/Elixir ecosystem already has good answers to most communication problems:

  • BEAM distribution — perfect ergonomics, but trust is one shared cookie per cluster. Anyone with the cookie has a remote shell on every member. Fine for trusted infrastructure; unsafe between mutually-suspicious peers.
  • Phoenix Channels / WebSockets — safe, well-trodden, but client-server. Needs servers running somewhere; introduces a relay you must trust.
  • WebRTC, libp2p, Iroh — peer-to-peer, but heavyweight, not BEAM-native, and designed primarily for streaming or distributed-storage workloads.

PeerNet fills the gap: BEAM-native, safe by default, peer-to-peer.

Quick start

# In your supervision tree:
children = [
  {PeerNet, [data_dir: "/var/lib/myapp/peer_net"]}
]

# On every node — the "server" facet. Default-deny, opt-in expose.
PeerNet.expose(:chat, fn _from, %{text: text} ->
  IO.puts("got: #{text}")
  :ok
end)

# Discover peers on the local network:
PeerNet.list_peers()
#=> [%{pubkey: <<...>>, status: :online, last_seen: ~U[2026-...]}]

# Pair with a peer (out-of-band, e.g. via QR):
PeerNet.pair(peer_pubkey)

# Send a message — addressed by pubkey, not IP:
PeerNet.send(peer_pubkey, :chat, %{text: "hi"})

Threat model

PeerNet defends against:

  • Passive eavesdropping — Noise XX with ChaCha20-Poly1305 AEAD.
  • Active MITM — handshake verifies the peer's static key against the trust list before completing.
  • Replay — Noise nonces.
  • Atom-exhaustion attacks — incoming wire is decoded with :safe ETF.
  • Unsolicited execution — every handle is closed by default; nothing is reachable until explicitly expose'd.
  • Impersonation — Ed25519 signatures bind every message to its sender's pubkey.

PeerNet does not defend against:

  • Side-channel timing attacks.
  • Traffic analysis (a passive observer can see who talks to whom and when).
  • Trusted peers misbehaving — once you've added a pubkey to the trust list, you've vouched for it.
  • OS-level compromise of either endpoint.

BEAM distribution compatibility

PeerNet runs alongside regular Erlang distribution on the same node — they use different ports and don't conflict.

For the case where you want full BEAM-dist semantics with a specific trusted peer (a phone controlling a Nerves device, say), the opt-in PeerNet.BeamDist module gives you :rpc.call-equivalent calls between two peers who have explicitly granted each other access:

# On the Nerves device — explicit grant per peer:
PeerNet.expose(:beam_admin, &PeerNet.BeamDist.handle/2,
  authorize: fn pubkey -> pubkey == @phone_pubkey end)

# On the phone — RPC-style sugar:
PeerNet.BeamDist.call(nerves_pubkey, MyMod, :restart_wifi, [])

Asymmetric: only the granted peer can make calls. The Nerves device cannot reciprocally call the phone unless the phone separately grants.

Installation

When published:

def deps do
  [
    {:peer_net, "~> 0.1.0"}
  ]
end

License

Apache-2.0. See LICENSE.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages