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:

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:

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:

PeerNet does not defend against:

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.