open-standards/wai/extensions/live-rooms.md
Transaction Science 1a8c1a5ead age assurance: one scope per room (live-rooms §10.4) refused by name otherwise, window redemption contexts, admission by the room's service over HTTP, ratings over MOQT
live-rooms §10.4's subscribing scopes did not fit the 255 bytes draft-ietf-moq-privacy-pass-auth-03
gives one MoQAuthorizationInfo: the room's scope is now one MoQAuthScope, the subscribing
actions on MATCH_PREFIX of [R] and on every track name, 78 bytes. A challenge's origin_info is
read by its binding: over MOQT the room's scope byte for byte (scope_malformed, scope_mismatch,
the previous revision's scopes among them); over HTTP RFC 9577's list of server names holding
the service's own. The corpus gains the scope and HTTP cases and RFC 9578 A.2's issuance, which
both verifiers check.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-21 16:15:15 -04:00

68 KiB
Raw Permalink Blame History

WAI Extension: Live Rooms

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

  • Room authority: the Ed25519 key (RFC 8032) that signs a room's descriptor and its grants.
  • Clock: the key, named by the descriptor, that stamps the room clock and authors the room's lifecycle events for the authority. It MAY be the authority's key.
  • Room key: the authority's key or the clock's.
  • Room clock, principal, event, chain: as in live-events §2.
  • Grant in effect: for a present principal, the grant its latest join or grant_exchange named (§9.3).
  • Slot: a virtual track the descriptor declares, which one live publisher stands behind at a time (§7.1).
  • Name: 1 to 32 bytes of a–z, 0–9, _ and -. A slot name is 1 to 32 bytes of a–z, 0–9 and _. A token is 1 or more bytes of ! to ~ (0x21–0x7E).
  • Kinds bitmap: 32 bytes in which kind k is set when bit k mod 8 of byte k div 8 is set, as live-events §5 writes kinds.

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.

field size meaning
magic 4 WRD1 (ASCII)
version 1 1 in this revision
authority 32 the room authority's Ed25519 key
clock 32 the clock's Ed25519 key
t0 8 u64: the room's t0, in microseconds since 1970-01-01T00:00:00Z
tick 8 u64: the room-clock tick, in microseconds, that a grant exchange takes effect on (§4.6)
origin long the origin whose key log lists the authority (§5); empty for none
max_present 4 u32: the most principals present at once (§3.5)
max_publishers 2 u16: the most principals publishing at once (§7.3)
reval 4 u32: the re-validation interval, in seconds, a token of the room carries (§10.2); 0 for none
ceiling 65 a WEP1 record (live-events §5): the event ceiling no grant exceeds (§3.5)
roles 1 + … a u8 count, then each role: name (short), publish (u8), subscribe (u8), max_layer (u8), powers (u8), events (32, a kinds bitmap) (§3.3)
slots 1 + … a u8 count, then each slot: name (short), media (u8), redundancy (u8), failover_ms (u32) (§7.1)
profiles 1 + … a u8 count, then each publish profile (§8.1)
review 37 kinds (32, a kinds bitmap), deadline_ms (u32), fallback (u8) (§3.6)
recording 12 + … mode (u8), retention_days (u16), commit_interval_ms (u32), jitter_ms (u32), then a u8 count and that many 2-byte regions (§3.7)
sig 64 §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:

  • publish: bit m − 1 for each media kind m the role may publish: 1 audio, 2 video, 3 screen (§6.1).
  • subscribe (0 or 1) and max_layer: whether the role may subscribe, and the highest layer (§6.3) it may subscribe to; 0 when it may not subscribe.
  • events: the event kinds it may send, a kinds bitmap within the ceiling's kinds.
  • powers: bit 0 moderate (its decision events, live-events §4.11, are a moderator's), bit 1 direct (it may send control commands other than recording), bit 2 compose (it may sign the room's composition descriptor and publish a composed track, live-compose §3.5, §7), bit 3 record (it may send record_start and record_stop), bit 4 seek (it may fetch a track of the room below its live edge, §10.6).

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:

  • tick is above 0; origin is empty or a token of at most 255 bytes; max_present and max_publishers are above 0.
  • ceiling is a policy live-events §5 reads, and it permits no lifecycle kind (0x10 to 0x1F).
  • There are 1 to 16 roles, each named by a name no other role has, with subscribe 0 or 1, publish setting no bit above bit 2, powers none above bit 4, max_layer 0 when subscribe is 0, events within the ceiling's kinds, and the powers §3.3 requires of its events.
  • There are at most 16 slots, each named by a slot name no other slot has, of a media kind of §6.1, with redundancy 1 and failover_ms 0, or redundancy 2 and failover_ms above 0.
  • There are at most 16 profiles, each meeting §8.1 and named by a name no other profile has. A profile whose name §8.2 registers is that registered profile, field for field.
  • The review policy and the recording policy meet §3.6 and §3.7.

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

3.5 Limits

  • max_present: a join that would make more principals present than this is refused participant_limit (§7.2).
  • max_publishers: §7.3.
  • ceiling: no grant's policy exceeds it (§4.3 step 8): its max_payload, max_text_cp, max_events and max_reactions are at most the ceiling's, and its window_us and reaction_window_us at least the ceiling's, so every window of the ceiling's length holds no more events than the ceiling allows. The ceiling's kinds bound the roles'.

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: 0 the room is never recorded; 1 it is recorded from a record_start command (live-events §4.9) of a principal with the record power; 2 it is always recorded.
  • retention_days: the most days a recording is kept; 0 states no bound.
  • commit_interval_ms and jitter_ms: how often a device capturing a take commits to it in the room's events, and the jitter its timing is allowed (capture-binding §5, §7). With mode 0 both are 0. Otherwise commit_interval_ms is above 0, 2 000 (two seconds) by default, and jitter_ms is at most commit_interval_ms, 500 by default.
  • regions: the region pin, for data residency: ISO 3166-1 alpha-2 codes, each two capitals A–Z, in ascending order, at most 16. A recording of the room, and the media and events a relay keeps of it beyond delivery, MUST be stored only within those regions; none means no pin. A take's upload receipts state where its master is stored (capture-binding §6.3), and a recording's manifest where the package is kept (live-recording §4.4); each is checked against the pin.

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

3.8 AI usage preferences

The descriptor's layout holds no AI usage preference. A room states one in a room usage record, WAU1 (ai-usage §4.2): a usage statement the authority signs, naming the room id, chained so that a preference can change at an instant of the room clock and never for what was published before it. An object of the room is governed by the record in effect at its time, and by its own statement where its manifest carries one (ai-usage §5.1). A grant states the terms its principal is granted in the attribute ai_usage (§4.4), and a principal whose grant carries terms is an agent principal, which a relay MAY require consumption claims of before it serves it (ai-usage §7).

4. The room grant

4.1 Layout

field size meaning
magic 4 WRG1 (ASCII)
version 1 1 in this revision
room 32 the room id
principal 32 the Ed25519 key the grant is for
prev 32 the digest of the grant this one is exchanged for (§4.6); 32 zero bytes for a first grant
effective 8 u64: for an exchanged grant, the room-clock tick it takes effect at; 0 for a first grant
nbf, exp 8 each u64: seconds since 1970-01-01T00:00:00Z, as RFC 8392's NumericDate: the grant is good from nbf until before exp
role 1 the index of its role in the descriptor
publish, subscribe, max_layer, powers 1 each its capability set (§3.3), within its role's
precedence 1 its precedence on a slot (§7.1)
once 16 a 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
viewer 16 the viewer id revocation names (§5); zero for none
policy 65 a WEP1 record (live-events §5): the event kinds it may send, its payload and text bounds, and its rate windows
joules_ceiling 8 u64: the energy its use may spend, in microjoules; 2^64 − 1 for no ceiling
funds_ceiling 8 u64: the funds its use may spend, in minor units of currency; 2^64 − 1 for no ceiling
currency 3 ISO 4217 alphabetic code; three zero bytes when there is no funds ceiling
profiles 1 + n a u8 count, then the index in the descriptor of each publish profile it publishes with
origins 1 + … a u8 count, then each delivery origin it is good for (short): a URI's scheme and authority, such as https://relay.example
attrs long its attributes (§4.4)
sig 64 §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, which age-assurance §8 also reads: a room in which a minor's grant that may publish takes effect meets its minors' profile. Age assurance (age-assurance §5.4) reads age: the threshold, in years, the authority admitted the principal under, a decimal integer from 1 to 21. AI usage (ai-usage §4.3) reads ai_usage: the canonical form of a usage statement, the terms the authority grants the principal, which override the room's preference for the uses they name. Attributes are signed with the grant, so a layout computed from them, and the terms, are what the authority stated.

One more attribute marks an AI participant: ai, the digest naming the model that acts for the principal (a prior's digest, SPEC §3.1), as 64 lowercase hexadecimal digits. A grant whose ai is not that form breaks this section's rule (§4.3 step 5, grant_invalid). The authority states it, as it states minor; a principal is an AI participant from the instant a grant carrying it is its grant in effect until one without it is. An AI participant's chat, reaction and caption events state that a machine produced them, with that model (§9.2 step 6), and the tracks it publishes are synthesized media (synthesis §8). A relay that forwards a room's events MUST NOT forward one that §9.2 step 6 refuses. A sink MUST show, with every presence, voice, avatar and message of an AI participant, that it is an AI system: the information Regulation (EU) 2024/1689 Article 50(1) asks a system that interacts with people to give. The sink's showing it is an engineering requirement of this extension, and is not a statement about the Regulation.

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:

  • A grant is revoked by adding its digest to the head's revoked_modules.
  • A viewer is revoked by adding its tag, SHA-256 over the 17 bytes wai:live-viewer (ASCII) and 0x01 followed by the 16-byte viewer, to the same list. Every grant naming that viewer is revoked with it, in every room whose descriptor names that origin.
  • The authority is revoked as any key is (jwp-receipts §7.6): with the key, every descriptor and grant it signed is refused.

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):

  • its Track Namespace is three fields:
    1. the room id, as 64 lowercase hexadecimal digits;
    2. the owner: the publishing principal's key as 64 lowercase hexadecimal digits, or, for a slot (§7.1), _ followed by the slot's name;
    3. the media kind: audio (1), video (2) or screen (3); or events for the principal's events track (live-events §8.1);
  • its Track Name is the layer (§6.3) in decimal, with no leading zero: 0, 1, …; the events track's is 0.

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

  • a media track's receipts track (moq-streaming-format §4.1), which carries the track's receipts, is in the media track's namespace, and its Track Name is the media track's followed by _receipts: 0_receipts, 1_receipts, … A media track that carries receipts has exactly this receipts track. The events track has none: each of its records is signed (live-events §3);
  • a publisher's catalog track (MSF §5), which carries its MSF catalog (§6.2), has a Track Namespace of the first two fields alone, the room id and the owner, and the Track Name catalog.

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:

  • single: one track, layer 0.
  • simulcast: layer k is the profile's k-th layer, an independent encoding on a track of its own.
  • staged: layer 0 is the base and layer k the k-th refinement (staged-delivery §1), each on a track of its own.
  • svc: one track, layer 0, carries every layer of the profile's scalability mode, named as the W3C "Scalable Video Coding (SVC) Extension for WebRTC" names them (L1T3, S3T3). A relay that forwards it to a subscriber whose max_layer is m forwards the spatial layers 0 to m alone.

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.

  • Takeover. A principal whose grant in effect has a higher precedence than the live publisher's replaces it: the clock records a takeover, the new publisher goes live, and the replaced one leaves the slot. A takeover by a precedence not higher is refused takeover_not_higher.
  • Redundancy. With redundancy 2, two publishers are bound to the slot: the first to publish into it is live, the second its standby. A third is refused slot_full, as is a second where redundancy is 1.
  • Failover. When the live publisher's media stops, the clock records a failover to the standby. The rule: the standby goes live only once failover_ms milliseconds of room clock have passed since the live publisher's last object reached the relay, which the event states as last_seen; earlier is refused failover_early. A failover to a principal that is not the standby is refused failover_not_standby. The failed publisher leaves the slot, and can publish into it again as the 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.

kind detail
join grant (32): the digest of the grant the subject takes part under
leave reason (u8): 1 left, 2 removed, 3 its grant ended, 4 connection lost
publish slot (empty for an own track), media (u8), profile (u8, the index of the profile in the descriptor), layers (u8, at least 1)
unpublish slot (empty for an own track), media (u8)
grant_exchange old (32), new (32): the grants' digests
takeover slot (not empty), media (u8), profile (u8), layers (u8, at least 1), previous (32): the live publisher replaced
failover slot (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:

  • join: the grant is one the verifier holds (grant_unknown); its principal is the subject (grant_wrong_principal); the subject is not present (already_present); the grant is not revoked (§5, grant_revoked); a grant whose once is not zero names a once no earlier join named (grant_reused); the grant is good at t (outside_grant_window); and the room holds fewer than max_present principals (participant_limit). The subject is present, under that grant.
  • leave: the subject is present (not_present). It is not present any more, its own tracks are unpublished, and it leaves every slot; a standby whose live publisher leaves goes live.
  • publish: the subject is present (not_present); a slot named is a slot of the descriptor of that media kind (slot_unknown); the grant in effect may publish the media kind (publish_not_permitted), and its profiles hold profile, a profile of that media kind with at least layers layers (profile_not_granted); the subject does not already publish that own track or stand in that slot (already_published); the slot has room (§7.1, slot_full); and then at most max_publishers principals publish (§7.3, publisher_limit). An own track is published with layers tracks; a slot's publisher is live if the slot has none, and its standby otherwise.
  • unpublish: the subject is present (not_present); a slot named is a slot of the descriptor of that media kind (slot_unknown); the subject publishes that own track or stands in that slot (not_published). It is unpublished; a standby whose live publisher unpublishes goes live.
  • grant_exchange: the subject is present (not_present); old is its grant in effect (exchange_not_current); new is a grant the verifier holds (grant_unknown) whose prev is old (exchange_unlinked); t is new's effective (exchange_off_tick); new is not revoked (grant_revoked) and is good at t (outside_grant_window); and for everything the subject publishes, its own tracks by media kind and then its places in slots in the descriptor's order, new may publish the media kind (publish_not_permitted) and gives the profile (profile_not_granted). new is then the grant in effect.
  • takeover: the subject is present (not_present); the slot is the descriptor's, of that media kind (slot_unknown); previous is its live publisher (not_published); the grant in effect may publish the media kind and gives the profile with at least layers layers (publish_not_permitted, profile_not_granted); the subject is not previous, and its grant's precedence is higher than the grant in effect of previous (takeover_not_higher); and then at most max_publishers principals publish (publisher_limit). The subject is live, with layers layers; previous leaves the slot, and the subject is no longer its standby.
  • failover: the subject is present (not_present); the slot is the descriptor's, of that media kind (slot_unknown); failed is its live publisher (not_published); the subject is its standby (failover_not_standby); last_seen is at most t, and t − last_seen is at least failover_ms × 1000 (failover_early). The standby is live, and failed leaves the slot.

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.

field size meaning
name short the profile's name
media 1 its media kind (§6.1)
codecs 1 + … 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
layering 1 0 single, 1 simulcast, 2 svc, 3 staged (§6.3)
mode short for svc, the scalability mode (§6.3): 1 to 16 of A–Z, 0–9 and _; otherwise empty
layers 1 + 14 × n a u8 count (1 to 8; exactly 1 for single), then each layer: width, height, fps (u16 each), min_kbps, max_kbps (u32 each)
sample_rate 4 u32: audio's sample rate in Hz; 0 for video
channels 1 audio's channel count, 1 to 8; 0 for video
processing 1 audio'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

name media codecs layering layers (width × height @ fps, kbps) audio
speech audio opus single 16–64 48 000 Hz, 1 channel; echo cancellation, noise suppression, gain control and DTX on
music audio opus single 128–510 48 000 Hz, 2 channels (stereo, fullband); no echo cancellation, noise suppression, gain control or DTX
screen screen av01.0.08M.08, avc1.640028 svc, L1T3 1920 × 1080 @ 15, 300–2500 —
camera-720 video av01.0.05M.08, avc1.64001F simulcast 640 × 360 @ 30, 150–500; 1280 × 720 @ 30, 500–1500 —
camera-1080 video av01.0.08M.08, avc1.640028 simulcast 640 × 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.
  6. A chat, reaction or caption event of a principal whose grant in effect carries ai (§4.4) states origin 2 (machine) or 3 (machine-reviewed), with the model ai names (live-events §4.4, §4.5, §4.10). One that states no origin, origin 1, or another model is ai_unmarked.

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:

  • present: each present principal and its grant in effect;
  • published: each own track, by principal and media kind, with its profile and its number of layers;
  • slots: each slot's live publisher and standby, and the live publisher's precedence: the slot's current precedence holder;
  • active grants: each change of a principal's grant in effect (a join or a grant_exchange), with its t, in the order they took effect.

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

  • refuse: the reason and where (§9.1). Step 5's tally_mismatch (live-events §7) is at the record of its poll_close.
  • gap: no record refused, and the live-events verifier does not hold every seq of every chain (live-events §6.2): the gaps, under the code delivery_gap. The room state is the replay of what it holds, and a tally it computes is unchecked.
  • accept: no record refused, no gap, and every stated tally equal to the replay.

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 claim CWT / CAT claim mapping
the authority iss (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
principal sub (2) the principal's did:key
room aud (3) the room id, 64 lowercase hexadecimal digits
nbf, exp nbf (5), exp (4) the same NumericDates
once cti (7), catreplay (308) a once not zero is the token's cti, with catreplay set to prohibit replay; zero, catreplay permits it
origins catu (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 renewal catr (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 binding catdpop (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, geography catnip (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 slots moqt §10.2
reval (descriptor) moqt-reval §10.2
role, powers, precedence, policy, profiles, attrs, joules_ceiling, funds_ceiling, viewer none WAI'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 claim URI Signing claim mapping
the authority iss (§2.1.1 there) as §10.1
principal sub (§2.1.2) the principal's did:key, as a JWE as that section requires
nbf, exp nbf (§2.1.5), exp (§2.1.4) the same, with no leeway
once jti (§2.1.7) a once not zero, so a CDN that keeps token ids refuses a replay
origins, room, max_layer cdniuc (§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 network cdniip (§2.1.10) no grant claim; a deployment MAY add it
renewal cdniets, cdnistt, cdnistd (§2.1.12–§2.1.14) a renewed token's exp is never later than the grant's
the rest none as §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).

The scope. The origin_info of a challenge for the room R is a MoQAuthorizationInfo of exactly one MoQAuthScope:

  • actions: SUBSCRIBE_NAMESPACE (3), SUBSCRIBE (4), FETCH (7) and TRACK_STATUS (8), in that order (§3.2.2 there);
  • namespace_match: MATCH_PREFIX (1) on the tuple of one element, R as 64 lowercase hexadecimal digits (§3.2.3 there);
  • track_name_match: MATCH_PREFIX on the empty string, a prefix of every track name (§3.3.1 there).

Its bytes, with R the 64 digits: 4d (the scopes, 77 bytes); 04 03 04 07 08 (the actions); 01 00 42 00 40 and R (the namespace match: its type, the tuple's 66 bytes, the element's 64); 01 00 00 (the track match: its type and an empty value). That is 78 bytes, the scopes taking 77 of the 255 the draft allows them.

Why one scope: the draft bounds the scopes of one MoQAuthorizationInfo to 255 bytes, and each scope restates its namespace match, 69 bytes for [R], so at most three scopes over R fit; the subscribing scopes of §10.2 step 5, two and then two for each layer, are four at the least, 320 bytes, and do not. A shorter form of the room id would rename every track of every binding (§6.1), and a digest of the room's subscription set is not a match rule the draft defines, so no relay could evaluate it. One scope with a prefix match is within the draft's grammar, binds the token to the room, and leaves 178 bytes whatever the room's layers. What it does not carry is applied from the room's own records: the token admits every track of the room, and a relay serves each track under the room's other rules, an age rating's among them (age-assurance §5.5). A room that bounds a token viewer's layers admits it by a grant instead, which its service issues once it has redeemed the token (age-assurance §5.6).

Reading the scope. A verifier reads the origin_info of a token's challenge by the draft's grammar (§3.2.4 there, in the TLS presentation language): the length of the scopes from 1 to 255, then exactly that many bytes of whole scopes and nothing after them; in each scope, actions of 1 to 255 bytes, each a MoQAction of the draft's table (0 to 8); each match type one of the table's four (0 to 3); each tuple, tuple element and track name whole within its length. An origin_info that does not read is refused scope_malformed. One that reads and is not, byte for byte, the one scope above for the room is refused scope_mismatch; so is an empty one, which scopes the token to no origin. The encoding the revision before this one gave, the scopes of §10.2 step 5 each with a MATCH_EXACT track match, is refused by these names and never read again as anything else: three of its scopes read, and are scope_mismatch; four or more do not fit the one byte of their length, and are scope_malformed. The codes are age-assurance's (its §5.2 step 2 checks a token's challenge, and its §11 lists them).

grant claim Privacy Pass field mapping
room, subscribe origin_info the one scope above
max_layer none the scope names every track of the room; a room that bounds layers admits by a grant (age-assurance §5.6)
nbf, exp redemption_context (§3.2.1 there; RFC 9577 §2.1.1.2) fresh for one session, or fixed for a window of time; a token is good while its challenger accepts its challenge (age-assurance §5.1)
once the token's nonce every token is single-use (§3.3.2 there); the challenger keeps each nonce it redeemed while a token of that challenge can still be presented (age-assurance §5.3)
principal, viewer none the token names no one; revocation is of the issuer's key, not of a viewer
policy, publish caps, ceilings, attrs none a 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). A room that admits viewers by an age threshold names the issuers of such a token, with one key per threshold, in an age policy, and a relay serves each track to them only at or above its rating (age-assurance §4, §5).

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

field size meaning
magic 4 WRP1 (ASCII)
version 1 1 in this revision
grant 32 the digest (§4.2) of the grant it is presented with
key 32 the Ed25519 key that signs it: the grant's principal
challenge 32 the verifier's challenge (§10.5.2, §10.5.3)
context long what the proof is for (below): ASCII, ! to ~ and space
sig 64 Ed25519 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.

  • Over MOQT the verb is SETUP, and the URI is the session's: the WebTransport URL the client opened, the URL of a WebSocket binding, or, over native QUIC, the moqt URI whose AUTHORITY and PATH options it sends (MOQT §9.1.1, §9.1.2). For https://relay.example/moq the context is SETUP //relay.example/moq.
  • Over HTTP the verb is the request's method and the URI its target: POST //gateway.example/whip/r1.

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

  • The challenge. The relay's SETUP carries the Setup Option GRANT_CHALLENGE (type 0x3A47, its value 32 bytes the relay draws for this session from a cryptographically secure generator). The relay sends its SETUP as soon as the session opens, without waiting for the client's: MOQT lets either endpoint send first (MOQT §6.3), and nothing in the relay's SETUP depends on the client's. A relay never issues one challenge twice.
  • The proof. A client that presents a grant waits for the relay's SETUP, then sends its own with the grant as an AUTHORIZATION TOKEN (MOQT §9.1.4: Alias Type USE_VALUE or REGISTER, Token Type 0, the value the grant's record, §4.1) and, beside it, the Setup Option GRANT_PROOF (type 0x3A49, its value a proof record over the session's challenge, the verb SETUP). GRANT_PROOF may appear once for each grant the SETUP presents (MOQT §9.1 lets an option's definition allow it), each proof naming its grant. The extension changes how the session is authorised, so the client sends its SETUP only after the relay's (MOQT §9.1: such an endpoint does not pipeline).
  • The code points. MOQT sets aside no range of Setup Options for unregistered use (MOQT §16.4): GRANT_CHALLENGE and GRANT_PROOF are proposed for its registry, and are not registered. A relay that does not know them ignores them (MOQT §9.1), and then honours no grant whose rule asks for a proof.
  • The check. The relay checks each proof by §10.5.4 before it serves any request of the session, a request that arrived before the SETUP among them (MOQT §6.3). A SETUP that presents a grant without a proof that passes is refused: the relay closes the session with UNAUTHORIZED (MOQT §12.2, 0x2), as the client is not authorised to establish it. A grant presented later, in a request's AUTHORIZATION TOKEN (MOQT §9.20.2), is honoured only where the session's SETUP proved possession of it; a request whose rights rest on another is refused with REQUEST_ERROR UNAUTHORIZED (0x1).
  • Its bytes. For a grant G and a relay at https://relay.example/moq, the client's SETUP carries AUTHORIZATION TOKEN = 03 00 ‖ G's record (USE_VALUE, type 0, the value), and GRANT_PROOF = WRP1 01 ‖ digest(G) ‖ principal ‖ challenge ‖ 19 00 ‖ SETUP //relay.example/moq ‖ sig: 4 + 1 + 32 + 32 + 32 + 2 + 25 + 64 = 192 bytes. The relay's GRANT_CHALLENGE is the 32 bytes alone.

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:

  • Authorization: Bearer and the grant's record in base64url without padding (RFC 4648 §5);
  • WAI-Room-Proof: and a proof record in base64url without padding, the verb the request's method and the target its own;
  • the challenge, from the response header WAI-Room-Challenge: and 32 bytes in base64url without padding. A server sends one on a 401 Unauthorized to a request that carries a grant and no proof that passes, with WWW-Authenticate: Bearer error="invalid_token" (RFC 6750 §3), and on each response to a request it accepted, for the client's next request: a WHIP or WHEP session's PATCH and DELETE (RFC 9725) each carry a proof over the challenge the previous response gave. A first request carries none, is answered 401 with a challenge, and is sent again with a proof over it, as a resource server's DPoP nonce is (RFC 9449 §9).

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:

reason raised 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
ai_unmarked §4.4, §9.2 step 6
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.

  • Accepted: a lesson-style room (a director, a performer, an audience, and the director's remote record control of the performer, under a key log whose head revokes a grant and a viewer the room never admits); a broadcast room (one publisher, many viewers, a moderator); a takeover; a redundant pair with failover; a grant exchange that promotes a viewer to a publisher; and a broadcast room with a DVR window, whose viewers' role holds the seek power, one viewer's grant holding it and another's narrowing it away (§10.6). Each states the room id, every grant's digest, the merged order with the grant each record was verified under, the room state (§9.3) with the names of the tracks a subscriber can reach (§6.1), the tally (live-events §7), each grant's moqt scopes and re-validation interval (§10.2), and whether the grant holds the seek power (§3.3).
  • Gap: a chain with a record not delivered.
  • Refused: each reason of §11 at least once, each vector a control of one defect, and the codes of live-events §9 that a descriptor, a grant or a record of a room raises; among them a grant with the seek power whose role lacks it, a grant and a role with a power above bit 4, a grant whose ai attribute does not read, and an AI participant's chat that states no origin, one that states origin 1 and one that names another model.
  • AI participant: a host whose grant names the model that acts for it, its chat and its caption stating that model, beside a person whose chat and reaction state no origin.
  • Proofs (§10.5), in the file's proofs: a grant of a room presented with a proof over MOQT (verb SETUP) and over HTTP (a WHIP POST and a DELETE, in their header forms), each accepted; and, each refused with its reason, a grant presented with no proof, a proof presented again on a later session or request, a proof by a key other than the grant's principal, one naming the principal and signed by another key, one over another session's challenge, one naming another grant, one for another endpoint or method, a MOQT proof presented over HTTP, and records that do not read. Each states the grant, the proof (or the headers), the challenge the verifier holds, the challenges it consumed, the verb and target it received, and the outcome.
  • Seeking back (§10.6), in the file's seek: requests on a track of the DVR room, each naming a grant of it by the vector's name and the grant's index, and stating the live edge, the request's start Location and whether it is a FETCH or the fill of a SUBSCRIBE. Served: a FETCH and a fill below the live edge under a grant with the seek power; and, under a grant without it, a FETCH from the live edge's first object and from within its group, a fill that joins the group in progress, and a FETCH from the start of a track whose live edge is group 0. Refused seek_not_granted: a FETCH and a fill below the live edge under the viewer's grant that narrows the power away, among them one from the group just below the edge, and a FETCH below it under the publisher's grant, whose role lacks the power. Each states the outcome, serve or refuse with its reason.

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

  • Every claim a principal acts under is signed by the room authority, and every event a principal sends by the principal; a relay can drop either but alter neither, and a room verifier replays the room from them alone.
  • t is its author's claim (live-events §11). The clock's lifecycle events fix when a principal was present and under which grant; a participant's t outside its grant's window or outside its presence is refused. A clock that lies about time is the room's own key lying, which its signature makes evidence of.
  • A grant is public, and is a capability only together with the principal's key: every event under it is signed by that key, so a copied grant lets no one else send, and every binding honours it only beside a proof of that key over the verifier's fresh challenge, bound to one session or one request and to the endpoint addressed (§10.5), so a copied grant admits no one else either. A token mapped from it (§10) is bound to the principal's key where the format allows (catdpop, cnf), and its window is fourteen days at most.
  • A proof is good for the session or the request whose challenge it signs, at the target it names. A verifier that issued a challenge and did not keep it refuses a proof over it; one that keeps consumed challenges tells a replay from a stranger's challenge, and both are refused.
  • Revocation is through the key log, so it is as available as the log (jwp-receipts §7.8): a relay with no fresh status for the authority's origin fails closed.
  • A viewer's tag is a hash of an id the authority assigns; the key log publishes the tag, not the id, and an authority SHOULD assign ids that are not otherwise meaningful.
  • A grant names its principal, so a room's grants link that principal's participation; a principal that needs participation unlinkable across rooms uses a key per room, and a viewer that needs no identity is admitted by Privacy Pass (§10.4).
  • The region pin (§3.7) binds where a recording is kept; checking a recording's receipt against it is the recording extension's.