Skip to main content

WAI Extension: Live Rooms

Mirrored from the canonical text at commit f871af55 ().

Status: Draft. Registers the record wai.live.room: the signed room descriptor that fixes a room, its id, its clock and what each role may do; and the signed room grant under which one principal takes part, carrying the event policy live-events §5 holds it to. A grant is exchanged for another at a tick of the room clock, and revoked through the room authority’s key log (jwp-receipts §7). Tracks are named in the MoQ namespace tuple; a slot lets a publisher of higher precedence take a virtual track over, and a standby take it on failover; the publish profiles fix codecs, layers and limits as data. A room verifier replays every principal’s events under the grant in effect at each instant of the room clock. No new transport and no new token format: the grant maps onto existing token formats (§10). The reference implementation is the live_room module of wai-rs (feature live_room); the corpus is live-conformance/rooms/, and its second implementation is live-conformance/live_rooms_verify.py. Keywords MUST, MUST NOT, SHOULD, MAY are RFC 2119/8174.

Drafts read for this revision: draft-ietf-moq-transport-22 (MOQT), draft-ietf-moq-msf-01 (MSF), draft-ietf-moq-c4m-01 and draft-ietf-moq-privacy-pass-auth-03, all Internet-Drafts, none past Working Group Last Call; with RFC 9246, RFC 8392, and CTA-5007-B as the IANA “CBOR Web Token (CWT) Claims” registry lists its claims (keys 308 to 323). A section number of one of those drafts in this text is that revision’s.

1. Scope

In scope: the room descriptor, its digest (the room id) and signature (§3); the room grant, its checks, its attributes, how a receipt names it, and its exchange (§4); revocation through the key log (§5); track naming (§6); precedence and redundancy over slots, and the detail of every lifecycle kind of live-events §4.13 (§7); the publish profiles (§8); room verification (§9); and the bindings onto token formats (§10), with the proof of possession every binding requires (§10.5) and seeking back below a track’s live edge (§10.6).

Out of scope: a transport; how a principal obtains a grant; composition, which reads the grant’s attributes (live-compose); the review workflow, of which this extension fixes only the policy’s fields and their meaning (§3.6), and which live-moderation defines; and what a recording holds, which the recording policy constrains (§3.7) and later extensions define: a device’s master recording is capture-binding’s take.

Relationship to the core spec: wai.live.room is a record (SPEC §5): a signed record a WAI tool emits and verifies. It names no payload format and is not dispatchable.

2. Terms

3. The room descriptor

3.1 Layout

Integers are little-endian. A short field is a u8 length and that many bytes; a long field is a u16 length and that many bytes.

fieldsizemeaning
magic4WRD1 (ASCII)
version11 in this revision
authority32the room authority’s Ed25519 key
clock32the clock’s Ed25519 key
t08u64: the room’s t0, in microseconds since 1970-01-01T00:00:00Z
tick8u64: the room-clock tick, in microseconds, that a grant exchange takes effect on (§4.6)
originlongthe origin whose key log lists the authority (§5); empty for none
max_present4u32: the most principals present at once (§3.5)
max_publishers2u16: the most principals publishing at once (§7.3)
reval4u32: the re-validation interval, in seconds, a token of the room carries (§10.2); 0 for none
ceiling65a WEP1 record (live-events §5): the event ceiling no grant exceeds (§3.5)
roles1 + …a u8 count, then each role: name (short), publish (u8), subscribe (u8), max_layer (u8), powers (u8), events (32, a kinds bitmap) (§3.3)
slots1 + …a u8 count, then each slot: name (short), media (u8), redundancy (u8), failover_ms (u32) (§7.1)
profiles1 + …a u8 count, then each publish profile (§8.1)
review37kinds (32, a kinds bitmap), deadline_ms (u32), fallback (u8) (§3.6)
recording12 + …mode (u8), retention_days (u16), commit_interval_ms (u32), jitter_ms (u32), then a u8 count and that many 2-byte regions (§3.7)
sig64§3.2

3.2 Digest, room id and signature

The digest of a descriptor is SHA-256 (FIPS 180-4) over its record from magic through recording: every byte but sig. It is the room id, the 32 bytes every event of the room names (live-events §3.1).

The signature is Ed25519 by authority over the 14 bytes wai:live-room (ASCII) and 0x01, followed by the bytes the digest covers. It is verified strictly, as SPEC §7 Signatures requires: a key that fails step 1 or 2 is bad_key, and a signature that fails a later step is bad_signature.

3.3 Roles and capability sets

A role maps a name to a capability set:

A role whose events permit decision has the moderate power; one whose events permit control has direct or record. A grant narrows its role’s set and never widens it (§4.3).

3.4 Reading and field rules

A reader reads exactly the bytes it is given as one descriptor, in the order of §3.1, and refuses at the first of these that holds:

  1. Fewer than 4 bytes: truncated. A magic other than WRD1: malformed. No byte after the magic: truncated. A version other than 1: unknown_version.
  2. The bytes end before a field the layout requires: truncated.
  3. Bytes after sig: malformed.

It then holds the descriptor to these rules, and refuses one that breaks any of them as descriptor_invalid:

Then the signature (§3.2): bad_key or bad_signature.

3.5 Limits

3.6 The review policy

kinds names the event kinds held for review; all zero, nothing is reviewed. An event of a reviewed kind is shown by a sink only once a principal with the moderate power has sent a decision on it (live-events §4.11), or once deadline_ms milliseconds of room clock have passed after its t with none, when the fallback verdict applies: 1 allow, 2 deny. With no reviewed kind, deadline_ms and fallback are 0; with one, the kinds are within the ceiling’s, deadline_ms is above 0, and fallback is 1 or 2. This revision fixes the policy’s fields and their meaning; the review workflow and its verification are live-moderation’s, whose review policy keeps this field (§3.5 there).

3.7 The recording policy

mode is at most 2, and commit_interval_ms and jitter_ms keep the rule above.

4. The room grant

4.1 Layout

fieldsizemeaning
magic4WRG1 (ASCII)
version11 in this revision
room32the room id
principal32the Ed25519 key the grant is for
prev32the digest of the grant this one is exchanged for (§4.6); 32 zero bytes for a first grant
effective8u64: for an exchanged grant, the room-clock tick it takes effect at; 0 for a first grant
nbf, exp8 eachu64: seconds since 1970-01-01T00:00:00Z, as RFC 8392’s NumericDate: the grant is good from nbf until before exp
role1the index of its role in the descriptor
publish, subscribe, max_layer, powers1 eachits capability set (§3.3), within its role’s
precedence1its precedence on a slot (§7.1)
once16a single-use nonce: when it is not zero, no two joins name grants with this once (§7.2); zero, the grant may be used again within its window
viewer16the viewer id revocation names (§5); zero for none
policy65a WEP1 record (live-events §5): the event kinds it may send, its payload and text bounds, and its rate windows
joules_ceiling8u64: the energy its use may spend, in microjoules; 2^64 − 1 for no ceiling
funds_ceiling8u64: the funds its use may spend, in minor units of currency; 2^64 − 1 for no ceiling
currency3ISO 4217 alphabetic code; three zero bytes when there is no funds ceiling
profiles1 + na u8 count, then the index in the descriptor of each publish profile it publishes with
origins1 + …a u8 count, then each delivery origin it is good for (short): a URI’s scheme and authority, such as https://relay.example
attrslongits attributes (§4.4)
sig64§4.2

4.2 Digest and signature

The digest of a grant is SHA-256 over its record from magic through attrs. The signature is Ed25519 by the descriptor’s authority over the 20 bytes wai:live-room-grant (ASCII) and 0x01, followed by the bytes the digest covers, verified strictly (SPEC §7 Signatures).

4.3 Checking the grants

A verifier is given a room’s grants in a list. It checks each in the list’s order and refuses at the first check that fails, naming the grant by its index:

  1. The record reads, as §3.4 steps 1 to 3 read a descriptor with WRG1 for WRD1: truncated, malformed or unknown_version.
  2. Its signature verifies under the descriptor’s authority (§4.2): bad_signature.
  3. When prev is not zero, it is the digest of a grant earlier in the list (exchange_unlinked), and that grant’s room and principal are this one’s (exchange_mismatch).
  4. Its room is the room id: wrong_room.
  5. Its fields keep these rules, and grant_invalid refuses it otherwise:
    • exp is above nbf;
    • a first grant’s effective is 0, and an exchanged grant’s a multiple of the descriptor’s tick;
    • role names a role; subscribe is 0 or 1; publish sets no bit above bit 2, powers none above bit 4; max_layer is 0 when subscribe is 0;
    • policy is a policy live-events §5 reads, permits no lifecycle kind, and the powers §3.3 requires of its kinds are among powers;
    • funds_ceiling is 2^64 − 1 exactly when currency is three zero bytes, and currency is otherwise three capitals A–Z;
    • each entry of profiles names a profile of the descriptor, of a media kind the grant may publish, and no two name profiles of one media kind;
    • there are at most 8 origins, each a token;
    • attrs meets §4.4;
    • its digest is no earlier grant’s.
  6. exp − nbf is at most 1 209 600 seconds, fourteen days: otherwise grant_too_long.
  7. It is within its role (§3.3): its publish and powers set no bit its role’s do not, it subscribes only where its role does, its max_layer is at most its role’s, and its policy’s kinds are within its role’s events. Otherwise caps_exceed_role.
  8. Its policy is within the room’s ceiling (§3.5): otherwise grant_exceeds_room.

A grant is good at room-clock t when nbf ≤ ⌊(t0 + t) / 10^6⌋ < exp.

4.4 Attributes

attrs is a sequence of entries, each a key (a short field holding a slot name) and a value (a long field holding UTF-8, RFC 3629). Keys are in strictly ascending byte order, and the entries take at most 1024 bytes, not counting the field’s own length. An entry that runs past the field’s end breaks the rule.

Attributes are for composition: live-compose lays a room out from them. It reads featured (true or false), order (a decimal integer) and pip (true or false), and gain and pan (decimal integers) for the mix, under the keys a composition descriptor names, and gives each its syntax and its meaning (live-compose §4.3); this revision checks only the form of the entries. Capture binding (capture-binding §3) reads two more: subject, the principal a device captures for, as 64 lowercase hexadecimal digits of its key; and minor (true or false), whether the authority marks that principal a minor. Attributes are signed with the grant, so a layout computed from them is computed from what the authority stated.

4.5 How a receipt names a grant

A receipt that names the grant it was carried under does so with the grant reference WAI’s receipts already carry, the members grant_hash, capability, joule_ceiling_micro and funds_ceiling that a package receipt (packaging §9) and a playlist receipt (http-delivery, Appendix H) write: grant_hash is the grant’s digest (§4.2), capability is wai.live.room, joule_ceiling_micro is joules_ceiling (2^64 − 1 for no ceiling, as there), and funds_ceiling is funds_ceiling, or none when that is 2^64 − 1. The reference carries no currency; the grant it names does. Every object’s receipt so names the grant it was carried under, and the grant states the ceilings a meter holds that use to.

4.6 Grant exchange

A principal’s grant is exchanged for a new grant that is chained to it: the new grant’s prev is the old grant’s digest, and its room and principal are the old one’s (§4.3 step 3). The exchange takes effect at the new grant’s effective, a tick of the room clock, and is recorded as the clock’s grant_exchange lifecycle event at that t (§7.2). An exchange can widen what a principal may do (a viewer promoted to publish) or narrow it, and only the authority can make one, since only it signs grants.

An exchange MUST NOT leave the principal publishing what its new grant does not let it publish: an authority that narrows a publisher’s grant unpublishes first (§7.2).

5. Revocation through the key log

A room whose descriptor names an origin is revocable. Its authority’s key is an entry of that origin’s key log (jwp-receipts §7.2) whose may_sign holds the authority live/room (jwp-receipts §7.5). Revocation then goes through that log:

The list is append-only (jwp-receipts §7.2 rule 6), so a revocation stays. A relay checks a grant against the key status it holds (http-delivery §8.4) at admission and at each re-validation (§10.2), so a grant revoked stops being honoured within reval plus the head’s status_max_age_s seconds.

A room verifier is given the head the caller reached for the descriptor’s origin (jwp-receipts §7.4 step 3): anchoring the origin and walking its log to the head are the caller’s, and the verifier is a function of the head it is given. When origin is empty, no key log is read, a head given is not read, and nothing is revoked. Otherwise, in this order:

  1. A head is given: otherwise key_log_missing.
  2. It is held for origin: jwp-receipts §7.2 rules 1 to 3 hold for it. Otherwise key_log_not_held.
  3. It has an entry whose key is the descriptor’s authority and whose may_sign holds live/room: otherwise authority_not_listed.
  4. That entry’s status is not revoked: otherwise authority_revoked.

A grant whose digest, or whose viewer’s tag, the head’s revoked_modules lists is revoked. A revocation refuses every use of the grant (§7.2): like a key’s, it is not bounded by a time, since a revoked grant may have been in another party’s hands. An authority that only wants a principal gone from a room removes it with leave (§7.2).

6. Track naming

6.1 The Full Track Name

A track of a room is named by a MOQT Full Track Name (MOQT §2.4.1):

Two more tracks carry what a publisher’s media tracks need, and are named alongside them:

Every byte of these fields is one of a–z, 0–9 and _, so the bytes are their own canonical form (MOQT §2.4.1) and their rendering in MOQT §8.8’s format is themselves: the fields joined by -, then -- and the name, as <room>-<principal>-video--1, <room>-<principal>-video--1_receipts and <room>-<principal>--catalog. No field is empty and none begins with . (MOQT §2.4.3, §8.7), and the whole is 64 + 64 + 6 + 12 bytes at most, within MOQT §8.7’s limit.

The layer is the Track Name, and not a fourth namespace field, because moq-streaming-format §4.1 has the tracks of a staged set share one namespace. Namespace prefix matching (MOQT §2.4.2) on the first field reaches every track of a room; on the first two, every track of one publisher or slot.

6.2 The catalog

A publisher’s MSF catalog is the object of its catalog track (§6.1). Its entry for a track carries its namespace (MSF §5.2.2) as the MOQT §8.8 rendering of the namespace, and its name (MSF §5.2.3) as the Track Name. A media track that carries receipts declares wai_receipts true and names its receipts track (§6.1) in wai_receipts_track, and the receipts track has an entry of its own, of packaging "wai-receipts" (moq-streaming-format §4). The codec, dimensions, frame rate, bitrate, sample rate and channels of the entry are the track’s profile layer’s (§8.1). A track that carries WAI envelopes declares packaging "wai" (moq-streaming-format §3), and the tracks of a staged profile carry wai_staged with layer equal to the Track Name (moq-streaming-format §4.1). An anchor’s track (live-events §3.1) is SHA-256 over that rendering of the namespace, one zero byte, and the Track Name.

6.3 Layers

A layer is an index from 0, the lowest. A profile’s layering (§8.1) says what its layers are:

A subscriber whose grant’s max_layer is m subscribes to the tracks named 0 to m, their receipts tracks and the publishers’ catalog tracks (§10.2). A layer_request command (live-events §4.9) asks a publisher for a layer; it grants nothing.

7. Precedence and redundancy

7.1 Slots, takeover and failover

A slot is a virtual track the descriptor declares: a name, a media kind, a redundancy of 1 or 2, and a failover_ms. Subscribers subscribe to the slot’s tracks (§6.1, owner _<name>), and which principal publishes behind them can change without any subscriber subscribing again: a relay switches the source and the subscription stands. A slot has a live publisher, and, when its redundancy is 2, a standby.

A standby whose live publisher unpublishes, or leaves the room, goes live with no event of its own: its going live follows from the unpublish or leave.

7.2 Lifecycle detail

The lifecycle events of live-events §4.13 are authored by a room key. Their payload is the subject, the principal the event is about, then detail, which this section defines for each kind. A slot field is a short field holding a slot name or, where the table allows, nothing (the subject’s own track). media is a media kind of §6.1.

kinddetail
joingrant (32): the digest of the grant the subject takes part under
leavereason (u8): 1 left, 2 removed, 3 its grant ended, 4 connection lost
publishslot (empty for an own track), media (u8), profile (u8, the index of the profile in the descriptor), layers (u8, at least 1)
unpublishslot (empty for an own track), media (u8)
grant_exchangeold (32), new (32): the grants’ digests
takeoverslot (not empty), media (u8), profile (u8), layers (u8, at least 1), previous (32): the live publisher replaced
failoverslot (not empty), media (u8), failed (32): the live publisher that stopped, last_seen (u64): the room clock at its last object

A detail that does not read, with bytes after its last field, or with a value the table does not allow, is lifecycle_malformed. Each event is then checked against the room state (§9.3) at its t, in the order listed, and applied:

7.3 Publishers

A principal publishes while it has an own track published or stands in a slot, live or standby. At most max_publishers principals publish at once; a publish or a takeover after which more would is refused publisher_limit.

8. Publish profiles

8.1 The profile record

A profile fixes, as data, what a publisher sends on one media kind: its codecs, its layers and their limits, and its audio processing.

fieldsizemeaning
nameshortthe profile’s name
media1its media kind (§6.1)
codecs1 + …a u8 count (1 to 4), then each codecs value (RFC 6381) as a short field holding a token of at most 64 bytes, in the order a publisher prefers them
layering10 single, 1 simulcast, 2 svc, 3 staged (§6.3)
modeshortfor svc, the scalability mode (§6.3): 1 to 16 of A–Z, 0–9 and _; otherwise empty
layers1 + 14 × na u8 count (1 to 8; exactly 1 for single), then each layer: width, height, fps (u16 each), min_kbps, max_kbps (u32 each)
sample_rate4u32: audio’s sample rate in Hz; 0 for video
channels1audio’s channel count, 1 to 8; 0 for video
processing1audio’s processing: bit 0 echo cancellation, bit 1 noise suppression, bit 2 automatic gain control, bit 3 discontinuous transmission (DTX); 0 for video

Each layer’s max_kbps is above 0 and min_kbps at most max_kbps: min_kbps is a floor a publisher SHOULD NOT send below while the network allows, and max_kbps a ceiling it MUST NOT exceed. An audio layer has width, height and fps 0, and an audio profile a sample_rate above 0, 1 to 8 channels, and processing setting no bit above bit 3; a video or screen layer has all three above 0. A publisher publishing under a profile uses one of its codecs, its layering and its layers, and turns on exactly the processing it names.

8.2 Registered profiles

namemediacodecslayeringlayers (width × height @ fps, kbps)audio
speechaudioopussingle16–6448 000 Hz, 1 channel; echo cancellation, noise suppression, gain control and DTX on
musicaudioopussingle128–51048 000 Hz, 2 channels (stereo, fullband); no echo cancellation, noise suppression, gain control or DTX
screenscreenav01.0.08M.08, avc1.640028svc, L1T31920 × 1080 @ 15, 300–2500—
camera-720videoav01.0.05M.08, avc1.64001Fsimulcast640 × 360 @ 30, 150–500; 1280 × 720 @ 30, 500–1500—
camera-1080videoav01.0.08M.08, avc1.640028simulcast640 × 360 @ 30, 150–500; 1280 × 720 @ 30, 500–1500; 1920 × 1080 @ 30, 1500–4000—

opus is RFC 6716 audio, av01 AV1 and avc1 ITU-T H.264. music’s 128 kbps floor and its processing turned off keep what an instrument plays: echo cancellation, noise suppression and gain control are built for speech and remove parts of music, and DTX stops sending in what it takes for silence. screen drops frames rather than resolution (L1T3, temporal layers alone), since text on a screen does not survive scaling. A room lists the profiles it allows in its descriptor, under these names with these values or under names of its own; wai_live_room_vectors writes each registered profile’s record into the corpus, so an implementation checks its table against them.

9. Room verification

9.1 Inputs and steps

A room verifier is given a descriptor, a list of grants, the head of the authority’s key log (or none), and the room’s chains: a list of lists of WEV1 records, each list one principal’s records in its stream order (live-events §6). It runs these steps and stops at the first refusal, which it reports with the reason and where:

  1. The descriptor (§3.4, §3.2): its refusal, at descriptor.
  2. The key log (§5): its refusal, at key_log.
  3. The grants (§4.3): the refusal, at the grant’s index.
  4. The records, in the merged order (§9.2), each by §9.2: the refusal, at the record’s chain and its index in that chain.
  5. The outcome (§9.4).

9.2 The merged order and each record

The merged order takes the records of every chain into one sequence, keeping each chain’s own order: repeatedly, of the chains with records left, it takes the next record of the one whose next record has the least key. A record’s key is its t, then 0 for a room key’s record and 1 for any other, then its chain’s index; a record that live-events §3.3 does not read has the key (0, 0, its chain’s index). Lifecycle events therefore take effect before the events of other principals at the same t.

One live-events verifier (live-events §6) verifies every record, for the room id. Before it is offered a record, the verifier is given the policy the record is held to. For each record, in the merged order:

  1. live-events §6.1 steps 1 to 4: the record reads, its kind is defined, its room is the room id, its signature verifies. Otherwise that section’s reason.
  2. A record whose digest is that of a record accepted before is a duplicate: it is offered under the policy it was accepted under, which live-events §6.2 step 1 ignores, and it changes nothing.
  3. A room key’s record is offered under the room authority’s policy (live-events §5), and refused with live-events’ reason. A lifecycle event then has its detail read and is applied (§7.2).
  4. Any other principal’s record: the principal is present (not_present), its grant in effect is good at the record’s t (outside_grant_window), and the record is offered under that grant’s policy and refused with live-events’ reason: a kind the grant does not permit is kind_not_permitted, a payload over its bound payload_too_large, and an event past its rate window rate_exceeded. A participant’s lifecycle event is therefore kind_not_permitted, as no grant permits one (§4.3 step 5).
  5. A control event of such a principal needs a power of its grant in effect (§3.3): record_start and record_stop the record power, any other command the direct power. Otherwise power_not_granted.

Each principal’s effective policy at t is so the policy of its grant in effect at t, and the room keys’ is the authority’s. Each record is reported with the grant it was verified under, or none for a room key’s.

9.3 Room state

The room state is replayed from the lifecycle events alone, in the merged order:

The tracks a subscriber of the room can subscribe to are named by §6.1: each own track’s layers, and each slot’s live publisher’s.

9.4 Outcomes

10. Bindings

A grant is WAI’s own signed record. A relay, a CDN or an origin that authorises with an existing token format is given a token that maps the grant’s claims onto that format’s; WAI defines no second token format. A claim the grant has and the format does not stays in the grant, which the relay holds beside the token (§10.1). A grant is public, so it is honoured, under every binding, only beside a proof that the presenter holds its principal’s key (§10.5).

10.1 Common Access Token (CTA-5007-B)

A Common Access Token is a CWT (RFC 8392). Its claims are named here by name and by the key the IANA CWT Claims registry gives them; the value syntax of each CAT claim is CTA-5007-B’s.

grant claimCWT / CAT claimmapping
the authorityiss (1)the authority’s signer_id (jwp-receipts §7.1): origin-bound, with its key-log entry’s id, where origin is set; its did:key otherwise
principalsub (2)the principal’s did:key
roomaud (3)the room id, 64 lowercase hexadecimal digits
nbf, expnbf (5), exp (4)the same NumericDates
oncecti (7), catreplay (308)a once not zero is the token’s cti, with catreplay set to prohibit replay; zero, catreplay permits it
originscatu (312), cath (315)each origin as a URI match on scheme and host; a deployment that matches on a request header carries it in cath
the token’s renewalcatr (323)a renewed token’s exp is never later than the grant’s; an exchange (§4.6) issues a new token
principal, as a key bindingcatdpop (321), cnf (8)proof of possession by the principal’s key: cnf names the key, and catdpop requires a proof with each presentation, which §10.5 gives as a WRP1 record over a fresh challenge; cattpk (319) binds a TLS key, and applies only where the client’s TLS key is the principal’s
client network, geographycatnip (311), catgeoiso3166 (316)no grant claim: a deployment MAY add them; a room’s restriction by place is a sink-evaluated predicate (unidirectional-delivery §2)
caps, max_layer, the slotsmoqt§10.2
reval (descriptor)moqt-reval§10.2
role, powers, precedence, policy, profiles, attrs, joules_ceiling, funds_ceiling, viewernoneWAI’s alone; the relay holds the grant, and a deployment that links the two inside the token carries the grant’s digest in a private-use claim (RFC 8392 §9.1)

catv (310) is the version CTA-5007-B gives; catpor (309), catm (313), catalpn (314), catgeocoord (317), catgeoalt (318), catif (322) and catifdata (320) have no grant claim. This revision implements no CAT encoding: the crate has no CBOR or COSE, and the claims above are mapped by name and key.

10.2 MOQT: the moqt claim (draft-ietf-moq-c4m-01)

A token for MOQT carries the moqt claim, an array of scopes, each [actions, namespace matches, track match] (draft-ietf-moq-c4m-01 §2.1). Every action is blocked unless a scope enables it (§2 there), so the scopes a grant maps onto are all it can do. With the room id R and the principal P as 64 lowercase hexadecimal digits, the scopes are, in this order:

  1. [[0]]: CLIENT_SETUP.
  2. For each media kind the grant may publish, in ascending order of its code, with m its name (§6.1): [[2, 6], [R, P, m, nil]] (PUBLISH_NAMESPACE and PUBLISH of its own tracks of that kind: their layers and their receipts tracks), then, for each slot of the descriptor of media kind m, in the descriptor’s order, [[6], [R, "_" + slot, m, nil]] and [[6], [R, "_" + slot, nil], "catalog"] (the slot’s tracks, and its catalog track, which its live publisher publishes).
  3. When the grant may publish any media kind: [[2, 6], [R, P, nil], "catalog"], its catalog track (§6.1).
  4. When the grant’s policy permits any kind: [[2, 6], [R, P, "events", nil], "0"].
  5. When the grant may subscribe: [[3], [R]] (SUBSCRIBE_NAMESPACE), then [[4, 7, 8], [R], "catalog"] (SUBSCRIBE, FETCH, TRACK_STATUS of every publisher’s catalog track), then for each layer l from 0 to max_layer, [[4, 7, 8], [R], "l"] and [[4, 7, 8], [R], "l_receipts"] (any track of the room named l, and its receipts track).

Each namespace or track match is a byte string, an exact match; nil ends a namespace match at that length. The token’s moqt-reval is the descriptor’s reval: a relay re-validates the token, and with it the grant’s revocation (§5), at least that often, and stops what the grant authorised when it fails (§2.2 there). A reval of 0 asks for no re-validation, and then only expiry ends the token. A grant exchange (§4.6) issues a new token at the tick. The corpus states each accepted vector’s scopes per grant, written as JSON: an exact match as a string and nil as null.

A grant’s seek power is applied by the relay, §10.6.

10.3 URI Signing (RFC 9246)

Over HTTP, a room’s delivery (an LL-HLS or DASH rendition of its tracks) is authorised with a URI Signing JWT:

grant claimURI Signing claimmapping
the authorityiss (§2.1.1 there)as §10.1
principalsub (§2.1.2)the principal’s did:key, as a JWE as that section requires
nbf, expnbf (§2.1.5), exp (§2.1.4)the same, with no leeway
oncejti (§2.1.7)a once not zero, so a CDN that keeps token ids refuses a replay
origins, room, max_layercdniuc (§2.1.11)a regex: container (§2.1.15.2) matching the grant’s origins, the room’s path, and the renditions of layers 0 to max_layer
client networkcdniip (§2.1.10)no grant claim; a deployment MAY add it
renewalcdniets, cdnistt, cdnistd (§2.1.12–§2.1.14)a renewed token’s exp is never later than the grant’s
the restnoneas §10.1

cdniv is 1.

10.4 Privacy Pass (draft-ietf-moq-privacy-pass-auth-03)

A viewer that need not be identified is admitted by a Privacy Pass token instead of a grant. The token’s challenge carries the scope in its origin_info (MoQAuthorizationInfo, §3.2.4 there), and its presentations are unlinkable to one another (§2.2 there).

grant claimPrivacy Pass fieldmapping
room, subscribe, max_layerorigin_info scopesthe subscribing scopes of §10.2 step 5, each a MoQAuthScope whose namespace_match is MATCH_PREFIX on [R] and whose track_name_match is MATCH_EXACT on catalog, on the layer, or on the layer’s receipts track
nbf, expredemption_context (§3.2.1 there)a hash of the time window the token is good for
oncethe token’s nonceevery token is single-use (§3.3.2 there)
principal, viewernonethe token names no one; revocation is of the issuer’s key, not of a viewer
policy, publish caps, ceilings, attrsnonea Privacy Pass viewer sends no event and publishes nothing

A viewer that is to send events holds a grant for a key of its own; a key per room keeps its events unlinkable across rooms (live-events §11).

10.5 Proof of possession

Every participant holds every grant of its room, to verify the room (§9), so presenting a grant proves nothing about who presents it, and no binding takes a grant, or a token mapped from it, as a bearer credential. A verifier — a relay admitting a MOQT session, a server admitting an HTTP request — honours a grant only beside a proof: a signature by the grant’s principal over a fresh challenge the verifier chose, bound to the session (MOQT) or to the request (HTTP). It is what catdpop and cnf state for a CAT (§10.1): the token names the key, and the presenter shows that it holds it. Both bindings carry the same record.

10.5.1 The proof record

fieldsizemeaning
magic4WRP1 (ASCII)
version11 in this revision
grant32the digest (§4.2) of the grant it is presented with
key32the Ed25519 key that signs it: the grant’s principal
challenge32the verifier’s challenge (§10.5.2, §10.5.3)
contextlongwhat the proof is for (below): ASCII, ! to ~ and space
sig64Ed25519 by key over the 20 bytes wai:live-room-proof (ASCII) and 0x01, followed by the record from magic through context

The context is a verb, a space, and the target: //, then the authority and the path of the URI the client addressed, with no scheme, query or fragment.

A verifier compares the target with the authority and the path it received: the :authority and :path of a WebTransport CONNECT, the Host and path of an HTTP request or a WebSocket upgrade, the AUTHORITY and PATH options of native QUIC. The scheme is left out because a front that terminates TLS changes the one the verifier sees. The context keeps a proof made for one endpoint, or for one method, from being presented at another, so a server the client chose cannot pass its challenge on and present the client’s proof elsewhere.

10.5.2 Over MOQT

10.5.3 Over HTTP

WHIP (RFC 9725) and its egress counterpart authenticate with an HTTP Bearer token (RFC 6750), as an HTTP delivery of the room’s tracks may (§10.3). A grant presented over HTTP is carried so:

WAI-Room-Proof and WAI-Room-Challenge are proposed for the HTTP Field Name Registry (RFC 9110 §16.3), and are not registered.

Each challenge is good for one request: the server consumes it with the request whose proof it accepts, and keeps the challenges it issued and has not consumed for a time it states, past which a proof over one is refused (pop_wrong_challenge). A request whose grant has no proof that passes is refused 401 with a fresh challenge.

10.5.4 Checking a proof

A verifier is given the grant presented, which it has read and checked as §4.3, §5 and the grant’s window require; the challenge it holds for the session or the request; the challenges it consumed; and the verb and target it received. It refuses at the first of these that fails:

  1. A proof is presented beside the grant (pop_missing).
  2. It reads: WRP1, version 1, each field of §10.5.1 whole, context of ! to ~ and space, and no byte after sig (pop_malformed).
  3. Its grant is the grant’s digest (pop_wrong_grant).
  4. Its key is the grant’s principal (pop_wrong_key).
  5. sig verifies under key, strictly (SPEC §7 Signatures) (pop_bad_signature).
  6. Its challenge is the one the verifier holds for this session or request. A challenge it consumed before is a proof presented again (pop_replayed); any other, another session’s or request’s or none it issued, is pop_wrong_challenge.
  7. Its context is the verb and the target the verifier received (pop_wrong_context).

It then consumes the challenge. A verifier that keeps no consumed challenges reports a proof presented again as pop_wrong_challenge, and refuses it all the same.

10.6 Seeking back

A relay that keeps a window of a room’s tracks, a DVR window (live-recording §8), lets a viewer seek back into it. The live edge of a track at a relay is the group of the Largest Object the relay knows of the track (MOQT §3.1.4). A FETCH (MOQT §3.2) seeks back when its range starts in a group below the live edge, and so does the fill of a SUBSCRIBE (MOQT §3.4); a fill that joins the group in progress (MOQT §3.5) starts at the live edge, and does not.

A relay serves a request that seeks back on a track of the room only to a session whose SETUP proved (§10.5) a grant that gives it the track (§10.2) and holds the seek power (§3.3). It refuses any other with REQUEST_ERROR UNAUTHORIZED (MOQT §12.3) and the reason phrase seek_not_granted. The power has no moqt action of its own: draft-ietf-moq-c4m-01 enables FETCH for a track, not for a range of it, so §10.2 step 5 enables FETCH for every grant that may subscribe, and the relay, which holds the grant beside the token (§10.1), applies this rule. A viewer admitted by Privacy Pass (§10.4) holds no grant, and does not seek back.

The rule is a function of the grant, the live edge and the request’s start: with L the live edge and (g, o) the start Location, the request seeks back when g < L, and is then served only when the grant’s powers has bit 4.

11. Reasons

The codes this extension adds, each with where it is raised:

reasonraised at
descriptor_invalid§3.4
key_log_missing§5 step 1
key_log_not_held§5 step 2
authority_not_listed§5 step 3
authority_revoked§5 step 4
grant_invalid§4.3 step 5
grant_too_long§4.3 step 6
caps_exceed_role§4.3 step 7
grant_exceeds_room§4.3 step 8
exchange_unlinked§4.3 step 3, §7.2 grant_exchange
exchange_mismatch§4.3 step 3
lifecycle_malformed§7.2
grant_unknown§7.2 join, grant_exchange
grant_wrong_principal§7.2 join
grant_revoked§5, §7.2 join, grant_exchange
grant_reused§7.2 join
already_present§7.2 join
not_present§7.2, §9.2 step 4
participant_limit§3.5, §7.2 join
outside_grant_window§4.3, §7.2, §9.2 step 4
power_not_granted§9.2 step 5
publish_not_permitted§7.2 publish, grant_exchange, takeover
profile_not_granted§7.2 publish, grant_exchange, takeover
already_published§7.2 publish
not_published§7.2 unpublish, takeover, failover
slot_unknown§7.2
slot_full§7.1, §7.2 publish
publisher_limit§7.3
exchange_not_current§7.2 grant_exchange
exchange_off_tick§4.6, §7.2 grant_exchange
takeover_not_higher§7.1, §7.2 takeover
failover_not_standby§7.1, §7.2 failover
failover_early§7.1, §7.2 failover
pop_missing§10.5.4 step 1
pop_malformed§10.5.4 step 2
pop_wrong_grant§10.5.4 step 3
pop_wrong_key§10.5.4 step 4
pop_bad_signature§10.5.4 step 5
pop_replayed§10.5.4 step 6
pop_wrong_challenge§10.5.3, §10.5.4 step 6
pop_wrong_context§10.5.4 step 7
seek_not_granted§10.6

Of live-events §9’s codes, a room verifier also raises truncated, malformed and unknown_version when a descriptor or a grant does not read (§3.4, §4.3); bad_key and bad_signature for their signatures (§3.2, §4.2); wrong_room for a grant of another room (§4.3 step 4); every code live-events §6 raises for a record (§9.2); tally_mismatch (§9.4); and delivery_gap for a gap, which is not a refusal.

12. Conformance

live-conformance/rooms/ holds the corpus, in one file, vectors.json, written from fixed seeds by wai_live_room_vectors gen. It has the records of each registered profile (§8.2), and vectors: a descriptor, grants, a key-log head or none, and chains, with the outcome §9.4 gives them.

A conforming verifier gives every vector the outcome, the reason, the place, the gaps and the state, order, tally, scopes and seek power the corpus states, every proof its outcome, and every request of seek its outcome. wai_live_room_vectors verify and the test tests/live_room.rs check that the file regenerates byte for byte, that wai-rs gives every vector its outcome, and that §11 lists exactly the reference’s codes. live-conformance/live_rooms_verify.py is the second implementation, written from this text: it shares no code with wai-rs, verifies signatures with WAI’s strict rule written from RFC 8032 (claim-conformance/_ed25519.py), verifies each event with the live-events corpus’s own second implementation (live_events_verify.py), and gives every vector, every proof and every request of seek the same outcome.

13. Security and privacy considerations