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]>
1104 lines
68 KiB
Markdown
1104 lines
68 KiB
Markdown
# 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](live-events.md) §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](jwp-receipts.md) §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/`](../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](live-compose.md)); the review workflow, of which this extension
|
||
fixes only the policy's fields and their meaning (§3.6), and which
|
||
[live-moderation](live-moderation.md) 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](capture-binding.md)'s take.
|
||
|
||
Relationship to the core spec: `wai.live.room` is a **record** ([SPEC](../SPEC.md) §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](../SPEC.md) §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](live-compose.md) §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](live-moderation.md)'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](capture-binding.md) §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](capture-binding.md) §6.3), and a recording's manifest where the
|
||
package is kept ([live-recording](live-recording.md) §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](ai-usage.md) §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 `join`s 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](live-compose.md) 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](capture-binding.md) §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](age-assurance.md) §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](ai-usage.md) §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](synthesis.md) §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](packaging.md) §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](jwp-receipts.md) §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](http-delivery.md) §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](moq-streaming-format.md)
|
||
§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](moq-streaming-format.md) §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](moq-streaming-format.md) §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](staged-delivery.md) §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](unidirectional-delivery.md) §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](age-assurance.md) §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](age-assurance.md) §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](../SPEC.md) §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](live-recording.md) §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/`](../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.
|