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]>
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 itssource,issuer,thresholdandkey(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'snonce, itsblindr and its PSSsalt, thetoken_requestandtoken_responseof its issuance, and thetoken, in hexadecimal. A conforming verifier finds that the token'schallenge_digestis the SHA-256 oftoken_challenge, itstoken_key_idthe SHA-256 ofpkI, and its authenticator s a valid RSASSA-PSS signature underpkI(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 reproducestoken_requestandtokenfrom 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) andtoken(hexadecimal);cases: each aname, anabout, akind(one of §9.1's), its inputs, andexpect, 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:acceptwithclaims, each itslink,signer,subject(the subject'stype),ageandderived(1 for a claim with inputs, 0 otherwise); orrefuseat["claim", i].admission: a roomdescriptor, an agepolicy, the session'schallenges, the noncesredeemed, atoken, the agerequiredand the binding'sorigin:nullover MOQT, where a challenge'sorigin_infois 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:acceptwith theissuer, thethresholdand thekey_idthe token admits under; orrefuseat["policy"]or["token"].serving: a roomdescriptor, atrackof it (namespace, a list of its fields, andname),claims, andrequests, each athreshold, agroupandheld, 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), itsservice: theproof(hexadecimal) and thechallengeandcontextthe relay holds for the session.expect:acceptwith thetrack's digest and, for each request, the group'sageand itsoutcome,serve(withservicetruefor the room's own service) orrefusewith thereason; orrefuseat["claim", i].predicate: a sink'sanchor(each key'sissuer,thresholdandkey), itscredentials(tokens, hexadecimal), theclaimsit holds, andobjects, each a position: acontent_hash, atrackdigest and agroup, any of themnull.expect:acceptwith the sink'sstateand, for each object, itsageandverdict,presentorwithhold.receipt: a roomdescriptor, an agepolicy, areceipt, andadmissions: the name of an admission log, ornullfor none.expect:acceptwith the receipt'slinkand itscells(eachwindow,issuer,threshold,admittedandsuppressed); orrefuseat["policy"],["admission", i]or["receipt"].room: a room.expect:acceptwith the room's own outcome (room_outcome,acceptorgap),meets_profile(1 or 0), each grant'sthresholdsin the order given, and theminor_grantsthat took effect, each a grant digest in the merged order; orrefuseat["room", …]with the room verifier's own place (["room", "record", c, i]for recordiof chainc),["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 .