C2PA 2.4 extends live video to dynamically packaged content and recommends deterministic signatures for streaming (§19.2.1, §13.2.1, naming ISO/IEC 23009-9); it adds the repository receipt assertion (§18.27). Every manifest identifier is now derived from the stream, a role and the segment's sequence number, so packagers that share a key write identical segments and packagers that do not chain across a failover. The initialization manifest may carry an update manifest with the parent as ingredient and a c2pa.repository-receipt whose proof is an RFC 9942 receipt of inclusion in an RFC 9162 log, verified against the repository's key. Three reasons: manifest_id_mismatch, update_manifest_invalid and repository_receipt_invalid; a failover vector, a registered vector and their negatives; c2pa_live_verify.py reads all of it from the text. The CI step, the registry entry and its export, SPEC §5, the CHANGELOG, the live-conformance README and capture-binding §11 come with it. Co-Authored-By: Claude Opus 5.5 <[email protected]>
729 lines
41 KiB
Markdown
729 lines
41 KiB
Markdown
# WAI Extension: Capture Binding
|
||
|
||
> 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](live-events.md) §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 its C2PA live video output is a take stream of
|
||
> [live-provenance](live-provenance.md) (§11). The
|
||
> reference implementation is the `capture_take` module of `wai-rs` (feature
|
||
> `capture_take`); the corpus is [`live-conformance/takes/`](../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](live-recording.md)); 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](../SPEC.md) §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_commit` events 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_ms` and `jitter_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](staged-measurement.md) 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: `n` 0, `phase` open, `prev` zero, `first` and
|
||
`size` 0, and no proof. The device emits it when the start reaches it, before its
|
||
capture begins.
|
||
- Each later commitment `n` has `n` one above the previous one's, `prev` the previous
|
||
one's digest, `first` the previous one's `size`, and `phase` running 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, `phase` close, 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`, with `held` 0;
|
||
- `progress`: `held` above the previous receipt's, as parts arrive;
|
||
- `resume`: `held` equal to the previous receipt's: an upload interrupted and resumed,
|
||
which continues from the chunk at `held`;
|
||
- `sealed`: `held` equal 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](jwp-receipts.md) §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`:
|
||
|
||
- `claims` sets no bit above bit 2, and `purposes` none above bit 3;
|
||
- *n* is at least 1, each chunk's `index` is its position in the list, and each `len` is
|
||
at least 1;
|
||
- `root` is 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 `principal` is
|
||
one of them;
|
||
- `take` is the take id of `room`, `device` and `start` (§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:
|
||
|
||
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:
|
||
|
||
- **refuse**: the reason and where (§9.1).
|
||
- **verdicts**: the verdict of `single-take`, `aligned`, `complete`, and `publishable`
|
||
for each of the four purposes, each `holds` or its reason; and whether the manifest
|
||
**stands**: every claim it makes, and `publishable` for 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 `start` into the chunk, and *d* − chunk 0's `start`
|
||
into 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 onto the C2PA Technical Specification's live video provenance (§19
|
||
there). A take's CMAF master is a stream of kind `take` of [live-provenance](live-provenance.md)
|
||
(§3.1 there), which writes and verifies the C2PA structures; this section states what a take
|
||
puts in them. The reference implementation is `live-provenance`'s: the `live_provenance`
|
||
module of `wai-rs` writes a take's initialization segment and segments under both of §19's
|
||
methods and verifies them, and its corpus holds a take (`live-conformance/c2pa-live/take-both`).
|
||
The take's commitments are the WAI side's witness, which §9 verifies; 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 to
|
||
`1000`, byte 8's high two bits to `10`, 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 segment *i*, and chunk 0 holds
|
||
the initialization segment before it (§4.1), with the C2PA Manifest Box §19.2.3 requires.
|
||
The segment's `sequenceNumber`, in both methods, is the `mfhd` `sequence_number`
|
||
(ISO/IEC 14496-12) of its first movie fragment, the device numbering its movie fragments
|
||
from 1 across the take (live-provenance §3.3): *i* + 1 where each segment is one movie
|
||
fragment.
|
||
- **The hashes.** A chunk's `hash` covers every byte of the chunk, its C2PA boxes
|
||
included; the segment's `c2pa.hash.bmff.v3` excludes 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 whose manifest holds, as live-provenance §6 lists
|
||
them, a `c2pa.hash.bmff.v3` over the segment, `c2pa.actions.v2` (each segment's manifest is a
|
||
standard manifest, which §15.10.1.2 there requires to hold one) whose one action is
|
||
`c2pa.created` with the IPTC digital source type `digitalCapture` (§18.15.2), and the
|
||
assertion `c2pa.livevideo.segment` with `sequenceNumber` as above, `streamId` the take's,
|
||
`continuityMethod` `c2pa.manifestId`, and `previousManifestId` the manifest identifier of
|
||
the segment before it, or of the initialization segment's manifest for segment 0.
|
||
|
||
Every manifest of the take holds the assertion `science.transaction.wai.take`
|
||
(live-provenance §5.3), a map of `take` (the take id), `room` (the room id) and `device` (the
|
||
device's key), each a byte string; a segment's also holds `commit`, the digest of the device's
|
||
last `take_commit` before the segment was written, and the initialization segment's `epoch`,
|
||
the room-clock instant of the master's media time 0 (§10), 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, with `kid` the key's 32 bytes; `minSequenceNumber` 1; `createdAt`
|
||
the opening commitment's instant, `t0` + `t`; `validityPeriod` from then to the `exp`
|
||
of the device's grant; and `signerBinding`, 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 `emsg` box of version 0, `scheme_id_uri`
|
||
`urn:c2pa:verifiable-segment-info`, `value` `fseg`, `presentation_time_delta` 0,
|
||
`timescale` and `event_duration` covering the segment, `id` the `sequenceNumber`, and
|
||
`message_data` a COSE_Sign1_Tagged by the session key whose payload holds
|
||
`sequenceNumber`, `bmffHash` (the segment's, excluding its C2PA `emsg` boxes, with no
|
||
`merkle` field) and `manifestId` (§19.4.1, §19.4.2), as live-provenance §7.2 writes it.
|
||
- A COSE Sig_structure begins with the context string `Signature1`, and every message
|
||
WAI signs begins with a `wai:` domain, so no signature of one is a signature of the
|
||
other.
|
||
|
||
### 11.4 A room's CMAF output
|
||
|
||
A CMAF rendition of a room's live tracks is a stream of kind `track` of live-provenance, as
|
||
its own live video session: its own `streamId`, its own session key, its own claim signer,
|
||
each segment tied to the JWP receipt of its group (§8 there). That is the publisher's or 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 no `c2pa.ai-disclosure`: its source type is
|
||
a digital capture. A room track or a compositor's output that is labelled machine-made
|
||
discloses its models as live-provenance §9 has it, and captions a model produced as
|
||
[timed-text](timed-text.md) §10.3 has it.
|
||
- **Sustainability** (§18.26, `c2pa.environmental-sustainability`). An emitter writes
|
||
`energy_kwh` only from a measured figure with its class, under
|
||
[energy-binding](energy-binding.md)'s rules, and never `carbon_kgco2e` or
|
||
`water_litres` derived 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/`](../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_start` with a count-in,
|
||
the performer's chunks and commitments, the director's markers, `record_stop`, the
|
||
upload and the manifest), whose every claim holds but `publishable` for `train`, 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 (`complete` fails,
|
||
`single-take` holds), 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 `t` is 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 `hash` is 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 `held` is 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. `publishable` reads 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's `purposes` and `exp` bound what may be done with it.
|