Description
Changelog
Reviews (0)

Godot Freelay

Serverless multiplayer for Godot 4 on top of plain MQTT brokers, implementing the Freelay protocol (see freelay-spec.md): signed public channels, end-to-end encrypted private sessions, presence with tamper resistance, and an optional WebRTC upgrade for direct peer connections.

Dependencies

Addon Version Provides
mqtt-node ≥ 0.2.0 MqttNode (WebSocket MQTT 3.1.1, LWT, binary payloads, auto-reconnect)
gd-ed25519 ≥ 1.0.0 Ed25519, Ed25519Keypair, X25519, X25519Keypair, Monocypher
webrtc-native (optional) - WebRTCPeerConnection on desktop/mobile; built into web exports

Copy addons/mqtt-node, addons/ed25519 and addons/freelay into your project. Without webrtc-native everything works relay-only; upgrade_to_rtc() then logs an error and the session stays on MQTT.

Quick start

See example.gd. In short:

var client := RelayClient.new()
client.app_id = "my-cool-game"
client.broker_urls = ["wss://broker.hivemq.com:8884/mqtt"]
add_child(client)
client.open()
await client.opened

var lobby := client.join("lobby")           # public, signed, plaintext
await lobby.joined
lobby.send({"hello": "world"})

var conn := client.connect_peer(peer_id)    # private, E2E encrypted
await conn.ready
conn.send({"secret": 42})
conn.upgrade_to_rtc()                       # optional direct connection

API surface

  • RelayClient (Node) - open(), close(), join(name) -> RelayChannel, connect_peer(peer_id) -> RelayPeerConnection, mute_peer(id). Signals: opened, reconnected, closed(reason), peer_connection_requested(conn), outbound_throttled(count). Config: app_id, broker_urls, profile, auto_accept_peers, outbound_limit, inbound_limit_per_peer (both RelayRateLimit, disabled by default).
  • RelayProfile - generate(), from_seed(seed), save(path, password), load_from(path, password), peer_id, public_key. The seed never leaves the class except via save().
  • RelayChannel - send(data, reliable=true), leave(), peers. Signals: joined, left(reason), message(peer, data), peer_joined(peer), peer_left(peer).
  • RelayPeerConnection - send(data, reliable=true), accept(), reject(), close(), upgrade_to_rtc(ice_servers). Signals: ready, message(data), transport_changed("relay"|"rtc"), closed(reason).
  • RelayPeer - peer_id, public_key, mute()/unmute(), meta.

Internal (protocol-level) classes: Freelay, FreelayCrypto, FreelayEnvelope, FreelayPresenceManager.

Addon API status

All gd-ed25519 calls are isolated in freelay_crypto.gd and were verified against the v1.0.0 doc-classes:

  • Ed25519.sign(message, private_key, public_key) - public key passed empty and derived internally (mismatch would fail fast).
  • Ed25519Keypair.generate() / from_seed() / get_seed() / get_public_key().
  • X25519.generate_keypair() and X25519Keypair.get_private_key() / get_public_key().
  • X25519.shared_secret(own_private, their_public) - returns null on low-order points; treated as fatal handshake failure.
  • Monocypher.aead_encrypt(key, nonce, plaintext, ad) / aead_decrypt(key, nonce, ciphertext_with_tag, ad) - note the key-and-nonce-first parameter order; both return null on failure, which the adapter maps to an empty array (encrypt) or passes through (decrypt).
  • Monocypher.blake2b(data, out_len) - null on out-of-range output length.

Still assumed (not covered by the uploaded doc-classes): MqttNode.close() for clean disconnect (guarded with has_method).

Design notes / limitations

  • No gameplay trust: signatures prove sender identity, not honesty. Anti-cheat (lockstep, host authority, replays) is an application concern.
  • Channel traffic is signed plaintext by design; only peer sessions are encrypted.
  • The profile KDF is iterated BLAKE2b (not memory-hard). Fine against casual attackers; switch to Argon2 if/when the crypto addon exposes it.
  • If the broker refuses the connection with a non-retryable CONNACK code, mqtt-node disables auto-reconnect; RelayClient currently does not surface this as closed - watch MqttNode.disconnected if you need it.
  • bytes_to_var is used without object decoding (safe), so payloads are Godot Variants. For cross-language interop send Dictionary/Array of primitives, or extend send() with a raw-bytes mode.
  • Public brokers can still be flooded topic-wide; inbound limits and muting are local defenses only (spec §11).

Changelog for version 0.0.1

No changelog provided for this version.

Reviews

Freelay has no reviews yet.

Login to write a review.