WAI Extension: Live Worlds (a shared world in a room)
Mirrored from the canonical text at commit 260cbd09 ().
Status: Draft. Registers the record
wai.live.world: the room authority’s signed world statement,WLW1, that a room of live-rooms carries a shared world, which world its operations replay in, and the bounds every operation holds to. Each principal whose grant lets it act publishes a world track in the room: its signed operations (interactive-worlds §5.1), in op batches, one per group, each group signed into the publisher’s receipt chain as a room’s media track is. Every participant subscribes to every world track; one that enters late fetches each track from its first group; and each replays the merged operations under the canonical order to one session hash, whatever order they arrived in. In an end-to-end encrypted room (confidentiality) the world tracks are sealed, and each member carries the operations it holds into every epoch it reaches, so a member that joins later replays the world from its start. No new transport and no new receipt: the tracks, grants, receipts, sealing and metering are the live family’s. The reference implementation is thelive_worldmodule ofwai-rs(featurelive_world); the corpus islive-conformance/worlds/, and its second implementation islive-conformance/live_worlds_verify.py. Keywords MUST, MUST NOT, SHOULD, MAY are RFC 2119/8174.Texts read for this revision: the live family this extension builds on, as merged (live-rooms, live-events, live-recording, live-metering, confidentiality), interactive-worlds, jwp-receipts and moq-streaming-format;
draft-ietf-moq-transport-22(MOQT), an Internet-Draft, a section number of which in this text is that revision’s; RFC 8032 and FIPS 180-4.
1. Scope
In scope: the world statement, its signature and its rules (§3); the world track, its objects and their receipts (§4); the grant attribute that lets a principal act, and the rights it maps onto at a relay (§5); how the room’s lifecycle records an actor (§6); catching up (§7); private worlds, sealed in an end-to-end encrypted room (§8); verification and the session hash (§9); metering (§10).
Out of scope: a transport; the world’s simulation and its rendering, which are the world
capability’s (interactive-worlds §6; for wai.world.model, SPEC §5);
how a principal obtains a grant.
Relationship to the core spec: wai.live.world is a record (SPEC §5): a
signed record a WAI tool emits and verifies. It names no payload format and is not
dispatchable. It is not the capability wai.world.live of interactive-worlds §3, whose payload
is a world container with an open operation log: a room of this extension carries the
operations themselves, on tracks.
2. Terms
- Room, room authority, clock, principal, grant, grant in effect: as in live-rooms §2.
- Op: one
CausalOp(interactive-worlds §5.1), signed by its actor. Its op id is(actor, lamport). - Op batch: one or more ops, each whole, concatenated with nothing between them, as interactive-worlds §8 batches them.
- Actor: a principal whose grant in effect holds the attribute
worldwith the valueact(§5). A principal’s actor key is its room key: an actor signs its ops with the key its grant names. - World track: an actor’s track of op batches (§4.1).
- Admitted op: an op a participant has taken into its copy of the world (§9.2).
- Private world: a world in a room whose descriptor has a confidentiality record (confidentiality §3).
- Carry: in a private world, a member’s publication of the ops it holds (§8.2).
3. The world statement
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 | WLW1 (ASCII) |
version | 1 | 1 in this revision |
room | 32 | the room id (live-rooms §3.2) |
capability | short | the capability whose ops the world tracks carry: wai.world.model in this revision |
world | long | the world the ops replay in, in the capability’s form (§3.2) |
max_op_bytes | 4 | u32: the most bytes one op takes, from 136 to 65 536 |
max_ops | 2 | u16: the most ops one object holds, from 1 to 4 096 |
sig | 64 | §3.3 |
136 bytes is the size of an op with no dependency and an empty mutation (interactive-worlds §5.1).
3.2 The world
For wai.world.model, world is the 52 bytes of the model header the session hash binds
(§9.3): the model’s 32-byte identifier, then the generation seed as a u64, then the frame
width, the frame height and the frame rate, each a u32. The width, the height and the frame
rate are not 0.
3.3 Digest and signature
The digest of a statement is SHA-256 (FIPS 180-4) over its record from magic through
max_ops. The signature is Ed25519 (RFC 8032) by the room descriptor’s authority over
the 15 bytes wai:live-world (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.4 Reading and rules
A reader reads exactly the bytes it is given as one statement, in the order of §3.1, and refuses at the first of these that holds:
- Fewer than 4 bytes:
truncated. A magic other thanWLW1:malformed. No byte after the magic:truncated. A version other than 1:unknown_version. - The bytes end before a field the layout requires:
truncated. Bytes followsig:malformed. capabilityis notwai.world.model:world_capability_unknown.worldis not §3.2’s form,max_op_bytesis outside 136 to 65 536, ormax_opsis outside 1 to 4 096:world_statement_invalid.roomis not the room id of the descriptor the statement is read beside:wrong_room.- The signature does not verify under the descriptor’s
authority(§3.3):bad_keyorbad_signature.
A room has at most one world statement, for the whole room: a room that needs another world is another room.
4. The world track
4.1 Naming
An actor P of room R publishes one world track. With R and P as 64 lowercase
hexadecimal digits, its Track Namespace is [R, P, "world"] and its Track Name is 0; its
receipts track (moq-streaming-format §4.1) is in the same namespace, named 0_receipts. The
names keep live-rooms §6.1’s rules: every byte is one of a–z, 0–9 and _, and
namespace prefix matching on [R] reaches every world track of the room, as it reaches every
other track of it. A world track is not a media track: it has no layers, no profile and no slot,
and it is never composed. A publisher MAY list it in its catalog (live-rooms §6.2), with
packaging "wai-world"; this extension reads no catalog.
4.2 Objects
Each group of a world track holds exactly one object, Object ID 0, whose payload is one op
batch of at most max_ops ops, each at most max_op_bytes bytes. The groups are numbered from
0 with no gap: the actor’s nth object is group n − 1. The object is not a WAI envelope, and
carries no envelope flag.
In a world that is not private, every op on P’s track has P as its actor. In a private
world a track may also carry other actors’ ops, in its carries (§8.2).
An op byte for byte the same as one already admitted, carried again on any track, is a
redelivery, and changes nothing (interactive-worlds §5.3). A second op with the op id of an
op already admitted and other bytes is equivocation (interactive-worlds §5.3), wherever it
is carried. An op of P that P’s track carries, and that is not a redelivery, has a lamport
greater than that of every op of P the track carried before it, redeliveries among them
(interactive-worlds §5.1’s discipline, held per track).
4.3 Receipts
A world track carries receipts exactly as a room’s media track does
(moq-streaming-format §4.1, §5), not with the session receipts of interactive-worlds §8: each
group’s object is signed by P’s key into a group receipt
of one object (jwp-receipts §2.2, §2.3), its content hash taken over the op
batch as jwp-receipts §2.1 takes it over an envelope, each group receipt chained to the group
before it; the group’s receipts, in the binary form of jwp-receipts §2.7.3, are the receipts
track’s object of the same group, sent no later than the object; and the object carries its
WAI_RECEIPT property, except where it is sealed (§8.1; confidentiality §4.3). Its receipts’ track_alias (jwp-receipts §2.4.1) is 4, the
world track’s receipt alias: an actor’s catalog that lists the track states it as
wai_receipt_alias, and gives no other of the actor’s tracks that alias. The figure each
receipt carries is the publisher’s energy figure for the object, under its acquisition class
(energy-measurement §2), or the unmetered marker where it measured
none: a reader shows the marker as unmetered, never as a figure of zero joules.
A relay that checks receipts at a door holds every world track to its owner’s key: a door rule
over ["*", "*", "world"] whose trusted field is the namespace’s second (live-rooms §6.1). The
receipts bind the batch to the track and its position; each op’s own signature binds it to its
actor. A participant checks both (§9).
5. Rights
5.1 The attribute world
A grant lets its principal act in the room’s world when its attributes (live-rooms §4.4) hold
the key world with the value act. A grant without the key does not; one whose world holds
another value does not either. A grant that may subscribe (live-rooms §3.3) reads every world
track of the room, as it reads every track of the room. An actor’s grant need publish no media
kind: acting is not publishing media.
5.2 At a relay
A grant whose world is act maps onto one scope beyond live-rooms §10.2’s, after its step 4:
[[2, 6], [R, P, "world", nil]] (PUBLISH_NAMESPACE and PUBLISH of its world track and the
track’s receipts track). A relay that honours a room’s grants (live-rooms §10.5) accepts a
PUBLISH under [R, P, "world"] only from a session whose SETUP proved such a grant for P in
R, and refuses any other with REQUEST_ERROR UNAUTHORIZED (MOQT §12.3): a principal never
publishes another’s world track, and a grant without the attribute publishes none.
5.3 Seeking back
Catching up (§7) fetches a world track below its live edge, which live-rooms §10.6 serves only to a grant that holds the seek power. A room authority that admits actors SHOULD give every grant of a world room the seek power.
6. The lifecycle
An actor is recorded in the room’s lifecycle (live-rooms §7.2) by the room’s clock, as any
principal is: the clock signs its join, naming its grant, when its world track appears at the
relay, and its leave when the track ends. Its world track is not a media track, so the clock
signs no publish for it. A participant learns the room’s actors from the clock’s chain
(live-events §8.1): every principal whose join names a grant holding the attribute, present
or since gone. A page authors none of the lifecycle.
7. Catching up
A participant that enters a room in progress, for each actor §6 names, fetches the actor’s world track and its receipts track from group 0 to the live edge (MOQT §3.2; live-rooms §10.6), and admits the fetched objects in group order before any object of that track it received live. An object received both ways is admitted once. It does so for an actor that has left as well: its ops are part of the world.
A relay that carries a world room SHOULD keep every group of the room’s world tracks and of
their receipts tracks, from group 0, while the room lasts: for example a DVR window
(live-recording §8) over ["*", "*", "world"], which the ops’ small size
makes cheap. Because a track’s groups are numbered from 0 with no gap (§4.2), a participant
knows when its copy of a track is whole: a fetch that does not return every group from 0 to the
live edge leaves the world incomplete, world_incomplete, at that track and its first
missing group. A participant whose world is incomplete MUST NOT report a session hash as the
room’s; it MAY show the world it holds, marked incomplete.
8. Private worlds
8.1 Sealing
In a private world every object of every world track is sealed as confidentiality §4 seals the
object of a media track, under the actor’s keys of its current epoch (confidentiality §5.7),
and opened as confidentiality §6 opens one: this extension adds the world track to the tracks
confidentiality §1 seals. The original payload (confidentiality §4.4) is the op batch. The
receipt is over the sealed bytes (confidentiality §8.1), so a relay routes, caches, receipts and
meters ciphertext alone. An actor’s grant may subscribe, so its principal is a member once the
clock records its join (confidentiality §5.4); a participant outside the room’s group opens
nothing. Every world track remains its owner’s: confidentiality §6 step 6 refuses an object
sealed under another member’s Key ID.
8.2 Carries
A member opens only what is sealed in the epochs it holds, from the epoch that added it (confidentiality §5.7), and a relay keeps nothing of such a room (confidentiality §9). Fetching cannot bring a later member the ops sealed before it joined. Members carry them instead:
- A member MUST, in each epoch it reaches, its first included, before any other object it
seals in that epoch, publish a carry: every op it has admitted, of every actor, the actors
in ascending bytewise order of their keys and each actor’s ops in ascending
lamport, in as many objects of its own world track asmax_opsrequires, each sealed in that epoch. The order keeps §4.2’s rule for the member’s own ops, which a later member may not have held. - Until it holds an epoch, a member publishes nothing; an op it makes meanwhile waits, and is published after its first carry.
- A carry is published once. A member that enters SHOULD follow the world track of every actor the room’s log records before it publishes its own: its own track is what makes the clock record its join (§6), and so brings the epoch the members carry in. A carry published before the member followed its sender’s track does not reach it.
- A participant admits each op of a carry as it admits any op (§9.2). A carried op of another actor is that actor’s, by its own signature: a carry cannot make an op, and an op it carries again is a redelivery.
A carry can leave out an op, and no participant can tell from the carry alone (§12).
9. Verification
9.1 Inputs
A verifier is given: the room’s descriptor and grants, which it checks as live-rooms §4.3 does; the world statement; whether the world is private; and, for each world track, its owner and its objects in group order, each with its Group ID, its receipts-track object and the bytes its receipt covers, and, in a private world, the op batch that opened from them (opening is confidentiality §6’s, and its corpus is confidentiality’s). Each track’s objects are what the verifier received of it, live and fetched, each location once.
9.2 Steps
The verifier reads the descriptor (live-rooms §3.4), checks the grants in order (live-rooms §4.3) and reads the statement (§3.4), and refuses at the first failure, naming the descriptor, the grant or the statement. It then takes the tracks in the order given, and each object of a track in group order through every step below before the next, and refuses at the first of these that fails, naming the track and the group:
- The owner has a grant in the list whose
worldisact(§5.1):world_not_granted. - The track’s first object is group 0, and each later object is the group after the one before
it:
world_gap. In a private world the first object is the first the verifier opened, at any group, since a member opens nothing sealed before it joined (§8.2); each later object is still the group after the one before it. - The receipts-track object reads (jwp-receipts §2.7.4) and holds one group receipt and one
object receipt of that group:
malformed. Its group receipt is signed by the owner’s key, withtrack_alias4 (§4.3), and both receipts name the object’s Group ID, the object receipt Object ID 0: otherwisemoqt_mismatch(moq-streaming-format §4.1), before any signature is checked. The receipts then verify by jwp-receipts §3 against the bytes the receipt covers: the group’s signature, the content hash, the object’s proof to the signed root, the group’s total (the object’s figure is the group’s, and a labelled group’s class is the one its object composes to), and the chain, each group receipt’s parent the link hash of the group receipt before it on the track and the first’s none. In a private world the first object the verifier opened, when it is above group 0, names the group before it, which the verifier does not hold: its parent is not checked, and it starts the chain. The reason jwp-receipts §3.3 names is reported. - The op batch is one or more ops, each whole (interactive-worlds §5.1):
op_malformed. It holds at mostmax_opsops:batch_too_large. Each op is at mostmax_op_bytesbytes:op_too_large. - Each op, in batch order: its signature verifies under its actor (interactive-worlds §5.1,
SPEC §7 Signatures):
bad_op_signature; its actor is the owner, in a world that is not private:actor_not_owner; it is an op of the statement’s capability (forwai.world.model, an action, mutation kind 1, whose body reads):op_not_action; it is not equivocation (§4.2):equivocation; where its actor is the owner and it is not a redelivery, it keeps §4.2’s Lamport rule:lamport_regress.
An op that passes is admitted; a redelivery was admitted already, and is not admitted again.
9.3 The session hash
The room’s world is the set of admitted ops, each once. For wai.world.model, its session
hash is the action trace’s (SPEC §5): BLAKE3 over the 24 bytes wai:world-model-session
(ASCII) and 0x01, followed by the statement’s world, chained through each op in canonical
order (interactive-worlds §5.2): the chain’s next value is BLAKE3 over that domain, the value
before it and the op’s whole bytes, its signature included. Two participants that hold the
same admitted ops hold the same session hash, whatever order the ops arrived in and whichever
tracks carried them.
9.4 Outcomes
A verification that refuses states the reason and where. One that passes states the session hash, the number of admitted ops and the actors whose ops it admitted, each with the number of its ops; and, for a participant that caught up, whether its world is whole (§7).
10. Metering
A relay’s per-hop session record (live-metering §3) counts a room’s world tracks with its other tracks: neither media tracks nor events tracks, they are counted together under the record’s entry for the room’s other tracks. A world adds no record of its own.
11. Reasons
The codes this extension adds, each with where it is raised:
| reason | raised at |
|---|---|
world_capability_unknown | §3.4 step 3 |
world_statement_invalid | §3.4 step 4 |
world_not_granted | §5.1, §9.2 step 1 |
world_gap | §4.2, §9.2 step 2 |
world_incomplete | §7 |
op_malformed | §9.2 step 4 |
batch_too_large | §4.2, §9.2 step 4 |
op_too_large | §4.2, §9.2 step 4 |
bad_op_signature | §9.2 step 5 |
actor_not_owner | §4.2, §9.2 step 5 |
op_not_action | §9.2 step 5 |
lamport_regress | §4.2, §9.2 step 5 |
equivocation | §4.2, §9.2 step 5 |
Of live-events §9’s codes, a verifier also raises truncated, malformed and
unknown_version when a statement does not read, bad_key and bad_signature for its
signature, and wrong_room for a statement of another room (§3.4). Of jwp-receipts §3.3’s, it
raises moqt_mismatch and those its §3 raises for a receipt, chain_break and
parent_not_held among them (§9.2 step 3). world_incomplete is a participant’s
report about its own copy, not a refusal of the room.
12. Conformance
live-conformance/worlds/ holds the corpus, in one file,
vectors.json. Each vector gives the descriptor, the grants, the statement, whether the world
is private, and the world tracks, and either the outcome (§9.4) or the refusal, its reason and
where. wai-rs’s wai_live_world_vectors gen writes it from fixed seeds, byte for byte the
same each time, and check verifies it; live-conformance/live_worlds_verify.py, written from
this text, verifies it too, and the two agree on every vector. The corpus holds, as negative controls,
every code §11 adds that a verifier raises, the codes of §3.4 a statement can reach (bad_key
cannot be: a statement’s key is the descriptor’s authority, which the descriptor’s own signature
held to SPEC §7 already), and of jwp-receipts §3.3 malformed, moqt_mismatch,
signature_verify_failed, content_hash_mismatch, group_total_mismatch, chain_break and
parent_not_held; and accepted worlds, public, and private with carries that bring a late member
the ops sealed before it joined.
A browser participant is the reference room client’s world page (joule-moq-web, web/world/),
which runs §9.2 in the page through wai-live-web and checks receipts as the room client does.
The room service joule-moq-room keeps world rooms ([world]), and its tests and the world
and privateworld scenarios of joule-moq-web/e2e/ run two participants and a late one
through joule-moq-relay.
13. Security and privacy considerations
- What a receipt proves, and what an op’s signature proves. A world track’s receipts prove that its owner published each batch at its position in its chain, and the door that checked them; an op’s signature proves its actor made it. Neither proves the op was sensible: a world’s simulation decides what an op does (interactive-worlds §6), and every participant decides it the same way.
- Withholding. A relay can withhold groups of a world track, as it can any track’s; a participant whose fetch has a gap knows its world is incomplete (§7), and two participants that compare session hashes see that they differ. In a private world a member can leave ops out of its carry, which a later member cannot detect from that carry; it can compare its session hash with another member’s, and the other members carry what they hold too.
- Equivocation. An actor that signs two ops with one op id is refused at the second wherever it arrives (§4.2), so it cannot show two participants two different worlds under one history without being caught by whichever receives both.
- Flooding.
max_op_bytesandmax_opsbound an object; a relay’s per-session limits bound a session’s rate; the room authority bounds the actors by the grants it issues. - Privacy. A public world’s ops, actors and timing are visible to every participant and every relay. A private world’s ops are visible to its members alone; its relays see the ciphertext’s size and timing, the track names (and so which principals act) and the receipts, as for any sealed track (confidentiality §8.3).