open-standards/wai/extensions/c2pa-binding.md
Transaction Science 313ddaec36
Some checks failed
wai-quantum for JavaScript / does this change touch it (push) Successful in 29s
joule-moq-room room service / joule-moq-room · does this change touch it (push) Successful in 49s
wai-quantum studio and desktop / does this change touch it (push) Successful in 43s
joule-moq-remote host agent / joule-moq-remote · does this change touch it (push) Successful in 1m3s
joule-moq-web browser client / joule-moq-web · does this change touch it (push) Successful in 49s
wai-fleet service / does this change touch it (push) Successful in 42s
wai-ledger service / does this change touch it (push) Successful in 1m3s
wai-sink-android native sink library / does this change touch it (push) Successful in 1m14s
wai-sink-swift native sink package / does this change touch it (push) Successful in 1m12s
WAI Studio / does this change touch it (push) Successful in 43s
WAI operation catalogue / does this change touch it (push) Successful in 1m18s
wai-app and Create / wai-app · does this change touch it (push) Successful in 1m18s
wai-audit collector and player integration / does this change touch it (push) Successful in 1m19s
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (push) Waiting to run
wai-app and Create / wai-app · Create, browser end-to-end (macos) (push) Waiting to run
WAI Studio / Studio in Chromium and Firefox · macos (push) Waiting to run
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Failing after 3m52s
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (push) Successful in 3m27s
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (push) Successful in 11s
wai-quantum for JavaScript / javascript · linux (push) Successful in 3m24s
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (push) Successful in 33s
joule-moq-room room service / joule-moq-room, every target (push) Successful in 15m51s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (push) Successful in 14m25s
wai-quantum studio and desktop / studio · browser (push) Successful in 4m42s
wai-ledger service / service · linux (push) Successful in 3m11s
wai-audit collector and player integration / collector · linux (push) Successful in 2m16s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (push) Successful in 2m25s
wai-quantum studio and desktop / desktop app · windows (push) Successful in 10m41s
wai-fleet service / service · linux (push) Successful in 6m21s
wai-quantum studio and desktop / desktop app · linux (push) Successful in 12m1s
WAI operation catalogue / the catalogue and every door · linux (push) Successful in 11m2s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (push) Successful in 3m23s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (push) Successful in 7m23s
WAI Studio / Studio in WebKit · linux (push) Successful in 23m33s
wai-quantum studio and desktop / desktop app · macos (push) Successful in 4m31s
joule-moq-remote host agent / joule-moq-remote, every target (macos) (push) Successful in 49s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (push) Has started running
wai-fleet service / service · macos (push) Successful in 40s
wai-ledger service / service · macos (push) Successful in 1m4s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (push) Has started running
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (push) Has started running
Identity assertions: the holder signs the core deterministic encoding, as Identity Assertion 1.3 §8.2.1 has it; the SDK's other order is stated and pinned
The C2PA SDK c2pa_emit signs through (c2pa 0.91.7) reports cawg.x509.signature.mismatch of
every identity assertion WAI writes. Identity Assertion 1.3 (DIF Ratified, 17 August 2026)
§8.2.1: the signer_payload "MUST be serialized as described in Section 4.2.1, 'Core
Deterministic Encoding,' of RFC 8949" and "the resulting byte string is presented to the
credential holder for signature". That is what c2pa-binding §4.10 signs (role, sig_type,
referenced_assertions). The SDK verifies over the schema's member order instead.

WAI's bytes stay. c2pa-binding §9.3 states the difference with the clause, decisions D6
records the reading, and a test of c2pa_emit::identity_corpus pins both halves: the corpus's
good vectors and a manifest signed in the test are accepted here and get the SDK's mismatch
code; a manifest whose assertion is signed over the schema's order gets no such code from
the SDK and identity_signature_mismatch here. A later SDK that verifies other bytes fails it.

SPEC.md's paragraph on the store beside an envelope says a store also carries the ISCC as the
io.iscc.v0 soft binding (c2pa-binding §4.9); the sentence is outside every core span.

Co-Authored-By: Claude <[email protected]>
2026-11-10 23:05:55 -05:00

55 KiB
Raw Permalink Blame History

WAI Extension: C2PA Binding of a Stored Envelope

Status: Draft. Registers the record wai.asset.c2pa: a C2PA Manifest Store bound to one stored WAI1 or WAI2 envelope (SPEC §2, §7.1), the envelope treated as a C2PA asset and left byte for byte as it is. The store is a sidecar served beside the object. Its hard binding is a c2pa.hash.data over every byte of the envelope, and its WAI assertion names the envelope's content hash (jwp-receipts §2.1), the capabilities it carries, and the JWP group receipt the envelope was delivered under, by that receipt's link hash and its publisher key. One verification then establishes the store's signer and assertions, the envelope's bytes, and the receipt that delivered them. The store is the profile live-provenance §4 writes: its JUMBF layout, its claim, its EdDSA claim signature and its claim signer's certificates are that extension's. No envelope byte, no manifest member and no transport changes. The reference implementation is the c2pa_envelope module of wai-rs (feature c2pa_envelope); the corpus is live-conformance/c2pa-envelope/, and its second implementation is live-conformance/c2pa_live_verify.py. After its three assertions a store may carry soft bindings of WAI's picture watermark, each naming the robustness claim that states what the mark survives (§4.8); the content's ISCC, as the soft binding io.iscc.v0 of IEP-0020 and as an identifier (§4.9); and the Creator Assertions Working Group's identity assertions, by which a credential holder signs the assertions it stands behind (§4.10). A verifier may hold its trust anchors as trust lists in the form the C2PA Trust List is published in, each pinned by the digest of its bytes (§6.4). Their corpus is live-conformance/c2pa-identity/, with the same second implementation. A WAI tool that signs through a C2PA SDK writes the same identity assertion into the manifest the SDK lays out, in the placeholder that path needs, and verifies it with the same checks (§4.11); its corpus is live-conformance/c2pa-sdk-identity/. 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), §11.4 (external manifests and their media type), §15.5.3.1 and §15.5.3.2 (locating a store by reference and by Link header), §15.5.5, §15.12.1 (validating a data hash) and §18.5 (the data hash assertion), with the sections live-provenance lists for the store, the claim, its signature and the certificates it shares with this extension; RFC 8288 (Link); and RFC 9562. For §4.8 to §4.10, §6.4 and §9.2: the C2PA Technical Specification, version 2.5 (October 2026), §12.2.3, §13.4, §13.5.1, §14.2.2, §14.7, §16.3.7 and §16.5.13.4.3; the Creator Assertions Working Group's Identity Assertion, version 1.3 (ratified 17 August 2026), §5, §6, §7 and §8.2; ETSI TS 119 602 V1.1.1 (2025-11), §6.3.2, §6.3.14, §6.3.15, §6.6.3 to §6.6.5 and §6.8; the C2PA Trust List as the C2PA published it on 4 November 2026 (sequence number 4), for its form; and the C2PA soft-binding algorithm list as fetched on 10 November 2026, for its entries' form; and, for §4.9, IEP-0020 (ISCC C2PA Conformance, a draft updated 2026-10-28), §4.3 and §5. A section number in this text is C2PA 2.4's unless it names another document or version.

1. Scope

In scope: which bytes a store binds (§3); the store, its manifest identifier, its hard binding, its WAI assertion and its actions (§4); the soft bindings, the ISCC identifier and the identity assertions a store may carry after them (§4.8 to §4.10); where a store is served beside its envelope (§5); the verification that ties the store, the envelope and the envelope's receipts together (§6), and the trust lists a verifier holds its anchors in (§6.4); its reasons (§7); and what a writer of a store does (§8).

Also in scope: the identity assertion of §4.10 in a manifest a C2PA SDK lays out (§4.11).

Out of scope: a transport; a store embedded in an envelope (§4.6); ingredients, update manifests, time-stamps and the manifest repositories of live-provenance §4.7; an identity credential other than an X.509 certificate (the identity claims aggregation of Identity Assertion 1.3 §8.1); revocation (C2PA 2.5 §13.5.2); a lookup of a store by its soft binding (C2PA 2.5 §16.3.7.5); a store about one component of a WAI2 fetched alone (a component's exploded envelope, SPEC §7.1, is a WAI1 and takes a store of its own); the carriage of a store over MoQ, where an object is a group's member and a stream of objects takes live-provenance's methods; and what a party does with a verdict.

Relationship to the core spec: wai.asset.c2pa is a record (SPEC §5): a signed record a WAI tool writes and verifies. It names no payload format and is not dispatchable, so it is never a component of an envelope in any role (SPEC §7.1, Component identity).

Relationship to provenance: wai.asset.provenance signs a WAI lineage of an asset's steps under a WAI key. This extension states an envelope's provenance in C2PA's own form, so that a C2PA validator reads it, and binds it to the envelope's delivery receipt. Relationship to deriving-edge: a store bound to an input as this extension binds one is a store "carried beside it" there (§6.1), named by its SHA-256 and carried forward.

2. Terms

  • Envelope: one WAI1 or WAI2 envelope, as SPEC §2 and §7.1 frame it.
  • Content hash, cid: the envelope's jwp-receipts §2.1 content hash, BLAKE3("moq-jwp:object\x01" ‖ envelope), 32 bytes.
  • Store: a C2PA Manifest Store, a JUMBF superbox (live-provenance §4.1).
  • Group receipt, object receipt, link hash: as in jwp-receipts §2.4.1, §2.4.2 and §3 step 3. The publisher key is the group receipt's signer_pubkey.
  • Claim signer: the holder of the X.509 end-entity certificate that signs the store's claim (live-provenance §2).
  • T: the instant a verification is made.

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 envelope

The asset a store binds is the envelope's bytes exactly: for a WAI1, 12 + man_len + payload_len bytes; for a WAI2, its head and its payloads, 10 + man_len + 8·n + Σ lengths bytes; nothing before the magic and nothing after the last payload byte, as http-delivery §5.1 requires of an object. Its manifest reads as SPEC §3 (a WAI1) or §7.1 (a WAI2) has it. Its capabilities are, for a WAI1, its model_requirement.capability, and for a WAI2, the capability of each entry of renditions, in table order, companions and refinements included.

A store binds the envelope whole. A WAI2 client that fetched some of its components by range (http-delivery §7) holds the envelope's cid from its head proof, not its bytes, and verifies the store once it holds the envelope whole.

4. The manifest store

4.1 The store

The store is the one live-provenance §4.1 lays out: a JUMBF superbox (c2pa) of one standard manifest (c2ma), the active one, whose content is its assertion store, its claim and its claim signature, each assertion a CBOR content superbox. Its claim is live-provenance §4.2's claim-map-v2 with exactly the fields that section lists, its created_assertions naming the assertions below in this order; its claim signature is live-provenance §4.3's COSE_Sign1_Tagged with EdDSA and the claim signer's x5chain; and its claim signer's certificates meet live-provenance §4.4. The manifest holds, in this order:

  1. c2pa.hash.data, the hard binding (§4.3);
  2. c2pa.actions.v2, the actions (§4.5);
  3. science.transaction.wai.envelope, the WAI assertion (§4.4);

and after them, each optional, in this order and nothing else:

  1. soft bindings, labelled c2pa.soft-binding, c2pa.soft-binding__1, … (C2PA 2.4 §6.4): those of WAI's watermark (§4.8), then at most one of ISCC (§4.9), the last of them;
  2. at most one ISCC identifier, science.transaction.wai.iscc (§4.9);
  3. identity assertions (§4.10), labelled cawg.identity, cawg.identity__1, ….

A store holds no detection verdict (detection §6): a verdict is a detector's claim of its own, not a statement of the claim signer's about how the content came to exist.

4.2 The manifest identifier

The manifest's identifier is urn:c2pa: followed by an RFC 9562 UUID of version 4 in the 8-4-4-4-12 lowercase form, as live-provenance §4.2 writes one, whose 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:c2pa-binding:manifest\x01" ‖ cid ‖ receipt ‖ SHA-256(EE)),

cid and receipt the WAI assertion's contentHash and receipt (§4.4) and EE the DER of the claim signer's end-entity certificate. The identifier is a function of the envelope, the receipt and the signer: two signers of one envelope write two identifiers, and one signer that signs one envelope under one receipt again writes the same one.

4.3 The hard binding

The hard binding is a c2pa.hash.data assertion (§18.5), the map:

  • alg: sha256;
  • hash: the SHA-256 of every byte of the envelope (§3);
  • pad: the empty byte string.

It holds no exclusions, name, pad2 or url. A store beside its asset is in none of the asset's bytes, so nothing is excluded: the data hash covers the envelope whole, and §15.12.1's validation of it is the comparison of one SHA-256. A C2PA validator given the envelope and its store (§15.5.3) reports assertion.dataHash.match or assertion.dataHash.mismatch for it.

4.4 The WAI assertion

science.transaction.wai.envelope, a CBOR map under C2PA's entity-specific namespace (§6.2.1), with exactly these keys:

key value
contentHash the envelope's cid, 32 bytes
capabilities the envelope's capabilities (§3), an array of one or more text strings
receipt the link hash of the group receipt the envelope was delivered under, 32 bytes
publisher that group receipt's signer_pubkey, 32 bytes

The group receipt is in the labelled payload (jwp-receipts §2.3, sign_version 2), or, for a verifier that implements pq-signatures, its hybrid form: a group in the legacy payload has a link hash that does not cover its parent, so a store would not name one chain (http-delivery §8.1 refuses it as a receipt file for the same reason). receipt is also where the receipt is served over HTTP, rcpt/<receipt>.json (http-delivery §8.1), so the store itself names the file a client fetches to check it.

4.5 The actions

c2pa.actions.v2 (§18.15.2), the map {"actions": [{"action": "c2pa.created", "digitalSourceType": DST}]}, as live-provenance §4.6 writes it: DST is a term of the IPTC digital source type vocabulary, one or more ASCII letters, as a URI under http://cv.iptc.org/newscodes/digitalsourcetype/. The claim signer states it; this extension does not check it against the content. A verifier reports it (§6.3).

4.6 Beside the envelope, not in it

The store is never carried inside the envelope it binds. An envelope that held its own store would have other bytes and so another cid, and the receipts already signed for the envelope would no longer name it; and the store names the cid, which an envelope cannot carry about itself. A WAI2 component is applied with or instead of a rendition and names a payload format (SPEC §7.1); a store describes the whole envelope and is a record, which is no component. Beside the envelope, the store leaves the object, its cid, its receipts and its head proof as they were, and a store can be added to an object published before it, replaced when its signer's certificate is renewed, or joined by another signer's store, without a new object.

4.7 The envelope's picture

The picture of an envelope is its first wai.image.png payload, a WAI1's own or, for a WAI2, that of the first entry of renditions whose capability is wai.image.png, decoded to its 8-bit samples (an 8-bit greyscale or RGB PNG without interlace, as packaging §5, paragraph 5.7, has a still read), a grey sample taken as R = G = B. An envelope has none where no payload is one or where it does not decode. §4.8 and §4.9 read it.

4.8 Soft bindings

A soft binding of a watermark is a C2PA soft-binding assertion (C2PA 2.5 §16.3.7) for a WAI watermark marked in the envelope's picture, the map:

  • alg: the scheme's identifier in the soft-binding algorithm list's form (§16.3.7.4 there): science.transaction.wai.tent64 for wai-mark:tent64 (synthesis §7.5), the scheme this revision binds by;
  • blocks: one block, {"scope": {}, "value": V}: the empty scope, the whole picture, and V the 8-byte payload marked in it (synthesis §7.2), a byte string;
  • bindingMetadata: {"science.transaction.wai.robustnessClaim": L}, L the link hash of the robustness claim (robustness-evidence §8) that states what the mark survives, a field of an entity-specific name (§16.3.7.3.1 there).

It holds no other key: no pad, pad2, name, alg-params, url or metadata. C2PA matches a soft binding by alg and value (§16.3.7.5.1 there), and a validator does not use bindingMetadata to validate one; this profile reads it to find the claim the binding rests on (§6.2 step 10). A soft binding names the content it is in: its value is the mark the envelope's picture carries, so a store cannot carry the mark of other content, and the claim it names is held to the records it cites, so it cannot claim more than was measured.

The entries these identifiers take in the C2PA soft-binding algorithm list are written in c2pa-soft-binding-entries.json in the list's form (§16.3.7.4 there), for wai-mark:tent64 and wai-mark:band64 (synthesis §7.5, §7.10), the schemes whose robustness records the corpora hold, each naming, under an entity-specific member, the link hashes of its robustness claim and of the records the claim cites, which robustness-evidence §11 maps onto the proposed robustnessEvidence object. An entry's identifier is the C2PA's to assign, and its contact and the date it is entered the submitter's to give, so none is written; submitting the entries is not this text's.

4.9 The ISCC soft binding and the ISCC identifier

A store carries the envelope's ISCC (iscc, ISO 24138's codes) in two assertions, each optional.

The ISCC soft binding is a C2PA soft-binding assertion of the algorithm io.iscc.v0, entry 3 of the C2PA soft-binding algorithm list, as IEP-0020 §4.3 has one, the map:

  • alg: io.iscc.v0;
  • blocks: one block, {"scope": {}, "value": V}: the empty scope, and V a byte string, the ISCC-SEQ (iscc §3.3) of the binding's units. A value is never the sequence's text, and this profile does not write IEP-0020's other form, an array of integers;
  • bindingMetadata: {"informationalUrl": "https://ieps.iscc.codes/iep-0020/", "io.iscc.generator": {"name": N, "version": R}}, N and R text strings that are not empty, the software that computed the units and its version (IEP-0020 §4.3). The reference writer's are wai-rs and iscc-1, the revision of iscc §4's computation, which changes when a code it computes of the same content changes.

It holds no other key. The binding's units are the ones IEP-0020 §5 has a claim generator compute from the source, which for a store beside its asset is the envelope's bytes (§3), each of 256 bits, in this order:

  1. the Image-Code of the envelope's picture (iscc §4.2, after iscc §4.3's pre-processing), where the envelope has one and the code is not featureless (iscc §5; IEP-0020 §5 has no binding include a featureless Image-Code);
  2. the Data-Code of the envelope's bytes (iscc §4.6);
  3. the Instance-Code of the envelope's bytes (iscc §4.1).

A soft binding of a watermark and the ISCC soft binding are two kinds, and neither is read as the other. A watermark's value is a payload its detector reads out of the picture, and its bindingMetadata names the robustness claim that states what the mark survives. An ISCC binding's value is computed from the envelope and marks nothing: no detector reads it, and no robustness record of this revision measures io.iscc.v0, so it names no robustness claim. A verifier holds it to the units computed again from the envelope (§6.2 step 11), each equal: a binding that iscc §5 would match by similarity, its Data-Code or its Image-Code near or equal and its Instance-Code another's, is refused. Matching by similarity is what a party does with a binding and an asset that has no store (iscc §5); in a store, the envelope is at hand.

IEP-0020 §4.3 has a validator ignore what it does not know of such an assertion (a block of another scope, alg-params, a bindingMetadata it cannot read) and accept the array form. This profile is the form §8's writer writes, and a verifier refuses a store whose ISCC binding is in another (manifest_malformed): every binding this profile accepts is one an IEP-0020 validator reads whole.

The ISCC identifier is science.transaction.wai.iscc, a CBOR map under C2PA's entity-specific namespace of exactly one key, iscc: a byte string holding the ISCC-SEQ (iscc §3.3) of, in this order, the Image-Code of the envelope's picture, where the envelope has one, featureless or not, and the Instance-Code of the envelope's bytes, each of 256 bits. It identifies the content by ISO 24138's codes: the Instance-Code its bytes exactly, the Image-Code its picture by similarity, which survives a re-encoding the hard binding does not. Both are functions of the envelope, so a verifier recomputes them (§6.2 step 11). It is an identifier under WAI's own label and not a soft-binding assertion: a C2PA validator that resolves soft bindings reads the ISCC soft binding, and a WAI tool that held stores before this revision reads the identifier, which is unchanged.

4.10 Identity assertions

An identity assertion is the Creator Assertions Working Group's (Identity Assertion 1.3 §5), with an X.509 credential and a COSE signature (§8.2 there, sig_type cawg.x509.cose), by which a credential holder, the named actor, signs the assertions it stands behind:

  • the map {"signer_payload": P, "signature": S, "pad1": h''}, with no pad2;
  • P, the signer-payload-map (§5.2 there): {"referenced_assertions": R, "sig_type": "cawg.x509.cose"}, with "role": [...] where the holder states its relationship to the content (§5.1.2 there, cawg.creator, cawg.publisher, …) and only then; R holds a hashed URI, url and hash as the claim's created_assertions writes them, for each assertion the holder chooses, in the order it chooses, each once, the hard binding among them (§5.1.1 there), and none written after the identity assertion itself;
  • S, a COSE_Sign1_Tagged written as the claim signature is (live-provenance §4.3) with the claim replaced by P's encoding in RFC 8949 §4.2.1's core deterministic encoding (§8.2.1 there): the protected header {1: -8, 33: [EE, CA…]} with the holder's end-entity certificate and its intermediates, the unprotected header empty, the payload detached;
  • the holder's end-entity certificate meets live-provenance §4.4's profile with id-kp-documentSigning (1.3.6.1.5.5.7.3.36), the EKU §8.2.4 there accepts, in the place of c2pa-kp-claimSigning.

Every signature is Ed25519 and every encoding deterministic, so the store's writer knows the hash of each assertion an identity references before the holder signs, and no placeholder (§6.3 there) is needed: pad1 is empty. Several identity assertions describe several actors; one may reference another written before it (§5.1.1 there).

4.11 Identity assertions in a manifest a C2PA SDK lays out

A WAI tool that signs an asset through a C2PA SDK, as energy-binding §6 has one emit a manifest, does not lay the manifest out: the SDK draws its identifier, salts each assertion, writes the claim and fixes the hard binding last. Such a manifest is not a store of §4.1 and is not verified by §6.2. The identity assertion it carries is §4.10's all the same: the same map, the same signer-payload-map, the same COSE_Sign1_Tagged over the payload's core deterministic encoding, written by the same writer and held to §6.2 step 12's checks by the same verifier. Three things are the path's and none is the assertion's form.

The placeholder. With a data hash the file's layout is fixed before the hard binding's hash is known, and the layout needs the assertion's size, which is not known until its holder has signed. The writer reserves room for the assertion before the layout (Identity Assertion 1.3 §6.3) and, once the holder has signed, writes pad1 as the zero bytes that bring the assertion's encoding to exactly the room reserved (§6.2 there). A byte string of 256 to 65 535 bytes has a head of three bytes, so every room that leaves pad1 a length in that range is filled by pad1 alone; the writer reserves such a room and writes no pad2. One that would leave pad1 another length writes nothing.

The referenced assertions. The holder signs the hashed URIs the claim holds when the SDK asks for the assertion's content, each url and hash as the claim writes it: the hard binding first, which is the claim's one assertion whose label begins c2pa.hash., then the assertions the holder chose, in its order. A label the claim does not list, a label named twice, or the label of an identity assertion stops the signing. An identity assertion on this path references no other identity assertion: the other's content is still its placeholder when the payload is signed, so its hash in the payload would name bytes the manifest does not hold.

The claim's list. The SDK lists an assertion its signer supplies while signing in the claim's gathered_assertions, where a store of §4.1 lists every assertion in created_assertions. The identity assertion is the named actor's statement and not the claim signer's, and which list names it changes nothing step 12 checks.

Verification. A verifier holds the asset, its manifest store, the identity trust anchors it accepts and T. A C2PA validator first validates the manifest against the asset (C2PA 2.4 §15: the claim signature, the hashed URIs and the hard binding); this section does not repeat that. The verifier then reads the store itself, in this order:

  1. The store is one JUMBF superbox labelled c2pa of one or more manifests, and its last manifest, the active one, holds one assertion store and one claim labelled c2pa.claim.v2, a CBOR map with created_assertions, an array of hashed URIs (url, a text string, hash, 32 bytes, and alg, where present, sha256), and gathered_assertions, where present, another: otherwise manifest_malformed. The claim is read in any well-formed definite-length encoding, since an SDK's claim is not in the core deterministic one.
  2. Every hashed URI of the two lists names, under one prefix that ends c2pa.assertions/, exactly one assertion of that assertion store (manifest_malformed) whose superbox, less its 8-byte header, has the SHA-256 the URI states: otherwise assertion_hash_mismatch.
  3. created_assertions names exactly one hard binding: otherwise manifest_malformed.
  4. Each assertion labelled cawg.identity, cawg.identity__1, …, in the assertion store's order, is named by one of the two lists, and no other assertion's label begins cawg.identity: otherwise manifest_malformed. Its content is one CBOR box in the core deterministic encoding: otherwise identity_malformed. It then passes §6.2 step 12's checks in their order, with their reasons and codes (§7.2), the hard binding being step 3's and the claim's assertions being those of created_assertions.

A verifier MAY be configured to look for a referenced assertion in gathered_assertions as well, as Identity Assertion 1.3 §7.1 admits; it states that it did. §6.2 step 12 holds a store of §4.1, which has no second list, to created_assertions, and that is this section's rule unless the verifier is configured otherwise. It never looks in an ingredient's manifest.

It states, for each identity assertion, its label, its roles, the named actor (the SHA-256 of its end-entity certificate), the labels of the assertions it references and the SHA-256 of the anchor its chain reached. It establishes what §10 says of an identity assertion and nothing about the claim signer, which the C2PA validator reports.

5. Carriage

5.1 HTTP

An origin that serves an envelope at <cid>.wai (http-delivery §5.2) serves its publisher's store, where it has one, at the same path with the filename extension .c2pa: <cid>.c2pa, where §15.5.3.1 has a validator look for one. It is served:

Content-Type: application/c2pa          (§11.4)
Cache-Control: max-age=300, no-transform
Access-Control-Allow-Origin: *

and not as immutable, since a store may be replaced (§4.6). A response for the object MAY carry Link: <<cid>.c2pa>; rel="c2pa-manifest" (RFC 8288; §15.5.3.2), which names the store for a client that holds the object under an alias. A client reads at most 1 048 576 bytes of a store and refuses a longer one (manifest_malformed): a store of this profile is three assertions and a certificate chain, a few kilobytes. A name at which nothing is served leaves the object without a store (store_missing).

The receipts the verification needs are where http-delivery §8 serves them: the group receipt at rcpt/<receipt>.json (§8.1 there), receipt being the WAI assertion's, and the object receipt in the object's receipt index, <cid>.wai.receipts (§8.2 there).

5.2 Other stores

Another party's store for the same envelope binds it in the same way wherever it is served, and a verifier given it verifies it as §6 states. Each store is verified on its own; one store's verdict says nothing of another's.

6. Verification

6.1 Inputs

A verifier holds: the trust anchors it accepts for c2pa-kp-claimSigning (§14.4.1), and the trust lists of claim signers it is configured with (§6.4); the identity trust anchors it accepts for id-kp-documentSigning, and the trust lists of identity credentials it is configured with (§6.4); the instant T; the envelope; the store served beside it, or none; the group receipt it was given for the store's receipt, or none; the envelope's object receipt under that group, or none, each receipt in its JSON form (jwp-receipts §2.4.1, §2.4.2); and the robustness records and claims it was given (robustness-evidence §9.1), in order. It verifies in the order below and stops at the first refusal, which it reports with the reason and the code §7 gives it.

6.2 Steps

  1. Every trust list the verifier is configured with is the bytes it pinned, reads, and is in force at T (§6.4): otherwise trust_list_mismatch, trust_list_malformed or trust_list_stale. The anchors each gives at T join the verifier's for their purpose.
  2. The envelope reads as §3 has it: otherwise envelope_malformed. Its cid and capabilities follow.
  3. A store is held: otherwise store_missing. It is at most 1 048 576 bytes and reads as §4.1 lays it out, one standard manifest whose assertions are each a CBOR content superbox: otherwise manifest_malformed.
  4. Live-provenance §11.2 steps 1 to 6 over the store: its claim and signature's form (manifest_malformed), the claim signer's certificates (credential_invalid), their chain to a trust anchor (credential_untrusted), the claim signature (claim_signature_mismatch), their validity at T (credential_expired), and the hashed URIs (assertion_hash_mismatch).
  5. An assertion labelled science.transaction.wai.detection is a detection verdict offered as the store's provenance: detection_as_provenance (detection §6). The assertions are the three of §4.1, in that order, followed only by what §4.1 admits after them, in its order, each readable as §4.3, §4.4, §4.5, §4.8 and §4.9 write it (a soft binding of the algorithm io.iscc.v0 the last of the soft bindings and read as §4.9 writes one, every other as §4.8 writes one; an ISCC soft binding's value and an ISCC identifier's bytes each an ISCC-SEQ, iscc §3.3): otherwise manifest_malformed.
  6. The manifest's identifier is §4.2's for the WAI assertion's contentHash and receipt and the claim signer's end-entity certificate: otherwise manifest_id_mismatch.
  7. The hard binding's hash is the SHA-256 of the envelope: otherwise hard_binding_mismatch.
  8. The WAI assertion's contentHash is the envelope's cid (content_hash_mismatch), and its capabilities are the envelope's, in order (capabilities_mismatch).
  9. A group receipt is held: otherwise receipt_missing. It reads as jwp-receipts §3 step 1 reads one, in the labelled payload (§4.4): otherwise receipt_invalid. Its link hash is the WAI assertion's receipt: otherwise receipt_mismatch. It verifies under jwp-receipts §3 step 2: otherwise receipt_invalid. Its signer_pubkey is the WAI assertion's publisher: otherwise receipt_signer_mismatch.
  10. An object receipt is held, reads as jwp-receipts §2.4.2 has it, and passes jwp-receipts §3 steps 4 and 5 against the group receipt over the envelope: its content_hash is the cid, it stands at its own position in the group, and its leaf walks to the group's root. Otherwise object_receipt_invalid.
  11. Each watermark's soft binding's value is the payload the scheme's detector (synthesis §7.5.5) reads from the envelope's picture: otherwise, and for an envelope with no picture, soft_binding_mismatch. Then the robustness records and claims the verifier was given verify in order as robustness-evidence §9.2 has it, under the policy stated, and each of those soft bindings' robustnessClaim is the link hash of a claim among them about the binding's scheme: otherwise soft_binding_unevidenced. An ISCC soft binding whose bindingMetadata names a robustnessClaim is soft_binding_unevidenced whatever the verifier was given: no claim is about io.iscc.v0 (§4.9).
  12. The ISCC soft binding's units are the binding's units §4.9 computes from the envelope, each equal and in its order, and the ISCC identifier's units are the ones §4.9 computes for it: otherwise iscc_mismatch.
  13. Each identity assertion, in order, by Identity Assertion 1.3 §7.1 and §8.2.2.1, its checks in this order:
    • it holds signer_payload, a map, signature and pad1, byte strings, and pad2 a byte string where present; its payload holds referenced_assertions, a non-empty array of hashed URIs (each a map with url, a text string, hash, a byte string, and alg, a text string, where present), sig_type, a non-empty text string, and role where present a non-empty array of non-empty text strings; members neither table defines are not considered (§7.1 there): otherwise identity_malformed;
    • each referenced assertion is in the claim's created_assertions with the same url and hash (and an alg, where given, of sha256): identity_assertion_mismatch; none is named twice: identity_assertion_duplicate; the hard binding is among them: identity_hard_binding_missing;
    • sig_type is cawg.x509.cose: otherwise identity_sig_type_unknown; pad1 and pad2 hold only zero bytes: otherwise identity_pad_invalid;
    • the signature reads as a COSE_Sign1_Tagged whose protected header holds an alg: otherwise identity_malformed; that alg is EdDSA: otherwise identity_algorithm_unsupported; and it reads as §4.10 writes it, a detached payload, an empty unprotected header and an x5chain in the protected one: otherwise identity_malformed;
    • the end-entity certificate and the intermediates meet §4.10's profile: identity_credential_invalid; a chain leads from them to an identity trust anchor, as live-provenance §11.2 step 3 builds one: identity_credential_untrusted; the signature verifies under the end-entity's key over the payload's encoding by the strict rule: identity_signature_mismatch; T is within the validity of every certificate of the chain, the anchor's included: identity_credential_expired.

A binding every step accepts is accepted.

6.3 What an accepted binding states

  • the envelope's form (WAI1 or WAI2), its cid and its capabilities;
  • the manifest's identifier, and the source type the claim signer states (§4.5), as its URI;
  • the claim signer, as the SHA-256 of its end-entity certificate;
  • the receipt: its link hash, its publisher key, its track_alias and group_id, and the object receipt's object_id;
  • where the store or the verifier has them: the trust lists of claim signers that held the chain's anchor, each by its URI, its type, its sequence number as its identifier and its digest (C2PA 2.5 §14.2.2's trust-list-uri-map and its digest); each watermark's soft binding's algorithm, scheme, value and robustness claim; the ISCC soft binding's algorithm, its sequence in its canonical form (iscc §3.2) and its generator's name and version; the ISCC identifier, in its canonical form; and each identity assertion's label, roles, named actor (the SHA-256 of its end-entity certificate), the labels of the assertions it references, and the identity trust list that held its anchor, where a list did.

6.4 Trust lists

A verifier MAY hold the anchors of a purpose (c2pa-kp-claimSigning for claim signers, id-kp-documentSigning for identity credentials) as a trust list: a list of trusted entities in the JSON form the C2PA Trust List is published in (ETSI TS 119 602's data model, the list's LoTE object), configured by a URI, a type (c2pa.trustList for claim signers, C2PA 2.5 §14.2.2; science.transaction.wai.identityTrustList, an entity-specific type, for identity credentials) and the digest of its bytes, the SHA-256 the verifier pinned when it chose the list. At T (§6.2 step 0):

  1. the bytes it holds are the pinned ones: otherwise trust_list_mismatch. A list is used as the bytes that were chosen, never as whatever a URI serves at T: an anchor added to a list after it was pinned is in other bytes;
  2. it reads: a JSON object whose LoTE holds ListAndSchemeInformation, with an integer LoTESequenceNumber of at least 1 (§6.3.2 there) and the date-times ListIssueDateTime and NextUpdate (§6.3.14, §6.3.15), and TrustedEntitiesList, an array of trusted entities, each with TrustedEntityServices, an array of services, each with ServiceInformation holding ServiceDigitalIdentity.X509Certificates, an array of {"val": B}, B a certificate's DER in base64 with padding (RFC 4648 §4), ServiceStatus, a URI, and StatusStartingTime, a date-time (§6.6.3 to §6.6.5); every other member is left as it is: otherwise trust_list_malformed;
  3. T is from its ListIssueDateTime to its NextUpdate: otherwise trust_list_stale.

Its anchors at T are the certificates of each service whose ServiceStatus is http://c2pa.org/conformance/trust-list/trusted, the status the C2PA Trust List gives an entity it trusts, from a StatusStartingTime not after T (C2PA 2.5 §13.4.1's notBefore), that this profile reads (live-provenance §4.4: an Ed25519 certificate). A certificate of another key type anchors nothing for this profile and does not make the list malformed: the C2PA Trust List's anchors are of other key types today, so a verifier of this profile pinned to it trusts none of them, and a claim signer of this profile is trusted through a list that holds its anchor. A signer whose chain leads to no anchor of its purpose, a withdrawn entity's or one whose trust has not yet started among them, is credential_untrusted (C2PA 2.5 §14.7's signingCredential.untrusted), and an identity credential's identity_credential_untrusted.

A list is signed by its operator (§6.8 there); this profile does not read the signature, and the pinned digest is what binds the verifier to the list it chose. A verifier that updates its list pins the new one's digest, and reports the sequence number its verdicts were made under.

7. Reasons

7.1 The envelope, the store and the receipts

reason raised at C2PA 2.4 §15 code
envelope_malformed §6.2 step 1 —
store_missing §6.2 step 2 —
manifest_malformed §6.2 steps 2, 3, 4 claim.malformed
credential_invalid §6.2 step 3 signingCredential.invalid
credential_untrusted §6.2 step 3 signingCredential.untrusted
claim_signature_mismatch §6.2 step 3 claimSignature.mismatch
credential_expired §6.2 step 3 claimSignature.outsideValidity
assertion_hash_mismatch §6.2 step 3 assertion.hashedURI.mismatch
manifest_id_mismatch §6.2 step 5 —
hard_binding_mismatch §6.2 step 6 assertion.dataHash.mismatch
content_hash_mismatch §6.2 step 7 —
capabilities_mismatch §6.2 step 7 —
receipt_missing §6.2 step 8 —
receipt_invalid §6.2 step 8 —
receipt_mismatch §6.2 step 8 —
receipt_signer_mismatch §6.2 step 8 —
object_receipt_invalid §6.2 step 9 —

C2PA 2.4 gives no code for an asset at which no store is located (§15.5.3), so store_missing has none.

7.2 Trust lists, soft bindings, the identifier and identities

The code is the Identity Assertion's (1.3 §7.2.2, §8.2.2.4) for an identity assertion's refusal; C2PA 2.5 gives none for the others, which are this profile's checks. A signer outside every trust list it was configured with is §7.1's credential_untrusted.

reason raised at code
trust_list_mismatch §6.2 step 0, §6.4 —
trust_list_malformed §6.2 step 0, §6.4 —
trust_list_stale §6.2 step 0, §6.4 —
detection_as_provenance §6.2 step 4 —
soft_binding_mismatch §6.2 step 10 —
soft_binding_unevidenced §6.2 step 10 —
iscc_mismatch §6.2 step 11 —
identity_malformed §6.2 step 12 cawg.identity.cbor.invalid
identity_assertion_mismatch §6.2 step 12 cawg.identity.assertion.mismatch
identity_assertion_duplicate §6.2 step 12 cawg.identity.assertion.duplicate
identity_hard_binding_missing §6.2 step 12 cawg.identity.hard_binding_missing
identity_sig_type_unknown §6.2 step 12 cawg.identity.sig_type.unknown
identity_pad_invalid §6.2 step 12 cawg.identity.pad.invalid
identity_algorithm_unsupported §6.2 step 12 cawg.x509.algorithm.unsupported
identity_credential_invalid §6.2 step 12 cawg.x509.credential.untrusted
identity_credential_untrusted §6.2 step 12 cawg.x509.credential.untrusted
identity_signature_mismatch §6.2 step 12 cawg.x509.signature.mismatch
identity_credential_expired §6.2 step 12 cawg.x509.signature.outside_validity

The Identity Assertion has no code for a credential outside its profile; its trust model accepts a certificate for id-kp-documentSigning (§8.2.4 there), so one without that purpose is not trusted, and identity_credential_invalid reports cawg.x509.credential.untrusted. An algorithm this profile does not verify is reported as the Identity Assertion reports one off C2PA's lists; EdDSA is the only one this profile writes or reads.

8. Writing a store

A writer holds the envelope, the group receipt it was delivered under, the source type, and the claim signer's key and certificates. It MUST write the store §4 lays out over the envelope as §3 reads it, and MUST NOT write one for an envelope that does not read, for a group receipt in the legacy payload, or with a source type that is not a term (§4.5). Every signature is Ed25519 and every encoding deterministic, so one writer given the same inputs writes the same store byte for byte. The reference writer is c2pa_envelope::sign.

A writer that adds §4.8 to §4.10's assertions holds, beside those: for each soft binding, the payload it marked into the envelope's picture and the link hash of the robustness claim that states what the mark survives; and for each identity assertion, the credential holder's signature over the payload it was shown, with the labels it chose and the roles it states. It MUST write a watermark's soft binding only with the payload the picture carries, the ISCC soft binding and the ISCC identifier only as §4.9 computes them from the envelope it binds, the binding with the name and the version of the software that computed its units, and an identity assertion only over assertions written before it, the hard binding among them. The reference writer is c2pa_envelope::sign_with.

A writer that signs through a C2PA SDK (§4.11) holds the credential holder's key and certificates, the labels it chose and the roles it states. It MUST reserve the assertion's room before the SDK fixes the hard binding, MUST write the assertion §4.10 writes with pad1 filling that room and no pad2, and MUST NOT write one that references an assertion the claim does not list or another identity assertion. The reference writer is c2pa_emit::identity::IdentitySigner, a claim signer that hands the SDK the assertions to place; the reference verifier is c2pa_emit::identity::verify_store.

9. Conformance

9.1 The envelopes and the receipts

live-conformance/c2pa-envelope/ holds the corpus, written from fixed seeds by wai_c2pa_envelope_vectors gen: vectors.json, the trust anchor, and for each vector its envelope, its store and its receipts, each a file of its own, the good envelopes and their stores named as an origin serves them (objects/<cid>.wai and objects/<cid>.c2pa). Every key and certificate is derived from the seeds, and none is a key anyone holds. The envelopes carry pictures the generator draws: a WAI1 of one wai.image.png, and a WAI2 of the same picture at two sizes, each entry with its id and sha256. Both are objects of one labelled group receipt.

  • Good bindings: a store bound to the WAI1, and one bound to the WAI2.
  • Negatives, each a control of one defect: a changed byte of the WAI1 and of the WAI2's second rendition; an envelope truncated by one byte and one with a byte after it; the store stripped, and truncated by one byte; the WAI1's store beside the WAI2; a store without the WAI assertion, one with its actions and WAI assertion in each other's place, one whose data hash excludes four bytes of the envelope, and one whose WAI assertion has a key §4.4 does not define; an end-entity certificate valid for time-stamping alone, one under an anchor the verifier does not hold, and one that has expired; a changed byte of the claim signature and of the WAI assertion; an identifier that is not derived; a WAI assertion that names another envelope's cid, and one that names other capabilities; the group receipt withheld, swapped for the group after it, forged, and in the legacy payload; a WAI assertion that names another publisher; and the object receipt of another object of the group, withheld, and with a changed proof. Every reason of §7.1 refuses one.

A conforming verifier gives every vector the outcome, the reason, the C2PA code and, for an accepted binding, what §6.3 states, as the corpus states them. wai_c2pa_envelope_vectors verify and the test tests/c2pa_envelope.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 §7.1 refuses a vector, and that §7 lists exactly the reference's codes with the code it reports. live-conformance/c2pa_live_verify.py is the second implementation, written from this text: it shares no code with wai-rs; it reads the envelopes, CBOR, COSE, JUMBF 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; mutates copies of the good vectors, one defect each, and requires every copy to be refused; and drops each of its own checks in turn and requires the corpus to catch it. The corpus's README records what an independent C2PA validator reports for its stores.

9.2 Trust lists, soft bindings, the identifier and identities

live-conformance/c2pa-identity/ holds the corpus of §4.8 to §4.10 and §6.4, written from fixed seeds by wai_c2pa_identity_vectors gen: vectors.json; the trust lists, each a file in the C2PA Trust List's form, which each vector names with the URI, the type and the digest its verifier pins; the robustness record and claims; and for each vector its envelope, its store and its receipts. Every key and certificate is derived from the seeds, under four test anchors of the corpus's own (claim signers, another of claim signers, identity credentials, another of identity credentials), and none is a key anyone holds. The envelopes are three WAI1s of one 128 × 128 picture each, drawn by the generator, the first two marked with wai-mark:tent64, all three objects of one labelled group receipt, and a fourth of a picture of one grey, the object of the group after it. The robustness record measures wai-mark:tent64 over the first two pictures under five attacks, its table the harness's (robustness-evidence §6) and so reproducible by anyone who holds the pictures; the claim is every attack the record shows survived.

  • Good bindings: a store with a soft binding, the ISCC identifier and the creator's identity assertion; one with two identity assertions, the publisher's referencing the creator's; one with an identity assertion alone, over the unmarked picture; one with a watermark's soft binding, the ISCC soft binding, the ISCC identifier and the creator's identity assertion over both soft bindings; one with the ISCC soft binding alone; and one over the picture of one grey, whose ISCC soft binding holds no Image-Code and whose identifier holds the featureless one.
  • Negatives, each a control of one defect: a trust list with an anchor added after it was pinned, one past its next update, and one without a sequence number; a claim signer under an anchor no list holds, one under an anchor its list withdrew, and one whose anchor's trust has not yet started; a store holding a detection verdict; a soft binding of the other picture's mark, one over the unmarked picture, one whose claim names an attack no record measured, and one whose claim the verifier was not given; an ISCC identifier whose Instance-Code was computed over altered bytes, and one whose Image-Code is the other picture's; an ISCC soft binding computed from another envelope, one whose Data-Code is the envelope's and whose Instance-Code is that of the envelope with one bit changed (a candidate match of iscc §5, and refused), one without its Instance-Code, one of 64-bit units, one holding a featureless Image-Code, one whose sequence is cut by a byte, one whose value is the sequence's text, one without its generator, a second one, one written before the watermark's, and one that names the watermark's robustness claim as its own; and an identity assertion carried from another store, one whose certificates are another actor's under the first actor's signature, one under an anchor no list holds, one whose credential expired, one credentialed for claim signing alone, one with a non-zero pad, one of an unknown signature type, one naming an assertion twice, one without the hard binding, one without pad1, and one signed under an algorithm C2PA does not admit for a claim. Every reason of §7.2 refuses one.

A conforming verifier gives every vector the outcome, the reason, the code and, for an accepted binding, what §6.3 states, as the corpus states them. wai_c2pa_identity_vectors verify and the test tests/c2pa_identity.rs check that every file regenerates byte for byte, that no other file is there, that wai-rs gives every vector its outcome, and that every reason of §7.2 refuses a vector. c2pa_live_verify.py verifies this corpus too, from this text: it reads the trust lists and the identity assertions itself, detects the mark with the synthesis corpus's second implementation (_wai_mark.py), verifies the robustness evidence with robustness-evidence's (robustness_verify.py) and recomputes the ISCC units with iscc's (iscc_verify.py); it gives every vector the same outcome, requires every mutated copy of a good vector to be refused, and drops each of its own checks in turn and requires the corpus to catch it.

9.3 Identity assertions in manifests a C2PA SDK signed

live-conformance/c2pa-sdk-identity/ holds the corpus of §4.11: vectors.json and, for each vector, a PNG a C2PA SDK signed with the manifest energy-binding §6 emits, one assertion more left in gathered_assertions, and the vector's identity assertions. Every key and certificate is derived from fixed seeds, and none is a key anyone holds. The SDK draws a manifest's identifier, a salt for each assertion and an ingredient's instance identifier at random, so its output is not a function of its inputs and this corpus does not regenerate byte for byte: its files were written once by wai_c2pa_sdk_identity_vectors gen, and vectors.json pins each by its SHA-256.

  • Good: the creator's identity assertion over the hard binding, the actions and the energy binding, with a role; two identity assertions, the creator's and the publisher's; one over the hard binding alone, with no role; and one over a gathered assertion, read by a verifier configured to admit gathered_assertions.
  • Negatives, each a manifest the SDK signed around an identity assertion with one defect: an identity assertion signed for another manifest and carried into this one; one over a gathered assertion, held to created_assertions; one without the hard binding; one naming an assertion twice; one whose pad1 ends in a non-zero byte; one without pad1; one of an unknown signature type; one signed under an algorithm this profile does not verify; the creator's certificates over another key's signature; a credential under an anchor the verifier does not hold; an expired one; and one credentialed for claim signing alone. Every reason §7.2 gives an identity assertion refuses one.

A conforming verifier gives every vector the outcome, the reason, the code and, for an accepted manifest, what §4.11 states, as the corpus states them. wai_c2pa_sdk_identity_vectors verify and the tests of c2pa_emit::identity_corpus check that every file is the one pinned and no other is there, that the SDK validates every file's claim signature and hard binding, that wai-rs gives every vector its outcome, that every reason refuses one, and that manifests signed again, which differ from the pinned ones in their random members, get the same outcomes. live-conformance/c2pa_sdk_identity_verify.py is the second implementation: it reads the PNG, the store and the claim itself, verifies each identity assertion with the verifier c2pa_live_verify.py holds WAI's own stores to, gives every vector the same outcome, requires every mutated copy of a good vector to be refused, and drops each of its checks in turn and requires a vector or a mutated copy to catch it. The corpus's README records what the SDK's own validator reports of each file.

A known difference in the bytes signed. Identity Assertion 1.3 §8.2.1 states that the signer_payload "MUST be serialized as described in Section 4.2.1, 'Core Deterministic Encoding,' of RFC 8949" and that "the resulting byte string is presented to the credential holder for signature". That encoding orders a map's keys bytewise by their encodings, shorter first: role, sig_type, referenced_assertions. §4.10 signs those bytes and §6.2 step 12 verifies over them, and this profile keeps them. The SDK the corpus was signed with reads identity assertions itself and verifies a signature over its own serialization of the payload, the members in the order the schema lists them (§5.2 there): referenced_assertions, sig_type, role. With or without a role the two serializations differ, so that SDK's validator reports cawg.x509.signature.mismatch of every identity assertion of this profile, the good ones included, and this profile refuses an assertion signed over the schema's order (identity_signature_mismatch). The two differ in which bytes are signed and in nothing else that was observed: of a manifest whose assertion is §4.10's but for a signature over the schema's order, that validator does not report the code. The test the_sdks_validator_verifies_the_signer_payload_in_another_order_than_8_2_1s of c2pa_emit::identity_corpus pins both halves at the SDK's version, on the corpus's good vectors and on manifests signed in the test, and fails when an SDK's validator verifies other bytes than that one does: this paragraph is then rewritten, and the assertion's bytes are not (decisions D6).

10. What an accepted binding establishes

That the claim signer, whose certificate chains to an anchor the verifier holds, signed a C2PA manifest over exactly these envelope bytes; that the same signer named the receipt the envelope was delivered under and its publisher key; and that the publisher key signed a group that holds the envelope at its stated position. It does not establish that the claim signer holds the publisher key: the two are tied by the claim signer's statement, and a verifier reports both (§6.3). It does not establish the source type, which the claim signer states (§4.5), nor anything about a store other than the one verified (§5.2).

Where the store carries them: that the envelope's picture carries each soft binding's mark, and that the robustness claim each names is held to the records it cites, which says what the mark survived under the battery measured and no more (robustness-evidence §14); that the ISCC soft binding and the ISCC identifier are the envelope's, computed again from its bytes and its picture, which says nothing of what the codes survive and nothing of the generator the binding names, which the claim signer states; and that each named actor, whose credential chains to an identity anchor the verifier holds, signed the assertions it references, the hard binding among them, and so these envelope bytes. An identity assertion establishes the actor's signature over those assertions, not the roles it states, and not the assertions it does not reference: a claim generator can sign the actor's assertion into a manifest of other assertions (Identity Assertion 1.3 §9.4.7), and a verifier reports which assertions each actor referenced. A trust list establishes which anchors the verifier chose, by the digest it pinned; it does not establish that the list's operator vets anyone in particular.