wai.live.e2ee: end-to-end encrypted rooms on MoQ secure objects and MLS keying (L10) #345

Merged
dcharlot merged 13 commits from wai/e2ee into main 2026-10-20 03:13:01 -04:00
Owner

L10 of WAI's live and interactive work: end-to-end encrypted rooms. Every object a member publishes is sealed by its sender, so relays route, cache, meter and receipt only ciphertext, and only the room's current members open it. Group keys come from MLS (RFC 9420), and the group's membership follows the room's grants and lifecycle.

Documents (pinned)

draft-ietf-moq-secure-objects-01 (6 July 2026), draft-jennings-moq-mocha-mls-keying-00 (6 July 2026), RFC 9605 (SFrame), RFC 9420 (MLS), RFC 9750 (MLS architecture), on draft-ietf-moq-transport-22. confidentiality.md is rewritten onto these and no longer follows the expired draft-jennings-moq-e2ee-mls. SPEC §5 and §6 withdraw the expired construction.

What is in it

  • wai/extensions/confidentiality.md registers wai.live.e2ee with a signed confidentiality record (WRE1).
    • Object sealing follows secure objects: the Key ID property, the Serialized Full Track Name, key, salt, nonce and AAD, the ciphersuites, and seal and open.
    • MLS keying (§5):
      • Credentials: each MLS credential is the member's Ed25519 principal key from its room grant.
      • Proposals: the room's clock is the MLS external sender, proposing Add and Remove from the lifecycle (join, leave, revocation, expiry).
      • Who commits (§5.5): the member at the lowest leaf index that the commit does not remove. It commits the clock's first k proposals for the epoch, with an UpdatePath, and every member checks each commit in a stated order.
      • Keys: per-epoch, per-sender keys come from the MLS exporter as the keying draft says, with rotation and retention per epoch.
    • Receiving, multi-sender rooms, and receipts and metering over ciphertext: receipts bind the sealed bytes, and an inner digest lets a member check the plaintext.
    • Recording: refused (§9). A room with a confidentiality record is never recorded. Escrowing epoch keys to the authority would make a reader no member sees in the roster, and keys kept for a recording outlive their epoch, which undoes forward secrecy and cuts off no removed member. The spec states this reasoning.
  • joule-moq: secure_objects (feature secure-objects), seal and open per the draft, with its vectors.
  • joule-moq-mls (new crate): the room's MLS binding on openmls =0.9.1, openmls_rust_crypto =0.6.0 and openmls_traits =0.6.0, all pinned. It holds the membership model, the confidentiality record, the strict checks, real-MLS room tests, and the corpus generator joule_moq_e2ee_vectors.
  • joule-moq-relay: tests/sealed_objects.rs shows sealed objects through the unchanged relay. No relay forwarding code changes.
  • Corpus wai/live-conformance/e2ee/: sealed-object vectors with their keys, per-epoch exporter secrets and derived per-sender keys, a multi-sender room transcript with a removal, and negatives. Generated deterministically, with a README.
  • e2ee_verify.py, standard library only, with a pure-Python AEAD for vectors only. It does not implement MLS: it takes each epoch's exporter secret from the corpus and checks everything after it. The spec states that boundary.
  • CI: joule-code-conformance runs the secure-objects tests, joule-moq-mls and the corpus's regeneration; the independent-verifiers job runs e2ee_verify.py. Also SPEC §5/§6, REGISTRY, the regenerated export (wai.live.e2ee, WRE1, SECURE_OBJECT_KEY_ID, WAI_INNER_DIGEST), SECURITY.md and moq-streaming-format.

Checks on the build host (rb, at c3c777a0)

run result
every Cargo.lock with cargo metadata --locked --offline all current (the vendored jouledesk/…/vendor/libwebrtc is skipped, as check-lockfiles.sh skips it)
cargo test -p joule-moq --features secure-objects --lib secure_objects and --test secure_objects_vectors 0
cargo test -p joule-moq-mls 0
joule_moq_e2ee_vectors verify ../../wai/live-conformance/e2ee 0: the corpus regenerates byte for byte
cargo test -p joule-moq-relay --test sealed_objects 0
cargo check -p joule-moq --no-default-features 0 (the codec still builds without its runtime)
cargo clippy -p joule-moq-mls -p joule-moq --all-targets 0; the three notes in joule-moq-mls fixed in this PR's last commit, none left in its code
wai registry export --check, --lib codecs::registry, the header with cbindgen.toml touched 0; include/wai.h unchanged
python3 -I live-conformance/e2ee_verify.py live-conformance/e2ee 0: every vector verifies, 18 of 18 mutated copies fail

Out of scope

  • The browser member (L10b): MLS in WebAssembly, and sealing encoded frames as the W3C encoded-transform interface does. Nothing here depends on it.
  • Hardware-protected content (DRM, Lane A).

The slice was built by a builder that stopped at the account's weekly limit before writing its report. The final checks above, the clippy fixes and this PR were done after it stopped.

🤖 Generated with Claude Code

L10 of WAI's live and interactive work: **end-to-end encrypted rooms**. Every object a member publishes is sealed by its sender, so relays route, cache, meter and receipt only ciphertext, and only the room's current members open it. Group keys come from MLS (RFC 9420), and the group's membership follows the room's grants and lifecycle. ## Documents (pinned) `draft-ietf-moq-secure-objects-01` (6 July 2026), `draft-jennings-moq-mocha-mls-keying-00` (6 July 2026), RFC 9605 (SFrame), RFC 9420 (MLS), RFC 9750 (MLS architecture), on `draft-ietf-moq-transport-22`. `confidentiality.md` is rewritten onto these and no longer follows the expired `draft-jennings-moq-e2ee-mls`. SPEC §5 and §6 withdraw the expired construction. ## What is in it - **`wai/extensions/confidentiality.md`** registers `wai.live.e2ee` with a signed confidentiality record (`WRE1`). - **Object sealing** follows secure objects: the Key ID property, the Serialized Full Track Name, key, salt, nonce and AAD, the ciphersuites, and seal and open. - **MLS keying** (§5): - **Credentials:** each MLS credential is the member's Ed25519 principal key from its room grant. - **Proposals:** the room's clock is the MLS external sender, proposing Add and Remove from the lifecycle (join, leave, revocation, expiry). - **Who commits** (§5.5): the member at the lowest leaf index that the commit does not remove. It commits the clock's first *k* proposals for the epoch, with an UpdatePath, and every member checks each commit in a stated order. - **Keys:** per-epoch, per-sender keys come from the MLS exporter as the keying draft says, with rotation and retention per epoch. - **Receiving, multi-sender rooms, and receipts and metering over ciphertext:** receipts bind the sealed bytes, and an inner digest lets a member check the plaintext. - **Recording: refused** (§9). A room with a confidentiality record is never recorded. Escrowing epoch keys to the authority would make a reader no member sees in the roster, and keys kept for a recording outlive their epoch, which undoes forward secrecy and cuts off no removed member. The spec states this reasoning. - **`joule-moq`:** `secure_objects` (feature `secure-objects`), seal and open per the draft, with its vectors. - **`joule-moq-mls`** (new crate): the room's MLS binding on `openmls` =0.9.1, `openmls_rust_crypto` =0.6.0 and `openmls_traits` =0.6.0, all pinned. It holds the membership model, the confidentiality record, the strict checks, real-MLS room tests, and the corpus generator `joule_moq_e2ee_vectors`. - **`joule-moq-relay`:** `tests/sealed_objects.rs` shows sealed objects through the **unchanged** relay. No relay forwarding code changes. - **Corpus** `wai/live-conformance/e2ee/`: sealed-object vectors with their keys, per-epoch exporter secrets and derived per-sender keys, a multi-sender room transcript with a removal, and negatives. Generated deterministically, with a README. - **`e2ee_verify.py`**, standard library only, with a pure-Python AEAD for vectors only. It does not implement MLS: it takes each epoch's exporter secret from the corpus and checks everything after it. The spec states that boundary. - **CI:** `joule-code-conformance` runs the secure-objects tests, `joule-moq-mls` and the corpus's regeneration; the independent-verifiers job runs `e2ee_verify.py`. Also SPEC §5/§6, REGISTRY, the regenerated export (`wai.live.e2ee`, `WRE1`, `SECURE_OBJECT_KEY_ID`, `WAI_INNER_DIGEST`), SECURITY.md and moq-streaming-format. ## Checks on the build host (rb, at c3c777a0) | run | result | |---|---| | every `Cargo.lock` with `cargo metadata --locked --offline` | all current (the vendored `jouledesk/…/vendor/libwebrtc` is skipped, as `check-lockfiles.sh` skips it) | | `cargo test -p joule-moq --features secure-objects --lib secure_objects` and `--test secure_objects_vectors` | 0 | | `cargo test -p joule-moq-mls` | 0 | | `joule_moq_e2ee_vectors verify ../../wai/live-conformance/e2ee` | 0: the corpus regenerates byte for byte | | `cargo test -p joule-moq-relay --test sealed_objects` | 0 | | `cargo check -p joule-moq --no-default-features` | 0 (the codec still builds without its runtime) | | `cargo clippy -p joule-moq-mls -p joule-moq --all-targets` | 0; the three notes in `joule-moq-mls` fixed in this PR's last commit, none left in its code | | `wai registry export --check`, `--lib codecs::registry`, the header with `cbindgen.toml` touched | 0; `include/wai.h` unchanged | | `python3 -I live-conformance/e2ee_verify.py live-conformance/e2ee` | 0: every vector verifies, 18 of 18 mutated copies fail | ## Out of scope - **The browser member (L10b):** MLS in WebAssembly, and sealing encoded frames as the W3C encoded-transform interface does. Nothing here depends on it. - **Hardware-protected content (DRM, Lane A).** The slice was built by a builder that stopped at the account's weekly limit before writing its report. The final checks above, the clippy fixes and this PR were done after it stopped. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Rewrites the extension onto draft-ietf-moq-secure-objects-01 and
draft-jennings-moq-mocha-mls-keying-00, over draft-ietf-moq-transport-22:
the room's confidentiality record (WRE1, wai.live.e2ee), object sealing,
one MLS group per room driven by the room's lifecycle with the clock as
external sender and a committer rule every member computes, per-epoch
per-sender keys from the exporter, the receiver's checks and reasons,
receipts and metering over ciphertext, and recording refused.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Feature secure-objects: the AEADs of the SFrame suites, RFC 9605 SFrame for
its vectors, the Key ID property, the plaintext and its Encrypted Properties
List, key/salt/nonce/AAD, and a room member's keyring with the receiving
checks of WAI confidentiality §6. The published vectors of RFC 9605 and of
the draft's Appendix B are in wai/live-conformance/e2ee/.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Records, key schedule, sealed objects with every derived value, the room
transcript with a removal, a late joiner and a change of committer, and a
control for each reason of confidentiality §13.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Standard library only: AES, AES-GCM and RFC 9605's counter mode written from
their specifications for vectors only; HKDF and HMAC from the standard
library; BLAKE3 and strict Ed25519 from claim-conformance. It checks RFC
9605's and secure objects' published vectors, every record, key schedule,
sealed object and room of vectors.json, and requires every mutated copy to
fail.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
joule-moq-mls: clippy's notes on the model and the corpus generator
All checks were successful
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 1m37s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 2m49s
joule-moq-room room service / joule-moq-room · does this change touch it (pull_request) Successful in 5s
joule-moq-web browser client / joule-moq-web · does this change touch it (pull_request) Successful in 5s
wai-web reach builds / wai-web reach · does this change touch it (pull_request) Successful in 5s
joule-code conformance gate / Build + round-trip C1-C4 vectors (pull_request) Successful in 2m32s
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Successful in 2m15s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 27s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 6m6s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 8m38s
wai-pack / wai-pack · linux (pull_request) Successful in 1m34s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 3m17s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 18m33s
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Successful in 2m43s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 31m58s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 15m58s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 2m17s
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Successful in 4m17s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 30s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 1m35s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 5m37s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 35m59s
joule-moq-room room service / joule-moq-room, every target (pull_request) Successful in 1m27s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 23m26s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 24s
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Successful in 54s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 8m8s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 4m20s
wai-pack / wai-pack · macos (pull_request) Successful in 1m6s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Successful in 4m3s
c3c777a06f
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Transaction-Science/open-standards!345
No description provided.