WAI Extension: Forensic Watermark Sessions
Mirrored from the canonical text at commit b1681667 ().
Status: Draft. Registers the record
wai.live.watermark: a signed record of each session in which an edge chose forensic A/B variants for a viewer of a room, as ETSI TS 104 002 has an edge choose them. The edge signs a session record,WWS1, when its part in the session ends: the room and the content, the span, the content keys the variants were encrypted under, the watermark token by its digest, the positions of the pattern it embedded with the variant at each, and a salted commitment to the session’s grant and principal that only the room authority can open. Anyone who recovers a variant sequence from a leaked copy states it in a signed recovered sequence,WWQ1; a deterministic rule says which session records it agrees with. For one matched session, the room authority signs a disclosure,WWD1, that opens the commitment: a third party checks it against that session record and the grant it names, and learns nothing of other sessions. The record covers Lane A delivery alone (SPEC §6.5): a room that is end-to-end encrypted has no edge that can mark it, and a record for one is refused. No new transport and no new token format: the token is TS 104 002’s. The reference implementation is thelive_watermarkmodule ofwai-rs(featurelive_watermark); the corpus islive-conformance/watermark/, and its second implementation islive-conformance/live_watermark_verify.py. Keywords MUST, MUST NOT, SHOULD, MAY are RFC 2119/8174.Texts read for this revision: ETSI TS 104 002 V1.1.1 (2023-08), “the TS”, downloaded from the ETSI deliverables server, whose terms allow reading it and refer reproduction to ETSI: this text cites its clauses and reproduces none of it; RFC 8392 (CWT), which the TS requires the token to be; RFC 8949 §4.2 (deterministically encoded CBOR) and RFC 8610 (CDDL); RFC 9052 (COSE structures, which obsoletes the RFC 8152 that RFC 8392 cites, with the same structures) and RFC 9053 (COSE algorithms), which the TS cites; and RFC 4648 §5. A clause or section number of one of those texts is that revision’s.
1. Scope
In scope: where the record applies, and why only Lane A (§3); how a WAI verifier reads the TS’s watermark token and what it takes from it (§4); the session record, its positions and bits, the commitment to the session’s grant, its signature, and who may sign it (§5); what the edge keeps, where, for how long, and who reads what (§6); the recovered sequence and the rule that matches it against session records (§7); the disclosure, when the authority signs one, and how a third party checks it (§8); and verification (§9).
Out of scope: a transport; the TS’s content preparation, its WMPaceInfo carriage and its origin endpoints, which this extension reads and does not change; how a vendor’s detector recovers a pattern from a copy, which the TS leaves out of its own scope (§5.8 there) and which this extension does not define either (§7.5); the revocation of a token, also out of the TS’s scope; client-driven and client-side watermarking, which the TS leaves out (§4 there); and how a recovered sequence or a disclosure reaches anyone.
Relationship to the core spec: wai.live.watermark 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 one of the records SPEC §6.5 names for a Lane A title.
Relationship to live-rooms: a session record is bound to the grant its session ran under (§4 there), through a commitment (§5.3), and an edge is admitted to sign one through the authority’s key log (§5 there; §5.6 here). The room’s region pin and retention bound (§3.7 there) bound what the edge keeps (§6).
Relationship to confidentiality: a room with a confidentiality record is Lane B and has no session record (§3.2).
Relationship to live-metering: an edge that signs session records is a hop of the room’s sessions and signs its per-hop record there as well; neither record names the other. A per-hop record names grants in its ledger, which the deployment gives only to the parties that verify the receipt (live-metering §11); a session record names none.
2. Terms
- Room, room id, room authority, clock, room key, principal, grant: as in live-rooms §2.
- Viewer: a principal that subscribes to a room’s tracks under a grant.
- Edge: the server that chooses, for each segment of a session, which variant it serves, and signs the session record: the TS’s edge (§5.1 there), here the hop that serves the viewer’s session (§3.3).
- Segment, variant, WM pattern, sequencing: as in the TS’s §3.1. A variant is
numbered as the TS’s
variantIdnumbers it, from 0: variant 0 is A, variant 1 is B. - Token: the TS’s WM token (§5.4 there), as the bytes base64url (RFC 4648 §5) decodes it to: one CBOR data item.
- Pattern bit p: bit p of the WM pattern, p from 0, addressed big-endian as the TS’s §5.4 requires: bit 7 − (p mod 8), the most significant first, of byte ⌊p / 8⌋.
- Session: what an edge serves one viewer under one grant and one token: the subscriber sessions (MOQT sessions, or the HTTP requests of a playback) in which that token is presented under that grant (§3.3).
- Claimant: whoever states a recovered sequence and signs it.
- u64, u32, u16, u8: little-endian unsigned integers of that many bits. A short
field is a u8 length and that many bytes. A digest is SHA-256 (FIPS 180-4), 32 bytes.
Ed25519 is RFC 8032’s, verified strictly (SPEC §7 Signatures): a key that fails step 1 or
2 is
bad_key, and a signature that fails a later step isbad_signature. - A bitmap of a pattern of
patlenbits is ⌈patlen/ 8⌉ bytes, bit p addressed as a pattern bit is, and its bits frompatlenon, in its last byte, are 0.
3. Lane A only
3.1 Where the record applies
The record applies to a session whose media the edge delivered as Lane A content (SPEC §6.5): segments encrypted under content keys that a platform’s protected media path decrypts, with the decoded frames out of the application’s reach. That is the delivery the TS is written for: its functional architecture has the content encrypted and protected by a DRM system, with the A and B variants of a representation encrypted under one content key, so that one licence opens whichever variant the edge serves (§5.2, §5.6.1 there).
A session record names the key identifier (KID) of every content key its variants were
encrypted under, the KID a release decision of wai.video.keys names (SPEC §5), so the
session is tied to the release decisions of those keys without the record naming the licence
or the viewer. A record that names none is refused not_protected. A deployment that marks
content delivered in the clear is refused by nothing in WAI, and this record does not cover
it: a plaintext object of a room is already named by its content hash and its receipt, and an
edge’s choice between two plaintext variants is not a Lane A delivery.
3.2 Rooms that are end-to-end encrypted
A room with a confidentiality record (confidentiality §3) is Lane B, and has no edge that can mark it:
- Each object of its media tracks is sealed by its publisher, and the AEAD authenticates the
object’s Full Track Name, Group ID and Object ID (confidentiality §4.1, §4.7). An edge that
served an object of a variant track as an object of the viewer’s track would have it refused
by the receiver,
bad_tag; and MOQT holds one object at one location (confidentiality §6 step 8), so one track cannot carry one object to one subscriber and another to the next. - The only parties that could vary what one member receives are its publisher, which holds no per-viewer choice in this model, and the member’s own device, which is client-side watermarking, outside the TS (§4 there).
- Lane B does not keep the decoded output from the member’s application (SPEC §6.5), so the premise of the TS’s server-driven model, that the device only conveys the token and cannot see or choose its variants, does not hold for it.
A verifier given a room’s confidentiality record refuses every session record of the room
e2ee_room (§9.1 step 2). An edge MUST NOT sign a session record for such a room.
3.3 The edge
The edge is the server of the TS’s server-driven architecture (§5.1, §5.2 there): it validates the viewer’s token, which accompanies every request for a watermarked segment (§5.7.5.1 there), reads each segment’s WMPaceInfo (§5.5, §5.7.5.2 there), and serves the variant the pattern selects. In a room, it is the hop that serves a subscriber session one of the room’s tracks:
- Over HTTP, a rendition of the room’s tracks (live-rooms §10.3) served as the TS’s §5.7 serves a manifest’s segments, the token carried as its §5.7.3 carries it.
- Over MOQT, a relay serving a track of the room (live-rooms §6.1): a segment is one group of the track; the variants of the track are two upstream tracks whose names are the deployment’s (this revision names none); the subscriber receives the track under the name live-rooms §6.1 gives it, as the TS keeps the manifests a device receives neutral (§5.6.4 there). How a MOQT subscriber presents the token is not defined in this revision; an edge that receives it by the deployment’s means signs the same record.
The edge admits the session under the viewer’s grant, with proof of possession of its key (live-rooms §10.5), so it knows the grant and the principal. A session admitted without a grant (live-rooms §10.4) names no principal, and has no session record.
A session record covers one session. An edge binds a token to the grant under which it is first presented, and refuses it under any other grant, so one token is one session’s (§9.1 step 3.8); a viewer that reconnects under its grant with its token continues its session. An edge that is given a new token ends the record of the old one and starts another, and the records of one viewer’s sessions have spans that do not overlap.
4. The watermark token
4.1 Its bytes and its digest
The token travels as the TS’s §5.4 has it: a CWT (RFC 8392) whose claims set has integer keys and is deterministically encoded (RFC 8949 §4.2), signed as RFC 8392 §7 has it, and base64url-encoded. Its bytes are what the base64url text decodes to, and its digest is SHA-256 over those bytes. A session record names its token by the digest alone (§5.1).
The record does not carry the token, for two reasons. The token may carry what identifies a
viewer: RFC 8392’s sub, and in the TS’s indirect mode a wmid that its vendor derives the
pattern from, which the TS leaves to the vendor and which may be an account’s identifier (§5.4
there); a record that carried it would put that in the hands of everyone who matches recovered
sequences. And until its exp, a token can be presented again to obtain segments marked with
its pattern. The token travels beside the record to the parties that check it (§5.7, §6.3).
4.2 Reading a token
A reader refuses a token at the first of these steps that fails, with the code it names:
token_malformed where the token is not what the TS and the RFCs it cites require, and
token_unsupported where it is a form they allow that this revision does not read.
-
CBOR. The bytes are one data item (RFC 8949 §3), with no byte after it, read by these rules, item by item from the start, each refused at the first rule it breaks:
- an item nested more than 16 deep (the token itself at depth 1, an item inside an array,
a map or a tag one deeper than it) is
token_unsupported, checked before the item is read; - a head whose additional information is 28, 29, 30 or 31 (a reserved value, or an
indefinite length or a break, which deterministic encoding forbids) is
token_malformed; so is an argument not written in its shortest form (RFC 8949 §4.2.1); - in major type 7, a simple value written in one extra byte below 32 is
token_malformed(RFC 8949 §3.3); a floating-point value, or any simple value other than false, true and null, istoken_unsupported; - a byte or text string longer than the bytes left, an array or a map that runs past them,
and a text string that is not UTF-8 (RFC 3629) are
token_malformed; - a map whose keys, each read when it is reached, are not in strictly ascending bytewise
order of their encodings (RFC 8949 §4.2.1, which also rules out a key repeated) is
token_malformed.
- an item nested more than 16 deep (the token itself at depth 1, an item inside an array,
a map or a tag one deeper than it) is
-
The CWT tag. When the item is tag 61 (RFC 8392 §6), the item it holds is read on, and is a COSE tag (16, 17, 18, 96, 97 or 98, RFC 9052 §2): otherwise
token_malformed(RFC 8392 §7.2 step 2). -
The COSE tag. The item is tag 17, COSE_Mac0, or tag 18, COSE_Sign1: the two structures of the TS’s algorithms (§4.3). Another COSE message, or an untagged one, whose type RFC 8392 §7.2 step 3 leaves to an application context the TS does not fix, is
token_unsupported. -
The structure. The tag holds an array of four: a byte string (the protected header), a map (the unprotected header), a byte string or null (the payload), and a byte string (the tag, or the signature). Otherwise
token_malformed. A null payload, detached, istoken_unsupported. -
The headers. The protected header is empty, or holds one data item, read as step 1 reads, that is a map (
token_malformed). Every label of the two maps is 1 (alg) or 4 (kid): another istoken_unsupported(RFC 8392 §7.2 step 4). A label in both maps istoken_malformed(RFC 9052 §3).algis in the protected map (token_malformed), and is 5 or −7 (token_unsupported); 5 needs tag 17 and a tag of 32 bytes, −7 tag 18 and a signature of 64 bytes (token_malformed). Akidis a byte string (token_malformed). -
The payload holds one data item, read as step 1 reads. A tag 61 or a COSE tag there is a nested CWT (RFC 8392 §7.2 step 6),
token_unsupported. Otherwise it is a map (token_malformed) whose every key is an integer (token_malformed, the TS’s §5.4): the claims set. -
The claims, all
token_malformedbut where another code is named:exp(4) andiat(6) are present, as the TS requires, and are unsigned integers;nbf(5), when present, is an unsigned integer;iss(1),sub(2) andaud(3), when present, are text strings, andcti(7) a byte string;wmver(300) is present and an unsigned integer, and is 1 (token_unsupported: the TS describes version 1);wmvnd(301) is present and an unsigned integer of at most 255, andwmpatlen(302) one of at most 65 535;wmsegduration(303), when present, is an array of two unsigned integers;wmid(305), when present, a text string;wmopid(306) andwmkeyver(307), when present, unsigned integers;- with
wmpattern(304) present, the token is in direct mode, and the claim is a byte string of exactly ⌈wmpatlen/ 8⌉ bytes, the pattern in the clear; or an array of three items, a byte string, a map, and a byte string or null (COSE_Encrypt0, RFC 9052 §5.2), or of four whose fourth is an array of at least one item (COSE_Encrypt, §5.1), the pattern encrypted. Without it, the token is in indirect mode, its pattern derived by the vendor’s core (Annex A there), andwmid,wmopidandwmkeyverare present.
Other claims are not read: the TS’s claims set admits any integer-keyed extension.
4.3 What a token gives
A token that reads gives: its alg; its vendor, wmvnd; its pattern length,
wmpatlen; its source, 1 for a pattern in the clear, 2 for an encrypted one, 3 for a
derived one; the pattern itself where it is in the clear; and its window: it is good at the
instant u, in microseconds since 1970-01-01T00:00:00Z, when
max(iat, nbf) × 10^6 ≤ u < exp × 10^6, nbf counting as 0 when absent.
4.4 What a WAI verifier does not check
A WAI verifier does not check the token’s MAC or signature. HMAC 256/256 is keyed by a secret the token’s issuer shares with the edge, and ES256 by the issuer’s key, neither of which a WAI verifier holds; the edge checks it (the TS’s §5.7.5.1), and an edge MUST NOT sign a session record for a session whose token did not validate. The edge’s signature on the record is the edge’s statement that it did. Nor does a WAI verifier open an encrypted pattern or derive an indirect one: the record states the pattern the edge sequenced by, and §5.7 checks it against the token where the token holds it in the clear.
5. The session record
5.1 Layout
| field | size | meaning |
|---|---|---|
magic | 4 | WWS1 (ASCII) |
version | 1 | 1 in this revision |
room | 32 | the room id |
edge | 32 | the edge’s Ed25519 key, which signs the record |
owner | short | the content: the owner field of its Track Namespace (live-rooms §6.1), 64 lowercase hexadecimal digits of its publisher’s key or _ and a slot’s name |
media | 1 | the content’s media kind: 1 audio, 2 video, 3 screen |
kids | 1 + 16 × k | a u8 count (0 to 8), then the KID of each content key the variants were encrypted under (§3.1), strictly ascending |
start, end | 8 each | u64: the session’s span on the room clock, in microseconds: from start, up to and not including end |
token | 32 | the digest of the session’s token (§4.1) |
vendor | 1 | the token’s wmvnd |
source | 1 | how the edge has the pattern: 1 in the clear, 2 decrypted, 3 derived (§4.3) |
patlen | 2 | u16: the token’s wmpatlen |
served | ⌈patlen / 8⌉ | a bitmap: the positions at which the edge served the session a sequenced segment (§5.2) |
bits | ⌈patlen / 8⌉ | a bitmap: at each position of served, the variant it served there (0 or 1); 0 elsewhere |
segments | 4 | u32: the sequenced segments it served the session |
unmarked | 4 | u32: the segments it served the session that were not sequenced (§5.2) |
binding | 32 | the commitment to the session’s grant and principal (§5.3) |
region | 2 | where the edge keeps the record: an ISO 3166-1 alpha-2 code, two capitals (§6.1) |
expires | 8 | u64: seconds since 1970-01-01T00:00:00Z, the instant from which the record, its token and its opening are kept no more (§6.1) |
sig | 64 | §5.4 |
5.2 Positions and bits
Each segment the edge serves carries a WMPaceInfo position (the TS’s §5.5.2). The edge
sequences a segment when sequencing is enabled at it (§5.3 there) and the position is at
least 0 and below patlen: it serves the variant the pattern bit at that position selects,
variant 0 for 0 and variant 1 for 1 (§10.3), sets the position in served and the variant in
bits, and counts the segment in segments.
Every other segment it serves is unmarked, served from variant A’s endpoint as the TS’s
§5.3 and §5.7.5.1 have it, counted in unmarked, and in neither bitmap: a segment of position
−1 (not watermarked, or a contingency of §5.7.5.1 there, which that clause keeps out of
detection); one served while sequencing was disabled at the edge, when every viewer receives
variant A; and one whose position is at or beyond patlen, which has no bit in the pattern,
since the TS addresses bits within the pattern and defines no wrap. An edge that serves an ad
from another edge that does not sequence (§5.7.2 there) serves nothing for it.
One position may be sequenced many times in a session, as positions repeat: each time the same
bit selects the same variant. So bits is the pattern at the positions of served, and 0
elsewhere: what the edge embedded in the session, and nothing it did not.
5.3 The binding
The binding is SHA-256 over the 27 bytes wai:live-watermark-binding (ASCII) and 0x01,
followed by salt (32), grant (32) and principal (32):
salt: 32 bytes the edge draws for the session from a cryptographically secure generator;grant: the digest of the grant the session ran under (live-rooms §4.2);principal: that grant’sprincipal.
The opening of a record is its digest (§5.4), salt, grant and principal. The edge
gives the opening to the room authority when it signs the record (§6.2). Without the salt, the
binding reveals nothing of the grant or the principal: a verifier holding every grant of the
room cannot test them against it.
Why a commitment, and not the grant’s digest in the clear: every participant of a room holds its grants (live-rooms §10.5), and a grant names its principal (live-rooms §13), so a grant’s digest names a viewer to anyone who holds the room’s grants. The record goes to the parties that match recovered sequences (§6.3), and names no viewer to them.
5.4 Digest and signature
The digest of a record is SHA-256 over its bytes before sig. The signature is
Ed25519 by edge over the 27 bytes wai:live-watermark-session (ASCII) and 0x01, followed by
the bytes the digest covers.
5.5 Reading and rules
A reader reads exactly the bytes it is given as one record, in the order of §5.1, and refuses at the first of these that holds:
- Fewer than 4 bytes:
truncated. A magic other thanWWS1: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 after
sig:malformed.
It then refuses as record_invalid a record that breaks any of these rules:
owneris 64 lowercase hexadecimal digits, or_followed by a slot name (live-rooms §2);mediais 1, 2 or 3;kidshas at most 8 entries, strictly ascending, none of them 16 zero bytes;startis belowend;sourceis 1, 2 or 3;patlenis at least 1;servedandbitsare bitmaps (their bits frompatlenon are 0), andbitssets no bitserveddoes not;segmentsis at least the number of positionsservedsets;regionis two capitalsA–Z.
5.6 Who may sign
Where the room names an origin, the edge’s key has an entry in the head given for the room
(live-rooms §5) whose may_sign holds the authority live/watermark
(jwp-receipts §7.5): otherwise edge_not_authorised. An entry whose
status is revoked is edge_revoked. Where the room names no origin, the edge is a room key
(live-rooms §2): otherwise edge_not_authorised.
live/watermark is an authority of its own, and not live/relay: a relay’s key signs what it
carried, and a session record is a forensic statement about one viewer’s session that an
origin gives to the edges it trusts with it.
5.7 The token beside the record
A verifier given the token with a record checks, in this order:
- its digest is the record’s
token: otherwisetoken_mismatch; - it reads (§4.2):
token_malformed,token_unsupported; - its vendor, pattern length and source are the record’s
vendor,patlenandsource; and, for a pattern in the clear, the record’sbitsare the pattern’s bits at the positions ofserved: otherwisetoken_mismatch; - the session’s span is within the token’s window (§4.3): the token is good at
t0+startand att0+end− 1,t0the descriptor’s (live-rooms §3.1). Otherwiseoutside_token_window.
A record given without its token is verified all the same, and the verifier states that its token was not given; the vendor, pattern length and bits are then the edge’s statement alone.
6. Retention and privacy
6.1 Where, and for how long
An edge keeps each session record, its token and, until it is delivered (§6.2), its opening,
and keeps them within the room’s region pin (live-rooms §3.7). The pin covers what a relay
keeps of a room beyond delivery, and a record of a session of the room is such; this extension
holds it to the pin as capture-binding holds a take’s master and live-recording a package.
region states where the record is kept, and a verifier refuses a region outside a pin that is
not empty: region_not_pinned.
expires is the instant from which none of them is kept, by the edge or by the room authority.
It is at or after the session’s end, expires × 10^6 ≥ t0 + end (otherwise
record_invalid), and, when the descriptor’s retention_days is not 0, at most that many days
after it: expires × 10^6 ≤ t0 + end + retention_days × 86 400 × 10^6, otherwise
retention_exceeded. The room id commits to the descriptor, so every viewer that joined the
room joined under the bound on what is kept of its sessions.
6.2 The opening
The edge gives each record’s opening (§5.3) to the room authority when it signs the record, by
the deployment’s means, and MUST NOT keep it once the authority holds it. The authority
keeps the openings within the pin until each record’s expires, and deletes them then. Nothing
but an opening links a record to a viewer: the edge admitted the session under its grant, and
keeps no other record of which grant a record’s session ran under once the opening is
delivered.
6.3 Who reads what
- A session record goes to the parties the deployment names to match recovered sequences (a rights holder, and its agents), and to a third party checking a disclosure. It names no grant, principal or viewer: its span, its content, its token’s digest and its pattern bits identify a session, and only an opening links a session to a principal.
- A token goes to the room authority and to a third party checking a disclosure (§8.4). A matching party needs only the records.
- An opening stays with the room authority, and is disclosed one session at a time (§8).
A pattern the token’s issuer draws once per session links nothing across sessions. A pattern drawn per device or per account (the TS allows either, §4 there) is the same in every session of that device or account, so the records of its sessions share their bits; a deployment that needs a viewer’s sessions unlinkable to a matching party draws a pattern per session.
7. Matching a recovered sequence
7.1 The recovered sequence
A claimant that recovers a variant sequence from a copy states it in a recovered sequence,
WWQ1:
| field | size | meaning |
|---|---|---|
magic | 4 | WWQ1 (ASCII) |
version | 1 | 1 in this revision |
room | 32 | the room id |
owner | short | the content, as in §5.1 |
media | 1 | the content’s media kind |
vendor | 1 | the wmvnd of the vendor whose detector recovered it |
patlen | 2 | u16: the pattern length it was recovered at |
start, end | 8 each | u64: the span of the room clock the copy was delivered in, as the claimant states it; 0 and 2^64 − 1 when it does not know |
known | ⌈patlen / 8⌉ | a bitmap: the positions recovered |
bits | ⌈patlen / 8⌉ | a bitmap: at each position of known, the variant recovered; 0 elsewhere |
copy | 32 | the digest of the copy it was recovered from, as the claimant holds it; 32 zero bytes when not stated |
claimant | 32 | the Ed25519 key that signs it |
sig | 64 | Ed25519 by claimant over the 28 bytes wai:live-watermark-sequence (ASCII) and 0x01, followed by the bytes before sig |
Its digest is SHA-256 over its bytes before sig.
7.2 Reading and rules
It is read as §5.5 steps 1 to 3 read a record, with WWQ1 for WWS1, and refused as
sequence_invalid when: owner and media break §5.5’s rules for them; patlen is 0;
start is not below end; known or bits is not a bitmap; bits sets a bit known does
not; or known sets none. Then its room is the room id (wrong_room), and its signature
verifies (bad_key, bad_signature).
7.3 Candidates and agreement
A session record S is a candidate for a recovered sequence Q when S’s owner,
media, vendor and patlen are Q’s, and their spans meet: S’s start is below Q’s
end and Q’s start below S’s end.
A candidate agrees with Q when, at every position Q’s known sets, S’s served
sets it and S’s bits holds Q’s bit. Otherwise its first disagreement is the lowest
such position at which one of the two fails: unserved where S did not sequence it,
differs where it did and embedded the other variant.
7.4 The outcome
Over the session records it is given, each verified (§9.1), a verifier states each candidate in the order given, with whether it agrees and, where it does not, its first disagreement and how; and the number of positions Q states. Then:
- matched: exactly one candidate agrees; the outcome names its digest;
- unmatched,
no_match: no candidate agrees, or there is no candidate; - unmatched,
ambiguous_match: more than one candidate agrees; the outcome names each.
7.5 What the rule leaves to detection
The TS leaves detection, and the statistics of its decoder, out of its scope (§5.8 there): a vendor’s detector extracts the identifier from a copy of several minutes, and the TS sets no error model, no tolerance and no threshold. This rule therefore binds what was embedded, and decides nothing about how it was recovered:
- A position the detector could not read with confidence is left out of
known: an erasure. A stated bit is compared exactly; this rule has no tolerance for a stated bit that disagrees, since the TS gives none. - How many positions tell sessions apart depends on how the token’s issuer drew the patterns (their length and their uniqueness, which the TS leaves to it); the outcome states the number of positions stated, and sets no minimum.
- A copy assembled from several sessions’ segments (collusion) is outside the TS’s model and this rule’s: it agrees with none of them at every stated position unless their bits coincide there.
- A segment served unmarked (§5.2) is in no record’s
served; a bit recovered from one makes a true session disagree (unserved), which is why a claimant states only positions recovered from the span in which the content was sequenced.
8. Disclosure
8.1 Layout
| field | size | meaning |
|---|---|---|
magic | 4 | WWD1 (ASCII) |
version | 1 | 1 in this revision |
room | 32 | the room id |
session | 32 | the digest of the session record it opens (§5.4) |
sequence | 32 | the digest of the recovered sequence it answers (§7.1) |
grant | 32 | the opening’s grant digest |
principal | 32 | the opening’s principal |
salt | 32 | the opening’s salt |
t | 8 | u64: seconds since 1970-01-01T00:00:00Z, when the authority signed it |
sig | 64 | §8.2 |
8.2 Digest and signature
The digest is SHA-256 over the 205 bytes before sig. The signature is Ed25519 by the
descriptor’s authority over the 30 bytes wai:live-watermark-disclosure (ASCII) and 0x01,
followed by the bytes the digest covers. A disclosure is read as §5.5 steps 1 to 3 read a
record, with WWD1 for WWS1; it has no other field rule.
8.3 When the authority discloses
The room authority signs a disclosure only:
- for a recovered sequence signed by its claimant, that reads and verifies (§7.2);
- whose match over every session record of that content the authority holds for the sequence’s span is matched (§7.4), and for that one session: a third party given one record cannot check this, and the authority, which holds the openings, answers for it;
- before the record’s
expires(§6.1).
It discloses one session per disclosure, and never an opening for a sequence that does not agree with its record. Whether a claimant’s request is lawful, and to whom the authority answers it, is the deployment’s and the law’s; a disclosure records that the authority answered this sequence with this session’s opening.
8.4 Checking a disclosure
A third party is given the room (a descriptor, its grants, the head of the authority’s key log or none, and the room’s confidentiality record if it has one), one session record and, if it holds it, its token, one recovered sequence, and the disclosure. It runs §9.3, and from an accepted disclosure learns: the session’s grant and principal, and that the edge embedded in that session, at every position the claimant states, the variant the claimant recovered. It learns nothing of another session: it holds no other record, and the disclosure names one.
9. Verification
9.1 Session records
A verifier is given the room (a descriptor, its grants, the head of the authority’s key log or none, and the room’s confidentiality record or none) and a list of session records, each with its token or none. It runs these steps and stops at the first refusal, which it reports with the reason and where:
- The room: the descriptor reads and verifies (live-rooms §3.4, §3.2), at
descriptor; the key log is held (live-rooms §5 steps 1 to 4), atkey_log; and the grants are checked (live-rooms §4.3), atgrantand the grant’s index; each with that section’s reasons. - Confidentiality, at
confidentiality, when a confidentiality record is given: it reads (confidentiality §3.3 step 1:truncated,malformed,unknown_version), names the room (wrong_room), and its signature verifies under the descriptor’sauthority(bad_key,bad_signature). A record that passes states that the room is end-to-end encrypted:e2ee_room(§3.2). Its suites are confidentiality’s to check, not this extension’s. - Each session record, in the list’s order, at
sessionand its index:- it reads (§5.5):
truncated,malformed,unknown_version,record_invalid; - its
roomis the room id:wrong_room; - its signature verifies (§5.4):
bad_key,bad_signature; - its edge may sign (§5.6):
edge_not_authorised,edge_revoked; - its content is the room’s: an
ownerof the form_and a slot’s name names a slot of the descriptor, of the record’smedia. Otherwisecontent_unknown. A publisher’s key asowneris not checked here; - it names a content key (§3.1):
not_protected; - its region and its
expires(§6.1):region_not_pinned,record_invalid,retention_exceeded; - its
tokenis no earlier record’s in the list:token_reused. One token is one session’s (§3.3), and two records that shared one would share its pattern; - with its token given, §5.7:
token_mismatch,token_malformed,token_unsupported,outside_token_window.
- it reads (§5.5):
The records are accepted when every one passes. The verifier states, for each, its digest,
its edge, its content, its span, its KIDs, its vendor, source and pattern length, the number of
positions it served, its segments and unmarked, its region and expires, and whether its
token was given, with what the token gives (§4.3) when it was.
9.2 A match
A verifier is given what §9.1 is given and a recovered sequence. It runs §9.1, then reads and
verifies the sequence (§7.2), at sequence, then states the outcome of §7.4.
9.3 A disclosure
A verifier is given what §8.4 lists. It runs these steps and stops at the first refusal:
- §9.1 over the one session record, with its token if given.
- The sequence (§7.2), at
sequence. - The disclosure, at
disclosure: it reads (§8.2:truncated,malformed,unknown_version); itsroomis the room id (wrong_room); its signature verifies under the descriptor’sauthority(bad_key,bad_signature). - Its
sessionis the record’s digest and itssequencethe sequence’s: otherwisedisclosure_mismatch. - The record is a candidate for the sequence and agrees with it (§7.3): otherwise
sequence_disagrees. - Its opening opens the record’s
binding(§5.3): otherwiseopening_mismatch. - A grant given has the digest
grant(grant_unknown), and itsprincipalis the disclosure’s (grant_wrong_principal). - That grant was good over the session’s span:
nbf× 10^6 ≤t0+startandt0+end≤exp× 10^6. Otherwiseoutside_grant_window. - Its
tis at or before the record’sexpires: otherwisedisclosure_expired.
A disclosure that passes is accepted, and the verifier states its digest, the session’s and the sequence’s digests, the grant’s digest, the principal, the number of positions the sequence states, and whether the session’s token was given.
9.4 Outcomes
- refuse: the reason and where.
- accept, for session records and a disclosure, with what §9.1 and §9.3 state.
- matched or unmatched, for a match (§7.4), with what it states.
10. Reading the documents together
10.1 Algorithm identifiers
The TS’s §5.4 names the algorithms a recipient supports as “HMAC 256/256 (kty number 5)” and
“ES256 (kty number −7)”, and its pattern encryption as “ECDH-SS+A128KW (key type −32)”. RFC
9053 registers 5 (§3.1), −7 (§2.1) and −32 (§6.4) as alg values, not key types; this
extension reads them as the alg of the token’s protected header (§4.2 step 5), and HMAC
256/256 as a COSE_Mac0 and ES256 as a COSE_Sign1, the structures RFC 9052 gives a MAC and a
signature with an implicit key.
10.2 COSE
RFC 8392 cites RFC 8152 for the COSE structures; RFC 9052 obsoletes it and keeps the
structures, their tags and their headers, and this extension reads them from RFC 9052. The
TS’s CDDL gives wmpattern as COSE_Encrypt0 // COSE_Encrypt // bytes, which in RFC 9052’s
CDDL are the untagged arrays; this extension reads them so (§4.2 step 7.6).
10.3 From a bit to a variant
The TS’s §4 has each of two variants carry a watermark that encodes ‘0’ or ‘1’, and its §5.4 addresses one bit of the pattern per position; it does not spell out which variant a bit selects. This extension reads a bit b as selecting the variant numbered b: 0 is A, the variant every viewer receives when nothing is sequenced (§5.3, §5.7.5.1 there), and 1 is B. A third variant has no bit to select it, and this revision records A/B sequencing alone.
10.4 Positions beyond the pattern
The TS bounds a position by its WMPaceInfo carriage (a 15-bit field, or a 2-byte integer, §5.5
there) and a pattern by wmpatlen, and does not say what an edge serves for a position at or
beyond wmpatlen. This extension serves such a segment unsequenced (§5.2), so that no record
states a bit the pattern does not hold.
11. Reasons
The codes this extension adds, each with where it is raised:
| reason | raised at |
|---|---|
record_invalid | §5.5, §6.1, §9.1 step 3 |
not_protected | §3.1, §9.1 step 3 |
e2ee_room | §3.2, §9.1 step 2 |
edge_not_authorised | §5.6, §9.1 step 3 |
edge_revoked | §5.6, §9.1 step 3 |
content_unknown | §9.1 step 3 |
region_not_pinned | §6.1, §9.1 step 3 |
retention_exceeded | §6.1, §9.1 step 3 |
token_reused | §9.1 step 3 |
token_malformed | §4.2, §5.7 |
token_unsupported | §4.2, §5.7 |
token_mismatch | §5.7 |
outside_token_window | §5.7 |
sequence_invalid | §7.2 |
no_match | §7.4 |
ambiguous_match | §7.4 |
disclosure_mismatch | §9.3 step 4 |
sequence_disagrees | §9.3 step 5 |
opening_mismatch | §9.3 step 6 |
disclosure_expired | §8.3, §9.3 step 9 |
A verifier of this extension also raises truncated, malformed and unknown_version for a
record, a sequence, a disclosure or a confidentiality record that does not read; wrong_room,
bad_key and bad_signature for one of another room or whose signature fails; every code a
descriptor, a key log or a grant raises (live-rooms §11 and the live-events codes it lists), at
the descriptor, the key log or the grant; and grant_unknown, grant_wrong_principal and outside_grant_window, which
live-rooms §11 lists, for a disclosure’s grant (§9.3 steps 7 and 8).
12. Conformance
live-conformance/watermark/ holds the corpus, in one file,
vectors.json, written from fixed seeds by wai_live_watermark_vectors gen. It has:
- Rooms: a broadcast room whose origin’s key log lists two edges with
live/watermark, a relay without it and an edge revoked; a room with no origin, whose clock is its edge; and an end-to-end encrypted room with its confidentiality record. - Tokens, each read by §4.2 with its outcome and what it gives: COSE_Mac0 tokens under HMAC 256/256 in each mode (a pattern in the clear, encrypted as COSE_Encrypt0 and as COSE_Encrypt, derived), one in the CWT tag, and one refused for each rule of §4.2 a corpus token breaks.
- Sessions: two edges’ records of four sessions of the broadcast room, each mode of token among them, with their tokens and without; the room with no origin; and one refused list for each reason of §11 a record or its token raises, each a control of one defect.
- Matches: a sequence recovered from one session’s copy with erasures, matched; one that agrees with two sessions; one whose bits disagree, one whose span meets no session, and one of another vendor, each unmatched; and sequences refused.
- Disclosures: the authority’s disclosure of the matched session, accepted, and one refused for each reason of §9.3, among them a disclosure of another session, an opening of another session, and a disclosure for a sequence that does not agree with its session.
The tokens’ MACs are HMAC-SHA-256 under a fixed test key, which the corpus states; no verifier checks them (§4.4).
A conforming verifier gives every vector the outcome, the reason, the place and the facts the
corpus states. wai_live_watermark_vectors verify and the test tests/live_watermark.rs check
that the file regenerates byte for byte, that wai-rs gives every vector its outcome, that
every reason of §11 appears, and that §11 lists exactly the reference’s codes.
live-conformance/live_watermark_verify.py is the second implementation, written from this
text: it shares no code with wai-rs; it reads descriptors, grants and key logs with the
live-rooms corpus’s second implementation (live_rooms_verify.py), verifies every signature
with WAI’s strict rule (claim-conformance/_ed25519.py), reads every token with a CBOR reader
of its own written from RFC 8949, and requires every mutated copy of the corpus to fail.
13. Security and privacy considerations
- What the record proves. An edge’s signature on a record is its statement that it served that session those variants at those positions under that token, and that it admitted the session under the grant its binding commits to. Nothing in the record proves the media were delivered as stated; an edge that lies about its own session is caught only where another record contradicts it. A recovered sequence that agrees with a record is evidence the copy came from that session to the extent the stated positions tell sessions apart (§7.5).
- Framing. A token is a capability to obtain segments marked with its pattern, and a copy
of them would match its viewer’s session. The edge binds a token to the grant it was first
presented under, admitted with proof of possession of the principal’s key (live-rooms
§10.5), and refuses it under any other (§3.3), so a copied token marks no other grant’s
session; two records that name one token are refused,
token_reused. Records do not carry tokens (§4.1). - Who learns a viewer. Only the room authority, through an opening, and only for the sessions it discloses. A party matching sequences holds records that name no grant; a third party checking a disclosure learns one session’s principal.
- The authority’s duty. The authority answers for the uniqueness of a match (§8.3), which a third party cannot check. A disclosure is signed, and names the sequence it answers and the claimant’s key through it, so every disclosure the authority makes is attributable to it and to a request.
- Retention. Records, tokens and openings are kept within the room’s region pin and until
expires, bounded by the room’s retention. A disclosure afterexpiresis refused, so an authority that keeps openings past the bound cannot present what it discloses as conforming. Thetof a disclosure is the authority’s own statement. - Linkability. Per-device or per-account patterns link a viewer’s sessions to anyone who holds their records (§6.3).
- Lane B. An end-to-end encrypted room offers no edge a choice to make, and its members’ applications hold the decoded output (§3.2, SPEC §6.5). Marking it would need the member’s device to mark what it decodes, which neither the TS nor this extension defines.