open-standards/wai/extensions/live-provenance.md
Transaction Science e77a94dec2
All checks were successful
joule-moq-room room service / joule-moq-room · does this change touch it (pull_request) Successful in 7s
joule-moq-web browser client / joule-moq-web · does this change touch it (pull_request) Successful in 6s
wai-web reach builds / wai-web reach · does this change touch it (pull_request) Successful in 6s
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Successful in 2m42s
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Successful in 4m36s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 1m25s
WAI conformance / does this change touch it (pull_request) Successful in 48s
WAI energy receipts / does this change touch it (pull_request) Successful in 50s
wai-pack / wai-pack · does this change touch it (pull_request) Successful in 2m1s
wai-quantum studio and desktop / does this change touch it (pull_request) Successful in 1m47s
wai-web wasm32 / does this change touch it (pull_request) Successful in 1m35s
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 7m53s
joule-moq-room room service / joule-moq-room, every target (pull_request) Successful in 2m59s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Successful in 12m59s
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Successful in 2m21s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 31s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 9m13s
wai-pack / wai-pack · linux (pull_request) Successful in 1m21s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 3m49s
wai-quantum studio and desktop / studio · browser (pull_request) Successful in 2m35s
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Successful in 15s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 11s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 29s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 18m48s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 1m32s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 4m18s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 12m19s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 1m28s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 17m57s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 18s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 1m22s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 4m50s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 25m35s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 8m40s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 15m15s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 20s
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Successful in 45s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 5m35s
wai-pack / wai-pack · macos (pull_request) Successful in 1m5s
wai-quantum studio and desktop / desktop app · macos (pull_request) Successful in 2m5s
wai: live provenance §10.3 names the deriving edge's ingredient rule for a packager that derives
Re-segmenting a stream, or writing its objects into another layout or codec, makes a
new stream that names the source segments' manifests as parentOf ingredients, as
deriving-edge §6.1 has an edge name its inputs' stores; packaging the same objects
into this profile's segment derives nothing.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-21 05:08:30 -04:00

59 KiB
Raw Permalink Blame History

WAI Extension: Live Provenance

Status: Draft. Registers the record wai.live.provenance: the live video provenance of C2PA 2.4 §19 for a room's CMAF tracks and for a device's CMAF take, written exactly as §19 defines it and tied to WAI's own per-object commitments. A room track packaged as CMAF is carried over MoQ under draft-ietf-moq-cmsf-01: each MoQ group is one CMAF segment and each object one CMAF chunk. Its publisher signs a JWP group receipt for each group (jwp-receipts), and this extension makes the segment's C2PA hard binding and that receipt's Merkle root commit to the same bytes: the segment, less its C2PA boxes, is the group's objects in order. The per-segment C2PA Manifest Box method (§19.3 there) names the group's receipt inside the signed manifest; the Verifiable Segment Info method (§19.4 there) is signed by a session key that is also the key that signs the group's receipts. One verification then establishes the C2PA provenance of every segment and the receipt of every object. The C2PA boxes of each segment ride a provenance track beside the media track, as the receipts ride the receipts track. The machine-readable marking of machine-made content, the IPTC digital source type and the c2pa.ai-disclosure assertion, follows the stream's disclosure label, as timed-text §10.3 has it for captions, and for a compositor's output, its inputs' labels. Every manifest identifier is derived from the stream and the segment, and every signature is Ed25519, so packagers that share a signing key write the same segment and packagers that do not still chain across a failover: the dynamically packaged content C2PA 2.4 §19.2.1 treats as live. The initialization manifest may carry C2PA's repository receipt (§18.27 there), whose proof is an RFC 9942 receipt of inclusion in a repository's RFC 9162 log. No new transport. The reference implementation is the live_provenance module of wai-rs (feature live_provenance); the corpus is live-conformance/c2pa-live/, and its second implementation is live-conformance/c2pa_live_verify.py. Keywords MUST, MUST NOT, SHOULD, MAY are RFC 2119/8174.

Texts read for this revision, each pinned to the version named: the C2PA Technical Specification, version 2.4 (April 2026), §5.3.1, §6.4, §8.1, §8.4.2, §10.2, §11.1, §11.2, §13.1, §13.2, §14.2, §14.4.1, §14.5, §15.5 to §15.12, §18.6, §18.15, §18.16, §18.25, §18.27, §18.28, §19 and Appendix A.5; RFC 9052 (COSE structures), RFC 9053 (EdDSA and OKP keys), RFC 9360 (x5chain), RFC 9942 (COSE Receipts) §2, §4 and §5, RFC 9162 §2.1, RFC 8949 §4.2.1, RFC 5280, RFC 8410, RFC 7093, RFC 9562 and RFC 4648 §5; draft-ietf-moq-cmsf-01 §3; the Event Message box syntax of ISO/IEC 23009-1 §5.10.3.3.3 as AOM id3-emsg v1.0.0 §2.2.2 reproduces it; the IPTC Digital Source Type vocabulary as released on 2024-10-23; and Regulation (EU) 2024/1689, Article 50. ISO/IEC 14496-12, ISO/IEC 23000-19, ISO/IEC 19566-5:2023 (JUMBF) and ISO/IEC 23009-9:2025 (REaP) were not read for this revision: the box layouts below are the ones C2PA 2.4 and packaging §7 (paragraph 7.6) state, REaP is named only as C2PA 2.4 §13.2.1 and §19.4.1 name it, and the corpus's README says what an independent C2PA validator reports for the corpus. A section number in this text is C2PA 2.4's unless it names another document.

1. Scope

In scope: what a stream is, how its segments correspond to a room track's groups or to a take's chunks, its sequence numbers, its stream identifier and its presentation time (§3); the C2PA structures this extension writes, which are a profile of C2PA 2.4: the manifest store, the claim and its derived identifier, its signature and the signer's certificates, the hard binding, the actions, and the repository receipt in an update manifest (§4); the WAI assertions (§5); the per-segment C2PA Manifest Box method (§6); the Verifiable Segment Info method, its session keys and their rotation (§7); the correspondence with the group receipts and the object receipts (§8); the digital source type and AI disclosure (§9); carriage over HTTP and over MoQ, and dynamic and redundant packaging (§10); verification (§11); and the reasons (§12).

Out of scope: a transport; the media codecs; MPEG transport streams, which §19.1 excludes; a track packaged as WAI envelopes (packaging "wai", moq-streaming-format §3), which is not ISO BMFF and so has no §19 provenance; time-stamps (§10.3.2.5) and revocation information (§14.5.2), which this revision neither writes nor reads; ingredients other than an update manifest's parent, update manifests other than §4.7's, compressed manifests, external manifests and the manifestUri of a segment-info map; the Merkle form of the BMFF hash (Appendix A.5.4), which §19 does not use; consistency proofs between a repository's tree heads (RFC 9942 §5.3); the segment indexing of ISO/IEC 23009-9 §6.2, which §19.4.1 offers as an alternative to mfhd numbering; re-encoding a source to check that two encoders' outputs agree (§10.3); and what a party does with a verdict.

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

Relationship to capture-binding: §11 there maps a take onto C2PA live video. A take is a stream of this extension of kind take (§3.1), and this extension is where that mapping is written and verified.

2. Terms

  • Room, principal, track, Full Track Name: as in live-rooms §2 and §6.1. Group receipt, object receipt, link hash: as in jwp-receipts §2.4 and §3 step 3.
  • Stream: one C2PA live video session (§19): an initialization segment and media segments, of one CMAF track.
  • Chunk: one moof box and the mdat box after it.
  • Segment: a media segment of the stream: its C2PA boxes (§3.2), then one or more chunks.
  • C2PA Manifest Box: the uuid box of Appendix A.5.1, with box_purpose manifest.
  • VSI box: an emsg box carrying a verifiable-segment-info (§19.4.2).
  • Claim signer: the holder of the X.509 end-entity certificate that signs a manifest's claim. Session key: a key of §19.4.4, which signs segment-info maps.
  • Label: the stream's disclosure label (§5.1): its origin (1 human, 2 machine, 3 machine-reviewed, as timed-text §3.1 has them) and a model digest for 2 and 3, or, for a compositor's output, its inputs' labels.
  • T: the instant a verification is made, in microseconds since 1970-01-01T00:00:00Z.

Integers in boxes are big-endian. A digest is SHA-256 (FIPS 180-4), 32 bytes, unless it is named BLAKE3. CBOR is written in RFC 8949 §4.2.1's core deterministic encoding.

3. The stream

3.1 Kinds

A stream is of one of two kinds:

  • track: a room track (live-rooms §6.1) of media kind audio, video or screen, packaged as CMAF and published under draft-ietf-moq-cmsf-01 (packaging "cmaf"). Each MoQ group of the track is one segment and each object one chunk; draft-ietf-moq-cmsf-01 §3.3 lets an object hold more than one chunk, and this extension requires exactly one. The publisher signs a group receipt for every group (jwp-receipts §2.4.1) and an object receipt for every object (§2.4.2 there), and §8 ties them to the segments.
  • take: the CMAF master of a take (capture-binding §3), whose chunk i is the segment i of the stream, chunk 0 holding the initialization segment before it (capture-binding §4.1, §11.1). The take's commitments in the room's events are its WAI witness, and capture-binding §9 verifies them; this extension verifies the C2PA side and the identity a take's manifests carry (§5.3).

3.2 Layout

The initialization segment is exactly three top-level boxes, in this order: ftyp, a C2PA Manifest Box, and moov, holding one track (Appendix A.5.3; §19.2.3); or, once registered, those three followed by the update box of §4.7. It holds no mdat (§19.7.1).

A segment is, in this order:

  1. its C2PA boxes: a C2PA Manifest Box, a VSI box, or the first then the second;
  2. one or more chunks.

It holds no other top-level box. A chunk's moof holds an mfhd, then one traf holding a tfhd with default-base-is-moof and the track's track_ID, a tfdt of version 1 and a trun of version 0 with a data offset and each sample's duration, size and flags, as packaging §7 (paragraph 7.6) writes a fragment; the data offset points at the mdat's payload, and the sizes fill it. The C2PA Manifest Box precedes the first mdat (Appendix A.5.3) and the VSI box precedes the first moof, as an Event Message box precedes the moof of its chunk in CMAF.

The bytes of a segment. A segment is its C2PA boxes followed by its chunks, concatenated, and nothing else. For a track, the chunks are the group's objects in object order, each exactly as it travelled (§10.2), so a segment is P ‖ O₀ ‖ … ‖ Oₙ₋₁, P its C2PA boxes and Oᵢ object i.

3.3 Sequence numbers

Every moof of the stream carries a sequence_number in its mfhd: 1 in the first chunk of the stream's first segment, and one more in each chunk after it, across segments. A segment's sequence number is the sequence_number of its first moof, and it is the sequenceNumber of its live video segment assertion (§19.3.2) and of its segment-info map (§19.4.1), each of which §19 requires to match mfhd.sequence_number. For a track, the segment of group g therefore has the sequence number

s(g) = 1 + Σ n(h), over the groups h of the stream before g,

where n(h) is the leaf_count of group h's receipt.

Where a segment holds more than one chunk, the sequence numbers of consecutive segments differ by the number of chunks of the earlier one. §19.4.1's note reads them as one apart, which is the case of one chunk per segment; its normative text, that the number matches mfhd.sequence_number and never decreases or repeats, holds either way.

3.4 The stream identifier

A stream's streamId (§19.3.2) is an RFC 9562 UUID of version 8, written in the 8-4-4-4-12 lowercase form. Its 16 bytes are, before byte 6's high four bits are set to 1000 and byte 8's high two bits to 10:

  • for a track: the first 16 bytes of SHA-256("wai:live-provenance:stream\x01" ‖ room ‖ u32(len(track)) ‖ track ‖ u64(epoch)), with the fields of the stream assertion (§5.1);
  • for a take: the take id's first 16 bytes (capture-binding §11.1).

3.5 Presentation time

The track's timescale is the timescale of its mdhd. A segment's presentation time is the baseMediaDecodeTime of its first tfdt, in that timescale. For a track its instant is epoch + ⌊baseMediaDecodeTime × 1 000 000 / timescale⌋ microseconds, with the epoch of the stream assertion (§5.1) or, for a take, of the take assertion (§5.3).

4. The C2PA structures

Every C2PA structure this extension writes is the one C2PA 2.4 defines; this section fixes the choices it leaves open, so that every conforming writer of a stream produces the same bytes from the same inputs and keys.

4.1 The manifest store

A C2PA Manifest Box is a uuid box with extended type D8FEC3D6-1B0E-483C-9297-5828877EC481, version 0 and flags 0, then box_purpose manifest and a zero byte, then 8 zero bytes (the offset of the first merkle box, of which a stream has none), then the manifest store (Appendix A.5.1 to A.5.3). It carries no padding.

The store is a JUMBF superbox (jumb, holding a description box jumd and then its content boxes) of a single manifest, the active one. Each description box is the JUMBF type UUID (16 bytes), the toggles byte 0x03 (requestable, label present; §11.1.4.1.2), and the label in UTF-8 followed by a zero byte. The boxes, with their type and label:

box JUMBF type UUID label content
the store 63327061-0011-0010-8000-00AA00389B71 (c2pa) c2pa the manifest
the manifest 63326D61-0011-0010-8000-00AA00389B71 (c2ma) its identifier (§4.2) the assertion store, the claim, the claim signature
the assertion store 63326173-0011-0010-8000-00AA00389B71 (c2as) c2pa.assertions one superbox per assertion, in the claim's order
an assertion 63626F72-0011-0010-8000-00AA00389B71 (cbor) the assertion's label one cbor box of its CBOR
the claim 6332636C-0011-0010-8000-00AA00389B71 (c2cl) c2pa.claim.v2 one cbor box of the claim
the claim signature 63326373-0011-0010-8000-00AA00389B71 (c2cs) c2pa.signature one cbor box of the COSE structure

Every box header is a 4-byte size, which includes the header, and a 4-byte type.

4.2 The manifest identifier and the claim

A manifest's identifier is urn:c2pa: followed by an RFC 9562 UUID of version 4 in the 8-4-4-4-12 lowercase form, with no claim generator identifier (§8.1). Its 16 bytes are, before byte 6's high four bits are set to 0100 and byte 8's high two bits to 10, the first 16 bytes of

SHA-256("wai:live-provenance:manifest\x01" ‖ u32(len(streamId)) ‖ streamId ‖ u8(role) ‖ u64(s)),

streamId in its text form (§3.4), role 0 for the initialization segment's manifest, 1 for a per-segment manifest (§6), 2 for a manifest that delivers session keys in a segment (§7.3) and 3 for the update manifest of §4.7, and s the segment's sequence number (§3.3), 0 for roles 0 and 3. The identifier is a function of the stream and the segment, not of the packager: every packager of a stream writes the same identifier for the same manifest (§10.3). No two manifests of a stream share one, and the 122 bits SHA-256 leaves are distributed as §8.1's random ones are; the identifier is not a secret, and a signature, not the identifier, binds a manifest.

The claim is a claim-map-v2 (§10.2.1) with exactly these fields:

  • instanceID: xmp:iid: followed by the manifest identifier's UUID;
  • claim_generator_info: {"name": "wai", "specVersion": "2.4.0"};
  • signature: self#jumbf=/c2pa/ ‖ the manifest identifier ‖ /c2pa.signature;
  • created_assertions: one hashed URI (§8.4.2.1) per assertion, in the order of §6 and §7: url self#jumbf=/c2pa/ ‖ the manifest identifier ‖ /c2pa.assertions/ ‖ the label, and hash the SHA-256 of the assertion's superbox less its 8-byte header (§8.4.2.3);
  • alg: sha256.

4.3 The claim signature

The claim signature is a COSE_Sign1_Tagged (CBOR tag 18; RFC 9052 §4.2) whose payload is detached (nil) and whose Sig_structure payload is the claim's CBOR (§10.3.2.4, §13.2.2):

  • protected header: the map {1: -8, 33: [EE, CA]}: the algorithm EdDSA, the Ed25519 instance (§13.2.1; RFC 9053 §2.2), and x5chain under its integer label (§14.5; RFC 9360) holding the claim signer's end-entity certificate and then every intermediate certificate, the trust anchor left out;
  • unprotected header: the empty map;
  • Sig_structure: ["Signature1", protected, h'', payload] (§13.2.3);
  • signature: the 64-byte Ed25519 signature (RFC 8032) of the claim signer over the Sig_structure's CBOR.

Ed25519 is deterministic, which §13.2.1 recommends for streaming so that equal segments remain equal. Every signature this extension writes is Ed25519.

4.4 Certificates

The claim signer's chain meets §14.5.1.1 and RFC 5280: X.509 version 3; signature algorithm id-Ed25519 (RFC 8410 §3), no parameters; Ed25519 subject public keys; no unique identifiers; a Basic Constraints extension, critical, asserting cA in a certificate authority's certificate and absent cA in the end-entity's; a Key Usage extension, critical, with keyCertSign (and cRLSign) in a certificate authority's and digitalSignature alone in the end-entity's; a Subject Key Identifier in every certificate, the leftmost 160 bits of the SHA-256 of the subject public key (RFC 7093 §2, method 1); an Authority Key Identifier, its key identifier, in every certificate not self-signed; and, in the end-entity's, an Extended Key Usage extension holding c2pa-kp-claimSigning (1.3.6.1.4.1.62558.2.1) and id-kp-documentSigning (1.3.6.1.5.5.7.3.36), which §14.4.1 recommends for older validators. Every name is an organization (id-at-organizationName) and a common name, each a UTF8String. Validity is written as UTCTime through 2049 and GeneralizedTime from 2050 (RFC 5280 §4.1.2.5).

A verifier of this profile accepts a chain under §14.5.1 and §15.7 (§11.2).

4.5 The hard binding

Every manifest holds exactly one hard binding, a c2pa.hash.bmff.v3 assertion (§18.6), the map:

  • exclusions: exactly these four, in this order:
    1. {"xpath": "/uuid", "data": [{"offset": 8, "value": h'D8FEC3D61B0E483C92975828877EC481'}]} (Appendix A.5.6);
    2. {"xpath": "/ftyp"};
    3. {"xpath": "/mfra"};
    4. {"xpath": "/emsg", "data": [{"offset": 12, "value": 'urn:c2pa:verifiable-segment-info' ‖ h'00'}]} (§19.4.1);
  • alg: sha256;
  • hash: the hash below;
  • sequenceNumber: in a segment's manifest, the segment's sequence number (§3.3); absent in the initialization segment's.

The hash (§18.6.2, Example 6) is SHA-256 over, for each top-level box of the segment that no exclusion matches, in order, the box's offset from the segment's first byte as a u64 followed by every byte of the box. An exclusion matches a box when the box's type is the xpath's and, at each data entry's offset from the box's first byte, the box holds the entry's value. Every box the profile excludes is excluded whole, so no subset applies. In the initialization segment the hash covers the moov, which §19.2.3 requires.

The fourth exclusion is the VSI box. §19.4.1 writes its data offset as 0, where every box holds its size; offsets count from a box's first byte (§18.6.2, as the first exclusion's 8 shows), and the scheme_id_uri of an emsg of version 0 begins at 12. This extension writes 12, the entry that excludes the box. The same list is written in every manifest and every segment-info map of a stream, whether or not the segment holds a VSI box, so a segment that carries both methods has one hash under both.

4.6 The actions

Every manifest holds a c2pa.actions.v2 assertion (§18.15.2) whose actions is one action: {"action": "c2pa.created", "digitalSourceType": DST}, DST the IPTC term of §9 as a URI under http://cv.iptc.org/newscodes/digitalsourcetype/. Each segment's manifest is a standard manifest and so holds its own (§15.10.1.2).

4.7 The repository receipt

A publisher that registers the initialization segment's manifest with a C2PA Manifest Repository records the repository's proof of ingestion in a c2pa.repository-receipt assertion (§18.27), which C2PA allows only in an update manifest (§11.2.3):

  • The layout. The initialization segment keeps its three boxes, its C2PA Manifest Box's box_purpose becoming original, and a fourth, a C2PA uuid box of box_purpose update, ends the file (Appendix A.5.3). Both purposes are eight bytes, so no offset moves and the hard binding is unchanged; both boxes are C2PA uuid boxes, which the hash excludes.
  • The update store holds one update manifest (JUMBF type 6332756D-0011-0010-8000-00AA00389B71, c2um), of role 3 (§4.2), whose claim (§4.2) and signature (§4.3) are as a standard manifest's, and which holds, in this order and nothing else:
    1. c2pa.ingredient.v3 (§18.16), the map {"relationship": "parentOf", "validationResults": V, "activeManifest": {"url": "self#jumbf=/c2pa/" ‖ P, "hash": H}, "claimSignature": {"url": "self#jumbf=/c2pa/" ‖ P ‖ "/c2pa.signature", "hash": G}}, P the parent's identifier, H and G the hashes of its manifest superbox and its claim signature superbox (§8.4.2.3), as §11.2.3 requires of an update manifest's one ingredient. V is the result of validating the parent, which §18.16.12.4.3 requires with an activeManifest: {"activeManifest": {"success": S, "informational": [], "failure": []}, "ingredientDeltas": [], "specVersion": "2.4.0"}, S the status maps {"code": C, "url": U} of claimSignature.validated, signingCredential.trusted and claimSignature.insideValidity with U the parent's signature URI, then assertion.hashedURI.match for each of the parent's assertions in its claim's order with U that assertion's URI (self#jumbf=/c2pa/ ‖ P ‖ /c2pa.assertions/ ‖ its label), then assertion.bmffHash.match with the hard binding's URI. A parent that does not verify is not registered, so S is the one list a valid parent gives, and a verifier recomputes it;
    2. c2pa.repository-receipt, a JSON content superbox (JUMBF type 6A736F6E-0011-0010-8000-00AA00389B71, one json box), the object of §18.27.2: {"@context": {"c2pa": "https://c2pa.org/ns/", "receipt": "https://c2pa.org/ns/repository-receipt#"}, "@type": "org.c2pa.repository-receipt", "repository": {"uri": R, "manifestId": P}, "anchor": {"uri": A, "proof": {"profile": "urn:ietf:rfc:9942", "entry": E, "receipt": C}}}, written compactly in this member order. R is the repository's base URI and A where the proof may be retrieved.
  • The proof. §18.27 leaves the proof object to the repository; this extension's is a receipt of inclusion. The repository keeps an RFC 9162 §2.1 log over SHA-256 whose entries are manifests by H; E is H in lowercase hexadecimal, and C is, in base64url without padding (RFC 4648 §5), an RFC 9942 §5.2.1 receipt of inclusion: a COSE_Sign1_Tagged with protected header {1: -8, 395: 1} (EdDSA, vds RFC9162_SHA256), unprotected header {396: {-1: [I]}} with one inclusion proof I, the CBOR of [tree_size, leaf_index, inclusion_path] in a byte string, and a detached payload. Its signature is the repository's Ed25519 key's over the Sig_structure whose payload is the tree's root.

A verifier holds the repositories it accepts, each its base URI and key. The receipt then establishes that the repository's log held the manifest at that tree size: the leaf is SHA-256(0x00 ‖ H), the inclusion path leads from it to a root (RFC 9162 §2.1.3.2), and the repository signed that root. It does not establish the manifest's content or its signer, which §11.2 checks, and it is a witness of registration, not a trust anchor (transparency §6).

5. The WAI assertions

The assertions in this section are CBOR maps under C2PA's entity-specific namespace (§6.2.1).

5.1 The stream assertion

A track's initialization manifest, and every manifest of a segment that holds no live video segment assertion (§7.3), holds science.transaction.wai.stream:

key value
room the room id, 32 bytes
track the track's Full Track Name in MOQT §8.8's rendering, as live-rooms §6.1 writes it: <room>-<owner>-<kind>--<layer>
trackAlias the wai_receipt_alias the track's receipts sign (moq-streaming-format §4)
epoch the instant of media time 0, in microseconds since 1970-01-01T00:00:00Z
firstGroup the group id of the stream's first segment
label {"origin": O} with O 1; {"origin": O, "model": M} with O 2 or 3 and M the model's 32-byte digest; or, for a compositor's output, {"inputs": [I, …]}, each I {"streamId": S, "track": N, "origin": O} or the same with "model": M

The track's first field is room in lowercase hexadecimal, and its kind is audio, video or screen. A stream assertion in a later manifest equals the initialization manifest's.

5.2 The group assertion

A track's per-segment manifest (§6) holds science.transaction.wai.group:

key value
groupId the group id of the segment's group
receipt the link hash of the group's receipt (jwp-receipts §3 step 3), 32 bytes
root the group receipt's root_hash, 32 bytes
objects the group receipt's leaf_count

5.3 The take assertion

Every manifest of a take holds science.transaction.wai.take, as capture-binding §11.2 has it: take (the take id), room (the room id) and device (the device's key), each 32 bytes, and commit (32 bytes), the digest of the device's last take_commit before the segment was written, absent in the initialization manifest. The initialization manifest's also holds epoch: the instant of the master's media time 0 on the room clock, in microseconds since 1970-01-01T00:00:00Z (capture-binding §10), which no later manifest holds. take, room and device are the same in every manifest of the take.

6. The per-segment C2PA Manifest Box method

Under §19.3, every segment holds a C2PA Manifest Box whose manifest holds, in this order:

  1. c2pa.hash.bmff.v3 over the segment (§4.5);
  2. c2pa.actions.v2 (§4.6);
  3. c2pa.livevideo.segment (§19.3.2): {"sequenceNumber": s, "streamId": S, "continuityMethod": "c2pa.manifestId", "previousManifestId": P}, s the segment's sequence number (§3.3), S the stream's identifier (§3.4), and P the identifier of the manifest of the segment before it, which for the first segment is the initialization segment's;
  4. the AI disclosure assertions of §9, if any;
  5. science.transaction.wai.group (§5.2) for a track, or science.transaction.wai.take (§5.3) for a take.

The initialization segment's manifest holds c2pa.hash.bmff.v3 over it, c2pa.actions.v2, c2pa.session-keys where §7 applies, the AI disclosure assertions, and the stream assertion (§5.1) for a track or the take assertion for a take.

The continuity chain makes each segment's manifest name the one before it. A segment that is lost, inserted or moved breaks it at the next segment (§19.7.2).

7. The Verifiable Segment Info method

7.1 Session keys

Under §19.4, the initialization manifest holds c2pa.session-keys (§18.25): {"keys": [K, …]}, each K:

  • key: an OKP COSE_Key (RFC 9053 §7.2): {1: 1, 2: kid, -1: 6, -2: x}, the key type OKP, its kid, the curve Ed25519 and the 32-byte public key x. kid is x itself, so a key names itself;
  • minSequenceNumber: the first sequence number it signs;
  • createdAt: CBOR tag 0 over an RFC 3339 date-time in UTC with no fraction, YYYY-MM-DDTHH:MM:SSZ;
  • validityPeriod: seconds from createdAt;
  • signerBinding: a COSE_Sign1_Tagged with protected header {1: -8}, an empty unprotected header and a detached payload (nil), whose Sig_structure payload is the claim signer's end-entity certificate, the first certificate of the manifest's x5chain. §18.25's example shows the payload as h''; §13.2.3 reserves nil for detached content, and the text calls the signature detached, so this extension writes nil.

A key is valid from createdAt to createdAt + validityPeriod (excluded). For a track, the session key that signs a segment's segment-info map is also the signer_pubkey of that segment's group receipt (§8.3). For a take, the session key is the device's key (capture-binding §11.3).

7.2 The VSI box

Every segment holds a VSI box (§19.4.2): an emsg of version 0 and flags 0 (ISO/IEC 23009-1 §5.10.3.3.3) with:

field value
scheme_id_uri urn:c2pa:verifiable-segment-info, then a zero byte
value fseg, then a zero byte
timescale the track's timescale (§3.5)
presentation_time_delta 0
event_duration the sum of the segment's sample durations, in the timescale
id the segment's sequence number
message_data the verifiable-segment-info

The verifiable-segment-info is a COSE_Sign1_Tagged with protected header {1: -8}, unprotected header {4: kid}, the session key's kid, and the payload present: the CBOR of the segment-info map {"sequenceNumber": s, "bmffHash": H, "manifestId": M}. s is the segment's sequence number; H is the segment's hash as §4.5 writes it, with its sequenceNumber and no merkle; M is the identifier of the active manifest: the manifest of the segment's own C2PA Manifest Box where it has one, and otherwise the last manifest the stream carried before it. The signature is the session key's over the Sig_structure ["Signature1", protected, h'', payload].

7.3 Rotation

A new session key is delivered in a manifest carried in a segment's C2PA Manifest Box (§19.4.3, §18.25), whose minimum sequence number is that segment's. Appendix A.5.3 requires every initialization segment of a stream to carry the same manifest store, so this extension rotates keys in a segment, not in a repeated initialization segment. That manifest holds, in this order, the hard binding over its segment, the actions, c2pa.session-keys with the new keys, the AI disclosure assertions, and the stream or take assertion; it holds no live video segment assertion unless the stream also uses §6, and it becomes the active manifest. Its segment's segment-info map names it and is signed by the new key.

A segment is signed by the current key for its sequence number s: among the keys the verifier holds, the one with the greatest minSequenceNumber not above s. A key's minSequenceNumber is at least the sequence number of the segment that delivers it.

8. The correspondence with WAI's receipts

8.1 The same bytes

For a track, the segment of group g and the group's receipts commit to the same objects:

  • the segment's chunks are the group's objects, one chunk an object, in object order (§3.2);
  • object i's content_hash is BLAKE3("moq-jwp:object\x01" ‖ Oᵢ) (jwp-receipts §2.1), over the chunk's bytes, its moof and its mdat, as the object travelled;
  • the group receipt's root_hash is the Merkle root of jwp-receipts §2.2 over the objects' leaves, and its leaf_count is the number of chunks;
  • the segment's hard binding covers the same chunks at their offsets in the segment (§4.5).

So no byte of an object can change without changing both the root the publisher signed and the hash the claim signer or the session key signed, and each covers exactly the objects: the C2PA boxes are what C2PA adds, and the group's objects are everything else. The objects carry no energy label in this profile, so each leaf takes jwp-receipts §2.2's first form.

8.2 The per-segment method

A per-segment manifest names its group's receipt in its group assertion (§5.2): groupId is the receipt's group_id, receipt its link hash, root its root_hash and objects its leaf_count. The claim signer thereby signs which receipt the segment is, and the receipt's signer signs which objects it holds.

8.3 The Verifiable Segment Info method

The session key that signs a segment's segment-info map is the signer_pubkey of the group's receipt: one key signs both, over different structures. A Sig_structure's CBOR begins with the byte 0x84 and a group signing payload with moq-jwp: (jwp-receipts §2.3), so no message of one is a message of the other. A rotation (§7.3) changes the receipt key at the same group, and the receipts of the new key start a chain of their own, whose first parent_receipt_hash is null (jwp-receipts §2.4.1).

8.4 Receipts

The group receipt of every group of the stream, from firstGroup on, verifies under jwp-receipts §3 steps 1 and 2, has the stream's trackAlias, and links to the receipt before it under the same key (§3 step 3 there). Every object receipt of the group is accepted under jwp-receipts §3 steps 1 to 5 against the group receipt and its chunk's bytes. The sequence numbers of §3.3 follow from the receipts' leaf_counts.

9. Digital source type and AI disclosure

9.1 A stream's own label

label digital source type AI disclosure
origin 1, kind audio or video digitalCapture none
origin 1, kind screen screenCapture none
origin 2, model M trainedAlgorithmicMedia one, M, fully_autonomous
origin 3, model M compositeWithTrainedAlgorithmicMedia one, M, human_validated
a take digitalCapture none

An AI disclosure is a c2pa.ai-disclosure assertion (§18.28): {"modelType": "c2pa.types.model", "modelIdentifier": "tag:transaction.science,2026:wai/model/" ‖ M, "contentProfile": {"humanOversightLevel": H}}, M the model's digest in lowercase hexadecimal, exactly as timed-text §10.3 writes it for a caption model. c2pa.types.model is Table 12's generic model type: an event names its model by digest and not by type. Several are labelled c2pa.ai-disclosure, c2pa.ai-disclosure__1, … (§6.4).

9.2 A compositor's output

The output of a server compositor (live-compose) is a stream whose label lists its inputs. Its digital source type is compositeCapture when every input is of origin 1 and kind audio or video; compositeSynthetic when any input is of origin 2 or 3; and composite otherwise. It carries one AI disclosure per model of its inputs, in the order models first appear among them, fully_autonomous when any input of that model is of origin 2 and human_validated otherwise. This is timed-text §10.3's rule for a set of cues of more than one origin, applied to inputs.

9.3 What is checked

Every manifest of a stream holds the digital source type and exactly the AI disclosures its label gives, in that order; a verifier refuses a manifest whose c2pa.created action names another source type (source_type_mismatch) or that lacks, adds or changes a disclosure (ai_disclosure_missing). The check is against the label the stream states. Whether content is machine-made is not decidable from its bytes: the label is the publisher's statement, signed with the stream's other assertions, as a caption's origin is its producer's (timed-text §14). What the check establishes is that a stream labelled machine-made carries C2PA's machine-readable marking of it, in every manifest, and a stream labelled human carries none.

Regulation (EU) 2024/1689, Article 50(2), requires the outputs of AI systems that generate synthetic audio, image, video or text to be marked in a machine-readable format and detectable as artificially generated or manipulated; Article 50(4) requires a deployer to disclose a deep fake. The digital source type and the AI disclosure assertion are such a marking, carried in every segment's provenance. Writing them is an engineering requirement of this extension, and meeting it is not a statement about the Regulation.

10. Carriage

10.1 HTTP

Over HTTP the initialization segment and each segment are files as §3.2 lays them out, and a playlist lists them as for any CMAF track (live-recording §7).

10.2 MoQ: the provenance track

Over MoQ the track's objects are its chunks, exactly as draft-ietf-moq-cmsf-01 §3.3 has them, and the initialization segment, its C2PA Manifest Box included, is the catalog's inline initialization data (§3.1 there). Each segment's C2PA boxes ride a provenance track:

  • it is in the media track's namespace and its Track Name is the media track's followed by _c2pa, as 0_c2pa (live-rooms §6.1's characters);
  • its group g holds one object, the C2PA boxes of the media track's group g, exactly as they lead the segment;
  • the media track's catalog entry names it in wai_c2pa_track, and its own catalog entry has packaging "wai-c2pa";
  • a relay forwards it as it forwards any track, unaltered.

A subscriber that verifies reassembles the segment of group g as the provenance object of group g followed by the media objects of group g in object order.

Why a track, not an object property. A MOQT object property (MOQT §8.4, as moq-streaming-format §5 uses one) rides the header of every copy of the object it is on, and a property of the first object would make every forwarded copy of that object kilobytes larger, since a manifest holds a certificate chain. A track keeps the media objects exactly the CMAF chunks a CMSF player plays and the receipts commit to (§8.1), lets a subscriber that does not verify leave it out, lets one that does fetch a past group's provenance by its group number (live-recording §8), and has the receipts track's form (live-rooms §6.1), with which it shares group numbers.

10.3 Dynamic and redundant packaging

C2PA 2.4 treats dynamically packaged content, packaged just in time on request, as live video (§19.2.1), and recommends deterministic signature algorithms for streaming so that segments packaged apart remain equal and cacheable, naming ISO/IEC 23009-9 (REaP) among the workflows that need it (§13.2.1). In this profile every byte of a segment is a function of the group's objects, the stream, the segment's sequence number and the claim signer's key:

  • the chunks are the objects as they travelled (§3.2), and the layout and every box are fixed (§4);
  • the manifest identifiers are derived (§4.2), not drawn;
  • CBOR is deterministic (§2), and Ed25519 signatures are deterministic (RFC 8032), as are the session keys' and the receipts'.

So two packagers that hold one claim signer's key and package the same group write the same segment, byte for byte: a segment from either is interchangeable with the other's and caches as one. Two packagers with different keys write segments whose manifests differ in their signatures and certificates but share their identifiers, so the continuity chain of §6 holds across a failover from one to the other, and both segments' chunks are the objects one group receipt commits to (§8.1). Under the Verifiable Segment Info method a session key also signs the receipts (§8.3), so a packager that is not the publisher uses the per-segment method.

A packager that changes what a segment holds, by re-segmenting a stream or writing its objects into another layout or codec, makes a new stream, derived from the first: its manifests are its own, under its own stream identifier, and where it holds the source segments' manifests, its output's manifest names each as a parentOf ingredient, with c2pa.repackaged for a repackaging and c2pa.transcoded for a re-encoding, as deriving-edge §6.1 has a deriving edge name its inputs' manifest stores. Packaging a group's objects into this profile's segment, which this section is about, changes no object: there is no derivation, and the objects stay under the publisher's receipt.

What a verifier checks of a repackaged segment is its own: the identifiers (§11.4 step 3), the manifest under its own signer, and the objects under the publisher's receipt, so it needs to trust neither packager for the objects. Checking that two encoders produced the same objects, by re-encoding the source under a WAI capability whose decode is replicate (determinism-tiers), is not in this revision: it needs the source and an encoder that is itself a pure function of it.

11. Verification

11.1 Inputs

A verifier holds: the trust anchors it accepts for c2pa-kp-claimSigning (§14.4.1); the manifest repositories it accepts, each its base URI and Ed25519 key (§4.7); the instant T; the stream's kind; its initialization segment; its segments in the order it received them, each with its group id for a track (the MoQ group it came in, or the playlist's); and, for a track, the group receipts and object receipts of the stream's groups. It verifies in the order below and stops at the first refusal, which it reports with the reason, where it was raised (init, segment k for the k-th segment received counting from 0, or group g), and the two C2PA codes §12 gives it. A stream every step accepts is accepted.

11.2 A manifest

A C2PA Manifest Box of a segment (or of the initialization segment) is verified against that segment's bytes, in C2PA's order (§15.6 to §15.12):

  1. The store, the manifest and its three parts read as §4.1 lays them out, the manifest identifier as §4.2 has it, the claim as well-formed deterministic CBOR with the fields of §4.2, and the claim signature as a COSE_Sign1_Tagged with an EdDSA algorithm, a detached payload and exactly one x5chain, in the protected header (§14.2): otherwise manifest_malformed.
  2. The end-entity certificate and the intermediates meet §4.4's profile and §14.5.1.1, and the end-entity's Extended Key Usage holds c2pa-kp-claimSigning and neither id-kp-timeStamping nor id-kp-OCSPSigning: otherwise credential_invalid (signingCredential.invalid).
  3. A chain is built from the end-entity through the intermediates to a trust anchor, each certificate's issuer name equal to the next's subject, its Authority Key Identifier to the next's Subject Key Identifier, and its signature verifying under the next's key: otherwise credential_untrusted (signingCredential.untrusted).
  4. The signature verifies under the end-entity's key over the claim (§4.3), with the strict Ed25519 rule of jwp-receipts §3.1 step 3: otherwise claim_signature_mismatch (claimSignature.mismatch).
  5. T is within the validity of every certificate of the chain, the anchor's included; with no time-stamp, §15.8 checks the current time: otherwise credential_expired (claimSignature.outsideValidity).
  6. Every hashed URI of created_assertions resolves to an assertion of the store whose hash it holds: otherwise assertion_hash_mismatch (assertion.hashedURI.mismatch), or, for a URI that resolves to nothing, manifest_malformed.
  7. The assertions are the ones §6 or §7 lists for the manifest, in that order and no others, the AI disclosures in any number at their place (§9.3 checks them), each readable with the fields it defines: exactly one hard binding (§15.10.1.2), with §4.5's exclusions and alg; the actions of §4.6, one c2pa.created action with a source type of the IPTC vocabulary; and the rest as their sections give them: otherwise manifest_malformed. Which list applies follows from where the manifest is and what it holds: the initialization segment's (§6), a segment's with a live video segment assertion (§6), or a segment's without one (§7.3).
  8. The hard binding's hash is the segment's hash (§4.5): otherwise hard_binding_mismatch (assertion.bmffHash.mismatch).

A refusal at steps 1 to 8 in any segment, the initialization segment included, carries §19.7's livevideo.manifest.invalid.

11.3 The initialization segment

  1. Its boxes read: otherwise segment_malformed. It holds an mdat, or is not ftyp, a C2PA uuid box and moov with one track whose tkhd, mdhd and trex read, then optionally a second C2PA uuid box: otherwise init_invalid.
  2. Its manifest verifies (§11.2): the store of the first C2PA box, of purpose manifest, or original where a second box follows.
  3. For a track, the stream assertion reads as §5.1 has it; for a take, the take assertion as §5.3 has it: otherwise stream_assertion_invalid. The stream identifier follows from it (§3.4), and the manifest's identifier is §4.2's of role 0: otherwise manifest_id_mismatch.
  4. The digital source type and the AI disclosures are the label's (§9.3).
  5. Every session key reads as §7.1 has it, its kid is its x, and its signerBinding verifies under it over the claim signer's end-entity certificate, and no two keys share an x: otherwise session_key_invalid. For a take, a session key is the take assertion's device.
  6. Where a second C2PA box follows, of purpose update (§4.7): its store reads as one update manifest, whose claim and signature verify as §11.2 steps 1 to 6 have them, a failure of form being update_manifest_invalid; its identifier is §4.2's of role 3 (manifest_id_mismatch); it holds exactly the ingredient and the repository receipt of §4.7, the ingredient naming the initialization manifest and its claim signature by their hashes, and the receipt's JSON reading as §4.7 writes it, naming that manifest and its hash (update_manifest_invalid); the repository is one the verifier accepts, and the receipt of inclusion verifies under its key for the entry H (repository_receipt_invalid).

11.4 Each segment

For the segment k received, of group g for a track:

  1. Its boxes read, and after a leading C2PA Manifest Box and then a VSI box, each where present, they are one or more chunks as §3.2 lays them out: otherwise segment_malformed. It holds neither C2PA box: provenance_missing. Its moofs carry consecutive sequence_numbers and the track's track_ID: otherwise segment_malformed.
  2. Its sequence number exceeds the previous segment's, and, for a track, g exceeds the previous segment's group: otherwise sequence_not_increasing.
  3. Where it holds a C2PA Manifest Box, the manifest verifies (§11.2), its identifier is §4.2's, of role 1 where it holds a live video segment assertion and 2 where it does not, for the segment's sequence number (manifest_id_mismatch), its hard binding's sequenceNumber is the segment's sequence number (sequence_mismatch), and then:
    • with a live video segment assertion (§6): its sequenceNumber is the segment's sequence number (sequence_mismatch); its streamId is the stream's (stream_id_mismatch); continuityMethod is c2pa.manifestId and previousManifestId is present (continuity_invalid); and previousManifestId is the identifier of the previous segment's manifest, or the initialization segment's for the first (continuity_broken). For a track the group assertion reads (§11.2 step 7) and is checked at step 6; for a take the take assertion's take, room and device are the initialization manifest's (stream_assertion_invalid);
    • without one (§7.3): the segment holds a VSI box, as the method that delivers keys signs its segments (provenance_missing); the manifest holds the stream or take assertion, equal to the initialization manifest's (stream_assertion_invalid), and new session keys, each read as §11.3 step 5 reads one, none with the x of a key already held, and each with a minSequenceNumber not below the segment's sequence number (session_key_invalid); the keys are added to those held, and the manifest becomes the active one;
    • its digital source type and AI disclosures are the label's (§9.3).
  4. Where it holds a VSI box:
    • the box has §7.2's fields, the verifiable-segment-info reads as a COSE_Sign1_Tagged with an EdDSA algorithm, a kid and a payload present, and the payload as a segment-info map with §4.5's exclusions and alg: otherwise segment_info_invalid;
    • the verifier holds a session key of that kid: otherwise session_key_unknown;
    • the signature verifies under it, by the strict rule: otherwise segment_info_signature_failed;
    • the key is the current key for the segment (§7.3), its minSequenceNumber is not above the segment's sequence number, and the segment's presentation instant (§3.5) is within its validity: otherwise session_key_out_of_range;
    • the map's sequenceNumber, and its bmffHash's, is the segment's sequence number: otherwise sequence_mismatch;
    • its manifestId is the active manifest's (§7.2): otherwise segment_info_invalid;
    • its bmffHash.hash is the segment's hash (§4.5): otherwise segment_hash_mismatch.
  5. For a track: the group receipt of every group from firstGroup to g is held (receipt_missing), and only one of group g (receipt_invalid), and the sequence number is s(g) (§3.3). For a take: the sequence number is one more than the last sequence_number of the previous segment, or 1 for the first. Otherwise sequence_mismatch.
  6. For a track, of group g's receipt (§8): it is of the legacy form (jwp-receipts §2.3; its total carries no class of its own, §8.1), it verifies, has the stream's trackAlias and group_id g, and links to the held receipt of the group before it under the same key, or has a null parent where that receipt is under another key or g is firstGroup (receipt_invalid); its leaf_count is the number of chunks and its root_hash the root over the chunks' leaves, and, for a per-segment manifest, the group assertion names it (receipt_mismatch); for a segment with a VSI box, its signer_pubkey is the session key that signed the segment-info map (receipt_signer_mismatch); and the group's object receipts are one per chunk, each accepted against its chunk (object_receipt_invalid).

11.5 The end of the stream

For a track, every group from firstGroup to the last of which the verifier holds a receipt has a segment: otherwise segment_missing, at the first group without one.

11.6 What an accepted stream states

The stream identifier; its digital source type; the initialization manifest's registration, where it carries one: the repository's base URI, the tree size and the leaf index; and, for each segment in order, its sequence number, its group (a track) or chunk index (a take), the methods it carries (manifest, vsi or both), the identifier of its active manifest, its number of chunks, and the root of its group (a track) or the SHA-256 of its take chunk (a take: chunk 0 is the initialization segment followed by segment 0).

12. Reasons

reason raised at C2PA 2.4 §15 code §19.7 code
segment_malformed §11.3 step 1, §11.4 step 1 — livevideo.segment.invalid
init_invalid §11.3 step 1 — livevideo.init.invalid
provenance_missing §11.4 steps 1, 3 — livevideo.segment.invalid
manifest_malformed §11.2 steps 1, 6, 7; §11.4 step 3 claim.malformed livevideo.manifest.invalid
credential_invalid §11.2 step 2 signingCredential.invalid livevideo.manifest.invalid
credential_untrusted §11.2 step 3 signingCredential.untrusted livevideo.manifest.invalid
claim_signature_mismatch §11.2 step 4 claimSignature.mismatch livevideo.manifest.invalid
credential_expired §11.2 step 5 claimSignature.outsideValidity livevideo.manifest.invalid
assertion_hash_mismatch §11.2 step 6 assertion.hashedURI.mismatch livevideo.manifest.invalid
hard_binding_mismatch §11.2 step 8 assertion.bmffHash.mismatch livevideo.manifest.invalid
manifest_id_mismatch §4.2, §11.3 steps 3, 6, §11.4 step 3 — —
stream_assertion_invalid §11.3 step 3, §11.4 step 3 — —
source_type_mismatch §9.3, §11.3 step 4, §11.4 step 3 — —
ai_disclosure_missing §9.3, §11.3 step 4, §11.4 step 3 — —
update_manifest_invalid §4.7, §11.3 step 6 manifest.update.invalid livevideo.manifest.invalid
repository_receipt_invalid §4.7, §11.3 step 6 — —
session_key_invalid §11.3 step 5, §11.4 step 3 — livevideo.sessionkey.invalid
sequence_not_increasing §11.4 step 2 — livevideo.assertion.invalid
sequence_mismatch §11.4 steps 3, 4, 5 — —
stream_id_mismatch §11.4 step 3 — livevideo.assertion.invalid
continuity_invalid §11.4 step 3 — livevideo.continuityMethod.invalid
continuity_broken §11.4 step 3 — livevideo.segment.invalid
segment_info_invalid §11.4 step 4 — livevideo.segment.invalid
session_key_unknown §11.4 step 4 — livevideo.segment.invalid
segment_info_signature_failed §11.4 step 4 — livevideo.segment.invalid
session_key_out_of_range §11.4 step 4 — livevideo.segment.invalid
segment_hash_mismatch §11.4 step 4 assertion.bmffHash.mismatch livevideo.segment.invalid
receipt_missing §11.4 step 5 — —
receipt_invalid §11.4 step 6 — —
receipt_mismatch §11.4 step 6 — —
receipt_signer_mismatch §11.4 step 6 — —
object_receipt_invalid §11.4 step 6 — —
segment_missing §11.5 — —

The two code columns are what a C2PA validator reports for the same defect where C2PA checks it: §15's failure code for a check of a manifest, and §19.7's for a check of live video, which for a manifest's own failure in a segment is livevideo.manifest.invalid (§19.7.1). A refusal reports both. — marks a check C2PA does not make: the rules of this extension; the sequence numbers' match with mfhd.sequence_number, which §19.4.1 requires of a writer and §19.7 does not check; the identifiers' derivation; and the repository's proof, whose form §18.27 leaves to the repository. update_manifest_invalid stands for every failure of the update manifest's form, which §15.10.1.3 reports as manifest.update.invalid or manifest.update.wrongParents; it reports the first. manifest_malformed stands for every failure of a manifest's form, whose §15 codes are many (claim.malformed, claim.cbor.invalid, claimSignature.missing, algorithm.unsupported, assertion.missing, claim.hardBindings.missing, assertion.bmffHash.malformed, …); it reports claim.malformed. Over the Verifiable Segment Info method alone, §19.7.3 states no order check, and sequence_not_increasing there is this extension's rule, reported with §19.7.2's code.

13. Conformance

live-conformance/c2pa-live/ holds the corpus, written from fixed seeds by wai_live_provenance_vectors gen: vectors.json, the trust anchor, and for each vector its initialization segment, its segments and, for a track, its receipts, each a file of its own. Every key and certificate is derived from the seeds, and none is a key anyone holds; every manifest identifier is its stream's (§4.2). The media are the AV1 floor of the packaging corpus's video-base-01 (packaging §7, paragraph 7.6), its samples cut into chunks of at most 15 frames, each group one of its pieces, so every segment begins at a key frame.

  • Good streams: a camera track under the per-segment method; the same track under the Verifiable Segment Info method, with the session key rotated at its third segment by a manifest in that segment, the receipts' chain restarting under the new key; a track labelled machine-made, with its AI disclosure; a compositor's output of a camera and that track; a take under both methods at once, the device's key as its session key; the camera track with a second packager, under its own claim signer, taking over at the third segment (§10.3); and the camera track with its initialization manifest registered, its update manifest and repository receipt after it (§4.7).
  • Negatives, each a control of one defect: a segment whose bytes changed under each method; a missing segment under each; a reordered segment; a manifest bound to the wrong receipt; a stream identifier that differs; an end-entity certificate that has expired, one whose Extended Key Usage is time-stamping, and one under an anchor the verifier does not hold; a machine-made track without its AI disclosure, and a compositor's output that names a source type of captures; a manifest whose identifier is not derived; a repository receipt signed by a key that is not the repository's, and an update manifest whose ingredient names another parent; and every other reason of §12.

A conforming verifier gives every vector the outcome, the reason, the place, the two C2PA codes and, for an accepted stream, what §11.6 states, as the corpus states them. wai_live_provenance_vectors verify and the test tests/live_provenance.rs check that every file regenerates byte for byte, that no other file is there, that wai-rs gives every vector its outcome, that every reason of §12 refuses a vector, and that §12 lists exactly the reference's codes. live-conformance/c2pa_live_verify.py is the second implementation, written from this text: it shares no code with wai-rs; it reads CBOR, COSE, JUMBF, ISO BMFF and X.509 DER itself, verifies signatures with WAI's strict rule written from RFC 8032 (claim-conformance/_ed25519.py) and hashes with BLAKE3 from claim-conformance/_blake3.py; gives every vector the same outcome; and mutates copies of the good vectors, one defect each, and requires every copy to be refused. The README of the corpus records what an independent C2PA validator reports for the corpus's files.

14. Security and privacy considerations

  • What is bound to what. The claim signer's certificate names who stands behind a manifest; a session key is bound to it by the signerBinding and, for a track, signs the group receipts too, so the C2PA signer, the session key and the receipts' signer are one chain of statements. Which principal of the room the receipts' key belongs to is the room's (jwp-receipts §7, live-rooms §4) and is not checked here.
  • Signed is not trusted. A chain to an anchor the verifier holds is a check of the chain, not of the anchor: the corpus's anchor is a test anchor, and §14.4's trust lists are the relying party's choice.
  • Order and loss. Sequence numbers, the continuity chain and the receipts' chain each detect a segment lost, repeated or moved; under the Verifiable Segment Info method alone a segment-info map does not name the one before it, so a lost segment is found by the receipts (§11.5), which a relay forwards for objects it did not forward (jwp-receipts §4.4).
  • Session keys. A session key signs every segment of its range. Its validity bounds how long a stolen key signs; rotation (§7.3) shortens it. §19.7.3 recommends that a verifier of a very long session check the claim signer's certificate again; this revision checks it once, at T.
  • Registration is a witness. A repository receipt (§4.7) shows that the repository's log held the manifest at a tree size under the repository's key; it says nothing of the manifest's truth, and a verifier that accepts a repository chooses whom it believes about registration, as it chooses anchors. Consistency between two tree heads, which would show the log did not fork, is not checked in this revision.
  • Derived identifiers. Anyone can compute the identifier of a future segment's manifest (§4.2). That forges nothing: a manifest is bound by its signature and its hard binding, and a verifier checks both before it reads the identifier. What derivation buys is that a packager cannot choose an identifier, so two packagers agree on it.
  • No time-stamps. Without a time-stamp a manifest is valid only while the signer's certificate is (§15.8): a recording verified after the certificate expires is refused credential_expired. A publisher that keeps recordings SHOULD time-stamp them, which a later revision adds.
  • The label is a statement. §9.3 checks that a stream labelled machine-made is marked as C2PA marks it; it cannot detect a machine-made stream labelled human. The label is signed with the stream, so a false one is attributable to its signer.
  • Privacy. A manifest carries the claim signer's certificate and, for a take, the device's key and the room's id; the stream assertion carries the room id and the publisher's key in the track name. A stream is public provenance of its publisher; a take's manifests link the device to the room, as capture-binding §14 says of a take.