Alpha Preview — This software is under active development. Expect rough edges and breaking changes.

Documentation

What a station and a realm are in macula 12, and what you do with each.

The mesh in one paragraph

A client is your program, using the macula library. It connects to one or more stations and through them calls procedures, publishes and subscribes, and stores and finds signed records. A station is infrastructure: it carries traffic and holds records, and it has no idea what any organisation is. A realm is an authority: it decides which organisations and members exist, and signs the records that say so. A realm is itself a client of stations. It publishes what it signs through them and does not run the mesh.

  • client, station and realm each have their own node identity, a key stored once per node
  • a client names each station it dials by address and by the node_id that station must prove
  • a client that works with a realm's organisations pins that realm's public key when it connects

Stations

What a station does

  • accepts QUIC connections from clients and from other stations, on one UDP port (4433 in the published image)
  • routes a CALL to the station its provider is connected to, and delivers publications to subscribers across stations
  • holds a share of the mesh's DHT: signed records, each verified before it is stored and dropped when it expires
  • announces itself with a signed record of its own, so clients and other stations can find it

Records live in memory. A restarted station starts with an empty store and the mesh fills it again as records are republished.

Identity and certificate

A station's identity is a node key in its data directory. It is generated on first start, and its node_id is what clients and other stations pin. The station mints its own TLS certificate from a separate key, self-signed with ML-DSA-87, and renews it itself. There is no certificate authority, no ACME client and no certificate file to provide.

Running one

  • the image is ghcr.io/macula-io/macula-station, run with Docker or Podman
  • it reads one JSON config file. data_dir, bind and port are required; outbound_peers lists the stations it dials, each with host, port and expected_node_id
  • a peer without expected_node_id is never dialled. A station proves who it is; it is never trusted by name
  • puzzle_enforcement is off, log_only or enforce: whether a connecting node must hold a node_id that took work to generate. It defaults to off
  • certfile and keyfile are refused: the station makes its own certificate

A user of the mesh runs no station. You need one station's address and node_id to dial, and the mesh does the rest.

Realms

What a realm does

  • it holds a realm signing key, and that key is the root of trust for everything below
  • it signs the realm's org directory, which names each organisation's key. An organisation's key in turn signs a procedure delegation naming the nodes allowed to serve its procedures
  • it admits members and issues them capability tokens (UCANs) signed with the realm key and bound to the member's node_id
  • a node that wants to serve procedures for an organisation claims it, and a human administrator admits it. A claim is never admitted automatically

How it relates to stations

A realm connects to stations as a client does, to seed stations it is configured with, each named by address and node_id. It does not discover stations on its own. What it signs reaches the mesh through those stations, and a realm with no station link publishes nothing until one comes back.

What you do with one

  • calling an organisation's procedures: pin the realm's public key when you connect (see below). For the io.macula realm, take it from the realm's key document and check it as shown next
  • serving procedures for an organisation: run your service under a stored node identity, claim the organisation through the realm, and wait for an administrator to admit the claim
  • running a realm: macula.io runs one. Talk to us before planning your own

Membership is not required to reach the mesh. Stations do not check realm membership when a client connects.

The io.macula realm's key

The realm publishes its public key at realm.macula.io/.well-known/macula-realm.json . This page is a second, independent place for the values it must match. Do not pin a key that does not match them.

realm
io.macula
realm_id
abb81b5a614b63551b400b810648c0c8a78efad845442630c94b46cc95d2fcd1
profile
pq_hybrid
key_id
a50fef96e52b4d68ad112ff42b55dc916cd70e5b283280e8cf3cc5e492459cf6

To check the document: base64-decode public_key, compute its key id with macula_node_keys:key_id(RealmKey, pq_hybrid), and compare it, hex-encoded, with the key_id above. Then pin it:

RealmKey = base64:decode(PublicKeyFromDocument),
<<"a50fef96e52b4d68ad112ff42b55dc916cd70e5b283280e8cf3cc5e492459cf6">> =
    binary:encode_hex(macula_node_keys:key_id(RealmKey, pq_hybrid), lowercase),
RealmId = binary:decode_hex(<<"abb81b5a614b63551b400b810648c0c8a78efad845442630c94b46cc95d2fcd1">>),
{ok, Pool} = macula:connect(Seeds, #{realm_trust => #{RealmId => RealmKey}}).

Connecting from code

The macula library, version 12, is the client. It is an Erlang/OTP library on hex and works from Elixir as well.

{macula, "~> 12.0"}
  • macula:connect(Seeds, Opts) starts a pool. Every seed carries expected_node_id; a pool will not start without one per seed
  • realm_trust => #{RealmId => RealmKey} pins realm keys, and connect is the only place it goes: on a call it is refused. Without the key for a realm, no advertisement of that realm's organisations is trusted, and a call returns {error, {unresolved, no_trusted_advertisement}}
  • there is one way to trust a station: its handshake, under the node_id you gave. verify in any value and pin_tls_cert => true are refused
  • a node has one identity per program and user account, stored and reused by every pool on that node, at <identity_dir>/<identity_name>.<profile>.key. identity_dir is the application setting and identity_name is given per connect (macula 14; the old node_identity_path setting is refused). A 13-era identity.key is moved to default.<profile>.key on first connect

Cryptography

  • node identities, records, tokens and handshakes are signed with ML-DSA-87
  • a node runs one of two profiles: pq_pure, ML-DSA-87 alone, or pq_hybrid, where every signature outside TLS also carries an RSA-PSS-4096 half and is valid only if both verify
  • every node offers only post-quantum hybrid key exchange groups, SecP384r1MLKEM1024 then SecP256r1MLKEM768, with no classical fallback