c2pa-binding §4.8 to §4.10 and §6.4: after its three assertions a store of wai.asset.c2pa may carry wai-mark:tent64 as a C2PA soft-binding assertion naming its robustness claim, the envelope's ISCC identifier, and the Creator Assertions Working Group's identity assertions (1.3, cawg.x509.cose); a verifier may hold its anchors as trust lists in the C2PA Trust List's form, pinned by the SHA-256 of their bytes. The algorithm-list entries for tent64 and band64 are written in extensions/c2pa-soft-binding-entries.json. extensions/iscc.md: ISO 24138's codec, ISCC-SEQ, Instance-, Image- and Audio-Codes and the ISCC-CODE from the IEP texts, replaying the public test vectors; WAI's integer pre-processing. extensions/detection.md: wai.media.detection, a detector's signed verdict at an operating point named by digest, its evidence a robustness record of binding type detector; refused as provenance, never a change to an origin claim's class. Corpora c2pa-identity/, iscc/ and detection/, each with a second implementation in Python. Co-Authored-By: Claude Opus 5.5 <[email protected]>
40 KiB
WAI Extension: C2PA Binding of a Stored Envelope
Status: Draft. Registers the record
wai.asset.c2pa: a C2PA Manifest Store bound to one storedWAI1orWAI2envelope (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 ac2pa.hash.dataover 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 thec2pa_envelopemodule ofwai-rs(featurec2pa_envelope); the corpus islive-conformance/c2pa-envelope/, and its second implementation islive-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 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 islive-conformance/c2pa-identity/, with the same second implementation. 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
Linkheader), §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. 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).
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
WAI1orWAI2envelope, 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:
c2pa.hash.data, the hard binding (§4.3);c2pa.actions.v2, the actions (§4.5);science.transaction.wai.envelope, the WAI assertion (§4.4);
and after them, each optional, in this order and nothing else:
- soft bindings (§4.8), labelled
c2pa.soft-binding,c2pa.soft-binding__1, … (C2PA 2.4 §6.4); - at most one ISCC identifier,
science.transaction.wai.iscc(§4.9); - 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 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.tent64forwai-mark:tent64(synthesis §7.5), the scheme this revision binds by;blocks: one block,{"scope": {}, "value": V}: the empty scope, the whole picture, andVthe 8-byte payload marked in it (synthesis §7.2), a byte string;bindingMetadata:{"science.transaction.wai.robustnessClaim": L},Lthe 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 identifier
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 (iscc §4.2, after iscc §4.3's pre-processing), where the
envelope has one, and the Instance-Code of the envelope's bytes (iscc §4.1), 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 and not a
soft-binding assertion: the C2PA soft binding of ISCC, io.iscc.v0 (IEP-0020), carries a
Data-Code as well, which iscc §1 leaves out; iscc §5 matches one.
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 nopad2; P, thesigner-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;Rholds a hashed URI,urlandhashas the claim'screated_assertionswrites 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, aCOSE_Sign1_Taggedwritten as the claim signature is (live-provenance §4.3) with the claim replaced byP'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 ofc2pa-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).
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
- 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_malformedortrust_list_stale. The anchors each gives at T join the verifier's for their purpose. - The envelope reads as §3 has it: otherwise
envelope_malformed. Its cid and capabilities follow. - 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: otherwisemanifest_malformed. - 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). - An assertion labelled
science.transaction.wai.detectionis 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 (an ISCC identifier's bytes an ISCC-SEQ, iscc §3.3): otherwisemanifest_malformed. - The manifest's identifier is §4.2's for the WAI assertion's
contentHashandreceiptand the claim signer's end-entity certificate: otherwisemanifest_id_mismatch. - The hard binding's
hashis the SHA-256 of the envelope: otherwisehard_binding_mismatch. - The WAI assertion's
contentHashis the envelope's cid (content_hash_mismatch), and itscapabilitiesare the envelope's, in order (capabilities_mismatch). - A group receipt is held: otherwise
receipt_missing. It reads as jwp-receipts §3 step 1 reads one, in the labelled payload (§4.4): otherwisereceipt_invalid. Its link hash is the WAI assertion'sreceipt: otherwisereceipt_mismatch. It verifies under jwp-receipts §3 step 2: otherwisereceipt_invalid. Itssigner_pubkeyis the WAI assertion'spublisher: otherwisereceipt_signer_mismatch. - 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_hashis the cid, it stands at its own position in the group, and its leaf walks to the group's root. Otherwiseobject_receipt_invalid. - Each 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 policystated, and each soft binding'srobustnessClaimis the link hash of a claim among them about the binding's scheme: otherwisesoft_binding_unevidenced. - The ISCC identifier's units are the ones §4.9 computes from the envelope, in its order:
otherwise
iscc_mismatch. - 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,signatureandpad1, byte strings, andpad2a byte string where present; its payload holdsreferenced_assertions, a non-empty array of hashed URIs (each a map withurl, a text string,hash, a byte string, andalg, a text string, where present),sig_type, a non-empty text string, androlewhere present a non-empty array of non-empty text strings; members neither table defines are not considered (§7.1 there): otherwiseidentity_malformed; - each referenced assertion is in the claim's
created_assertionswith the sameurlandhash(and analg, where given, ofsha256):identity_assertion_mismatch; none is named twice:identity_assertion_duplicate; the hard binding is among them:identity_hard_binding_missing; sig_typeiscawg.x509.cose: otherwiseidentity_sig_type_unknown;pad1andpad2hold only zero bytes: otherwiseidentity_pad_invalid;- the signature reads as a
COSE_Sign1_Taggedwhose protected header holds analg: otherwiseidentity_malformed; thatalgis EdDSA: otherwiseidentity_algorithm_unsupported; and it reads as §4.10 writes it, a detached payload, an empty unprotected header and anx5chainin the protected one: otherwiseidentity_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.
- it holds
A binding every step accepts is accepted.
6.3 What an accepted binding states
- the envelope's form (
WAI1orWAI2), 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_aliasandgroup_id, and the object receipt'sobject_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
identifierand its digest (C2PA 2.5 §14.2.2'strust-list-uri-mapand its digest); each soft binding's algorithm, scheme, value and robustness claim; the ISCC identifier, in its canonical form (iscc §3.2); 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):
- 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; - it reads: a JSON object whose
LoTEholdsListAndSchemeInformation, with an integerLoTESequenceNumberof at least 1 (§6.3.2 there) and the date-timesListIssueDateTimeandNextUpdate(§6.3.14, §6.3.15), andTrustedEntitiesList, an array of trusted entities, each withTrustedEntityServices, an array of services, each withServiceInformationholdingServiceDigitalIdentity.X509Certificates, an array of{"val": B},Ba certificate's DER in base64 with padding (RFC 4648 §4),ServiceStatus, a URI, andStatusStartingTime, a date-time (§6.6.3 to §6.6.5); every other member is left as it is: otherwisetrust_list_malformed; - T is from its
ListIssueDateTimeto itsNextUpdate: otherwisetrust_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 soft binding only with the payload the picture carries, the ISCC identifier only as §4.9
computes it, and an identity assertion only over assertions written before it, the hard binding
among them. The reference writer is c2pa_envelope::sign_with.
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 theWAI2. - Negatives, each a control of one defect: a changed byte of the
WAI1and of theWAI2's second rendition; an envelope truncated by one byte and one with a byte after it; the store stripped, and truncated by one byte; theWAI1's store beside theWAI2; 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. 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; and one with an identity assertion alone, over the unmarked picture.
- 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; 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.
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 identifier is the envelope's; 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.