open-standards/wai/live-conformance/age/README.md
Transaction Science 298eaa70f5 age × co: the room's own service is not held to the serving rule
A room with both `[age]` and `[co]` broke: co-performance's clock follows and
logs every performer's objects over a relay session of its own, which holds no
grant and no token, so age-assurance §5.5 served it nothing rated.

- age-assurance §5.5: a session that proves possession of a room key (the
  authority's or the clock's) is the room's own service, served every object of
  the room. The proof is a GRANT_PROOF naming no grant (32 bytes of zero) over
  the session's challenge, checked as live-rooms §10.5.4 checks a proof; one
  that does not pass refuses the session with that section's reason; one that
  passes by a key that is no room key gives nothing. Co-performance §8 points
  to it; §11 and §12 name the reasons and the new vectors.
- joule-moq: `grant_proof::NO_GRANT`, `add_room_key_proof`, `room_key_proofs`.
- joule-moq-relay: SETUP checks room-key proofs; a session proving a key the
  relay holds as a room key (an authority's, for its rooms; `[floor]` or `[dvr]`
  room keys, for every room) has `SessionCtx::service`, and its threshold there
  is past every age. `ClientOptions::room_keys` presents such proofs.
  tests/age_room_service.rs: a room rated 18 with the floor held; the clock's
  and the authority's proofs served every group, the clock's token alone and a
  non-room key refused `below_threshold`, a forged clock proof and one over
  another challenge closed UNAUTHORIZED.
- joule-moq-room: the clock's session proves both room keys it holds.
- wai-rs `age_assurance::room_service`, the corpus case `room_service`, and
  `age_assurance_verify.py` taught the rule, with three new checks and four
  mutations, all caught.
- e2e: scenario `ageco`, a room with both `[age]` and `[co]`.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-22 07:08:25 -04:00

5.6 KiB

live-conformance/age — age assurance

The corpus of extensions/age-assurance.md §12, in one file, vectors.json:

  • principals: each Ed25519 key, by name (informative);
  • issuer_keys: the three issuer keys of the policies, each its source, issuer, threshold and key (RFC 9578 §6.5's SubjectPublicKeyInfo, hexadecimal), informative;
  • rfc9578_a2_vector1: RFC 9578 Appendix A.2's first test vector, as published: pkI, token_challenge, the client's nonce, its blind r and its PSS salt, the token_request and token_response of its issuance, and the token, in hexadecimal. A conforming verifier finds that the token's challenge_digest is the SHA-256 of token_challenge, its token_key_id the SHA-256 of pkI, and its authenticator s a valid RSASSA-PSS signature under pkI (RFC 9578 §6.4); and that the issuance holds (§6.1 to §6.3 there, RFC 9474 §4.2 to §4.4): the request's blinded message is (s·r)^e mod n, and the response s·r mod n. A client of the issuance protocol reproduces token_request and token from the others;
  • admission_logs: admission logs by name, each a list of admissions, t (room-clock microseconds), challenges (the TokenChallenges the relay sent that session, hexadecimal) and token (hexadecimal);
  • cases: each a name, an about, a kind (one of §9.1's), its inputs, and expect, what a verifier gives it.

Every record is a JSON text (§2). A room's records are hexadecimal: a descriptor, grants and chains (each chain a list of WEV1 records), and key_log (null throughout), as the live-rooms corpus gives them.

Cases. By kind:

  • ratings: claims, rating claims. expect: accept with claims, each its link, signer, subject (the subject's type), age and derived (1 for a claim with inputs, 0 otherwise); or refuse at ["claim", i].
  • admission: a room descriptor, an age policy, the session's challenges, the nonces redeemed, a token, the age required and the binding's origin: null over MOQT, where a challenge's origin_info is the room's scope of live-rooms §10.4, or the service's server name over HTTP (§5.6), where it is RFC 9577's list of server names. expect: accept with the issuer, the threshold and the key_id the token admits under; or refuse at ["policy"] or ["token"].
  • serving: a room descriptor, a track of it (namespace, a list of its fields, and name), claims, and requests, each a threshold, a group and held, the indexes of the claims the relay holds when it serves the request, and, for a session that presented a proof of a room key (§5.5), its service: the proof (hexadecimal) and the challenge and context the relay holds for the session. expect: accept with the track's digest and, for each request, the group's age and its outcome, serve (with service true for the room's own service) or refuse with the reason; or refuse at ["claim", i].
  • predicate: a sink's anchor (each key's issuer, threshold and key), its credentials (tokens, hexadecimal), the claims it holds, and objects, each a position: a content_hash, a track digest and a group, any of them null. expect: accept with the sink's state and, for each object, its age and verdict, present or withhold.
  • receipt: a room descriptor, an age policy, a receipt, and admissions: the name of an admission log, or null for none. expect: accept with the receipt's link and its cells (each window, issuer, threshold, admitted and suppressed); or refuse at ["policy"], ["admission", i] or ["receipt"].
  • room: a room. expect: accept with the room's own outcome (room_outcome, accept or gap), meets_profile (1 or 0), each grant's thresholds in the order given, and the minor_grants that took effect, each a grant digest in the merged order; or refuse at ["room", …] with the room verifier's own place (["room", "record", c, i] for record i of chain c), ["grant", i] or ["record", c, i].

A verifier that reaches another outcome, reason, place or statement does not conform.

Provenance. Generated here: wai_age_assurance_vectors gen writes vectors.json from records authored in code, from fixed seeds. Every Ed25519 key is SHA-256 of a fixed label; every time is a constant (the rooms' t0 is 2026-10-21T00:00:00Z); every content hash, token nonce, redemption context and RSASSA-PSS salt is SHA-256 output of a fixed label; and Ed25519 signing is deterministic, so the file is the same bytes on every machine. The issuer keys are published test keys whose private keys are published with them: RFC 7517 Appendix A.2's RSA key (threshold 13), RFC 9500 §2.1's testRSA2048 (16) and RFC 9578 Appendix A.2's issuer key (18), each written as RFC 9578 §6.5's SubjectPublicKeyInfo; the generator signs each token's input with the key directly, the signature the blind protocol would also yield. rfc9578_a2_vector1 is that RFC's vector as printed. The challenges over MOQT carry the room's scope of live-rooms §10.4, and the refused ones the scopes of the revision before it, or a scope broken one way; those over HTTP carry room.example, the corpus's service. The rating schemes, their values and the issuers are .example names (RFC 2606) and a tag: URI written for the corpus: no scheme here is any classification body's, and nothing in the corpus states what any content deserves. No recorded data.

python3 -I ../age_assurance_verify.py .