Article
How do you know a C2PA verifier is right?
A verifier that wrongly says Valid is worse than no verifier at
all. So "it passes its own tests" can't be the bar. This is how I've tried to earn the other
kind of confidence for a new C2PA verifier in PHP, and what turned up when I did.
My first library signs through a separate
service, which is the right call for the private key. It also means most WordPress sites can't
read a credential at all. Shared hosting runs no second process, no PHP extension and no
binary, so no c2patool, no ext-c2pa and no Docker. The PHP plugins
that exist today find the manifest in the file and read a string out of it. Finding a manifest
isn't verifying it.
So there is now a second library that only verifies, and runs anywhere PHP 8.3 runs: c2pa-verifier. It checks the signature, the hash binding to the actual bytes of a JPEG, PNG, WebP, MP4, MOV, AVIF or HEIC file, the certificate chain against a trust list you supply, the timestamp, and the earlier manifests a file carries as ingredients. It makes no network connection and holds no keys.
$report = (new Verifier)->verify($stream, $settings);
$report->result->state; // Trusted, Valid or Invalid
Same answer as the reference
The bar I picked is simple to state: it should give the same answer as
c2patool, the reference tool built on c2pa-rs. Same verdict, same
status codes, file by file. Every fixture in the repository is compared with what
c2patool says, and those comparisons run again on every commit. Every rule added
since c2patool 0.28 came out was first measured on signed test files under both
0.27 and 0.28, then written down as a specification, then built.
Agreeing with one oracle twice isn't independence, though. A mistake c2pa-rs
and I happened to share would stay invisible. So I also ran an independently written verifier
in Go over the same 257 files. Where we differed, it was either one of us being deliberately
stricter, or, once, a real mistake. That one turned out to be on their side, in how an appended
update manifest shifts the data hash. It's
reported now.
The last part is being honest about what isn't done. C2PA 2.4 has 111 obligations that
apply to these formats. docs/conformance.md goes through them one by one, and
names the 11 this verifier doesn't meet yet and what each would cost. Every place it answers
differently from c2patool is listed, with the reason, in
docs/comparison.md.
The time the reference was wrong
It has said Valid when it shouldn't three times, all found here, before anyone
used it. The third one is the instructive one.
A C2PA data hash covers the whole file except the bytes where the manifest itself lives. That
gap is declared as an exclusion. Three of the official test files, from Truepic, declare an
exclusion that holds the manifest and the whole EXIF block next to it. My verifier
accepted that, because the exclusion covered the manifest. So did c2patool 0.27.
I took a copy, changed the capture date in the EXIF, and the file stayed Trusted.
The date a photo was taken, changed, with a green check next to it.
The specification says an exclusion that holds the manifest may hold nothing else, and
c2patool 0.28 had started enforcing that. Measuring the whole corpus against the
new version is how the difference showed up. It was fixed the same day. Over 864 runs the fix
changed the verdict of those three files and no others.
That's the point of measuring rather than believing. My own tests were green the whole time. They tested what I thought the rule was.
How it's built
Like the first library, it's spec-driven. Every feature starts as a written specification,
and the tests are seen failing before any code exists. I built it with Claude Code, and the
repository keeps a log of what the AI produced in each step (AI-LOG.md). The
measurements against c2patool are what make that safe to rely on, whoever wrote
the code.
Limits, up front
- PHP 8.3. WordPress recommends 8.3 but still supports 7.4, so this belongs in a plugin, not in core.
- It's new. Measured on every file I have, but not yet run on anyone else's.
- No network, by design. Revocation responses stapled into the signature are checked; an online revocation check isn't made.
Where to start
- Try it in your browser: provemark.github.io/demo. It's the same PHP, compiled to WebAssembly, so your file never leaves your device.
- Install it:
composer require provemark/c2pa-verifier. - Read the code, the specifications and the measurements behind every step on GitHub.
- If you have a C2PA file it gets wrong, or one from a writer I haven't seen, that's the most useful thing you could send me.
As with the first library, I'd rather have a handful of careful users than a splash. If you work on the WordPress side of this, there's a proposal for keeping credentials on generated image sizes in WordPress/ai#1058.
c2patool's verdicts. Its NOTES.md is the full record of every step
and measurement above.