From 7788074fb74a996df937f45d00c0fce64f3dd7e9 Mon Sep 17 00:00:00 2001 From: Mark Xue Date: Thu, 24 Sep 2026 01:38:21 -0700 Subject: [PATCH] Describe the principal as a credential-scoped identity A key package half carries its own signing key, which is the key used in the group that half joins; one key per principal and a fresh key per half both conform. Co-Authored-By: Claude Opus 5.5 --- .changeset/book-principal-per-half-keys.md | 4 ++++ book/src/concepts.md | 17 ++++++++++------- book/src/introduction.md | 9 ++++----- book/src/session-lifecycle.md | 3 ++- 4 files changed, 20 insertions(+), 13 deletions(-) create mode 100644 .changeset/book-principal-per-half-keys.md diff --git a/.changeset/book-principal-per-half-keys.md b/.changeset/book-principal-per-half-keys.md new file mode 100644 index 0000000..1965064 --- /dev/null +++ b/.changeset/book-principal-per-half-keys.md @@ -0,0 +1,4 @@ +--- +--- + +Book-only: describe the principal as a credential-scoped identity whose key-package halves carry their own signing keys; nothing ships. diff --git a/book/src/concepts.md b/book/src/concepts.md index d270808..031aae4 100644 --- a/book/src/concepts.md +++ b/book/src/concepts.md @@ -11,13 +11,16 @@ operations for a credential funnel through the one object. TwoMLSPQ deliberately breaks this up into three app-facing objects, each owning exactly the state its job needs: -- **`TwoMlsPqPrincipal`** — the principal: a credential-scoped signing identity. Its job - is minting key packages and invitations and holding their private material only until it - is captured into an invitation (`generate_invitation` purges the principal's own copies). - It is not a hub for group operations. +- **`TwoMlsPqPrincipal`** — the principal: a credential-scoped identity (one MLS Basic + Credential). Its job is minting key packages and invitations and holding their private + material only until it is captured into an invitation (`generate_invitation` purges the + principal's own copies). It is not a hub for group operations. Each half of a key package + it mints carries a signing key, and that key is the one its owner signs with in the group + the half joins (see [Group Rules](./group-rules.md), rule 4). A principal may use one + signing key for every half it mints, or a fresh key per half; both conform. - **`TwoMlsPqInvitation`** — a self-contained receiving capability: one published - combiner key package's private material, the signing identity, and the - consumed-remote replay guard. It turns welcomes into sessions with no live client + combiner key package's private material (each half's HPKE and signing keys), the + credential it was minted under, and the consumed-remote replay guard. It turns welcomes into sessions with no live client and survives restarts through its own archive. TwoMLS manages the key package's lifetime itself rather than via mls-rs's on-the-wire last-resort extension: a *last-resort* invitation retains its key package to accept many welcomes, while a @@ -34,7 +37,7 @@ CommProtocol's `Agent` (this crate is CommProtocol-agnostic): | mls-rs | TwoMLSPQ | role | |---|---|---| -| `Client` | **`TwoMlsPqPrincipal`** | credential-scoped signer; mints invitations & sessions | +| `Client` | **`TwoMlsPqPrincipal`** | credential-scoped identity; mints key packages, invitations & sessions | | `KeyPackage` | **`TwoMlsPqInvitation`** | one published key package's private material | | group | **`TwoMlsPqSession`** | one established pairwise channel | diff --git a/book/src/introduction.md b/book/src/introduction.md index 9e0184b..58c54e7 100644 --- a/book/src/introduction.md +++ b/book/src/introduction.md @@ -39,11 +39,10 @@ It does not otherwise depend on CommProtocol's identities and exposes an MLS cli of basic credentials. The app hands TwoMLSPQ the opaque **`ClientId`** of one of its **agents** — identity bytes. -TwoMLSPQ builds a **`TwoMlsPqPrincipal`** for that ClientId, minting a fresh MLS leaf signing key -internally. The ClientId is carried as the MLS Basic Credential — identity trust comes -from the app layer, not an -external Authentication Service — and the signing key that authenticates the leaf lives -inside this library and never crosses the boundary. Everything above the ClientId is outside +TwoMLSPQ builds a **`TwoMlsPqPrincipal`** for that ClientId, and mints the MLS leaf signing +keys internally. The ClientId is carried as the MLS Basic Credential — identity trust comes +from the app layer, not an external Authentication Service — and the signing keys that +authenticate its leaves live inside this library and never cross the boundary. Everything above the ClientId is outside this library's boundary. ## How a session is built diff --git a/book/src/session-lifecycle.md b/book/src/session-lifecycle.md index 3fdbfc5..b400ea1 100644 --- a/book/src/session-lifecycle.md +++ b/book/src/session-lifecycle.md @@ -223,7 +223,8 @@ under it (Establishment, above). ## Invitations & replayed initial frames A published key package is backed by a self-contained **`TwoMlsPqInvitation`** (the -signing identity plus the key package's private material) rather than a live client; +credential plus the key package's private material, including its signing keys) rather +than a live client; one invitation services many welcomes, deduplicating repeats per remote (`DuplicateWelcome`). `receive(welcome, their_classical_kp, bootstrap_kp_commitment, spawn_token)` takes an opaque, caller-chosen, replay-stable token for the initial frame