agentbbs/docs/qrypt-invites.md
Anthony Ettinger 97da723c5c feat(qrypt): qrypt.chat anonymous-invite issuer
AgentBBS becomes a trusted Ed25519 issuer for qrypt.chat anonymous
accounts. Members mint a signed, single-use qci1 token (per the shared
invite contract) that qrypt.chat verifies and redeems.

- internal/qryptinvite: Mint / GenerateIssuerKey / ParsePrivateKey +
  Config (AGENTBBS_QRYPT_* env) with unit tests (independent verify,
  payload assertions, jti uniqueness, tamper rejection, seed/full key).
- store: qrypt_invites table + QryptInviteCount / RecordQryptInvite
  (per-member quota, enforced in a tx; ErrQuotaExceeded) + test.
- plugins/qryptinvite: hub plugin (members only) — checks quota, mints,
  records, prints token + redeem URL.
- cmd/agentbbs: `qrypt-invite <user>` and `qrypt-issuer-keygen`
  subcommands wired into dispatch.
- setup.sh env template + docs/qrypt-invites.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 11:35:05 +00:00

3.6 KiB

qrypt.chat anonymous invites

AgentBBS is a trusted issuer for qrypt.chat anonymous accounts. A verified AgentBBS member can mint a signed, single-use invite token; the separate qrypt.chat app verifies the signature and redeems the token into an anonymous account (no phone number). AgentBBS holds the private key and signs; qrypt.chat only ever sees the public key.

This is additive and isolated: it touches nothing in the join/SMS/pod paths.

The bridge

member  --ssh-->  AgentBBS (issuer, has Ed25519 priv key)  --signed token-->  qrypt.chat (verifier, has pub key)
  • AgentBBS mints qci1.<payload>.<sig> tokens with crypto/ed25519.
  • qrypt.chat looks up the issuer's public key by payload.iss in its invite_issuers table, verifies the signature, checks expiry, and burns the jti (single use) on redeem.

Token format (v1)

A single fixed algorithm (Ed25519) — not a JWT, no alg field.

token         = "qci1." + b64url(payloadJSON) + "." + b64url(sig)
signing input = "qci1." + b64url(payloadJSON)        # the first two segments
sig           = Ed25519.Sign(issuerPriv, []byte(signingInput))
b64url        = base64 URL-encoding, NO padding

payloadJSON:

{ "jti": "16-random-bytes-hex", "iss": "agentbbs", "tier": "anonymous",
  "iat": 1700000000, "exp": 1700604800, "uses": 1 }

Implemented in internal/qryptinvite. Verifier rules (qrypt.chat side): exactly 3 segments; segment[0] == "qci1"; known/enabled issuer; valid signature; now <= exp; jti not already redeemed.

Setup (operator, once)

  1. Generate a keypair on the AgentBBS host:

    agentbbs qrypt-issuer-keygen
    

    It prints a private seed (base64) and a public key (base64).

  2. Set the private seed in agentbbs.env (see setup.sh) and restart:

    AGENTBBS_QRYPT_ISSUER_KEY=<base64 seed>
    

    Until this is set, minting is disabled (the plugin says "not configured").

  3. Register the public key in qrypt.chat (service-role / SQL):

    INSERT INTO invite_issuers (id, name, ed25519_public_key)
      VALUES ('agentbbs', 'AgentBBS', '<base64 public key>');
    

    The id must match AGENTBBS_QRYPT_ISSUER_ID (default agentbbs).

Config (env)

Var Default Meaning
AGENTBBS_QRYPT_ISSUER_ID agentbbs issuer id; must match qrypt's invite_issuers.id
AGENTBBS_QRYPT_ISSUER_KEY unset base64 Ed25519 seed (32B) or full key (64B); required to mint
AGENTBBS_QRYPT_INVITE_TTL 168h token lifetime (Go duration)
AGENTBBS_QRYPT_REDEEM_URL https://qrypt.chat/anon?invite= redeem URL; the token is appended
AGENTBBS_QRYPT_INVITE_QUOTA 5 per-member cap (0 = unlimited)

Member usage (over SSH)

From the hub, pick "qrypt.chat invite":

ssh <name>@bbs.profullstack.com    # hub → "qrypt.chat invite"

It checks the member's quota, mints a single-use token, records it (incrementing the quota), and prints the redeem URL plus the raw token. The member opens the URL on qrypt.chat to create their anonymous account.

Ops usage (CLI)

agentbbs qrypt-invite <username>      # mint on behalf of a member (respects quota)
agentbbs qrypt-issuer-keygen         # print a fresh seed + public key (first-time setup)

Quota storage

Per-member issuance is counted in the AgentBBS SQLite qrypt_invites table (jti PRIMARY KEY, username, created_at). RecordQryptInvite enforces the cap inside a transaction, so concurrent mints can't both exceed it. The stored jti values are also an audit trail of what AgentBBS handed out.