Part 2 covered what is inside a manifest. Part 3 covers how that manifest becomes tamper-evident, and where the trust actually lives.
If Part 2 was where most people got lost, Part 3 is where most people get nervous. Cryptography has a reputation for being a black hole of math and acronyms. The good news is that you do not need to understand the math to understand C2PA. You need to understand the moving pieces, what each one is responsible for, and how they fit together. That is what this part is going to walk through.
What does tamper-evident actually mean?
Before getting into the mechanics, it is worth pausing on what we are even trying to do here.
C2PA does not make a file impossible to modify. Anyone with a hex editor can change the bytes of a JPEG. What C2PA does is make any modification visible. If a single byte changes after signing, verification fails. The signature does not prevent tampering. It makes tampering undeniable.
That distinction matters more than it sounds. The whole system is built on the idea that once content is signed, any future change is detectable, and any party can detect it independently. No central server, no phone-home check, no reliance on the original signer being reachable. The math does the work.
The way this gets achieved comes down to three pieces working together. The signature, the hash, and the certificate chain. We will take them one at a time.
What actually gets signed?
We touched on this in Part 2, but it is worth being precise here. The claim is what gets signed. Nothing else.
That is a small sentence with a big consequence. The assertions and ingredients are not signed individually. They are referenced by the claim, by hash, and the claim is what carries the signature. If you pull an assertion out of the manifest and try to verify it on its own, you cannot. There is no signature on it to check. The trust lives on the claim, and the claim’s references protect everything the claim points to.
The benefit of this design is that the signer signs once, over a small and well-defined structure. The cost is that you cannot mix and match. You cannot strip an inconvenient assertion out of a manifest without breaking the claim’s reference list. You cannot add a forged assertion either, because the claim does not point to it. The signature covers the whole document or none of it.
If you have worked with X.509, this should feel familiar. The TBSCertificate is what gets signed. Everything inside it, the subject, the public key, the extensions, is bound by that single signature. C2PA borrows the same shape and applies it to a different problem.
COSE, in plain language
C2PA uses COSE for its signatures. COSE stands for CBOR Object Signing and Encryption, originally defined in RFC 8152 and updated by RFC 9052. If you have not heard of it before, that is fine. The short version is that COSE is what JOSE and JWS would have looked like if they had been designed around CBOR instead of JSON.
COSE fits this problem well. CBOR is binary, compact, and well-suited to embedding inside a file you do not want to bloat. COSE is well-specified, has mature implementations, and supports the algorithms C2PA cares about. The closest alternative would have been JOSE/JWS — text-based JSON signatures — which would have required base64-encoding the manifest bytes just to sign them. For a format that is otherwise binary end to end, a binary signature wrapper avoids that overhead.
The specific construction C2PA uses is COSE_Sign1. This is the single-signer variant. One signer, one signature, over the claim. COSE supports a multi-signer variant too, but C2PA does not use it. Each manifest has exactly one claim, signed exactly once, by exactly one signer.
The supported signature algorithms are what you would expect from a modern spec:
- ES256, ES384, ES512 — ECDSA over P-256, P-384, P-521
- PS256, PS384, PS512 — RSASSA-PSS with the corresponding SHA-2 hash
- Ed25519 — Edwards-curve signatures (the spec restricts EdDSA to the Ed25519 instance; Ed448 is not allowed)
You will see ES256 and PS256 most often in the wild. The choice between ECDSA and RSA usually comes down to what the signer’s certificate authority issues. RSA is everywhere historically. ECDSA is smaller and faster, and is what most new C2PA-aware tooling defaults to.
The claim carries a default hash algorithm that applies to the assertions and ingredients it references, but individual assertions are allowed to override it. In practice the same hash gets reused throughout, but the spec does not require you to. The signature’s hash and the claim’s hash do not have to be the same algorithm either, although mixing them is unusual.
What gets hashed and how
The claim binds itself to the content through a hash. That hash is what makes the system tamper-evident at the byte level. Get the hashing wrong and the rest of the system is just decoration.
There are two flavors of content hashing in C2PA, and which one you use depends on the format.
Data hashing is the simple case. You take the bytes of the file, exclude the JUMBF box that holds the manifest itself, and hash everything else. The exclusion has to be there, because the manifest contains the hash, and a manifest cannot hash itself without going in circles. The result lives in a c2pa.hash.data assertion, which the claim references.
In practice, the assertion looks like this:
{
"label": "c2pa.hash.data",
"data": {
"exclusions": [
{ "start": 24, "length": 8742 }
],
"name": "jumbf manifest",
"alg": "sha256",
"hash": "b8c17e6a4f...truncated...",
"pad": "0000000000000000"
}
}
The exclusion ranges are explicit. The assertion records exactly which byte ranges were skipped during hashing. A verifier can replay the hash on their copy of the file using those same ranges and check that it matches. There is no ambiguity, and there is no place for a clever attacker to hide bytes outside the hash without it showing up. The pad field is a small chunk of zero-bytes that gives the assertion a stable serialized size before the hash is computed and written in. Without it, you would have a chicken-and-egg problem where adding the hash changes the size of the assertion, which changes the byte ranges, which changes the hash.
Box hashing is the more interesting case. It applies to formats structured as a tree of boxes, primarily BMFF-based formats like MP4, MOV, and HEIF. Instead of hashing raw byte ranges, the assertion records a hash for each relevant box. This matters for a few reasons. It tolerates harmless reorderings that the format spec allows. It localizes tampering to a specific box rather than just saying “something somewhere changed.” And for streaming formats, it lets a verifier check pieces of a file without having to hash the whole thing.
The current label for BMFF-based formats is c2pa.hash.bmff.v3, with c2pa.hash.boxes covering non-BMFF box-structured formats. Earlier versions of the spec used c2pa.hash.bmff.v2 and an unversioned c2pa.hash.bmff, and you will still see those in older files. The principle is the same across all of them. The hash binds the claim to the bytes, and the assertion records exactly enough information to let any verifier reproduce the calculation.
If even one byte changes outside the excluded ranges, the hash does not match, and the claim is invalid. That is the whole game.
Certificate chains and trust anchors
A signature is only as trustworthy as the key that signed it. A key is only as trustworthy as the chain behind the certificate that holds it.
C2PA uses standard X.509. No custom PKI, no novel trust model. The signer holds an end-entity certificate, issued by an intermediate CA, chained up to a root. The root is what anchors trust. If you trust the root, the chain validates, and the certificate is allowed to sign C2PA content, then the signature on the claim means something.
That last part is where C2PA adds its own constraints on top of generic X.509. Not every certificate is allowed to sign a C2PA claim. The end-entity certificate has to carry the dedicated c2pa-kp-claimSigning Extended Key Usage, which is what the C2PA Trust List itself now requires. Validators can be configured with separate trust anchors that accept other EKUs (for example the standard document-signing EKU), but for any certificate chaining to the official Trust List, c2pa-kp-claimSigning is the bar. Without it, the certificate is not a valid C2PA signer under that trust anchor, even if it is otherwise a perfectly good cert. This is the same pattern S/MIME and code signing use to keep certificates scoped to their intended purpose.
The trust anchors themselves are managed through the C2PA Trust List. This is a published list of root certificates that the spec recognizes as legitimate sources of C2PA signing certificates. Each CA on the list has gone through a review process. The trust list is what lets a verifier on a different machine, with no prior contact with the signer, decide whether to trust a signature at all.
It is worth being honest about something here. The trust list is a centralized choice in an otherwise decentralized system. Anyone can issue an X.509 certificate, but not every X.509 certificate counts as a valid C2PA signer. There is a gatekeeper, and the gatekeeper is the trust list. That is a deliberate design decision. Without it, anyone could mint a self-signed certificate and call themselves a verified publisher. With it, there is a known set of issuers whose practices have been reviewed.
SSL.com was the first publicly trusted CA in the C2PA ecosystem, and several others have since joined. The list will keep growing as more CAs build out the operational practices required to issue these certificates responsibly. We will spend more time on identity, signers, the audit framework behind the Trust List, and the limitations of that framework in Part 4.
Time and timestamping
A signature without a trustworthy time is half a signature.
Certificates expire. Keys get revoked. If someone signs a piece of content today and the signing certificate gets revoked next year, you need to know whether the signature was valid at the time it was applied. That is what timestamping is for.
C2PA supports RFC 3161 timestamps, the same kind of trusted timestamps you see in code signing. The signer requests a timestamp from a Time Stamp Authority, and that timestamp gets embedded alongside the signature inside the COSE structure, originally as a sigTst header and more recently through the improved sigTst2. The TSA’s signature attests that the claim’s signature existed at that moment. If the signing certificate is later revoked, but the timestamp is from before the revocation, the signature can still be treated as valid for content signed before that point. In practice, the verifier needs historical revocation data to confirm the cert was both valid and unrevoked at timestamp time, so this only fully works when that data is available.
The timestamp is its own little signed object, with its own certificate chain back to a TSA root. So when you verify a fully-formed C2PA signature, you are actually verifying two chains. The signer’s chain and the timestamp authority’s chain. The TSAs themselves are governed by the C2PA TSA Trust List, separate from the one for signing certificates. Both chains have to be valid for the signature to hold up under long-term verification rules.
Earlier 2.x versions of the spec defined time-stamp manifests as a separate construct signed by a TSA to establish the time-of-existence of a manifest without requiring a fresh content signature. Spec 2.2 introduced the time-stamp assertion (and a companion certificate-status assertion), carried inside an update manifest, as the replacement. As of 2.4, time-stamp manifests are marked HISTORICAL — claim generators must not write them, manifest consumers must not read them, and any found inside an ingredient are explicitly ignored. The goal of both designs is the same: extend the verifiable lifetime of content past the original signing certificate’s expiration. New tooling should use update manifests carrying time-stamp assertions.
In practice, well-behaved tooling timestamps everything. Less well-behaved tooling skips it. That is fine for short-term verification but causes problems years down the road when the original certificate has expired and there is nothing to prove the signature predates the expiration.
Walking through verification
This is where everything we have covered comes together. When a verifier sees a file, here is roughly what happens.
First, the verifier finds the JUMBF box and reads the manifest store. It picks the active manifest, the most recent one, and pulls out the claim, the signature, and the signer’s certificate chain.
Second, it locates and validates the claim itself. The claim has to be well-formed, reference its assertions and ingredients correctly, and carry a signer certificate the verifier can work with. The end-entity certificate has to carry an accepted C2PA EKU, and the chain has to walk back to a root on the C2PA Trust List, with each intermediate properly signed by its issuer and matching the basic-constraints rules for its position.
Third, it verifies the signature on the claim. Take the public key from the end-entity certificate, the signature from the COSE structure, and the claim payload. Run the signature algorithm. Either it verifies or it does not. If it does not, nothing else in the manifest can be trusted.
Fourth, it validates the time-stamp, if there is one. The TSA’s chain has to validate against the C2PA TSA Trust List the same way the signer’s chain validates against the regular Trust List. If the timestamp is present and valid, the verifier uses it as the reference time for the rest of the checks. If not, it falls back to the current time.
Fifth, it checks revocation. OCSP responses and CRLs for each certificate in both chains are evaluated against the reference time established in the previous step. Revoked certificates fail the check.
Sixth, it validates the assertions. For each assertion the claim references by hash, recompute the hash over the assertion’s stored bytes and compare. Any mismatch means an assertion was modified after the claim was signed, which invalidates the manifest.
Seventh, it walks the ingredients. For each ingredient that carries its own manifest, repeat the whole process recursively. If an ingredient has no manifest, that branch ends as ingredient.unknownProvenance. If an ingredient’s manifest is broken, the whole tree is flagged accordingly.
Eighth, it verifies the content hashes against the asset itself. For each c2pa.hash.data, c2pa.hash.bmff.v3, c2pa.hash.boxes, or c2pa.hash.collection.data assertion, recompute the hash using the assertion’s exclusion ranges or box list, and compare. Any mismatch means the file itself has been modified after signing. This is the hard binding from the manifest to the bytes; the spec also defines soft bindings (fingerprinting and watermarking) for cases where the file may be re-encoded, but those are handled separately and we will cover them later in the series.
If every step passes, the verifier reports the signature as valid, along with details about who signed it and when. If any step fails, it reports specifically where. Verification is not a single yes-or-no answer. It is a structured report that consumers can use to make their own trust decisions.
That structured failure mode is one of the most underrated parts of the spec. A verifier never lies and says everything is fine when something is broken. It tells you exactly what broke. The decision about how to react, whether to show a warning, refuse to display, or allow with caveats, is left to the platform consuming the data.
Wrapping up
The cryptography is the part that turns a structured record into something independently verifiable. Without it, a manifest is just metadata anyone could write. With it, every assertion in the chain is bound to a specific signer, at a specific time, over a specific set of bytes.
But none of this answers a much harder question. Who is actually allowed to be a signer in the first place, and what does it mean for a signer’s identity to be bound to a piece of content? That is where Part 4 picks up. We will look at identity, signers, the certificate practices behind them, and the messy human question of who gets to claim what online.