Skip to main content

WAI Extension: Confidentiality (MLS + SFrame)

Status: Draft. Sub-spec of WAI v1.0. Reference implementation: the moq-sframe (SFrame object encryption) and joule-comm-mls (MLS group key agreement) crates of the WAI reference tree, with browser bindings in joule-comm-mls-wasm. Live demonstration: wai.transaction.science/private-channel

Keywords MUST, MUST NOT, SHOULD, MAY, REQUIRED, OPTIONAL are RFC 2119 / RFC 8174.


1. Scope and model

This extension makes a WAI payload private: only members of a group may recover the content, while any relay or multiplexer that carries the payload can still route, meter, and attest the bytes without reading them. It is the e2e_encrypted path — the confidential counterpart to the public floor, where a shared world or a broadcast film is plaintext by design.

The construction is the draft-jennings-moq-e2ee-mls binding:

Confidentiality is orthogonal to the capability (WAI SPEC §5). SFrame wraps the payload; the capability still names how to decode the plaintext once recovered. A sink decrypts first, then runs capability dispatch (SPEC §4) on the plaintext.

In scope: the SFrame object format, its key schedule, the MLS exporter binding, the decrypt-then-dispatch integration, and relay transparency.

Out of scope:


2. Ciphersuites

A sink MUST reject an envelope whose declared security.encryption names a scheme or suite it does not implement (SPEC §6.4), rather than attempt to decode ciphertext.


3. SFrame object format

A sealed payload is header ‖ AEAD.Seal(...). The manifest’s payload bytes ARE this sealed object; nothing else wraps it.

3.1 Header (RFC 9605 §4.3)

The first byte is:

 bit  7   6 5 4   3   2 1 0
     +---+-------+---+-------+
     | R | LEN   | X | KID/  |
     |=0 | (3)   |   | KLEN  |
     +---+-------+---+-------+

The header is therefore 1 + (X ? kid_len : 0) + ctr_len bytes and is self-delimiting: (kid, ctr) and the header length are recoverable from the payload alone.

3.2 Key schedule (RFC 9605 §4.4.3)

From the 32-byte base key (§4), with suite = 0x00 0x06:

secret = HKDF-Extract(salt = "", IKM = base_key)              ; HMAC-SHA-512
key    = HKDF-Expand(secret, "SFrame 1.0 Secret key "  ‖ suite, Nk=32)
salt   = HKDF-Expand(secret, "SFrame 1.0 Secret salt " ‖ suite, Nn=12)

The label strings include their trailing space, immediately followed by the two suite bytes.

3.3 Nonce (RFC 9605 §4.4.4)

The per-object nonce is salt XOR ctr, where ctr is the 64-bit counter written big-endian and right-aligned into the 12-byte nonce (the low 8 bytes are XORed; the high 4 bytes are salt unchanged).

3.4 Seal / open

The sender MUST NOT reuse a (base_key, ctr) pair — GCM nonce reuse is catastrophic. Counters are per base key and strictly increasing.


4. MLS binding — deriving the base key

Every group member derives the SFrame base key from the MLS group’s exporter secret (RFC 9420 §8.5):

base_key = MLS-Exporter(label = "moq-jwp sframe base key",
                        context,
                        length = 32)

The SFrame kid (§3.1) names which base key opens an object; the MLS epoch names which generation of that key. A sink resolves both before open.


5. Decrypt-then-dispatch

This refines SPEC §6.4. Before capability dispatch (SPEC §4) a sink MUST:

  1. If the manifest has no security object, or security.encryption is null / "none", dispatch the payload unchanged.
  2. Else, for "mls-sframe": if the sink is not a member of the group (holds no MLS state) or holds no base key for security.kid, the file is inert at this sink — return a clear “no key” error. The sink MUST NOT feed ciphertext to a codec.
  3. Else open the SFrame object (§3.4) to recover the plaintext payload, then run SPEC §4 on the plaintext.

media, intent, conditioning.kind, and model_requirement.capability describe the plaintext codec and are NOT encrypted: confidentiality protects the content, not the shape, so a multiplexer can still route by capability without a key.


6. Relay transparency and JWP composition

A relay or multiplexer that carries a sealed payload MUST treat it as opaque: it MUST NOT require the plaintext to route, meter, or attest, and MUST NOT attempt capability dispatch on ciphertext.

Attestation composes with confidentiality because the JWP receipt chain (jwp-receipts) commits over the ciphertext content hash. A relay signs that it forwarded the exact sealed bytes it was handed — with the per-object energy and delivery lineage bound into the Merkle leaf — while never learning what they say. Content secrecy (SFrame/MLS) and delivery attestation (JWP) are independent layers over the same bytes:


7. Security considerations


8. Conformance