A .wai file can now be a C2PA asset without changing an envelope byte. extensions/c2pa-binding.md registers the record `wai.asset.c2pa`: a C2PA Manifest Store served beside the object at <cid>.c2pa (application/c2pa, C2PA 2.4 §11.4 and §15.5.3.1; an object's response may name it with Link rel="c2pa-manifest", §15.5.3.2), never inside it, so the object's content hash, its receipts and its head proof stay as they were. http-delivery §8.9 gives the location. The store is live provenance's profile (JUMBF layout, claim, EdDSA COSE claim signature, X.509 chain) with three assertions: c2pa.hash.data over every byte of the envelope with no exclusions and an empty pad (§18.5); c2pa.actions.v2 with the source type the signer states; and science.transaction.wai.envelope, which names the envelope's content hash, its capabilities (a WAI2's in table order) and the JWP group receipt it was delivered under, by link hash (also the name of its rcpt/ file), with that receipt's publisher key. The receipt must be in the labelled payload, whose link hash covers its parent. The manifest identifier is derived from the envelope, the receipt and the claim signer's certificate. A verifier checks the claim and chain (live-provenance §11.2 steps 1-6), the assertions, the identifier, the data hash, the content hash and capabilities, the receipt's link hash, signature and publisher, and the envelope's object receipt under it: seventeen reasons, each with its C2PA §15 code where C2PA gives one. The reference is the c2pa_envelope module of wai-rs (feature c2pa_envelope; writer c2pa_envelope::sign); the corpus is live-conformance/c2pa-envelope/ (stores bound to a WAI1 and to a WAI2 of one picture at two sizes, 27 negatives covering every reason), written from fixed seeds by wai_c2pa_envelope_vectors; c2pa_live_verify.py, the second implementation, now also verifies it: 29 vectors agree, 26 of 26 mutated copies are refused and 17 of 17 weakened verifiers are caught. Fourteen faults planted in the reference (each check of the verification skipped in turn, a legacy receipt, bytes after the envelope, a padded data hash, the identifier without its signer) each fail its tests. The C2PA Rust library (c2pa 0.91.7) reads both good stores beside their envelopes as Trusted with assertion.dataHash.match, and reports assertion.dataHash.mismatch when one envelope byte changes. Co-Authored-By: Claude Opus 5.5 <[email protected]>
4.1 KiB
live-conformance/c2pa-envelope — the C2PA binding of a stored envelope
The corpus of extensions/c2pa-binding.md §9: C2PA Manifest
Stores beside stored WAI envelopes, each with the JWP group receipt and object receipt its
verifier is given; the trust anchor every vector is verified under; and what a verifier states of
each.
Files. vectors.json lists the vectors, each its name, envelope, store (or null, a
store not served), receipts (a JSON file of group, the group receipt the verifier was given
for the store's receipt, and object, the envelope's object receipt, each in jwp-receipts
§2.4's JSON form or null) and expect. The good envelopes, their stores and their receipts sit
as an origin serves them: objects/<cid>.wai and, beside it, objects/<cid>.c2pa (§5.1). A file
whose bytes another vector already wrote is named by that vector's path, so a negative holds only
the files it changed. anchor.der is the trust anchor for c2pa-kp-claimSigning, and time the
instant T (§6.1).
Equivalence. A conforming verifier given a vector's files, the anchor and T reaches the
outcome expect states (§6):
refuse: thereasonof §7 and the C2PA 2.4 §15 code §7 gives it,c2pa,nullwhere §7 gives none;accept: what §6.3 states: the envelope'sform,contentHashandcapabilities, themanifestId, thesourceType, thesigner(the SHA-256 of the claim signer's end-entity certificate), the group receipt's link hash (receipt) and publisher key (publisher), itstrackAliasandgroupId, and the object receipt'sobjectId, hashes in lowercase hexadecimal.
The vectors.
- Good:
wai1-bound, a store bound to aWAI1of onewai.image.png, andwai2-bound, a store bound to aWAI2of the same picture at two sizes, each entry with itsidandsha256. Both envelopes are objects 0 and 1 of one group receipt in the labelled payload, unmetered. - One negative for each reason of §7, and for some reasons more than one; each name says its defect.
Provenance. wai_c2pa_envelope_vectors gen writes every file but this one from fixed seeds:
every key is SHA-256 of a label and no key is one anyone holds; the certificates are issued under
the corpus's own test anchor; the pictures are gradients the generator draws, written as PNG by
the packaging corpus's still writer, so each store states the source type algorithmicMedia.
Checked by. wai_c2pa_envelope_vectors verify and tests/c2pa_envelope.rs in wai-rs
(every file regenerates byte for byte and no other is here; every vector gets its outcome; every
reason refuses one; §7 lists the reference's codes), and ../c2pa_live_verify.py, the second
implementation, written from the extension's text, which also requires every mutated copy of a
good vector to be refused and every one of its own checks, dropped, to be caught by the corpus.
An independent C2PA validator. The good vectors' stores were also read with the C2PA Rust
library (the c2pa crate, version 0.91.7, which shares no code with this corpus's reference) on
2026-11-09, each handed to it as a sidecar with its envelope
(Reader::with_manifest_data_and_stream, the envelope's format given as
application/octet-stream), with anchor.der its trust anchor and c2pa-kp-claimSigning and
id-kp-documentSigning its accepted extended key usages:
- both stores validate against their envelopes, state
Trusted, withsigningCredential.trusted,claimSignature.validated,claimSignature.insideValidity,assertion.hashedURI.matchfor each of the three assertions, andassertion.dataHash.match; the store, the claim, its signature, the certificate chain and the data hash over the whole envelope are therefore read the same way by an implementation that shares nothing with this one; - with one byte of either envelope changed, the library reports
assertion.dataHash.mismatch, the code §7 giveshard_binding_mismatch, and the stateInvalid.
The library does not read the WAI assertion or the receipts, which the two implementations above check.