Skip to main content

WAI Extension: Capture Binding

Mirrored from the canonical text at commit f871af55 ().

Status: Draft. Registers the record wai.capture.take: the binding of a device’s full-quality master recording to a live room, so that the master, uploaded after the session, proves it is one continuous take committed while it was being captured. While it records, the device publishes rolling commitments to its master, take_commit events (live-events §4.12) in the room’s event track, where they are stamped with the room clock and held by the relay and by every other participant. After the session the device uploads the master, resumably and by content address, the store records the upload in signed receipts, and the device signs a take manifest listing the chunks, their root, every commitment and the consent of every principal the master records. A verifier checks the manifest against the room’s events and gives each claim its verdict: single-take, aligned, complete and publishable for each purpose. The reference case is a lesson or an audition, where the live call is low quality and the master is made on the performer’s own device. No new transport: the commitments are events, and the mapping onto C2PA live video output is field semantics (§11). The reference implementation is the capture_take module of wai-rs (feature capture_take); the corpus is live-conformance/takes/, and its second implementation is live-conformance/capture_take_verify.py. Keywords MUST, MUST NOT, SHOULD, MAY are RFC 2119/8174.

Texts read for this revision: RFC 6962 §2.1 and RFC 9162 §2.1.4 (the Merkle tree hash and its consistency proofs); ISO/IEC 23000-19 (CMAF) and ISO/IEC 14496-12; and the C2PA Technical Specification, version 2.4: §13.2.1, §18.15, §18.25, §18.26, §18.28 and §19. A section number of that specification in this text is that version’s.

1. Scope

In scope: how a device and a take bind to the principal they capture for, the take id, and what a record_start and record_stop pair brackets (§3); the chunks of a master and the tree over them (§4); the take_commit payload and the chain of a take’s commitments (§5); the upload, by content address, and the store’s signed receipts (§6); the take manifest (§7); the consent record (§8); verification of a take and the verdict of each claim (§9); the mapping from a room-clock instant to a position in the master, and its bound (§10); and the mapping of a take and its CMAF output onto C2PA live video (§11).

Out of scope: a transport; the master’s codec and container (a take commits to its bytes, whatever they encode); how a store is operated; the room’s own recording (live-recording); per-object receipts of the relay that witness when a commitment was seen (a later extension); and what a party does with a verdict.

Relationship to the core spec: wai.capture.take is a record (SPEC §5): a signed record a WAI tool emits and verifies. It names no payload format and is not dispatchable.

2. Terms

Integers are little-endian. A digest is SHA-256 (FIPS 180-4), 32 bytes. An event’s digest is live-events §3.2’s.

3. The device, its principal and the take

3.1 Binding a device to a principal

A device captures for one principal, which its grant names: the device’s grant in effect binds it to principal P when the grant’s attributes (live-rooms §4.4) hold the key subject whose value is P’s key as 64 lowercase hexadecimal digits, or, when they hold no subject, when P is the device’s own key. The room authority signs the grant, so the binding is the authority’s statement that this device captures for P: a device can be a key of its own (one per room, live-events §11) and still capture for a principal its participants know.

A principal is marked a minor in a take when the device’s grant that binds it holds the attribute minor with the value true, or when its consent record says so (§8.1).

3.2 The take id

A take is named by its take id, derived and never chosen: SHA-256 over the 18 bytes wai:capture-take (ASCII) and 0x01, then the room id, the device’s key, and the digest of the record_start event the take answers. One record_start addressed to one device names one take.

3.3 What a take brackets

A take answers a control event (live-events §4.9) whose command is record_start and whose target is the device, sent by a principal with the record power (live-rooms §3.3, §9.2 step 5). Its start is that event. Its capture instant is the room-clock instant t + count_in_ms × 1000 of the start: the count-in fixes when capture begins, for every participant alike.

Its stop is the first control event in the merged order after the start whose command is record_stop and whose target is the device; a take need not have one. A second record_start to the same device before the stop starts another take.

The device’s first commitment of the take opens it, in answer to the start; its last closes it, after the stop (§5.3). A room whose recording policy has mode 0 is never recorded, and no take in it is permitted (§9.1). The interval I and the jitter J a take is held to are the room’s (live-rooms §3.7), so no device chooses its own.

4. Chunks and the take tree

4.1 Chunks

The device cuts its encoded master into chunks, by bytes or by duration, in the order the master’s bytes run. Where the master is CMAF (ISO/IEC 23000-19), chunk 0 is the CMAF header followed by the first CMAF segment, and each later chunk is one CMAF segment, so that the header, whose edit lists could reorder what a segment presents, is committed with the first chunk. A chunk is described by 64 bytes, its descriptor:

offsetsizefieldmeaning
08indexu64: the chunk’s position in the master, from 0
88startu64: the device-clock instant its capture begins
168endu64: the device-clock instant its capture ends (exclusive)
248lenu64: its length in bytes, at least 1
3232hashthe digest of its bytes

A chunk holds the encoded samples captured from start to end. Its capture range is the device’s statement; the commitments fix it while the capture runs (§5), and the verdicts hold the device to it (§9.4).

4.2 The take tree

The take tree is the tree of staged-measurement Appendix C: RFC 6962’s Merkle tree hash over SHA-256, its node that appendix’s, over leaves of the take’s own domain:

leaf = SHA-256("wai:take-leaf\x01" ‖ descriptor)        64-byte descriptor, §4.1
node = SHA-256("wai:out-node\x01" ‖ left ‖ right)        staged-measurement Appendix C

The root over the descriptors of chunks 0 to n − 1, n ≥ 1, is RFC 6962 §2.1’s MTH: one leaf is itself; otherwise node(MTH(D[0:k]), MTH(D[k:n])), with k the largest power of two below n. No leaf is duplicated, so a root and its size fix the list of descriptors. The root over no chunk is 32 zero bytes. A commitment is a size and its root; as there, a root alone is not one. The leaf binds each chunk’s index, its capture range, its length and its hash, so a commitment fixes when each chunk was captured as well as what it holds.

4.3 Consistency

A commitment (n, root′) extends (m, root) when the first m chunks under root′ are the chunks under root. The proof of it is RFC 9162 §2.1.4.1’s consistency proof, the PROOF(m, D[n]) of RFC 6962 §2.1.2, a list of nodes, checked as RFC 9162 §2.1.4.2 checks one, with the node of §4.2 in place of its HASH(0x01 ‖ …). RFC 9162 leaves two cases to the caller, and this extension decides them: every commitment extends (0, zero), with an empty proof; and (m, root′) extends (m, root), with an empty proof, exactly when the roots are equal. Otherwise 0 < m < n and the proof is not empty.

A witness that holds only the previous commitment checks that the next extends it: the relay and every participant can check, while the take runs, that the device never rewrites what it has committed. A proof does not fix either size, as an audit path does not (staged-measurement Appendix C): a commitment states its size, and a verifier that holds the chunks recomputes every root under the size stated (§9.4).

5. Commitments

5.1 The take_commit payload

Every I while it captures, the device emits a take_commit event (live-events §4.12) in the room, on its own events track. Its payload:

fieldsizemeaning
take32the take id (§3.2)
n4u32: the commitment’s number in the take, from 0
phase11 open, 2 running, 3 close
prev32the digest of the take’s commitment n − 1; 32 zero bytes when n is 0
first8u64: the index of the first chunk this commitment adds
size8u64: the chunks committed so far; this commitment adds chunks first to size − 1
root32the root over chunks 0 to size − 1 (§4.2)
offset8i64, two’s complement: the offset (§2) the device measures at the event
proof_len1the number of nodes in proof, at most 64
proof32 × proof_lenthat (size, root) extends the previous commitment (first, its root) (§4.3)

The fixed part is 126 bytes, so a proof of up to 28 nodes fits live-events’ default max_payload of 1024. The chunk range it names, first to size − 1, is what a witness that missed the previous commitment still learns.

5.2 Reading a payload

A reader reads the payload in the order of §5.1 and refuses it as commit_malformed: when it ends before a field or holds bytes after proof; when phase is not 1, 2 or 3; when size is below first; when proof_len is above 64; or when size is 0 and root is not 32 zero bytes. A room verifier (live-rooms §9) carries the payload opaque, as live-events §4.12 has it; a take verifier reads it (§9.2).

5.3 The chain of a take’s commitments

A take’s commitments are the take_commit events of the device whose take is the take id, in the order of the device’s chain. They form their own chain:

The chain needs nothing a witness does not hold: n and prev make a commitment that was not delivered a gap in the take, as live-events §6.2 makes it one in the device’s chain, and the proof makes a rewritten history a commitment that does not extend.

6. Upload

6.1 Parts

After the session the device uploads its master to a store, chunk by chunk, each chunk a part: its index and its bytes, addressed by the chunk’s hash. The upload is resumable: a part that did not arrive is sent again, and a part sent twice is the same part. A verifier fetches the parts from the store by hash and is given them as a list of (index, bytes) in the order it received them.

6.2 The upload receipt

The store records the upload’s state in signed upload receipts, WUR1, one at each change of state:

offsetsizefieldmeaning
04magicWUR1 (ASCII)
41version1 in this revision
532storethe store’s Ed25519 key
3732takethe take id
698sequ64: the receipt’s position in the upload’s chain, from 0
7732prevthe digest of receipt seq − 1; 32 zero bytes when seq is 0
1098atu64: the store’s clock, in microseconds since 1970-01-01T00:00:00Z
1171state1 open, 2 progress, 3 resume, 4 sealed
1188heldu64: the chunks the store holds, from index 0 with none missing
12632rootthe root over those chunks (§4.2), zero when held is 0
1582regionISO 3166-1 alpha-2: where the store keeps the chunks
16064sigEd25519 by store

The digest of a receipt is SHA-256 over its bytes before sig. The signature is Ed25519 by store over the 17 bytes wai:take-upload (ASCII) and 0x01, followed by the bytes the digest covers, verified strictly (SPEC §7 Signatures).

A reader reads exactly the bytes it is given, as live-events §3.3 reads a record with WUR1 for WEV1: truncated, malformed or unknown_version.

6.3 The state machine

The receipts of an upload are a chain, and its states move only this way:

A verifier, given the receipts in a list, checks each in the list’s order and refuses at the first that fails, naming it by its index: the record reads (§6.2); its state is 1 to 4, its region two capitals A–Z, and its root zero when held is 0, otherwise upload_receipt_invalid; its store and take are the manifest’s (§7), upload_receipt_invalid; its signature verifies (bad_key, bad_signature); its seq is its index, its prev the previous receipt’s digest, its at not below the previous one’s, its region receipt 0’s, and its state one the list above allows after the previous receipt, otherwise upload_receipt_invalid; its held is at most the manifest’s chunk count and its root the root over the manifest’s first held chunks, otherwise upload_receipt_mismatch; and, where the room’s recording policy pins regions (live-rooms §3.7), its region is one of them, otherwise store_outside_region.

6.4 The store

A room whose descriptor names an origin (live-rooms §5) names its stores in that origin’s key log: a store’s key is an entry of the head whose may_sign holds the authority capture/store (jwp-receipts §7.5) and whose status is not revoked. A verifier refuses a take whose store the head does not so list as store_not_listed. In a room with no origin the store is the one the manifest names, and its receipts are its statement alone.

7. The take manifest

7.1 Layout

When the upload is complete, the device signs a take manifest, WTM1:

fieldsizemeaning
magic4WTM1 (ASCII)
version11 in this revision
room32the room id
device32the device’s key, which signs the manifest
principal32the principal the take is captured for (§3.1)
start32the digest of the take’s start (§3.3)
take32the take id (§3.2)
store32the store’s key (§6)
claims1the claims the manifest makes: bit 0 single-take, bit 1 aligned, bit 2 complete
purposes1the purposes it claims the master publishable for (§2)
chunks4 + 64 × na u32 count n, then each chunk’s descriptor (§4.1), in index order
root32the root over the n chunks (§4.2)
commits4 + 32 × ma u32 count m, then the digest of each of the take’s commitments, in the order of n
depicted1 + 64 × ka u8 count k, then each subject: its key (32) and the digest of its consent record (32; zero for none) (§8)
sig64Ed25519 by device

The digest of a manifest is SHA-256 over its bytes before sig; its signature is Ed25519 by device over the 21 bytes wai:capture-manifest (ASCII) and 0x01, followed by the bytes the digest covers, verified strictly (SPEC §7 Signatures).

7.2 Reading and rules

A reader reads exactly the bytes it is given, as live-events §3.3 reads a record with WTM1 for WEV1 (truncated, malformed, unknown_version), and refuses a manifest that breaks any of these rules as manifest_invalid:

It then refuses a manifest whose room is not the room’s as wrong_room, and one whose signature does not verify as bad_key or bad_signature.

The manifest lists every subject the master depicts or records, the take’s principal among them, and binds each to its consent. A claim bit or a purpose bit states what the device claims; a verifier gives every claim its verdict whether the manifest makes it or not (§9.4).

A subject’s consent is a signed record, WCN1:

offsetsizefieldmeaning
04magicWCN1 (ASCII)
41version1 in this revision
532subjectthe subject’s key
371minor1 when the subject is a minor, otherwise 0
3832guardianthe key of the subject’s guardian; 32 zero bytes for none
701purposesthe purposes consented to (§2)
7132roomthe room it is for; 32 zero bytes for any room
1038nbfu64: seconds since 1970-01-01T00:00:00Z from which it holds
1118expu64: the second before which it holds
11916noncedistinguishes two consents with the same fields
13564sigEd25519 by the guardian when guardian is not zero, by the subject otherwise

The digest of a consent is SHA-256 over its bytes before sig. The signature is over the 20 bytes wai:capture-consent (ASCII) and 0x01, followed by the bytes the digest covers, verified strictly (SPEC §7 Signatures): a minor’s consent is the guardian’s act, any other the subject’s own.

8.2 Reading

A verifier, given consent records in a list, checks each in its order and refuses at the first that fails, naming it by its index: the record reads, as live-events §3.3 reads one with WCN1 for WEV1; it holds these rules, otherwise consent_invalid: minor is 0 or 1, guardian is zero when minor is 0, guardian is not subject, purposes is not 0 and sets no bit above bit 3, and nbf is below exp; and its signature verifies (bad_key, bad_signature). A record marked a minor with no guardian reads: it is a record no minor’s consent can rest on (§9.4).

8.3 Revocation

A consent is revoked through the room authority’s key log, as a grant is (live-rooms §5): its digest is added to the head’s revoked_modules, which is append-only, so a revocation stays. A subject or guardian that withdraws consent asks the authority to list it. In a room whose descriptor names no origin no consent can be revoked, and a room that records subjects SHOULD name one.

9. Verification

9.1 Inputs and steps

A take verifier is given the room (a descriptor, grants, the head of the authority’s key log or none, and the chains, as live-rooms §9.1), the take manifest, the parts (§6.1), the upload receipts, the consent records, and an evaluation instant at, in seconds since 1970-01-01T00:00:00Z: the time its verdicts are about. It runs these steps and stops at the first refusal, which it reports with the reason and where:

  1. The room (live-rooms §9): a refusal is the take’s, at room, with the room’s own place. A gap is not a refusal: the steps go on over the records the room verifier holds, and §9.4 says what a gap fails.
  2. The manifest (§7.2): at manifest.
  3. The take in the room (§9.2): at manifest, or at the record it names.
  4. The store (§6.4): at store. The receipts (§6.3): at the receipt’s index.
  5. The consents (§8.2): at the record’s index.
  6. The verdicts (§9.4).

9.2 The take in the room

Over the records the room verifier accepted, in the merged order (live-rooms §9.2), with each one’s grant:

  1. The recording policy’s mode is not 0: otherwise recording_not_permitted.
  2. The manifest’s start is the digest of an accepted record that is a control event whose command is record_start and whose target is the manifest’s device: otherwise take_unknown.
  3. Every accepted take_commit event of the device has a payload §5.2 reads: otherwise commit_malformed, at that record. Those whose take is the take id are the take’s held commitments, in the device’s chain order.
  4. At each held commitment, the device’s grant in effect binds it to the manifest’s principal (§3.1): otherwise device_not_bound, at that record.
  5. The held commitments are the manifest’s commits, in its order, with those not held left out; a listed digest that is not held is the digest of no accepted record, and the room verifier reports a gap in the device’s chain. Otherwise manifest_mismatch.

A commitment listed and not held is so a commitment the relay did not deliver, and the device’s chain shows the gap; a manifest cannot list one the room holds out of its place, leave one out, or list one no gap can account for.

9.3 Mapping and timing

The take’s clock mapping is the offset o of its opening commitment: a device-clock instant d maps to the room-clock instant d + o, computed without overflow (the reference computes in 128-bit integers). A chunk’s mapped end is its end mapped. For each commitment, its t is the room-clock instant of its event; the capture instant, the start and the stop are §3.3’s. Every comparison in this section is between room-clock instants, in microseconds.

9.4 The verdicts

Each claim holds, or fails with the first reason below that applies to it.

single-take holds when the master is one continuous take, every chunk of it committed while it was captured, and every part uploaded is the chunk committed at its index:

  1. delivery_gap: a commitment the manifest lists is not held.
  2. commit_chain_broken: the held commitments are not a chain of §5.3: the first is not an opening one, a later one’s n, prev or first does not follow its predecessor, a later one is an opening one, or a commitment follows a closing one.
  3. root_not_extending: a commitment does not extend its predecessor (§4.3): its proof does not verify, a proof is given where §4.3 gives none, or a commitment that adds no chunk changes the root.
  4. take_not_closed: the last held commitment is not a closing one, or the take has no stop.
  5. manifest_not_committed: the manifest’s chunk count is not the closing commitment’s size, or, for a held commitment whose size is above 0, the root over the manifest’s first size chunks is not its root.
  6. clock_unstable: a commitment’s offset differs from o by more than J.
  7. capture_discontinuous: a chunk’s start is not below its end, or a chunk’s start is not the previous chunk’s end.
  8. chunk_too_long: a chunk’s end − start is above I.
  9. commit_early: the opening commitment’s t is below the start’s; the closing one’s is below the stop’s; or a commitment’s t + J is below the mapped end of a chunk it adds (it committed a chunk before the chunk was captured).
  10. commit_late: a commitment’s t is above the mapped end of a chunk it adds plus I plus J.
  11. Over the parts, in the order given, the first that is not the chunk committed at its index: chunk_extra when its index is not below the chunk count; chunk_reordered when its bytes’ digest and length are another committed chunk’s; chunk_altered otherwise.

By rules 8 and 10, every byte of the master was committed within 2 × I + J of its capture on the take’s clock mapping, and by rule 6 that mapping held to within J throughout.

A part missing from the upload does not fail single-take: what was uploaded is what was committed, and the commitments verify. It fails complete. A party promised an unedited single take needs single-take and complete together: single-take alone holds of an upload that left chunks out.

aligned holds when capture began at the instant the count-in fixed:

  1. delivery_gap: the first commitment the manifest lists is not held.
  2. commit_chain_broken: the first held commitment is not an opening one.
  3. manifest_not_committed: no held commitment adds a chunk, or the root over the manifest’s first size chunks is not the root of the first held commitment that does.
  4. misaligned: chunk 0’s start, mapped, differs from the capture instant by more than J.

complete holds when the upload covers every committed chunk and the store sealed it: with s and r the size and root of the last held commitment,

  1. manifest_not_committed: s is 0, s is above the manifest’s chunk count, or the root over the manifest’s first s chunks is not r.
  2. chunk_missing: an index below s has no part whose bytes’ digest and length are that chunk’s.
  3. upload_not_sealed: the last receipt is not sealed, or its held is not s.

publishable for purpose p holds when every subject the manifest lists consented to the recording and to p. For each subject, in the manifest’s order, the first that fails:

  1. consent_missing: its consent digest is zero, or no consent record given has it, or that record’s subject is not this subject, or its room is neither zero nor the room id.
  2. guardian_missing: the subject is marked a minor (§3.1: by the record’s minor, or, for the take’s principal, by the minor attribute of the device’s grant in effect at its first held commitment) and the record’s guardian is zero.
  3. consent_revoked: the head’s revoked_modules lists the record’s digest (§8.3).
  4. consent_expired: the record does not hold (nbf ≤ s < exp) at the start’s instant, s = ⌊(t0 + t) / 10^6⌋ of the start, or at at.
  5. purpose_not_consented: its purposes lack record, or lack p.

9.5 Outcome

A take verification gives one outcome:

10. Dual-tier binding

The live tracks of the room and the master share the room clock: every event of the room has its t on it (live-events §3.1), and the take’s mapping places the master on it (§9.3). A room-clock instant t within the take maps to the master:

An instant before chunk 0’s mapped start or at or after the last chunk’s mapped end is outside the master. A marker set on the live call at room time t (live-events §4.8) so lands at its instant in the master. The bound: where single-take and aligned hold, the mapping strays from the device’s own clock mapping at any commitment by at most J (rule 6), and the master’s start from the capture instant by at most J. The marker’s t is its author’s statement of the room clock (live-events §11), and a marker set by watching the live call lags what it marks by that call’s own delay; a marker anchored to a live object (live-events §3.1) names the object instead, and a sink that knows that object’s capture instant on the room clock maps that instant.

11. C2PA live video

This section maps a take, and a CMAF output of a room, onto the C2PA Technical Specification’s live video provenance (§19 there). It is field semantics, and this revision implements none of it beyond the stream id: wai-rs has no CBOR or COSE encoder, and the C2PA signer c2pa_emit uses writes whole-asset manifests and fragmented BMFF manifests (one manifest in the initialization segment, a Merkle tree over the fragments), with no live video segment assertion, no session keys assertion and no Verifiable Segment Info. The take’s commitments are the WAI side’s witness; the C2PA output is what a C2PA validator reads, and each is checked on its own.

11.1 What maps to what

11.2 Per-segment manifests (§19.3)

Each segment carries a C2PA Manifest Box in a uuid box, whose manifest holds a c2pa.hash.bmff.v3 over the segment and the assertion c2pa.livevideo.segment with sequenceNumber as above, streamId the take’s, continuityMethod c2pa.manifestId, and previousManifestId the manifest identifier of chunk i − 1’s segment. The initialization segment’s manifest carries c2pa.actions.v2 whose first action is c2pa.created with the IPTC digital source type digitalCapture (§18.15.2).

Each segment’s manifest MAY also carry the assertion science.transaction.wai.take, a map of take (the take id), room (the room id), device (the device’s key) and commit (the digest of the device’s last take_commit before the segment was written, absent before the first), each a byte string, so that a C2PA validator finds the room events that witness the take.

11.3 Verifiable Segment Info (§19.4)

11.4 A room’s CMAF output

A CMAF rendition a packager makes of a room’s live tracks maps the same way, as its own live video session: its own streamId, its own session key, its own claim signer. That is the packager’s provenance of its output, not the device’s of its take.

11.5 AI disclosure and sustainability

12. Reasons

The codes this extension adds, each with where it is raised:

reasonraised at
recording_not_permitted§9.2 step 1
take_unknown§9.2 step 2
commit_malformed§5.2, §9.2 step 3
device_not_bound§3.1, §9.2 step 4
manifest_invalid§7.2
manifest_mismatch§9.2 step 5
store_not_listed§6.4
upload_receipt_invalid§6.3
upload_receipt_mismatch§6.3
store_outside_region§6.3
consent_invalid§8.2
commit_chain_broken§9.4 single-take 2, aligned 2
root_not_extending§4.3, §9.4 single-take 3
take_not_closed§9.4 single-take 4
manifest_not_committed§9.4 single-take 5, aligned 3, complete 1
clock_unstable§9.4 single-take 6
capture_discontinuous§9.4 single-take 7
chunk_too_long§9.4 single-take 8
commit_early§9.4 single-take 9
commit_late§9.4 single-take 10
chunk_extra§9.4 single-take 11
chunk_reordered§9.4 single-take 11
chunk_altered§9.4 single-take 11
misaligned§9.4 aligned 4
chunk_missing§9.4 complete 2
upload_not_sealed§9.4 complete 3
consent_missing§9.4 publishable 1
guardian_missing§9.4 publishable 2
consent_revoked§9.4 publishable 3
consent_expired§9.4 publishable 4
purpose_not_consented§9.4 publishable 5

The first eleven are refusals; the rest are verdicts. A take verifier also raises every code a room verifier raises (live-rooms §11 and the live-events codes it lists), at room; truncated, malformed, unknown_version, bad_key and bad_signature for a manifest, a receipt or a consent record that does not read or whose signature fails; wrong_room for a manifest of another room; and delivery_gap as a verdict.

13. Conformance

live-conformance/takes/ holds the corpus, in one file, vectors.json, written from fixed seeds by wai_capture_take_vectors gen. Each vector is a room, a manifest, parts, receipts, consent records and an evaluation instant, with the outcome §9.5 gives them; one with verdicts also states the take id, its commitment count and digests, its size and root, its stream id (§11.1), and the master position of every marker of the room within the take (§10).

A conforming verifier gives every vector the outcome, the reason, the place, the verdicts and the data the corpus states. wai_capture_take_vectors verify and the test tests/capture_take.rs check that the file regenerates byte for byte, that wai-rs gives every vector its outcome, that every reason of §12 appears in the corpus, and that §12 lists exactly the reference’s codes. live-conformance/capture_take_verify.py is the second implementation, written from this text: it shares no code with wai-rs, verifies signatures with WAI’s strict rule written from RFC 8032 (claim-conformance/_ed25519.py), verifies the room with the live-rooms corpus’s own second implementation (live_rooms_verify.py, which verifies each event with live_events_verify.py), and gives every vector the same outcome; it also mutates copies of the corpus and requires every copy to fail.

14. Security and privacy considerations