← All docs

Freeq Protocol Notes

SASL Mechanism: ATPROTO-CHALLENGE

Freeq implements a custom SASL mechanism for authenticating IRC users with
AT Protocol (Bluesky) identities. The mechanism name is ATPROTO-CHALLENGE.

Flow

Client                          Server
  |                               |
  |  CAP REQ :sasl                |
  |------------------------------>|
  |  CAP ACK :sasl                |
  |<------------------------------|
  |  AUTHENTICATE ATPROTO-CHALLENGE
  |------------------------------>|
  |  AUTHENTICATE <base64 challenge>
  |<------------------------------|
  |  AUTHENTICATE <base64 response>
  |------------------------------>|
  |  900 RPL_LOGGEDIN             |
  |  903 RPL_SASLSUCCESS          |
  |<------------------------------|

Challenge Format

The server sends a JSON challenge encoded as base64url:

{
  "session_id": "<unique per TCP connection>",
  "nonce": "<32 bytes, cryptographically random, base64url>",
  "timestamp": <unix epoch seconds>
}

Response Format

The client responds with base64url-encoded JSON:

{
  "did": "did:plc:abc123...",
  "method": "crypto" | "pds-session" | "pds-oauth",
  "signature": "<base64url signature over raw challenge bytes>",
  "pds_url": "https://bsky.social"
}

Verification Methods

  1. crypto — Client signs the raw challenge bytes with a key listed in
    the DID document's authentication or assertionMethod sections.
    Supported curves: secp256k1 (required), ed25519 (recommended).

  2. pds-session — Client provides a Bearer JWT (from an app password
    session). Server calls com.atproto.server.getSession on the claimed
    PDS to verify the token belongs to the claimed DID.

  3. pds-oauth — Client provides a DPoP-bound OAuth access token.
    Server constructs a DPoP proof and calls getSession on the PDS.

Security Properties

  • Nonce uniqueness: Each challenge contains a 32-byte cryptographically
    random nonce. Challenges are single-use (invalidated after verification).
  • Timestamp window: Challenges expire after 60 seconds (configurable
    via --challenge-timeout-secs).
  • No private key transmission: The server never sees private keys.
    All verification uses public keys from DID documents or PDS token validation.
  • DID document resolution: The server resolves did:plc via plc.directory
    and did:web via HTTPS, then extracts verification keys.

Deviations from a Hypothetical IRCv3 Spec

  • JSON encoding: Both challenge and response are JSON (not binary).
    This aids debuggability at the cost of a few extra bytes. A production
    IRCv3 specification would likely use a binary format.
  • Multi-method auth: The mechanism supports three verification methods
    (crypto, pds-session, pds-oauth). A formal spec might split these into
    separate SASL mechanism names.
  • Signature over raw bytes: The signature is over the raw challenge
    bytes (the decoded JSON), not a hash. This is simpler but means the
    signed payload is larger than strictly necessary.

DID-Aware IRC Extensions

Nick Ownership

When a user authenticates, their nick is bound to their DID. This binding:
- Persists across server restarts (stored in SQLite)
- Prevents other users from using the nick
- Unauthenticated users claiming a registered nick are renamed to GuestXXXX
- Propagated across federated servers via CRDT

DID-Based Channel Authority

  • Founder: The first authenticated user to create a channel becomes its
    founder. Founder status is permanent and survives server restarts.
  • DID ops: Channel operators can be granted by DID. DID-based ops
    persist across reconnects and work across federated servers.
  • DID bans: MODE +b did:plc:xyz bans by identity rather than hostmask.
    DID bans survive nick changes.

WHOIS Extensions

Freeq adds custom WHOIS numerics:
- 330 (RPL_WHOISACCOUNT): Shows the authenticated DID
- 671: Shows the resolved AT Protocol handle (e.g. chadfowler.com)
- 672: Shows the iroh P2P endpoint ID (if connected via iroh)


Transport Stack

All transports feed into the same IRC protocol handler. The server is
transport-agnostic — clients can mix transports freely.

Transport Port Notes
TCP 6667 Standard IRC
TLS 6697 Standard IRC over TLS
WebSocket configurable IRC-over-WebSocket at /irc
iroh QUIC auto NAT-traversing, end-to-end encrypted

iroh Transport

The server advertises its iroh endpoint ID in CAP LS:

CAP * LS :sasl message-tags ... iroh=<endpoint-id>

Clients that support iroh can discover the endpoint and upgrade their
connection to QUIC, gaining NAT traversal and relay fallback.

S2S Federation

Servers connect to each other over iroh QUIC links using a JSON-based
protocol. State convergence uses Automerge CRDTs for:
- Channel membership
- Topics
- Nick ownership
- DID-based ops
- Bans

See docs/s2s-audit.md for details on the S2S protocol.


IRCv3 Capabilities

Freeq supports these IRCv3 capabilities:

Capability Notes
sasl ATPROTO-CHALLENGE mechanism
message-tags Full tag routing per client
server-time Timestamps on history replay
batch History wrapped in chathistory batch
multi-prefix All prefix chars in NAMES
echo-message Echo own messages back
account-notify ACCOUNT broadcast on auth
extended-join JOIN includes account + realname
draft/chathistory On-demand CHATHISTORY command

Plugin System

Freeq supports server plugins that hook into events:

Hook Description
on_connect New client connection (before registration)
on_auth SASL authentication complete (can override displayed identity)
on_join User joins a channel
on_message PRIVMSG/NOTICE (can suppress or rewrite)
on_nick_change Nick change

Plugins are compiled into the binary and activated by name via CLI or
TOML config files. See examples/plugins/ for examples.