Python port of the Macula mesh wire protocol
A native Python implementation of the client half of Macula's wire
protocol -- the same protocol macula-io/macula
(the Erlang/OTP SDK) speaks, and the same protocol
macula-go and
macula-rust already port.
Macula is a federated mesh for sovereign, end-to-end-encrypted
application networks; a station is the relay/DHT node, and this
package is what a leaf -- anything that isn't itself a station --
uses to join it.
Also lives as a runnable example -- python examples/quickstart.py:
import asyncio
import uuid
from macula_py import frame
from macula_py.connection import Session
from macula_py.identity import KeyPair
STATION_HOST = "station-de-frankfurt.macula.io"
STATION_PORT = 4433
REALM = bytes(32)
PROCEDURE = f"macula_py.quickstart_echo.{uuid.uuid4().hex}"
async def main() -> None:
# Puzzle-hardened identity -- required. An unhardened identity fails
# the handshake silently (QUIC/TLS looks healthy, HELLO never accepts).
provider_identity = KeyPair.generate()
caller_identity = KeyPair.generate()
async with await Session.connect(STATION_HOST, STATION_PORT, provider_identity) as provider:
await provider.advertise(REALM, PROCEDURE)
await asyncio.sleep(0.5) # ADVERTISE is fire-and-forget; give it a moment to land
async def echo(payload):
return payload
serve_task = asyncio.create_task(provider.serve_one_call(lambda realm, proc: echo, timeout=10))
async with await Session.connect(STATION_HOST, STATION_PORT, caller_identity) as caller:
deadline_ms = frame.current_millis() + 5_000
response = await caller.call(PROCEDURE, REALM, "hello", deadline_ms, timeout=5)
await serve_task
print(response)
asyncio.run(main())Two identities are used (a provider and a caller) because a station
kicks a connection the instant a second one arrives under the same
identity -- the same reason every one of this SDK's own live tests uses
separate KeyPairs for each role.
Built and verified in the order every sibling SDK was built in:
-
Identity (
macula_py.identity) -- Ed25519 keypairs, S/Kademlia puzzle-hardened generation (matchesmacula_identity.erl's own default: puzzle-hardened by default, no unhardened shortcut exposed), sign/verify, atomic key-file persistence in the exact wire formatmacula_identity:save/2uses. -
Deterministic CBOR (
macula_py.cbor) -- a hand-rolled codec matchingmacula_record_cbor.erlexactly, NOT a generic CBOR library (this wire's rules diverge from RFC 8949's own canonical form: floats are always full binary64 never the shorter widths, map keys sort by their own encoded bytes, and there is no boolean simple value on this wire at all -- every SDK's convention is 1/0). Verified byte-for-byte against the real Erlang encoder itself (seetests/test_cbor_golden_vectors.py), not just self-consistency. -
BLAKE3 (
macula_py.blake3_hash) -- content-addressing, backed by the same Rustblake3cratemacula_crypto_nifuses (not Erlang's own pure fallback, which its own source documents as NOT cryptographically real BLAKE3). Cross-verified against the real NIF's output. -
The frame envelope (
macula_py.frame) -- Ed25519-signed frame construction/verification and the length-prefixed wire codec, matchingmacula_frame.erlexactly, including its frame-level boolean-as-text-string convention (distinct frommacula_py.cbor's own payload-level 1/0 convention -- these are two different rules for two different layers, confirmed by reading the Erlang source directly). A signed CONNECT frame built entirely by this module was independently decoded and signature-verified by the real, unmodified Erlangmacula_framemodule -- genuine cross-language wire and cryptographic compatibility, not just self-consistency. -
QUIC transport + CONNECT/HELLO handshake (
macula_py.connection) -- built onaioquic. Live-verified against the real production station fleet (station-de-frankfurt.macula.io): a real handshake completes, the HELLO's signature verifies,acceptedistrue. -
Unary RPC, both roles (
Session.call/Session.advertise/Session.serve_one_call) -- BOLT#4 error taxonomy (macula_py.bolt4). Live-verified to the standard this org's own SDK work holds real proof to: not just "reached the call stage with a cleanunknown_next_peer" (which only proves the caller's own path works), but a genuine advertise+serve+call round trip returning an actual RESULT payload from an actual running handler, plus a handler that raises correctly reportingunknown_errorwith detail. -
PubSub, both roles (
Session.publish/Session.subscribe/Session.unsubscribe/Session.recv_event). Live-verified: a real publish/subscribe round trip within one session, a real cross-session delivery (separate identities, separate connections), and unsubscribe actually stopping delivery. Real finding, root-caused: SUBSCRIBE needs a moment to register at the station before a PUBLISH sent immediately afterward is reliably delivered -- same shape as RPC's own ADVERTISE-needs-a-moment finding, documented intests/test_pubsub_live.py. -
Content transfer (
macula_py.manifest,macula_py.content) -- fixed-size chunking, Merkle-root computation, and MCID derivation matchingmacula_manifest.erlexactly (cross-verified byte-for-byte against the real Erlang implementation for a 3-chunk input, including the odd-chunk-paired-with-itself fold case), plusput/getover the_content.*RPCs on a dedicated QUIC stream. Live-verified: single block and chunked (multi-block) put/get round trips, not_found handling, and cross-session put/get all pass against the real fleet. Found and fixed a genuine bug along the way in macula-station itself (not this SDK, and not SDK-specific -- it affected every client):_content.put_manifestcrashed with a generictemporary_relay_failurefor any manifest whosenamewasn't already an interned Erlang atom, i.e. any real content name. Root-caused via a standalone reproduction against the real unmodified station code, fixed at the source (macula-io/macula-station), and confirmed live against the production fleet before calling this piece done. -
Streaming RPC, both caller and provider roles (
Session.open_stream/Session.accept_stream,macula_py.connection.StreamHandle) -- STREAM_OPEN/DATA/END/ERROR/REPLY over their own dedicated QUIC stream (like content transfer, not the control stream), matchingmacula_frame.erl's constructors andmacula_station_link.erl's own open/dispatch sequencing exactly, including thesigner/responded_byfields non-OPEN stream frames carry for cross-hop authentication. Cross-verified in both directions against real Erlang-signed frames (this module decodes and signature-verifies frames built by the actualmacula_frame:stream_*/1+sign/2; frames this module builds decode and verify cleanly against the real Erlangmacula_frame:decode/1too) -- seetests/test_frame_streaming.py. A stream procedure is advertised exactly like a unary one (Session.advertise): the wire's ADVERTISE frame doesn't distinguish them, and what disambiguates on the receiving end is that STREAM_OPEN always arrives on a fresh dedicated stream while CALL always arrives on the control stream. Live-verified: realserver_streamANDclient_streamround trips against the production fleet -- provider-pushes-chunks and caller-pushes-chunks both confirmed working end to end, including the terminal STREAM_REPLY reaching the other side in both directions.client_stream/bidi's round trip wasxfailuntil 2026-09-05: macula-station's own stream-route lifecycle (macula_station_peer_observer.erl) used to drop the entire bidirectional route on the first terminal frame it saw for a stream_id rather than deciding per the session's actual mode, so aclient_streamcaller's own half-close (STREAM_END(role=send)) tore the route down before the provider's reply could be relayed back. Found via this SDK's own live testing, fixed at the source (mode-aware half-close semantics,macula-io/macula-stationcommit 07db0d8, verified against real production traffic patterns before shipping since a naive fix would have broken workingserver_streamproviders on the fleet), and confirmed live here.
Not yet built: direct-dial, periodic re-advertise, UCAN, cert-chain verification, and the supervised pubsub wrapper are explicitly OUT of scope for this first pass, matching the order every other Macula SDK was built and reviewed in. RPC telemetry facts are likewise deferred.
python3 -m venv .venv
.venv/bin/pip install -e ".[dev]"
.venv/bin/pytest # offline tests only
.venv/bin/pytest -m live # + live-fleet tests (dials the real production fleet)This repo pins Python 3.13 via .tool-versions -- aioquic's C
extension dependencies do not currently build against free-threaded
Python 3.14 builds (confirmed: pylsqpack's limited-API usage hits
missing internal refcounting symbols under 3.14t). 3.13 and non-free-
threaded 3.14 both build cleanly; 3.13 is pinned for reproducibility.
Apache-2.0. See LICENSE.
Built with the BEAM's protocol, ported to Python -- sponsor the work if this saved you some time