PeerNet.Trust (PeerNet v0.1.0)

Copy Markdown View Source

Persistent allowlist of peer public keys this node will talk to.

PeerNet's whole security model rests on this list. A peer is "trusted" iff their Ed25519 public key (32 bytes) appears here. Untrusted peers are rejected at handshake time — they cannot complete the Noise XX exchange, so the connection is closed before any application-level message is exchanged.

Pairing flow: out-of-band exchange of public keys (e.g. via QR code), then both sides call add/3 with the other's pubkey.

Pubkey format

All pubkeys are 32-byte binaries. Anything else is rejected with {:error, :invalid_pubkey}. This shape check is the entire input validation — no parsing, no decoding, no Base64. If a caller has a hex or Base64 representation, decode it first.

Persistence

The trust list is written to data_dir/trust.bin after every change. The file format is intentionally simple (a list of %{pubkey, label, added_at} maps serialized via :erlang.term_to_binary/2 with :safe decoding) so it can be inspected and recovered without this module.

Process model

Implemented as a GenServer so multiple callers can read and write concurrently without races. The list is held in memory; persistence is a side-effect of mutations, not the source of truth at runtime.

Examples

iex> uniq = "#{:os.system_time(:nanosecond)}_#{System.unique_integer([:positive])}"
iex> dir = Path.join(System.tmp_dir!(), "peer_net_doctest_#{uniq}")
iex> name = :"trust_doctest_#{uniq}"
iex> {:ok, pid} = PeerNet.Trust.start_link(data_dir: dir, name: name)
iex> PeerNet.Trust.list(pid)
[]
iex> :ok = PeerNet.Trust.add(pid, <<1::256>>, label: "test")
iex> PeerNet.Trust.trusted?(pid, <<1::256>>)
true

Summary

Types

An entry in the trust list.

Functions

Add pubkey to the trust list.

Returns a specification to start this module under a supervisor.

Return all trusted peer entries.

Remove pubkey from the trust list. No-op if it wasn't present.

Start the trust process.

True iff pubkey (a 32-byte binary) is in the trust list.

Types

entry()

@type entry() :: %{
  pubkey: <<_::256>>,
  label: String.t() | nil,
  added_at: DateTime.t()
}

An entry in the trust list.

  • pubkey: the peer's Ed25519 public key (32 bytes).
  • label: optional human-readable name for UI display.
  • added_at: when this peer was first added, for the trust UI.

Functions

add(server \\ __MODULE__, pubkey, opts \\ [])

@spec add(GenServer.server(), binary(), keyword()) :: :ok | {:error, :invalid_pubkey}

Add pubkey to the trust list.

Options:

  • :label — optional human-readable name shown in trust UI.

Returns :ok whether the pubkey was newly added or already present (idempotent). Returns {:error, :invalid_pubkey} if pubkey isn't a 32-byte binary.

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

list(server \\ __MODULE__)

@spec list(GenServer.server()) :: [entry()]

Return all trusted peer entries.

remove(server \\ __MODULE__, pubkey)

@spec remove(GenServer.server(), binary()) :: :ok

Remove pubkey from the trust list. No-op if it wasn't present.

start_link(opts)

@spec start_link(keyword()) :: GenServer.on_start()

Start the trust process.

Options:

  • :data_dir (required) — directory under which trust.bin will be read and written.
  • :name (optional) — process registration name.

trusted?(server \\ __MODULE__, pubkey)

@spec trusted?(GenServer.server(), term()) :: boolean()

True iff pubkey (a 32-byte binary) is in the trust list.

Returns false (not an error) for any non-32-byte input — this lets call sites screen unknown peers without a guard.