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_commitevents (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,completeandpublishablefor 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 thecapture_takemodule ofwai-rs(featurecapture_take); the corpus islive-conformance/takes/, and its second implementation islive-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
- Room, room clock, principal, event, chain, grant, grant in effect, room key, merged order: as in live-events §2 and live-rooms §2, §9.2.
- Device: the Ed25519 key (RFC 8032) of the device that captures. It takes part in the
room under a grant (live-rooms §4) as a principal: it signs its
take_commitevents and its take manifest. - Master: the encoded recording the device makes, at the quality it captures.
- Device clock: the clock the device stamps its capture with, in microseconds. Its epoch is the device’s.
- Offset: the room clock less the device clock, in microseconds, as the device measures it: a device-clock instant d is the room-clock instant d + offset.
- Chunk: a contiguous run of the master’s bytes (§4.1).
- Take: one continuous capture on one device, answering one
record_start(§3). - Store: the service the master is uploaded to, named by its Ed25519 key (§6).
- Subject: a principal the master depicts or records.
- Purpose: a use a subject consents to:
live(1),record(2),distribute(4),train(8), each a bit of a purposes byte. - I and J: the room’s
commit_interval_msandjitter_ms(live-rooms §3.7), each times 1000, in microseconds.
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:
| offset | size | field | meaning |
|---|---|---|---|
| 0 | 8 | index | u64: the chunk’s position in the master, from 0 |
| 8 | 8 | start | u64: the device-clock instant its capture begins |
| 16 | 8 | end | u64: the device-clock instant its capture ends (exclusive) |
| 24 | 8 | len | u64: its length in bytes, at least 1 |
| 32 | 32 | hash | the 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:
| field | size | meaning |
|---|---|---|
take | 32 | the take id (§3.2) |
n | 4 | u32: the commitment’s number in the take, from 0 |
phase | 1 | 1 open, 2 running, 3 close |
prev | 32 | the digest of the take’s commitment n − 1; 32 zero bytes when n is 0 |
first | 8 | u64: the index of the first chunk this commitment adds |
size | 8 | u64: the chunks committed so far; this commitment adds chunks first to size − 1 |
root | 32 | the root over chunks 0 to size − 1 (§4.2) |
offset | 8 | i64, two’s complement: the offset (§2) the device measures at the event |
proof_len | 1 | the number of nodes in proof, at most 64 |
proof | 32 × proof_len | that (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 opening commitment is the first:
n0,phaseopen,prevzero,firstandsize0, and no proof. The device emits it when the start reaches it, before its capture begins. - Each later commitment
nhasnone above the previous one’s,prevthe previous one’s digest,firstthe previous one’ssize, andphaserunning or close. It adds the chunks the device has completed since the previous one, perhaps none, and proves that its commitment extends the previous one (§4.3). - The closing commitment,
phaseclose, is the last: after the stop, it adds the chunks captured up to the stop, and no commitment of the take follows it.
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:
| offset | size | field | meaning |
|---|---|---|---|
| 0 | 4 | magic | WUR1 (ASCII) |
| 4 | 1 | version | 1 in this revision |
| 5 | 32 | store | the store’s Ed25519 key |
| 37 | 32 | take | the take id |
| 69 | 8 | seq | u64: the receipt’s position in the upload’s chain, from 0 |
| 77 | 32 | prev | the digest of receipt seq − 1; 32 zero bytes when seq is 0 |
| 109 | 8 | at | u64: the store’s clock, in microseconds since 1970-01-01T00:00:00Z |
| 117 | 1 | state | 1 open, 2 progress, 3 resume, 4 sealed |
| 118 | 8 | held | u64: the chunks the store holds, from index 0 with none missing |
| 126 | 32 | root | the root over those chunks (§4.2), zero when held is 0 |
| 158 | 2 | region | ISO 3166-1 alpha-2: where the store keeps the chunks |
| 160 | 64 | sig | Ed25519 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:
- receipt 0 is
open, withheld0; progress:heldabove the previous receipt’s, as parts arrive;resume:heldequal to the previous receipt’s: an upload interrupted and resumed, which continues from the chunk atheld;sealed:heldequal to the previous receipt’s; the store holds the master, and no receipt follows.
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:
| field | size | meaning |
|---|---|---|
magic | 4 | WTM1 (ASCII) |
version | 1 | 1 in this revision |
room | 32 | the room id |
device | 32 | the device’s key, which signs the manifest |
principal | 32 | the principal the take is captured for (§3.1) |
start | 32 | the digest of the take’s start (§3.3) |
take | 32 | the take id (§3.2) |
store | 32 | the store’s key (§6) |
claims | 1 | the claims the manifest makes: bit 0 single-take, bit 1 aligned, bit 2 complete |
purposes | 1 | the purposes it claims the master publishable for (§2) |
chunks | 4 + 64 × n | a u32 count n, then each chunk’s descriptor (§4.1), in index order |
root | 32 | the root over the n chunks (§4.2) |
commits | 4 + 32 × m | a u32 count m, then the digest of each of the take’s commitments, in the order of n |
depicted | 1 + 64 × k | a u8 count k, then each subject: its key (32) and the digest of its consent record (32; zero for none) (§8) |
sig | 64 | Ed25519 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:
claimssets no bit above bit 2, andpurposesnone above bit 3;- n is at least 1, each chunk’s
indexis its position in the list, and eachlenis at least 1; rootis the root over the chunks;- m is at least 1, and no digest is listed twice;
- k is 1 to 32, the subjects’ keys in strictly ascending byte order, and
principalis one of them; takeis the take id ofroom,deviceandstart(§3.2).
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).
8. Consent
8.1 The consent record
A subject’s consent is a signed record, WCN1:
| offset | size | field | meaning |
|---|---|---|---|
| 0 | 4 | magic | WCN1 (ASCII) |
| 4 | 1 | version | 1 in this revision |
| 5 | 32 | subject | the subject’s key |
| 37 | 1 | minor | 1 when the subject is a minor, otherwise 0 |
| 38 | 32 | guardian | the key of the subject’s guardian; 32 zero bytes for none |
| 70 | 1 | purposes | the purposes consented to (§2) |
| 71 | 32 | room | the room it is for; 32 zero bytes for any room |
| 103 | 8 | nbf | u64: seconds since 1970-01-01T00:00:00Z from which it holds |
| 111 | 8 | exp | u64: the second before which it holds |
| 119 | 16 | nonce | distinguishes two consents with the same fields |
| 135 | 64 | sig | Ed25519 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:
- 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. - The manifest (§7.2): at
manifest. - The take in the room (§9.2): at
manifest, or at the record it names. - The store (§6.4): at
store. The receipts (§6.3): at the receipt’s index. - The consents (§8.2): at the record’s index.
- 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:
- The recording policy’s
modeis not 0: otherwiserecording_not_permitted. - The manifest’s
startis the digest of an accepted record that is acontrolevent whose command isrecord_startand whosetargetis the manifest’sdevice: otherwisetake_unknown. - Every accepted
take_commitevent of the device has a payload §5.2 reads: otherwisecommit_malformed, at that record. Those whosetakeis the take id are the take’s held commitments, in the device’s chain order. - At each held commitment, the device’s grant in effect binds it to the manifest’s
principal(§3.1): otherwisedevice_not_bound, at that record. - 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. Otherwisemanifest_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:
delivery_gap: a commitment the manifest lists is not held.commit_chain_broken: the held commitments are not a chain of §5.3: the first is not an opening one, a later one’sn,prevorfirstdoes not follow its predecessor, a later one is an opening one, or a commitment follows a closing one.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.take_not_closed: the last held commitment is not a closing one, or the take has no stop.manifest_not_committed: the manifest’s chunk count is not the closing commitment’ssize, or, for a held commitment whosesizeis above 0, the root over the manifest’s firstsizechunks is not itsroot.clock_unstable: a commitment’s offset differs from o by more than J.capture_discontinuous: a chunk’sstartis not below itsend, or a chunk’sstartis not the previous chunk’send.chunk_too_long: a chunk’send−startis above I.commit_early: the opening commitment’stis below the start’s; the closing one’s is below the stop’s; or a commitment’st+ J is below the mapped end of a chunk it adds (it committed a chunk before the chunk was captured).commit_late: a commitment’stis above the mapped end of a chunk it adds plus I plus J.- Over the parts, in the order given, the first that is not the chunk committed at its
index:
chunk_extrawhen its index is not below the chunk count;chunk_reorderedwhen its bytes’ digest and length are another committed chunk’s;chunk_alteredotherwise.
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:
delivery_gap: the first commitment the manifest lists is not held.commit_chain_broken: the first held commitment is not an opening one.manifest_not_committed: no held commitment adds a chunk, or the root over the manifest’s firstsizechunks is not the root of the first held commitment that does.misaligned: chunk 0’sstart, 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,
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.chunk_missing: an index below s has no part whose bytes’ digest and length are that chunk’s.upload_not_sealed: the last receipt is notsealed, or itsheldis 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:
consent_missing: its consent digest is zero, or no consent record given has it, or that record’ssubjectis not this subject, or itsroomis neither zero nor the room id.guardian_missing: the subject is marked a minor (§3.1: by the record’sminor, or, for the take’s principal, by theminorattribute of the device’s grant in effect at its first held commitment) and the record’sguardianis zero.consent_revoked: the head’srevoked_moduleslists the record’s digest (§8.3).consent_expired: the record does not hold (nbf≤ s <exp) at the start’s instant, s = ⌊(t0+t) / 10^6⌋ of the start, or atat.purpose_not_consented: itspurposeslackrecord, or lack p.
9.5 Outcome
A take verification gives one outcome:
- refuse: the reason and where (§9.1).
- verdicts: the verdict of
single-take,aligned,complete, andpublishablefor each of the four purposes, eachholdsor its reason; and whether the manifest stands: every claim it makes, andpublishablefor every purpose it names, holds.
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:
- the device-clock instant is d = t − o;
- the chunk is the one whose
start≤ d <end; - the position is d − that chunk’s
startinto the chunk, and d − chunk 0’sstartinto 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
- The stream. A take is one live video session. Its
streamId(§19.3.2) is the take id’s first 16 bytes as an RFC 9562 UUID of version 8: byte 6’s high four bits set to1000, byte 8’s high two bits to10, written in the 8-4-4-4-12 lowercase form. The corpus states it for each take. - The segments. Where the master is CMAF, chunk i holds the segment whose movie
fragment the device numbers i + 1 in its
mfhdsequence_number(ISO/IEC 14496-12), and that is the segment’ssequenceNumberin both methods. Chunk 0 holds the initialization segment too (§4.1), with the C2PA Manifest Box §19.2.3 requires. - The hashes. A chunk’s
hashcovers every byte of the chunk, its C2PA boxes included; the segment’sc2pa.hash.bmff.v3excludes them by §19’s exclusions. The two are different digests of different spans.
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)
- The session key (§18.25,
c2pa.session-keys, in the initialization segment’s manifest): the device’s key, as an OKP COSE_Key of curve Ed25519, EdDSA being among §13.2.1’s algorithms, withkidthe key’s 32 bytes;minSequenceNumber1;createdAtthe opening commitment’s instant,t0+t;validityPeriodfrom then to theexpof the device’s grant; andsignerBinding, a COSE_Sign1 by the session key over the claim signer’s end-entity certificate, as §18.25 has it. The device’s key is then bound to the room by its grant and to the C2PA signer by the binding. - Each segment carries an
emsgbox of version 0,scheme_id_uriurn:c2pa:verifiable-segment-info,valuefseg,presentation_time_delta0,timescaleandevent_durationcovering the segment,idthesequenceNumber, andmessage_dataa COSE_Sign1_Tagged by the session key whose payload holdssequenceNumber,bmffHash(the segment’s, excluding its C2PAemsgboxes, with nomerklefield) andmanifestId(§19.4.1, §19.4.2). - A COSE Sig_structure begins with the context string
Signature1, and every message WAI signs begins with awai:domain, so no signature of one is a signature of the other.
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
- AI disclosure (§18.28,
c2pa.ai-disclosure). A take’s manifest has no field for a trained model, and its C2PA output carries noc2pa.ai-disclosure: its source type is a digital capture. A CMAF output that carries captions a model produced (live-events §4.10,origin2 or 3) discloses them:modelIdentifierissha256:followed by the caption’smodeldigest in lowercase hexadecimal;contentProfilehumanOversightLevelisfully_autonomousfororigin2 andhuman_validatedfor 3;modelTypeis required and an event does not carry it, so an emitter writes the assertion only where it knows the model’s type (§18.28’s Table 12). - Sustainability (§18.26,
c2pa.environmental-sustainability). An emitter writesenergy_kwhonly from a measured figure with its class, under energy-binding’s rules, and nevercarbon_kgco2eorwater_litresderived from a figure and a factor. A take carries no energy figure in this revision, so its output carries no such assertion.
12. Reasons
The codes this extension adds, each with where it is raised:
| reason | raised 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).
- Good takes: a lesson take (the director’s remote
record_startwith a count-in, the performer’s chunks and commitments, the director’s markers,record_stop, the upload and the manifest), whose every claim holds butpublishablefortrain, which the performer did not consent to; the same take with an upload interrupted and resumed; and a minor’s take under a guardian’s consent. - Negatives, each a control of one defect: a commitment not delivered, a spliced
chunk, a reordered one, an extra one never committed, a missing one (
completefails,single-takeholds), a late commitment, a root that does not extend its predecessor, a capture that ignored the count-in, a consent missing, expired or revoked, a minor without a guardian, a device not bound to the principal; and every other reason of §12, with a manifest of another room and one whose signature fails.
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
- The commitments are events: signed by the device, chained, stamped with the room clock and held by every participant. A relay can drop one, which the take’s chain and the device’s chain both show, but cannot alter one. A device that edits its master after committing it cannot upload the edit as its take: the edited chunk is not the committed one, and a history it rewrites does not extend what it committed.
- A commitment’s
tis its author’s statement (live-events §11). A device that pre-dates its commitments is held by the room’s other records: its events take their place in the merged order among everyone’s, and the per-hop receipts of a later extension witness when each was seen. The take’s timing rules bound everything to the room clock from the start’s instant on, within I and J. - The capture range of a chunk is the device’s statement. The rules bind it to the room
clock at every commitment (rules 6 to 10 of
single-take) and to the count-in (aligned), so a device can neither stretch a chunk over time it did not capture nor commit a chunk before its capture ends; it can still capture something other than the room, which no commitment proves. - Content addressing makes a part its own check: a part whose bytes do not hash to its
chunk’s
hashis not that chunk, whoever stored it. - The store’s receipts state the upload’s state machine; they cannot make a part the
committed chunk, and a store’s statement of
heldis checked against the manifest’s chunks. The region pin binds where the store keeps the master, as the room’s policy requires (live-rooms §3.7). - A consent is the subject’s act, or a guardian’s for a minor, and revocable through the
key log as a grant is.
publishablereads the head the caller gives: a revocation the head does not yet list is not seen. A minor is marked by the room authority in the device’s grant, and by the record itself, so a minor’s consent signed by the minor alone does not stand. - A take links the device’s key, the principal it captures for and every subject’s
consent. A manifest names subjects by key; who a key is stays with the room authority
and the store that keep the master. The master is personal data of everyone it records:
the region pin and the room’s
retention_days(live-rooms §3.7) apply to it, and a consent’spurposesandexpbound what may be done with it.