Skip to content

XIP-82: External commit invites#138

Open
tylerhawkes wants to merge 1 commit into
mainfrom
tyler/xip-82-external-commit-invites
Open

XIP-82: External commit invites#138
tylerhawkes wants to merge 1 commit into
mainfrom
tyler/xip-82-external-commit-invites

Conversation

@tylerhawkes
Copy link
Copy Markdown
Contributor

@tylerhawkes tylerhawkes commented May 22, 2026

Summary

Introduces External Commit Invites: a shareable invite token (QR / link / NFC) that lets a non-member join an XMTP group on their own via an atomic MLS External Commit (RFC 9420 §12.4.3.2), without any existing member being online at scan time.

The mechanism is a two-piece design:

  • ExternalInvitePayload — small shareable capability (key + service slot id + opaque service pointer). What's encoded in the QR.
  • EncryptedGroupInfoBlob — large encrypted MLS state held by an out-of-band service. Carries plaintext epoch + group_state_hash so the service can totally-order uploads by epoch and reject equal-epoch forks without ever seeing decrypted GroupInfo.

Admin control lives in a new well-known AppData component:

  • EXTERNAL_COMMIT_POLICY — runtime-toggleable master switch plus the active invite's symmetric_key and external_group_id. Storing the key in group state is what lets a printed QR survive epochs — any just-joined member can re-export and re-upload under the same key without the issuing admin being online.

Per-component declarative authorization for external committers lives in a new field on ComponentMetadata:

  • ComponentMetadata.external_committer_permissions — symmetric twin of the existing permissions field, evaluated against external commits. Absent = all-Deny.

Lifecycle invariants spelled out in the spec: enable must atomically populate the invite coordinates, revoke must atomically clear them, re-enable must use fresh material (no key revival).

Discussion

I've raised the design with the team in #review-napkin over the past week. Key decisions captured in the Rationale and "Alternatives considered" sections:

  • Atomic external commit shape required by libxmtp's tree-vs-AppData cross-layer invariants — no fast-follow workaround.
  • Two-layer authorization (master switch + per-component permissions) — defense in depth against a misconfigured component block.
  • Service slot identified by an application-defined external_group_id (≥4 bytes, 16 random RECOMMENDED) — not a hash of the group_id. AEAD already prevents the swap-ciphertext attack.
  • Application owns service transport; protocol does not constrain to a single service (apps MAY mirror across multiple).

Reference implementation

libxmtp PR stack (open):

  • #3664, #3665 — generic AppDataUpdate intent + proto bump
  • #3666, #3667, #3668 — EXTERNAL_COMMIT_POLICY component + validator + ingestion routing
  • #3669–#3671 — payload_encryption + invite::payload + invite::encrypted_group_info helpers
  • #3672 — openmls fork bump for by-value external-commit proposals
  • #3673, #3674 — MlsGroup::create_external_invite + Client::join_group_by_external_invite
  • #3675, #3676 — NAPI bindings
  • #3677 — end-to-end integration test

Proto: xmtp/proto#333 (generic intent), xmtp/proto#334 (this XIP's wire format).

openmls: upstream PR in flight; XMTP fork carries the patch.

Test plan

  • Spell-check clean (cspell against the repo dictionary).
  • Review of wire format + threat model by @xmtp/engineering.
  • Discussion link added once community thread is up.

🤖 Generated with Claude Code

Note

Add XIP-82 spec for External Commit Invites via QR/link-based MLS flow

Adds xip-82-external-commit-invites.md, a new XMTP Improvement Proposal defining a QR/link-based MLS External Commit invite flow.

  • Specifies wire formats for ExternalInvitePayload and EncryptedGroupInfoBlob, using HPKE for encryption
  • Introduces a new EXTERNAL_COMMIT_POLICY AppData component with field-coupling invariants and per-component external_committer_permissions
  • Defines atomic external commit shape and validation rules, plus a service contract for hosting encrypted GroupInfo blobs
  • Covers join/receive flows, rotation/revocation semantics, backward compatibility, and security considerations
  • Adds HPKE, NAPI, and openmls to the spell-check dictionary in cspell.json
📊 Macroscope summarized 1ac135e. 2 files reviewed, 1 issue evaluated, 0 issues filtered, 1 comment posted

🗂️ Filtered Issues

Shareable group invites enabling QR-code or link-based joins via atomic
MLS External Commits with admin-controlled policy.

Also adds HPKE, NAPI, and openmls to the cspell dictionary.
@tylerhawkes tylerhawkes requested a review from a team as a code owner May 22, 2026 00:15

### Join-side flow

A non-member with an `ExternalInvitePayload` and an `EncryptedGroupInfoBlob` performs the following:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High XIPs/xip-82-external-commit-invites.md:389

Step 5 in the Join-side flow says to verify EXTERNAL_COMMIT_POLICY.external_group_id == payload.external_group_id "after the commit lands," but the commit is not built until step 7 or published until step 8. An implementer following these steps sequentially would attempt to read group state before joining. Step 5 should be moved after step 8 (and before step 9) when the joiner actually has group state to read.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file XIPs/xip-82-external-commit-invites.md around line 389:

Step 5 in the Join-side flow says to verify `EXTERNAL_COMMIT_POLICY.external_group_id == payload.external_group_id` "after the commit lands," but the commit is not built until step 7 or published until step 8. An implementer following these steps sequentially would attempt to read group state before joining. Step 5 should be moved after step 8 (and before step 9) when the joiner actually has group state to read.

@macroscopeapp
Copy link
Copy Markdown

macroscopeapp Bot commented May 22, 2026

Approvability

Verdict: Needs human review

1 blocking correctness issue found. This PR adds a new XIP specification document to a directory owned by @jhaaaa, and the author is not the designated owner. New protocol specifications should be reviewed by the designated CODEOWNER. There is also an unresolved comment about step sequencing in the specification that needs attention.

You can customize Macroscope's approvability policy. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant