Extraction & Trust

Hash Chain Verification

Trustless binding from signature to metadata via cryptographic hash chains.

The previous section described how Apertrue extracts the C2PA manifest from an image. But extraction alone isn't proof of anything — a malicious client could extract data from one image and claim it belongs to another. The hash chain is what makes this impossible.

A C2PA manifest contains a chain of cryptographic hashes that bind every piece of metadata to the original signature. If any link in this chain is broken — a single byte of GPS data changed, one edit action removed, the image pixels modified — the verification fails. This section explains how that chain works and how Apertrue verifies it inside a zero-knowledge circuit.

The chain

The hash chain has four layers, each binding to the one above it:

Four stacked layers connected by arrows. Top: COSE Signature (ECDSA or RSA over message_hash). Second: Sig_structure to message_hash (SHA-256 of CBOR envelope). Third: Claim (CBOR with assertion hash references and content hash binding). Bottom: Assertions and image pixels. Signature covers message_hash, message_hash covers claim, claim references assertion hashes, assertions bind to raw data.
  1. COSE signature → message hash. The signature (ECDSA or RSA) covers the SHA-256 hash of the Sig_structure. This hash is what the ZK circuit verifies — if the signature is valid, the message hash is authentic.
  2. Message hash → claim bytes. The Sig_structure contains the raw claim bytes as its payload. The message hash commits to these exact bytes. Changing a single byte of the claim would change the message hash and break the signature.
  3. Claim → assertion hashes. The claim is a CBOR object that contains an array of assertion references. Each reference is a pair: a JUMBF path (identifying which assertion) and a SHA-256 hash (of that assertion's content). The claim doesn't contain the assertion data itself — just its hash.
  4. Assertion hashes → raw data + image pixels. Each assertion (EXIF metadata, edit history, AI declarations, content hash) is stored as a separate JUMBF box. Its SHA-256 hash must match the reference in the claim. The content binding assertion (c2pa.hash.data) contains the SHA-256 of the stripped image bytes — tying the pixels themselves to the signature.
Key Insight
The security property: any modification to any layer invalidates the signature. Change a GPS coordinate in the EXIF assertion → assertion hash changes → claim no longer matches its signed hash → COSE signature verification fails. This chain is checked entirely inside the ZK circuit, not by trusting the client.

Claim structure

The claim is a CBOR-encoded object at the heart of the hash chain. Its key fields are:

Simplified claim structure (CBOR)
{
  "dc:format": "image/jpeg",
  "claim_generator": "Leica Camera AG / ...",
  "alg": "sha256",
  "assertions": [
    {
      "url": "self#jumbf=.../c2pa.assertions/stds.exif",
      "hash": <32 bytes>  // SHA-256 of EXIF assertion box
    },
    {
      "url": "self#jumbf=.../c2pa.assertions/c2pa.actions.v2",
      "hash": <32 bytes>  // SHA-256 of actions assertion box
    },
    {
      "url": "self#jumbf=.../c2pa.assertions/c2pa.hash.data",
      "hash": <32 bytes>  // SHA-256 of content binding assertion
    }
  ]
}

Each entry in the assertions array is a commitment: the signer attests that at signing time, the assertion identified by that URL had exactly that SHA-256 hash. The ZK circuit verifies this by hashing the assertion bytes and comparing against the value embedded in the claim.

How offsets work

The circuit needs to find each assertion hash inside the raw claim bytes. Rather than parsing CBOR inside the circuit (which would cost thousands of additional constraints), the client-side extractor locates the byte offset where each hash appears in the claim and passes that offset as a circuit input. The circuit then reads 32 bytes from that position and compares them against the computed assertion hash.

This is safe because the circuit independently verifies that the claim bytes themselves hash to the value committed by the COSE signature. If the client provides a wrong offset, the extracted bytes won't match the assertion hash, and the circuit rejects the proof.

Content binding

The content binding assertion (c2pa.hash.data) is special. It ties the image pixels to the signature chain. Inside this assertion:

  • Hash algorithm — always SHA-256 in practice.
  • Content hash — the SHA-256 of the image bytes with exclusion ranges applied.
  • Exclusion ranges — byte ranges to skip when computing the hash, typically the location where the C2PA manifest itself is stored (since the manifest can't hash itself).

In JPEG files, the manifest is appended in APP11 markers — those byte ranges are excluded. In PNG files, the manifest lives in ancillary chunks that are excluded. The remaining bytes — the actual pixel data and compression structures — are what gets hashed.

The ZK circuit takes this content hash as a public input. It extracts the content hash from the claim bytes (at a client-provided offset) and asserts it matches the public input. Anyone verifying the proof can see the content hash and independently compute it from the image — confirming the proof covers that specific image.

What the circuit verifies

The hash chain verification runs inside ProofA (the certificate and claim verification circuit). It performs five checks:

  1. Claim hash. Compute SHA-256 of the raw claim bytes and verify it matches the expected claim hash (which is bound to the COSE signature through the Sig_structure).
  2. EXIF assertion hash. Read 32 bytes from the claim at the provided offset. Compute SHA-256 of the EXIF assertion bytes. Assert they match. This proves the EXIF data (device info, timestamps, GPS) is exactly what the signer committed to.
  3. Actions assertion hash. Same process for the edit history assertion. This proves the declared edit actions (AI modifications, software edits) are authentic.
  4. Content hash binding. Extract the content hash from the claim bytes and verify it matches the public content hash input. This proves the image pixels are bound to the signature.
  5. Link commitment. Compute a Pedersen hash of the intermediate certificate leaf, the claim hash (as a field element), and a random blinding factor. This value must match across ProofA and ProofB, binding them to the same claim and certificate chain.
Circuit hash chain verification (simplified)
// Step 1: Verify claim integrity
let computed_claim_hash = sha256(claim_bytes, claim_length);
assert(computed_claim_hash == expected_claim_hash);

// Step 2: Verify EXIF assertion binding
let exif_hash_from_claim = claim_bytes[exif_hash_offset..+32];
let computed_exif_hash = sha256(exif_assertion_bytes, exif_length);
assert(exif_hash_from_claim == computed_exif_hash);

// Step 3: Verify actions assertion binding
let actions_hash_from_claim = claim_bytes[actions_hash_offset..+32];
let computed_actions_hash = sha256(actions_assertion_bytes, actions_length);
assert(actions_hash_from_claim == computed_actions_hash);

// Step 4: Verify content hash binding
let content_hash_from_claim = claim_bytes[content_hash_offset..+32];
assert(content_hash_from_claim == public_content_hash);

// Step 5: Compute link commitment
let link_commit = pedersen([intermediate_leaf, claim_hash_field, link_blind]);

The link commitment

The hash chain culminates in a link commitment — a Pedersen hash binding the certificate chain, claim content, and a random blinding factor into a single value. This commitment is output as a public value by both ProofA and ProofB. The aggregation circuit checks that they match, preventing an attacker from mixing claims between the two split proofs. The full link commitment mechanism is detailed inSplit-Proof Architecture.

Client-side vs. in-circuit verification

Not everything is verified inside the ZK circuit. The design splits work between client-side extraction and in-circuit verification based on what needs to be trustless:

CheckWhereWhy
CBOR claim parsingClientExtracting offsets and data for circuit inputs — the circuit doesn't parse CBOR
Claim hash verificationCircuitCore binding — must be trustless
Assertion hash verificationCircuitCore binding — must be trustless
Content hash bindingCircuitCore binding — must be trustless
Certificate signatureCircuitCore binding — must be trustless
COSE signature verificationCircuitCore binding — must be trustless
Trust list membershipCircuitCore binding — must be trustless
GPS value extraction from EXIFClientParsing CBOR rationals in-circuit costs ~1000 constraints; hash chain prevents tampering
Timestamp extractionClientSame — hash chain guarantees the assertion bytes are authentic
Time/location range proofsCircuitPrivacy enforcement — must be in-circuit
Note
The trust model for client-extracted values (GPS, timestamps): the circuit verifies that the assertion bytes hash correctly into the signed claim. The client extracts specific values from those verified bytes. If the client lies about the values, the range proofs will fail — you can't prove a timestamp is within a valid range if you fabricated the timestamp. The hash chain guarantees the raw bytes are authentic; the circuit guarantees the disclosed values are within the claimed bounds.

Why this matters

Without the hash chain, Apertrue would have to trust the client to provide honest extraction results. The client could claim an image was taken in London when it was taken in Moscow, or strip edit history to hide AI modifications. With the hash chain verified inside the circuit:

  • The client cannot substitute assertion data — the hashes won't match the signed claim.
  • The client cannot swap claims between proofs — the link commitment binds them.
  • The client cannot modify image pixels — the content hash binding fails.
  • The verifier doesn't need to trust anything except the math.

This is what "trustless" means in practice. The hash chain converts a C2PA manifest — which is designed to be verified by trusting the signer — into a structure that can be verified by anyone, about anything the signer committed to, without revealing the underlying data. The next section explains how different signers are classified into trust tiers.