WAI Extension: Age Assurance
Mirrored from the canonical text at commit b1681667 ().
Status: Draft. Registers the record
wai.age.assurance: content ratings, admission by an age threshold without identity, and a profile for rooms in which a minor publishes. A rating claim (wai-rating) says, of an object, a track, a span of a track or a room, what the content is rated under one or more rating schemes, each named by its vocabulary URI and each stating the minimum age its value means; several national schemes sit side by side, and WAI adds none of its own. A claim is carried forward through every derivation: a derived object’s claim names its inputs’ claims, and a scheme an input is rated under is never dropped or lowered. A rating that rises for a span holds from the span’s first group, whenever the claim arrives. A room’s age policy (wai-age-policy), signed by the room authority, names the Privacy Pass issuers it admits by (RFC 9576, RFC 9577, RFC 9578), each with one key per threshold, so a token says “at or over T” and nothing else. A relay admits a viewer to a rated track by a token of type 0x0002 whose key’s threshold is at least the track’s minimum age, and learns only that threshold and the issuer; tokens are unlinkable to one another and to their issuance. A room’s service may redeem the token itself over HTTP (RFC 9577) and state the threshold in the grant it issues, and a relay is given ratings as a property of the tracks and objects it carries. On a bearer with no return path, a rating is a predicate the sink evaluates against an age credential it holds, and nothing leaves the device. An admission receipt (wai-age-admissions) counts admissions per threshold and issuer per window, with every count under 50 suppressed, so a service shows that it gated without a log of who. A room in which a minor publishes meets the minors’ profile: its roles hold neither the record nor the seek power, a role that publishes nothing sends nochatand noreaction, it is never recorded, and a relay’s DVR window keeps nothing of it. No new transport and no new cryptography. The reference implementation is theage_assurancemodule ofwai-rs(featureage_assurance); the corpus islive-conformance/age/, and its second implementation islive-conformance/age_assurance_verify.py. Keywords MUST, MUST NOT, SHOULD, MAY are RFC 2119/8174.Texts read for this revision, each pinned to the version named, and marked where this revision verified the text itself and where it relies on a summary:
- RFC 9576 (§3.3, §3.5.1, §4.4, §6.1), RFC 9577 (§2.1.1 the TokenChallenge, §2.1.1.2 its redemption context, §2.2.1 the token) and RFC 9578 (§6 the publicly verifiable issuance protocol, §6.4 token verification, §6.5 the issuer’s key, and Appendix A.2, whose key and first vector the corpus carries); RFC 9474; RFC 8017 §8.1.2 and Appendix B.2.1; RFC 9500 §2.1 and RFC 7517 Appendix A.2, whose published 2048-bit RSA test keys the corpus carries;
draft-ietf-moq-privacy-pass-auth-03§3.2 to §3.2.4 (the scope’s grammar), §3.3.1, §3.3.2, §3.4.1, §3.5.1, §3.5.5.2 and §3.5.5.3, an Internet-Draft not past Working Group Last Call, and the latest revision the IETF datatracker listed on 2026-10-21. Verified.- RFC 9577 §2.1.1.1 to §2.1.3 and §2.2.2 (the
PrivateTokenHTTP authentication scheme, its server names and its redemption contexts), RFC 9578 §4 (the issuer’s directory) and §6.1 to §6.3 (issuance) with Appendix A.2’s first issuance vector, RFC 9474 §4.2 to §4.4, RFC 2104 §2 and RFC 4648 §5, for the binding over HTTP (§5.6). Verified.- Commission Recommendation (EU) 2026/1035 of 29 April 2026 on establishing a common framework for EU wide Age Verification technologies (OJ L, 2026/1035, 8.5.2026), recitals (12) and (13) and points (4) and (5), as the Publications Office serves it. Verified.
- Communication from the Commission, Guidelines on measures to ensure a high level of privacy, safety and security for minors online, pursuant to Article 28(4) of Regulation (EU) 2022/2065 (C/2025/5519, OJ C, 10.10.2025), paragraphs 46 and 57. Verified.
- The consultation Additional Safety Measures under the United Kingdom’s Online Safety Act 2023 (published 30 June 2025, closed 20 October 2025), its chapter 6 and proposed measure ICU F3, read from the consultation document. Verified. Whether a statement has since adopted the measure was not checked. The Protection of Children Codes of Practice in force under that Act from 25 July 2025 were not read for this revision; they are cited from summaries only.
- Online Safety Amendment (Social Media Minimum Age) Act 2024 (Cth), No. 127, 2024, inserting sections 63C to 63F into the Online Safety Act 2021, read as made. Verified. The day section 63D took effect, 10 December 2025, is the day of the Minister’s instrument under section 63E as summaries give it; the instrument itself was not read.
- ETSI TS 102 796 V1.8.1 (2026-09) Annex A.2.28, for how a scheme fixes the ages its values mean. Verified.
A section or paragraph number of one of these texts is that version’s. Nothing in this extension states that any service meets any of these instruments; §10 maps what each asks for onto what WAI’s records do.
1. Scope
In scope: rating claims, what they cover, how they are carried through a derivation and how a rating that rises for a span holds (§3); the age policy (§4); admission by a threshold token, what a relay learns, grants that state a threshold, the rule a relay serves a rated track by, admission by a room’s service over HTTP, and ratings carried over MOQT (§5); the predicate a sink evaluates with no return path, and what a player owes (§6); the admission receipt and its suppressed counts (§7); the minors’ profile, and what a recording and a DVR window do in such a room (§8); verification (§9); and the mapping onto the instruments above (§10).
Out of scope: operating an issuer or an attester, and how an attester establishes a person’s age (RFC 9576 §3.5.1 leaves it to the attester, and the instruments above to their schemes); any identifier of a person; which rating a piece of content deserves; a transport; and a viewer’s device capturing what it displays by means outside the service (§8.3).
Relationship to the core spec: wai.age.assurance is a record (SPEC §5): a
signed record a WAI tool emits and verifies. It names no payload format and is not
dispatchable.
Relationship to live-rooms: a room admits a viewer that need not be identified
by a Privacy Pass token (§10.4 there); this extension fixes the issuers and keys of such a token
for a rated room, the rule a relay serves a rated track by, and the grant attribute age (§5.4)
that live-rooms §4.4 lists. It reads the attribute minor (§4.4 there) for the minors’ profile,
which is a condition on a descriptor’s role and event bitmaps (§3.3 there) and adds no field.
Relationship to synthesis and deriving-edge: a rating is
carried forward the way an origin claim is (synthesis §3.4): the derived object’s claim names
its inputs’ claims by link hash, and the verifier refuses one that drops what they rate. A
derivation record (deriving-edge §5) is named by the claim’s record, as an
origin claim names it.
Relationship to unidirectional-delivery: the sink’s predicate is a sink-evaluated predicate of §2 there, and the sink’s anchors for issuer keys are provisioned as §3 there has a trust anchor provisioned.
Relationship to live-recording and capture-binding: a
room whose recording policy has mode 0 holds no take (capture-binding §3.3) and no recording
package (live-recording §4.4 step 5), and a relay keeps no DVR window of a minors’ room
(live-recording §8.2).
2. Records
Every record of this extension is a JSON object (RFC 8259) under the encodings of
jwp-receipts §2.4.3: byte strings in lowercase hexadecimal; integers unsigned,
at most 2^53 − 1, in plain decimal; no other number; no boolean; a term, an absolute URI
and a date-time as defined there. A record is read as jwp-receipts §3.1 step 1 reads a
claim: RFC 8259’s grammar, no member named twice in one object, at most 32 levels of nesting.
Each record has exactly the members its section lists, no member missing and none added, at any
level, and a kind naming it; one that does not is record_malformed, except that a member
added to an admission receipt is receipt_identifies (§7.3). A record is read whole, by these
rules and its section’s, before its signature is verified.
Every record carries signer_pubkey (32 bytes, the Ed25519 key of RFC 8032 that signs it) and
sig (64 bytes):
sig = Ed25519(signer_key, "wai:age-assurance\x01" || JCS(record without "sig"))
link_hash = BLAKE3("wai:age-assurance-link\x01" || JCS(record))
JCS is RFC 8785, which on these values reduces to what jwp-receipts §2.4.3 states. The signature
is verified strictly (SPEC §7 Signatures): a key that fails step 1 or 2 is
bad_key, and a signature that fails a later step bad_signature. A record is named by its
link hash wherever another names it. issued_at, where a record carries it, is a date-time, the
instant its signer signed it, self-asserted.
An object’s content hash is jwp-receipts §2.1’s. A track’s digest is live-rooms §6.2’s: SHA-256 (FIPS 180-4) over the MOQT §8.8 rendering of its namespace, one zero byte, and its name. An age is an integer from 0 to 21, in whole years; 0 means no minimum.
3. Rating claims
3.1 The claim
A rating claim, kind "wai-rating", has these members:
| member | type | meaning |
|---|---|---|
kind | string | "wai-rating" |
subject | object | what it rates (§3.3): {"type": "object", "content_hash"}, one object by its content hash; {"type": "track", "track", "from_group"}, a track by its digest from the group from_group on; {"type": "span", "track", "from_group", "to_group"}, the groups from_group to to_group of a track, both included; or {"type": "room", "room"}, every track of a room, by its room id (live-rooms §3.2) |
ratings | array | one entry for each scheme the subject is rated under, 1 to 8, each {"scheme", "value", "min_age"}: the scheme’s vocabulary URI (an absolute URI), the value it gives (1 to 64 bytes of ! to ~), and the age that value means under the scheme |
inputs | array | for a derived subject (§3.4), the objects it was derived from, 0 to 16, each {"content_hash", "rating"}: the input’s content hash, and the link hash of the rating claim it was carried with, or null where it carried none; empty otherwise |
record | 32 B hex or null | the identity of a record that derived the subject (deriving-edge §5.2), where its deriver signs one; null otherwise |
issued_at | date-time | when the signer signed it |
signer_pubkey, sig | §2 |
Who signs: the party that rates the content: its publisher, its distributor, a room authority, a classification body. A claim carries its signer’s authority and no more; §3.6 says whose claims a room honours.
3.2 Reading
A claim is record_malformed when, besides §2: subject is not one of the four forms, a digest
or an id in it is not 32 bytes, or a span’s to_group is below its from_group; ratings
holds fewer than 1 or more than 8 entries, or entries not in strictly ascending byte order of
scheme (so no scheme twice); a value is empty, longer than 64 bytes, or holds a byte outside
! to ~; a min_age is above 21; or inputs holds more than 16 entries, or one content hash
twice.
The age of a claim is the greatest min_age among its ratings.
A scheme fixes the ages its values mean, and the claim restates them: under the parental rating
of ETSI EN 300 468, a rating value r from 1 to 15 means a minimum age of r + 3 years (ETSI
TS 102 796 Annex A.2.28, Table A.4). A verifier holds the claims given to it to one statement per
value: two claims that give one scheme’s one value two different ages are refused
rating_inconsistent, at the later one.
3.3 What a claim covers, and an object’s age
A claim covers an object o that a party holds at position (X, g), the group g of the track whose digest is X (or an object outside any track, at no position), when:
object: itscontent_hashis o’s;track: itstrackis X andfrom_group≤ g;span: itstrackis X andfrom_group≤ g ≤to_group;room: itsroomis the room of X, the first field of X’s namespace (live-rooms §6.1).
The age of o under a set of claims is the greatest age of the claims of the set that cover
it, and 0 when none does. Under each scheme, its label is the value of the claim that
covers it and gives that scheme the greatest age; a party that shows a label shows one of these.
Several schemes sit side by side on one claim and one object, each with its own value, and the age of an object is the greatest any of them gives. A party that gates by age gates by that number; WAI picks no scheme over another, and a sink MAY show the label of the scheme its settings name.
3.4 Through every derivation
A party that derives an object from others and publishes it signs a rating claim for it whose
inputs name each input, with the claim it was carried with. Every derivation of
synthesis §3.4’s table carries a rating: an encode, a transcode, a rung of a
ladder, a crop, a composition, an enhancement, a generated frame over its inputs, and a clip of a
recording. A relay that forwards an object derives nothing, and the object’s claim holds for it.
A derived claim carries its inputs’ ratings when, for each input:
- its
rating, where it is notnull, is the link hash of a claim the verifier holds; where that claim’s subject is an object, it is this input’s content hash. Otherwiserating_unknown; - its
ratingis notnullwhere the verifier holds a claim that covers the input as an object (its subject is the input’s content hash): a derivation that names no claim for a rated input drops it from the chain. Otherwiserating_dropped; - every claim the verifier holds that covers the input as an object, and the claim its
ratingnames, is carried: for each of that claim’s ratings, the derived claim rates the same scheme with amin_ageat least as great. Otherwiserating_dropped.
So a rating can rise along a chain of derivations and never fall, and no scheme an input is rated
under drops out: a composition of a 12-rated and an 18-rated input is rated at least 18 under
each scheme either carries, and a transcode of an 18-rated object that names null for its
input, or a lower value, is refused. A derived claim names its inputs’ claims by link hash, so a
verifier that holds the chain checks it hop by hop, as synthesis §3.4 has an origin claim
checked.
3.5 A rating that rises
A rating can rise partway through a live track: a claim of type span, or of type track from
a later group, signed while the track runs. Such a claim holds from its first group, whenever
it was issued and whenever a party receives it. A party that holds it withholds every group it
covers from every session, sink or presentation its rule does not admit (§5.5, §6.2), from the
instant it holds it: a group of the span delivered live before the claim arrived is not delivered
again below the age, by a FETCH, a join’s fill, a DVR window, a recording or a clip. What was
delivered before the claim arrived cannot be recalled, and this extension does not pretend
otherwise; a party that rates a span before its first group is published leaves no such window.
A claim lowers nothing: an object’s age is the greatest of the claims that cover it, so a later
claim with a lower age changes nothing, and a span’s rise ends at its to_group. A track whose
base rating is to fall later is rated by spans.
3.6 Who may rate a room’s tracks
A relay of a room honours a claim about the room, a track of it, a span of such a track, or an
object served on one, only when it is signed by a room key (the descriptor’s authority or
clock, live-rooms §2), or, for a track whose owner is a principal (live-rooms §6.1), by that
principal. A room claim is checked first for its room, then for its signer: one naming another
room is refused wrong_room, and a claim about the room or a track of it that another key signs
is refused rating_not_authorised. A principal therefore raises the rating of its own tracks and
of no one else’s; the authority rates the room and any track in it. A relay attributes a track
or span claim by its track digest and an object claim by the object it carries: a claim it
cannot attribute to the room, a track it serves or an object it carries is not about the room,
and it neither honours nor refuses it; such a claim covers nothing the relay serves.
4. The age policy
4.1 Members
A room that admits by age states which issuers it admits by in an age policy, kind
"wai-age-policy", signed by the room authority:
| member | type | meaning |
|---|---|---|
kind | string | "wai-age-policy" |
room | 32 B hex | the room id |
issuers | array | 1 to 16 issuers, each {"name", "token_type", "keys"}: the issuer name a TokenChallenge carries (RFC 9577 §2.1.1), which a policy holds to 1 to 255 bytes of ! to ~, the token type, 2 (0x0002, RFC 9578 §6), and its keys, 1 to 8, each {"threshold", "key"}: an age from 1 to 21, and the issuer’s public key for it, the DER SubjectPublicKeyInfo of RFC 9578 §6.5 |
window_s | integer | the length, in seconds, of the windows an admission receipt counts in (§7), 60 to 86 400 |
issued_at | date-time | when the authority signed it |
signer_pubkey, sig | §2 |
4.2 Rules
A policy is record_malformed when, besides §2: the issuers are not in strictly ascending byte
order of name; a name breaks §4.1; a token_type is not 2; an issuer’s keys are not in
strictly ascending order of threshold, or a threshold is outside 1 to 21; a key appears twice
in the policy, under one issuer or two; a key does not read as RFC 9578 §6.5 has it: the DER
SubjectPublicKeyInfo whose algorithm is id-RSASSA-PSS with the parameters RFC 9578 Appendix A.2’s
key carries (SHA-384, MGF1 with SHA-384, a salt of 48 bytes), byte for byte, and whose key is an
RSAPublicKey of a 2048-bit modulus and a positive exponent; or window_s is outside its range.
Then its room is the room id (wrong_room), its signer is the descriptor’s authority
(signer_not_permitted), and its signature verifies (bad_key, bad_signature).
One key per threshold. An issuer signs a token for threshold T only under its key for T,
and only after its attester has established that the person it issues to is at least T years
old (RFC 9576 §3.5.1: the attester; RFC 9578 §6: the issuance). A key is the threshold it stands
for: no key appears under two thresholds or two issuers, so a token’s token_key_id, the
SHA-256 of the key (RFC 9578 §6.5), names exactly one issuer and one threshold. An issuer that
offers thresholds 13, 16 and 18 holds three keys, and a person 17 years old can be issued tokens
under the first two.
Several issuers sit in one policy, as several Member States’ solutions sit beside one another under Recommendation (EU) 2026/1035; each issuer’s thresholds are its own.
A room has one policy in this revision. A later revision may chain policies, as live-moderation §3.4 chains review policies.
5. Admission
5.1 The challenge
A relay that admits a viewer of a room by age over MOQT sends, in its SETUP, one AUTH
CHALLENGE (draft-ietf-moq-privacy-pass-auth-03 §3.4.1) for each issuer of the room’s
policy, its Challenge a TokenChallenge of RFC 9577 §2.1.1:
token_type0x0002;issuer_namethe issuer’sname;redemption_contextthe same for each issuer of the session, and one of three:- per session: 32 bytes the relay draws for the session from a cryptographically
secure generator (
draft-ietf-moq-privacy-pass-auth-03§3.5.5.3); - per window: 32 bytes fixed for the room and a window of time, RFC 9577 §2.1.1.2’s
context bound to a time window. With W the challenger’s window in seconds and w =
⌊u / W⌋ for the Unix time u in seconds, the value is the same for every challenge
of the room in window w, and cannot be known before w begins: the reference
computes HMAC-SHA-256 (RFC 2104) under a 32-byte key it draws at its start from a
cryptographically secure generator and never discloses, over
wai:age-windowand 0x01, the room id (32 bytes) and w as 8 bytes big-endian. The challenger accepts the challenges of windows w and w − 1, so a token obtained just before a window ends is good through the next. A client may obtain tokens for the window ahead of the request it presents them with (RFC 9577 §2.1.4); - empty: the relay accepts a token issued for any session and relies on its record of redeemed nonces alone;
- per session: 32 bytes the relay draws for the session from a cryptographically
secure generator (
origin_infothe room’s scope of live-rooms §10.4: one MoQAuthorizationInfo scope, the actions SUBSCRIBE_NAMESPACE, SUBSCRIBE, FETCH and TRACK_STATUS onMATCH_PREFIXof the tuple of the room id and on every track name, 78 bytes. The relay holds each track to §5.5.
The challenge names no threshold. The room’s policy and its ratings are public, so a client
computes the age a track asks for (§3.3) and presents a token under a key of that threshold or
above (§5.3). The client presents it in its SETUP’s AUTHORIZATION TOKEN
(draft-ietf-moq-privacy-pass-auth-03 §3.5.1).
5.2 Checking a token
A relay is given the room’s policy, the challenges it sent the session, the nonces of the tokens it has redeemed under the policy, the token presented, and the age the request asks for. It refuses at the first of these that fails:
- The token reads as RFC 9577 §2.2.1 writes one of type 0x0002 (RFC 9578 §6): a
token_typeof 0x0002, then anonce, achallenge_digestand atoken_key_idof 32 bytes each, and anauthenticatorof 256 bytes; 354 bytes in all (token_malformed). - Its
challenge_digestis the SHA-256 of a challenge the relay accepts for the session (§5.1), and that challenge has the structure of RFC 9577 §2.1.1’s TokenChallenge, every field read as bytes and no byte afterorigin_info, withtoken_type0x0002, anissuer_nameof at least one byte and aredemption_contextof 0 or 32 bytes (token_wrong_challenge). Itsorigin_infois then the one its binding writes. Over MOQT (§5.1) it is live-rooms §10.4’s scope for the policy’sroom, read as that section reads it: one that does not read as a MoQAuthorizationInfo isscope_malformed, and one that reads and is not that scope, byte for byte,scope_mismatch. Over HTTP (§5.6) it is RFC 9577 §2.1.1’s list of server names: one or more names joined by,, each of!to~other than,and@(no userinfo, §2.1.1.1 there), or it isscope_malformed; and a name of the list is the challenger’s own, compared without regard to ASCII case (§2.1.3 there), or it isscope_mismatch. - Its
token_key_idis the SHA-256 of a key of the policy’s issuer whosenameis that challenge’sissuer_name(issuer_unknown). - Its
authenticatorverifies under that key: RSASSA-PSS-VERIFY (RFC 8017 §8.1.2) with SHA-384, MGF1 with SHA-384 and a salt of 48 bytes, over the token’s first 98 bytes, as RFC 9578 §6.4 has it (token_bad_signature). - Its
nonceis not among the nonces redeemed (token_replayed). The relay adds it. - The key’s threshold is at least the age asked for (
below_threshold).
A token that passes admits the session under that issuer and that threshold. A token refused at step 6 has been spent all the same (step 5 recorded it): a client presents a token of the threshold the track asks for.
5.3 What a relay learns
From a token that passes, a relay learns the issuer and the threshold of the key that signed it: “at or over T, by issuer K”. It learns no age but that bound, and nothing that names the person:
- the issuer signed the token blindly (RFC 9474; RFC 9578 §6.1 to §6.3), so the issuer cannot tell which of its issuances a redeemed token came from, and the relay cannot ask it;
- the
nonceis drawn by the client for that token alone, and theredemption_contextfor that session alone (RFC 9577 §2.2.1, §2.1.1.2), so two presentations by one person share nothing but the key, and the key is shared by everyone the issuer issued that threshold to; - the issuer learns no room and no relay: the challenge names the relay’s session only by a random context, and the attester, the issuer and the origin each see only their part (RFC 9576 §3.3, §4.4).
A client that presents a token of a higher threshold than a track asks for tells the relay that higher bound. A client that holds tokens of several thresholds presents the one the track asks for, the least bound that admits it.
A relay that admits by age keeps the nonces it redeemed, to refuse a replay, for as long as a token of the challenges it accepts can be presented: for a context per session, the session’s life; per window, the two windows it accepts (§5.1), each window’s nonces dropped when it stops accepting that window, since a token of it then fails step 2; for an empty context, as long as the policy’s keys are in use. It bounds the nonces it keeps per window or session, and once the bound is reached it redeems no further token of that window or session until it may drop them: it never forgets a nonce it must keep, since a nonce forgotten is a token that can be spent again. The reference keeps at most 65 536 a window: two windows of nonces, 32 bytes each. The nonces are random values that name no one; the counts of §7 are all a relay needs beyond them.
5.4 Grants that state a threshold
A principal that holds a grant (live-rooms §4) is admitted by its grant, not by a token. Its
authority states the threshold it admitted the principal under in the grant attribute age
(live-rooms §4.4): a decimal integer from 1 to 21 with no leading zero, the threshold of a token
the authority redeemed under this extension’s §5.2 before it issued the grant. A grant without
age admits at 0. A grant whose age does not read, or whose minor is true and whose age
is 18 or more, is refused age_attr_invalid. A principal’s threshold at room-clock t is the
age of its grant in effect at t.
5.5 Serving a rated track
A relay serves an object of a room’s track to a session only when the session’s threshold (§5.2,
§5.4; 0 for a session admitted by neither) is at least the object’s age (§3.3) under every claim
the relay holds for the room that §3.6 lets it honour. The rule holds for every way an object
reaches a session: a live subscription, the fill of a SUBSCRIBE, a FETCH, the cache, the DVR
window (live-recording §8). For a request whose start is at or past an object its rule does not
admit, the relay refuses with REQUEST_ERROR UNAUTHORIZED (MOQT §12.3) and the reason phrase
below_threshold. For a live subscription, from the instant it holds a claim that raises a
group’s age above the session’s threshold, the relay forwards none of that group’s objects to
the session, and none of a later group the claim covers.
The rule is a function of the session’s threshold, the group and the claims the relay holds when
it serves the object (§3.5): with T the threshold and A the age of group g under them, it
serves when T ≥ A, and refuses below_threshold otherwise. A session’s threshold for a room
is the greatest of its admissions there: a token’s (§5.2) and the age of each grant of the
room it proved (§5.4).
5.6 Admission by the room’s service (HTTP)
A room’s service that issues the room’s grants (live-rooms §4) over HTTP may admit by age itself, as a Privacy Pass origin of RFC 9577, and state the threshold it admitted under in the grant (§5.4). A relay then reads the threshold from the grant it honours beside its proof (live-rooms §10.5).
- The challenge. To a request for a grant that carries no token, in a room with an age
policy, the service answers
401 Unauthorizedwith oneWWW-Authenticatechallenge of the schemePrivateToken(RFC 9577 §2.1.2) for each key of the policy, in ascending order of threshold:challengethe TokenChallenge of the key’s issuer andtoken-keythe key, its SubjectPublicKeyInfo (§4.1), each in base64url with padding (RFC 4648 §5), andmax-agethe seconds for which the service accepts the challenge. The TokenChallenge is §5.1’s with a window context, which names the room, and anorigin_infoof the service’s own server name (RFC 9577 §2.1.1.1), as the client addressed it: over HTTPorigin_infois RFC 9577’s list of origins, so the room is bound by the context. - The token. The client presents one token,
Authorization: PrivateToken token="…"(RFC 9577 §2.2.2), on the request it repeats. The service checks it by §5.2 against the room’s policy, the challenges it accepts at that instant, the nonces it keeps (§5.3) and the room’s age: the greatest age of the claims it holds whose subject is the room. A token that passes admits the request: the service issues the grant withagethe threshold of the key that signed the token. A token that does not is answered403 Forbiddenwith its reason, and the challenges again. - What it counts. Each admission, at its instant on the room clock, under its issuer and threshold, for the receipts of §7, and nothing else of it.
A client chooses, among the challenges, the least threshold at or above the room’s age it can obtain (§5.3), checks the challenge as RFC 9577 §2.1.3 has it, and obtains the token from the issuer by RFC 9578 §6, at the request URL of the issuer’s directory (RFC 9578 §4). A publisher’s grant is requested with a token as a viewer’s is: every track of the room is held to its rating (§3.3), the room’s events and the other publishers’ tracks among them, and a holder admitted at no threshold would be served none of them.
What the relay learns is what a token would tell it: the grant names a key the client made for the visit (live-rooms §13) and states “at or over T”; the service learns the threshold and the issuer, and the address any HTTP server sees.
5.7 Ratings over MOQT
A rating claim rides MOQT as the property WAI_RATING, type 0x3A4D, in the range MOQT §8.4
leaves to applications and odd, so that its value is bytes: the claim’s text, as §2 writes a
record. It is a Track Property of a PUBLISH or a SUBSCRIBE_OK (MOQT §10), and an Object
Property of any object, and may appear more than once. A relay reads each WAI_RATING of a
track it carries and of each object it forwards, and from that instant holds each claim that
reads, verifies and §3.6 lets it honour; one it cannot attribute it passes over (§3.6). It
forwards the property as it forwards any other, and neither strips nor adds one.
A room’s service gives the room’s claims, signed by a room key, to each principal it grants, and each publisher carries them on every track it publishes, so a relay holds them from the PUBLISH that opens the track; the room’s clock carries them on its events track (live-events §8.1) too. A principal carries its claims about its own tracks on those tracks, and a claim that rises during a track rides an object of it from then on. A relay that holds no claim of a room serves its objects at age 0.
6. No return path
On a bearer with no return path (unidirectional-delivery), no relay admits anyone: every sink receives every byte. A rating is then a sink-evaluated predicate (unidirectional-delivery §2) of the object and the sink’s age state.
6.1 The age state
A sink’s anchor is a list of issuer keys provisioned on the device out of band, as a trust
anchor is (unidirectional-delivery §3), each {issuer name, threshold, key}. A sink’s
credentials are tokens of type 0x0002 that issuers signed for the device. A credential is
issued for the device challenge of its issuer: the TokenChallenge of RFC 9577 §2.1.1 with
token_type 0x0002, the issuer’s issuer_name, an empty redemption_context and an empty
origin_info, so that it names no origin and no session. A credential is never presented: the
device holds it and checks it, and spends nothing.
A credential holds under the anchor when it reads (§5.2 step 1), its challenge_digest is
the SHA-256 of the device challenge of an issuer the anchor names, its token_key_id is the
SHA-256 of a key the anchor lists for that issuer, and its authenticator verifies under that
key (§5.2 step 4). The sink’s age state is the greatest threshold of a credential that holds,
and 0 when none holds. It is the predicate’s state class (unidirectional-delivery §2).
6.2 The predicate
For an object o the sink holds, at its position: present when the sink’s age state is at
least o’s age under every rating claim the sink holds that reads and verifies (§2, §3.2) and
covers o (§3.3); withhold otherwise. A claim can only raise an age, so a sink honours every
claim that verifies, whoever signed it, and passes over one that does not read or verify. A track
of a bearer that is not a room’s track has no room, and a room claim covers nothing on it. Two
conforming sinks holding the same claims and the same credentials reach the same verdict on the
same object.
6.3 What a player owes
A player that presents media of a bearer with no return path:
- evaluates the predicate for each object before it presents it, and presents none whose verdict is withhold;
- evaluates it again for every object it still holds (in a buffer, a time-shift window or a recording on the device) each time it receives a rating claim, so that a span is withheld from its first group (§3.5);
- MUST NOT cause a request in evaluating it, and sends its verdict, its age state and its credentials nowhere (unidirectional-delivery §2);
- presents, with no credential that holds, only objects whose age is 0.
The predicate is deterministic and auditable, and enforces nothing against a sink that lies about its state (unidirectional-delivery §2). What it holds to is narrow: the source learns nothing, the device’s age state never leaves the device, and two sinks with the same state agree.
7. Evidence without identity
7.1 The admission receipt
A room key (live-rooms §2) signs, for a span of the room clock, an admission receipt, kind
"wai-age-admissions":
| member | type | meaning |
|---|---|---|
kind | string | "wai-age-admissions" |
room | 32 B hex | the room id |
policy | 32 B hex | the link hash of the age policy the admissions were made under |
start, end | integer | the span, room-clock microseconds, from start up to but not including end; start below end |
window_s | integer | the policy’s window_s |
cells | array | one cell for each window, issuer and threshold with at least one admission in the span, each {"window", "issuer", "threshold", "admitted", "suppressed"} |
issued_at | date-time | when it was signed |
signer_pubkey, sig | §2 |
A window w is the room-clock microseconds from w × window_s × 10^6 up to but not
including (w + 1) × window_s × 10^6. An admission at room-clock t is in window ⌊t /
(window_s × 10^6)⌋.
7.2 Cells and suppression
A cell counts, in its admitted, the sessions admitted by a token (§5.2) in its window under its
issuer and its threshold. A cell of fewer than 50 admissions is suppressed: its suppressed
is 1 and its admitted 0, as live-metering §4.7 suppresses a QoE cell under
the rule of delivery-telemetry §8 (its paragraph 8.4), suppressed and not rounded. Otherwise
suppressed is 0 and admitted the count.
A receipt holds no entry for a session, no token, no nonce, no key of a principal and no address: its members are the room, the policy, the span and the cells, and a cell is a count. A suppressed cell says that between 1 and 49 sessions were admitted at that threshold by that issuer in that window, and no more.
7.3 Reading and checking
A receipt is read as §2 reads a record, with one change: once its text reads as JSON under §2’s
grammar (RFC 8259, no member named twice, at most 32 levels), a receipt that holds a member §7.1
does not list, at its top or in a cell that is an object, is refused receipt_identifies, before
its other members are read. A member beyond the table is a place a session’s identity could
ride, and a verifier cannot tell an identifier from any other value, so it refuses the place. It
is then record_malformed when, besides §2: start is not below end; window_s is 0; the
cells are not in strictly ascending order of window, then issuer by bytes, then threshold; a
cell’s window does not overlap the span; a suppressed is not 0 or 1; or a suppressed cell’s
admitted is not 0.
Then: its room is the room id (wrong_room); its signer is a room key
(signer_not_permitted); its signature verifies (bad_key, bad_signature); its policy is
the link hash of the policy the verifier is given, its window_s is that policy’s, and each
cell’s issuer and threshold are a key of it (receipt_mismatch); and no cell that is not
suppressed has fewer than 50 admissions (count_not_suppressed).
A verifier that is given the admissions too (an auditor holding a relay’s redemptions for a
window, or the corpus), each an instant, the challenges the relay sent that session and the token
that admitted it (a token refused at §5.2 step 6 admitted no one, and is not among them), checks
each by §5.2 steps 1 to 5 in the order given, the nonces of the earlier ones redeemed (its
refusal, at that admission); counts each admission whose instant is in the span in its cell, and
none other; and, after every check above, requires the receipt’s cells to be exactly the cells
§7.2 writes from those counts (count_mismatch).
7.4 What a receipt shows
A receipt shows, under the room key’s signature, that the room admitted by age under the policy it names, and how many sessions it admitted at each threshold by each issuer in each window, above the floor. With the room’s ratings (§3) and its policy, it shows which ages each track asked for and which thresholds were presented for them. It does not show who was admitted, and nothing a service keeps under this extension does: a token names no one (§5.3), and a receipt counts.
Where a regime asks the operator of a service for the lawful basis on which it processes the
age-related attributes it receives, the operator states it in a lawful-basis declaration
(jwp-receipts §2.4.6) whose subject is the receipt’s link hash; this extension
registers no basis of its own.
8. The minors’ profile
8.1 The profile
A room descriptor (live-rooms §3) meets the minors’ profile, minors, when:
- its recording policy’s
modeis 0: the room is never recorded (live-rooms §3.7); - no role’s
powerssets bit 3 (record) or bit 4 (seek) (live-rooms §3.3); - no role whose
publishis 0 permits the event kindchat(0x03) orreaction(0x04) in itsevents(live-events §4.4, §4.5).
and its grants meet it when:
- no grant whose
publishis 0 permitschatorreactionin itspolicy.
The profile adds no field and no bit: the role bitmaps, the grants’ capability sets and policies
and the recording policy already express it, and this section names it. Every grant is within its
role (live-rooms §4.3 step 7), so in a room that meets rules 1 to 3 no grant holds the record or
the seek power. Rule 4 is a rule of grants and not only of roles: a grant may narrow a publishing
role’s publish to 0 and keep the role’s chat (live-rooms §4.3 step 7 lets it), and that grant’s
principal publishes nothing and could still comment. A viewer admitted by a token holds no grant,
sends no event and does not seek back (live-rooms §10.4, §10.6).
8.2 When a room meets it
A grant whose publish is not 0 and whose attrs hold minor with the value true is a
minor’s publishing grant: the room authority marks its principal a minor (live-rooms §4.4)
and lets it publish. A room in which a minor’s publishing grant takes effect, by a join naming
it or a grant_exchange to it (live-rooms §7.2), MUST meet the profile, its descriptor and
every one of its grants: a room verifier under this extension, at the first such join or
grant_exchange in the merged order, refuses it minor_profile_unmet, at its record, when the
descriptor breaks rule 1, 2 or 3, and otherwise refuses the first grant, in the order given, that
breaks rule 4 minor_profile_unmet, at that grant. The grants a verifier is given are the room’s
own, so a grant the room holds is held to rule 4 whenever it takes effect.
The descriptor is fixed when the room is made, so a room service that issues a minor’s publishing
grant issues it only in a room whose descriptor meets the profile. A room that does not meet it
holds no minor publisher under this extension. A room that records a minor’s take under a
guardian’s consent (capture-binding §9.4) has a recording policy whose
mode is not 0, so it does not meet the profile; capture-binding’s verifier is unchanged, and
such a room is outside what this extension admits.
8.3 What the profile removes
- Comments and reactions. A subscriber that publishes nothing sends no
chatand noreaction: its grant does not permit them (§8.1 rules 3 and 4), and a relay forwards no event a room verifier refuses (kind_not_permitted, live-rooms §9.2 step 4). - Gifts. WAI defines none: no event kind and no power of live-rooms moves funds from one principal to another, so there is nothing to remove. A gift a deployment carries outside WAI’s records is outside this profile.
- Recording and clips. No role holds the record power, so no
record_startis accepted (live-rooms §9.2 step 5); the recording policy’smodeis 0, so no take is permitted (capture-binding §3.3) and no recording package is accepted (live-recording §4.4 step 5). A clip is cut from a recording or fetched from a DVR window (live-recording §5, §8.1); there is no recording, no role seeks back below a live edge (live-rooms §10.6), and the DVR keeps nothing (§8.4), so there is nothing to cut one from. - Capture outside the service. A device that shows a stream can capture what it shows by means outside the service. WAI’s records do not reach them.
8.4 The DVR window
A relay keeps nothing of a minors’ room beyond delivery. It knows such a room by a minor’s
publishing grant: a session whose SETUP proved possession (live-rooms §10.5) of a grant signed by
an authority the relay trusts, whose publish is not 0 and whose minor is true. From then on
it keeps nothing of the room’s tracks in its DVR window, deletes what it kept of them, and
remembers the room across a restart, exactly as live-recording §8.2 has it do for a room whose
objects are sealed. It still delivers the room live, and still honours the moderation decisions
it reads for what it delivers. The rule is the relay’s: a room service cannot keep a relay from
storing what it carries, and the relay already reads every grant presented to it.
9. Verification
9.1 Inputs and outcomes
A verifier under this extension checks one case at a time. Each case is one of these, and gives accept with what the case states, or refuse with a reason and where:
- ratings (§3): rating claims. Each claim reads and verifies (§2, §3.2), in the order given;
then the claims are consistent (§3.2), the first that gives a value a second age refused; then
each claim with inputs, in the order given, carries them (§3.4), input by input and, for each
input, rule by rule, against every claim of the case. It states each claim’s link hash, signer,
subject type, age and whether it is derived. At
["claim", i]. - admission (§4, §5.2): a room descriptor, an age policy, the challenges, the nonces
redeemed, a token, the age asked for and the binding:
originisnullover MOQT (§5.1), or the challenger’s server name over HTTP (§5.6). The policy reads (§4.2), at["policy"]; the token is checked (§5.2), at["token"]. It states the issuer, the threshold and the key id admitted. - serving (§3.5, §3.6, §5.5): a room descriptor, a track of the room by its namespace and
name, rating claims, and requests, each a threshold, a group and the claims the relay holds when
it serves it. Each claim, in the order given, reads, verifies and is not refused by §3.6, at
["claim", i]; then each request is served or refusedbelow_threshold, and is stated with the age of its group. It states the track’s digest. - predicate (§6): an anchor, credentials, rating claims and objects, each at a position. It
states the sink’s age state and, for each object, its age and its verdict,
presentorwithhold. Nothing in it is refused: a credential that does not hold and a claim that does not read or verify are passed over (§6.1, §6.2). - receipt (§7): a room descriptor, an age policy, a receipt, and the admissions or none. The
policy reads, at
["policy"]; then each admission is checked, at["admission", i]; then the receipt is read and checked by §7.3 in its order, the recount last, at["receipt"]. It states its link hash and its cells. - room (§5.4, §8): a room as live-rooms §9.1 gives one. The room verifier’s refusal is the
case’s, at
["room", …]with its own place, whatever record it refuses; then each grant’sagereads (§5.4), in the order given, at["grant", i]; then the profile is held (§8.2), at["record", c, i]or["grant", i]. It states the room verifier’s own outcome, whether the descriptor meets rules 1 to 3 of the profile, each grant’s threshold, and each minor’s publishing grant each time one takes effect, in the merged order.
9.2 What a verifier does not decide
A verifier does not decide whether a rating is right for its content, whether an issuer’s attester established an age, or whether a service meets an instrument. It decides that the records are what they say, that a derivation carried its inputs’ ratings, that a token admits what its key stands for, that a relay’s rule serves only what it admits, that a receipt counts and suppresses as §7 has it, and that a room in which a minor publishes is held to the profile.
10. Mapping onto the instruments
Informative. What each text asks for, and the part of this extension that answers it.
| instrument | what it asks for | what WAI does |
|---|---|---|
| Recommendation (EU) 2026/1035, recital (12) | selective disclosure, by default a true or false answer to the age-related question the relying party asks, with no more about the person | a key per threshold (§4.2): a token answers “at or over T” and nothing else (§5.3) |
| Recommendation (EU) 2026/1035, recital (13); point (4)(e) | confirm only that a user is over a certain age, without revealing other information to the proof provider or the service; age verification determines whether a user meets a given age threshold | blind issuance and a fresh nonce per token (§5.3); the issuer learns no room, the relay no person |
| Recommendation (EU) 2026/1035, point (5) | Member States make a solution available by 31 December 2026, in a European Digital Identity Wallet or as a stand-alone application | several issuers in one policy (§4.2); how a wallet or an application presents a proof of age to an issuer’s attester is outside WAI (§1) |
| Guidelines C/2025/5519, paragraph 46 | double-blind age verification: the platform learns only whether the threshold is met, and the age verification provider learns nothing of the services it is used for | §5.3 |
| Guidelines C/2025/5519, paragraph 57(b)(i) and (ii) | by default, interaction with a minor only by accounts the minor accepted; no account can download or capture content a minor shared | the minors’ profile: no chat or reaction from principals that publish nothing, no record or seek power, no recording, no DVR window (§8) |
| Consultation Additional Safety Measures, measure ICU F3 (a) to (d) | for a one-to-many livestream by a child: no comments, no gifts, no reactions, no in-service capture or recording | §8.3, item for item: chat, none in WAI, reaction, and record, seek, recording, clips and the DVR |
| Consultation Additional Safety Measures, measure ICU F3 (e) | capture by tools outside the service, where technically feasible | outside WAI’s records (§8.3) |
| Act No. 127, 2024, sections 63D, 63DA and 63DB | reasonable steps to keep users under 16 from holding accounts; no information of a kind the legislative rules specify, and no government-issued identification material unless an alternative is offered, collected for that purpose | a threshold of 16 (§4.2); the service receives a token, which carries no identification material, and learns a threshold and an issuer (§5.3); a receipt counts (§7) |
11. Reasons
The codes this extension adds, each with where it is raised:
| reason | raised at |
|---|---|
record_malformed | §2, §3.2, §4.2, §7.3 |
signer_not_permitted | §4.2, §7.3 |
rating_inconsistent | §3.2 |
rating_unknown | §3.4 rule 1 |
rating_dropped | §3.4 rules 2 and 3 |
rating_not_authorised | §3.6 |
token_malformed | §5.2 step 1 |
token_wrong_challenge | §5.2 step 2 |
scope_malformed | §5.2 step 2; live-rooms §10.4 |
scope_mismatch | §5.2 step 2; live-rooms §10.4 |
issuer_unknown | §5.2 step 3 |
token_bad_signature | §5.2 step 4 |
token_replayed | §5.2 step 5 |
below_threshold | §5.2 step 6, §5.5 |
age_attr_invalid | §5.4 |
receipt_identifies | §2, §7.3 |
receipt_mismatch | §7.3 |
count_not_suppressed | §7.3 |
count_mismatch | §7.3 |
minor_profile_unmet | §8.2 |
A verifier under this extension also raises bad_key, bad_signature and wrong_room (live-events
§9) for a record’s signature and its room, and every code a room verifier raises (live-rooms §11
and the live-events codes it lists), at the room. A recording verifier raises capture-binding’s
recording_not_permitted for a package of a room never recorded (live-recording §4.4 step 5).
12. Conformance
live-conformance/age/ holds the corpus, in one file,
vectors.json, written from fixed seeds by wai_age_assurance_vectors gen. Its issuer keys are
published test keys: RFC 9578 Appendix A.2’s, RFC 9500 §2.1’s testRSA2048 and RFC 7517
Appendix A.2’s RSA key, each written as RFC 9578 §6.5’s SubjectPublicKeyInfo; with their private
keys published, they are test keys and nothing else. It holds RFC 9578 Appendix A.2’s first token
vector, which verifies under its key, so a verifier checks its RSASSA-PSS against the published
bytes, and that vector’s issuance: the client’s nonce, blind and salt, its TokenRequest and the
issuer’s TokenResponse, which a verifier checks against the token (with s its authenticator and
r the blind, the blinded message is (s·r)^e mod n and the response s·r mod n) and
a client of RFC 9578 §6 reproduces. It has:
- Ratings: a programme rated under two schemes; a transcode of it, and a composition of the
transcode with a 12-rated trailer, each carrying both schemes at the programme’s ages; and a
clip of the composition rated higher, under a third scheme too. Refused: a transcode that names
no claim for its rated input, one that lowers a scheme’s age and one that drops a scheme, a
composition that drops a scheme, a transcode that carries the claim it names and drops a scheme
of a second claim the verifier holds about its input (
rating_dropped); a transcode naming a claim the verifier does not hold and one naming another object’s claim (rating_unknown); two claims that give one value two ages (rating_inconsistent); claims that do not read: schemes out of order, an age above 21, a span that ends before it starts, a value holding a space, a scheme that is not an absolute URI, a member added, a member named twice, a boolean, a date that does not exist, a content hash of 31 bytes, no rating, an input named twice and the kind of another record (record_malformed); a claim signed under the identity point (bad_key); and a claim whose signature fails. - Admissions: under a policy of two issuers, one holding keys for 13 and 16 and one a key for
18: a token of each key admitted where its threshold is asked, a token of 18 admitted where 16
is asked, and a token of 13 on a track of age 0. Refused: a token from the 13 key presented
where 16 is asked and one from the 16 key where 18 is asked (
below_threshold); a token presented twice; a token issued for another session’s challenge, and one whose challenge names type 0x0001; challenges whoseorigin_infothe binding does not admit: over MOQT, the first three scopes of the revision before this one, the scope of another room and an emptyorigin_info(scope_mismatch), and all four of those scopes, their length past 255 written in its one byte, an action outside the draft’s table, a byte after the scope and a list of server names (scope_malformed); over HTTP, a token admitted under the service’s own name and under a list holding it in another case, and refused under another origin’s name and an empty list (scope_mismatch) and under the MOQT scope (scope_malformed); a token under a key the policy does not hold, and one named by one issuer’s challenge under the other’s key (issuer_unknown); a token whose authenticator was altered; tokens that do not read (one byte short, of type 0x0001); and policies that break §4.1 or §4.2: no issuer, a window of 30 seconds, a threshold of 22, a key of 1024 bits (RFC 9500’stestRSA1024), issuers or thresholds out of order, one key under two issuers, a key whose algorithm is not id-RSASSA-PSS, a token type of 1, a policy signed by the clock, one of another room and one whose signature fails. - Serving: a room rated 13 whose performer’s track rises to 18 for groups 40 to 59, a span
the authority rates after group 40 went out, and from group 80 on to 16, as the performer rates
its own track; requests within, before and after the span and the rise, at thresholds 0, 13, 16
and 18, before and after the relay holds each claim; and claims the relay cannot attribute to
the room, a viewer’s about a track of another room and a stranger’s about an object it does
not carry, which cover nothing. Refused: a claim about the room signed by a
viewer and one signed by the performer, a span of the performer’s track rated by a viewer and
one rated by the director (
rating_not_authorised), and a room claim naming another room. - Predicates: over a broadcast track rated 13 with a span rated 18, a feature rated 16 and an unrated object: a sink holding credentials of 13 and 16, one of 18, one with none, one whose only credential is under a key its anchor does not list, one whose 16 credential was issued for a relay session’s challenge, one whose 16 credential’s authenticator was altered, one whose anchor lists the 16 key under the other issuer, and the first sink before the span’s claim arrives.
- Receipts: a receipt over two windows, 50 admissions at 16 counted and 7 at 13 and 3 at 18
suppressed, recomputed from the admissions; the same checked without them; the same signed by
the clock; and a receipt over the first window alone, the admissions outside its span not
counted. Refused: a cell carrying a session’s nonce and a receipt naming a principal at its
top (
receipt_identifies); a cell of 7 admissions stated, not suppressed; a cell stated as 51 and a receipt that leaves a cell out (count_mismatch); a receipt naming another policy and a cell of an issuer the policy does not hold and a receipt counting in windows other than the policy’s (receipt_mismatch); a receipt of another room; a receipt signed by a viewer; one whose signature fails; receipts that do not read: a suppressed cell that states a count, asuppressedof 2, cells out of order, a cell outside the span, a span that ends where it starts, and one that ends where it starts with no cell; and admissions in which one token is redeemed twice. - Rooms: a minors’ room in which a minor performs and two viewers, one with the threshold 13,
watch and send nothing; a room that does not meet the profile and holds no minor. Refused: a
viewer of the minors’ room sending a chat its grant does not permit (
kind_not_permitted, at the room); a minor publishing in a room whose director holds the record power, whose viewers hold the seek power (a clip), whose viewers may chat, whose viewers may react, and that is always recorded, a viewer exchanged to a minor’s publishing grant in a room whose viewers chat, and a minor publishing beside a grant narrowed to publish nothing that keeps its role’schat, and one beside such a grant that keepsreaction(minor_profile_unmet); a grant whoseageis written with a leading zero and a minor’s grant stating 18 (age_attr_invalid).
A conforming verifier gives every case the outcome, the reason, the place and everything else the
corpus states. wai_age_assurance_vectors verify and the test tests/age_assurance.rs check that
the file regenerates byte for byte, that wai-rs gives every case its outcome, that every reason
of §11 refuses a case, and that §11 lists exactly the reference’s codes.
live-conformance/age_assurance_verify.py is the second implementation, written from this text:
it shares no code with wai-rs; it verifies each room with the live-rooms corpus’s second
implementation (live_rooms_verify.py), every Ed25519 signature with WAI’s strict rule
(claim-conformance/_ed25519.py), every link hash with BLAKE3 written from its specification
(claim-conformance/_blake3.py), and every token’s RSASSA-PSS in its own arithmetic; and it
mutates copies of the corpus and requires every copy to fail.
13. Security and privacy considerations
- What a token reveals. A token’s key is its threshold, so a relay learns “at or over T” from it and a bound from nothing else; the client chooses which bound to show (§5.3). Where few people present one threshold in one window, the threshold together with a time and an address the relay sees anyway narrows who they are; a relay that logs addresses beside admissions undoes §5.3, and the receipt’s suppression (§7.2) is there so that what is published does not do the same.
- What an issuer reveals. An issuer’s attester establishes a person’s age and sees the person; the issuer signs blindly and sees neither the token nor the room. Which attester and which proof of age an issuer accepts are the issuer’s, and the instruments’ schemes (§10), and this extension names none.
- Issuer keys. A policy pins each issuer’s keys, so a relay verifies a token with no request to the issuer: type 0x0002 is publicly verifiable (RFC 9578 §6). An issuer that rotates a key rotates the policy; a room’s policy is replaced, not amended (§4.2). A key that signs for two thresholds would let a token stand for either, which is why no key appears twice.
- Double spending. A nonce is redeemed once under a policy (§5.2 step 5); with a fresh
redemption_contextper session, a token is good for one session alone, and a stolen token admits one session at its threshold. With a context per window a token is good at one challenger, for its room, in two windows at most, and is spent at the first presentation; challengers that share a window’s challenges share its nonces (RFC 9577 §2.1.1.2), which the reference avoids by keying its contexts with a key of its own. A challenger whose nonce store is full refuses tokens rather than forget a nonce (§5.3). - Ratings. A claim raises and never lowers, so a forged claim can withhold content but never admit it. Within a room only room keys and a track’s owner are honoured (§3.6), so a viewer cannot withhold another’s track.
- What was delivered stays delivered. A span rated after its first group went out holds from then on (§3.5); nothing recalls bytes already presented, and a party that must not show a span rates it before publishing it.
- The sink’s state. On a bearer with no return path a sink can lie about its age state, and nothing here prevents it (unidirectional-delivery §2). Its credentials and its verdicts never leave the device (§6.3).
- Minors’ rooms. A relay knows a minors’ room only once a minor’s publishing grant is presented to it (§8.4); a room service issues such a grant only in a room that meets the profile (§8.2), so the room’s own records keep the profile from its first instant, and the DVR keeps nothing from the relay’s.