open-standards/wai/extensions/age-assurance.md
Transaction Science 94444cfbf6
Some checks failed
joule-code conformance gate / Build + round-trip C1-C4 vectors (pull_request) Has been cancelled
joule-code conformance gate / joule-moq-relay, every target (pull_request) Has been cancelled
joule-code conformance gate / joule-moq-ingest, every target (pull_request) Has been cancelled
joule-moq-room room service / joule-moq-room · does this change touch it (pull_request) Has been cancelled
joule-moq-room room service / joule-moq-room, every target (pull_request) Has been cancelled
joule-moq-web browser client / joule-moq-web · does this change touch it (pull_request) Has been cancelled
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Has been cancelled
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Has been cancelled
standards workspaces build + test / which workspaces this change touches (pull_request) Has been cancelled
standards workspaces build + test / macOS energy backends (pull_request) Has been cancelled
WAI conformance / does this change touch it (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · linux (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · macos (pull_request) Has been cancelled
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Has been cancelled
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Has been cancelled
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Has been cancelled
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · linux (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · macos (pull_request) Has been cancelled
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Has been cancelled
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Has been cancelled
WAI energy receipts / does this change touch it (pull_request) Has been cancelled
WAI energy receipts / energy receipts · linux (pull_request) Has been cancelled
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Has been cancelled
WAI energy receipts / energy receipts · macos (pull_request) Has been cancelled
wai-ops-gpu integer operators on GPUs / does this change touch it (pull_request) Has been cancelled
wai-ops-gpu integer operators on GPUs / builds · no adapter is not a pass (linux) (pull_request) Has been cancelled
wai-ops-gpu integer operators on GPUs / Metal GPU and browser WebGPU (macos) (pull_request) Has been cancelled
wai-pack / wai-pack · does this change touch it (pull_request) Has been cancelled
wai-pack / wai-pack · linux (pull_request) Has been cancelled
wai-pack / wai-pack · linux-arm64 (pull_request) Has been cancelled
wai-pack / wai-pack · macos (pull_request) Has been cancelled
wai-pack / wai-pack · independent verifier (Python) (pull_request) Has been cancelled
wai-pack / wai-pack · the floor through a media prober (pull_request) Has been cancelled
wai-pack / wai-pack · dependency licences (pull_request) Has been cancelled
wai-quantum studio and desktop / does this change touch it (pull_request) Has been cancelled
wai-quantum studio and desktop / studio · browser (pull_request) Has been cancelled
wai-quantum studio and desktop / desktop app · macos (pull_request) Has been cancelled
wai-web reach builds / wai-web reach · does this change touch it (pull_request) Has been cancelled
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Has been cancelled
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Has been cancelled
wai-web wasm32 / does this change touch it (pull_request) Has been cancelled
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Has been cancelled
age_serving: groups after the claim's object are sent once the relay holds it (each group is read by a task of its own); the corpus paragraph of §12 states each scope case's reason
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-21 19:46:46 -04:00

61 KiB
Raw Permalink Blame History

WAI Extension: Age Assurance

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 no chat and no reaction, 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 the age_assurance module of wai-rs (feature age_assurance); the corpus is live-conformance/age/, and its second implementation is live-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 PrivateToken HTTP 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: its content_hash is o's;
  • track: its track is X and from_group ≤ g;
  • span: its track is X and from_group ≤ g ≤ to_group;
  • room: its room is 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:

  1. its rating, where it is not null, 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. Otherwise rating_unknown;
  2. its rating is not null where 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. Otherwise rating_dropped;
  3. every claim the verifier holds that covers the input as an object, and the claim its rating names, is carried: for each of that claim's ratings, the derived claim rates the same scheme with a min_age at least as great. Otherwise rating_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_type 0x0002;
  • issuer_name the issuer's name;
  • redemption_context the 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-window and 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;
  • origin_info the room's scope of live-rooms §10.4: one MoQAuthorizationInfo scope, the actions SUBSCRIBE_NAMESPACE, SUBSCRIBE, FETCH and TRACK_STATUS on MATCH_PREFIX of 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:

  1. The token reads as RFC 9577 §2.2.1 writes one of type 0x0002 (RFC 9578 §6): a token_type of 0x0002, then a nonce, a challenge_digest and a token_key_id of 32 bytes each, and an authenticator of 256 bytes; 354 bytes in all (token_malformed).
  2. Its challenge_digest is 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 after origin_info, with token_type 0x0002, an issuer_name of at least one byte and a redemption_context of 0 or 32 bytes (token_wrong_challenge). Its origin_info is then the one its binding writes. Over MOQT (§5.1) it is live-rooms §10.4's scope for the policy's room, read as that section reads it: one that does not read as a MoQAuthorizationInfo is scope_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 is scope_malformed; and a name of the list is the challenger's own, compared without regard to ASCII case (§2.1.3 there), or it is scope_mismatch.
  3. Its token_key_id is the SHA-256 of a key of the policy's issuer whose name is that challenge's issuer_name (issuer_unknown).
  4. Its authenticator verifies 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).
  5. Its nonce is not among the nonces redeemed (token_replayed). The relay adds it.
  6. 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 nonce is drawn by the client for that token alone, and the redemption_context for 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).

  1. The challenge. To a request for a grant that carries no token, in a room with an age policy, the service answers 401 Unauthorized with one WWW-Authenticate challenge of the scheme PrivateToken (RFC 9577 §2.1.2) for each key of the policy, in ascending order of threshold: challenge the TokenChallenge of the key's issuer and token-key the key, its SubjectPublicKeyInfo (§4.1), each in base64url with padding (RFC 4648 §5), and max-age the seconds for which the service accepts the challenge. The TokenChallenge is §5.1's with a window context, which names the room, and an origin_info of the service's own server name (RFC 9577 §2.1.1.1), as the client addressed it: over HTTP origin_info is RFC 9577's list of origins, so the room is bound by the context.
  2. 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 with age the threshold of the key that signed the token. A token that does not is answered 403 Forbidden with its reason, and the challenges again.
  3. 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:

  1. evaluates the predicate for each object before it presents it, and presents none whose verdict is withhold;
  2. 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);
  3. MUST NOT cause a request in evaluating it, and sends its verdict, its age state and its credentials nowhere (unidirectional-delivery §2);
  4. 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:

  1. its recording policy's mode is 0: the room is never recorded (live-rooms §3.7);
  2. no role's powers sets bit 3 (record) or bit 4 (seek) (live-rooms §3.3);
  3. no role whose publish is 0 permits the event kind chat (0x03) or reaction (0x04) in its events (live-events §4.4, §4.5).

and its grants meet it when:

  1. no grant whose publish is 0 permits chat or reaction in its policy.

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 chat and no reaction: 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_start is accepted (live-rooms §9.2 step 5); the recording policy's mode is 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: origin is null over 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 refused below_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, present or withhold. 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's age reads (§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 whose origin_info the binding does not admit: over MOQT, the first three scopes of the revision before this one, the scope of another room and an empty origin_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's testRSA1024), 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, a suppressed of 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's chat, and one beside such a grant that keeps reaction (minor_profile_unmet); a grant whose age is 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_context per 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.