2.3

Inside a C2PA manifest

The on-the-wire structure of a Content Credential, from the JUMBF box that holds it to the CBOR encoding of each assertion and the signature that ties the whole thing together.

A C2PA manifest is not a single file. It is a nested structure of containers, encodings, and signed objects, each defined by a separate specification borrowed from a different corner of media standards work. Understanding the structure matters for anyone building tooling, parsing manifests in a script, or trying to figure out what a validator's reason codes actually mean. This page walks the structure from outside to inside: JUMBF, then manifest, then claim, then assertions, then bindings, then signatures.

The architecture descends from the JPEG ecosystem and inherits its priorities: compactness, schema flexibility, and embeddability inside existing media containers without breaking older parsers. The choice of JUMBF as the outer container, CBOR as the inner encoding, and X.509 / COSE as the signature stack reflects a deliberate decision to reuse mature standards rather than invent new ones. The C2PA Technical Specification, currently 2.4, defines the rules for combining them.

The JUMBF container

JUMBF stands for JPEG Universal Metadata Box Format, standardized as ISO/IEC 19566-5 in 2019. It is a generic box format — a sequence of typed boxes, each with a header and payload, recursively nestable — modeled on ISOBMFF, the container behind MP4 and HEIF. The JUMBF superbox carrying a C2PA manifest is labeled with the namespace urn:c2pa:manifest_store, and contains one or more manifest boxes plus an indication of which is the active manifest.

JUMBF was chosen because it embeds cleanly inside most major still-image and video container formats: JPEG (as an APP11 marker), HEIF / HEIC (as a top-level box), PDF (as a content stream), MP4 (as a top-level box), WAV (as a chunk). The format is opaque to legacy parsers — a JPEG decoder ignores APP11 markers it does not understand — so a C2PA-augmented file remains compatible with every tool that does not care about provenance. This compatibility is one of the major reasons C2PA achieved cross-vendor adoption.

Inside the manifest store, each manifest is itself a JUMBF box. A manifest box contains assertion boxes, a claim box, and a signature box, plus an index of assertion URIs used by the claim. The structure is recursive and self-describing, which is how validators handle manifests they have never seen before — they parse box headers, look up the contained type by URI, and apply the appropriate handler.

The CBOR encoding

The contents of each box — the assertion data, the claim object, the signature — are encoded in CBOR (Concise Binary Object Representation, RFC 8949). CBOR is a binary cousin of JSON: it has the same data model (maps, arrays, strings, numbers, byte strings, booleans, nulls) but encodes more compactly and supports binary data natively without base64 wrapping.

The choice of CBOR over JSON is consequential. CBOR halves typical sizes for the kind of structured metadata a manifest contains. It supports stable canonical encoding, which is essential for signing: a JSON-signed structure can be invalidated by reordering keys or changing whitespace, while a CBOR-signed structure with canonical encoding has a single byte representation that all implementations agree on. The C2PA spec mandates the Deterministic Encoding profile of CBOR for signed content.

For human inspection, validators decode the CBOR back to a JSON-equivalent representation. The c2patool output shown on the end-to-end page is JSON-rendered CBOR. The actual bytes on disk are smaller and unreadable without a decoder.

Assertions

An assertion is a labeled, CBOR-encoded statement about the asset. The label is a URI: c2pa.actions, c2pa.ingredient, stds.exif, c2pa.hash.data, and so on. The C2PA spec defines a core vocabulary; vendors can register custom assertions under their own namespaces. The full assertion catalog is covered on the assertions and claims page.

A simplified assertion, decoded to JSON-equivalent for reading, looks like this:

{
  "label": "c2pa.actions",
  "data": {
    "actions": [
      {
        "action": "c2pa.created",
        "softwareAgent": "Adobe Photoshop 25.5",
        "when": "2026-04-12T18:21:09Z"
      },
      {
        "action": "c2pa.color_adjustments",
        "parameters": { "type": "curves" }
      },
      {
        "action": "c2pa.cropped"
      }
    ]
  }
}

Each assertion is a JUMBF box. The manifest references each assertion by its URI inside the manifest store, allowing the claim to hash assertion contents indirectly.

The claim

The claim is the central signed object in a manifest. It aggregates hashes of all assertions, hashes of the asset binding, and metadata about the claim generator. The claim itself is CBOR-encoded, then wrapped in a COSE_Sign1 structure (RFC 9052) for signing. COSE — CBOR Object Signing and Encryption — is the CBOR-native analogue of JWS / JOSE.

The signature inside the COSE structure is computed over the canonical CBOR encoding of the claim. The signing certificate is included in the COSE protected headers, along with the time of signing and the algorithm identifier. C2PA permits ECDSA with P-256 / P-384 / P-521 curves and RSA-PSS with 2048-bit or larger keys. Hash algorithms are SHA-256, SHA-384, or SHA-512.

A claim looks, in pseudo-JSON, like:

{
  "claim_generator": "Adobe Photoshop 25.5",
  "claim_generator_info": [{
    "name": "Adobe Photoshop",
    "version": "25.5.0"
  }],
  "signature": "self#jumbf=c2pa.signature",
  "assertions": [
    {
      "url": "self#jumbf=c2pa.assertions/c2pa.actions",
      "hash": "h'a1b2c3d4...'",
      "alg": "sha256"
    },
    {
      "url": "self#jumbf=c2pa.assertions/c2pa.hash.data",
      "hash": "h'e5f6a7b8...'",
      "alg": "sha256"
    }
  ],
  "alg": "ES256",
  "created_at": "2026-04-12T18:22:17Z"
}

The list of assertion URIs and their hashes is what makes the claim tamper-evident over the whole manifest. If any assertion changes, its hash changes, and the claim signature no longer validates. This is the cryptographic heart of the structure.

Hard and soft bindings

The claim must include a binding to the asset itself. A hard binding is an assertion of type c2pa.hash.data (for whole-file hash) or c2pa.hash.boxes (for hash over specific media boxes, used when JUMBF presence itself perturbs the file). The hash covers the asset bytes excluding the manifest store, allowing the manifest to embed without disturbing its own hash.

A soft binding is an assertion of type c2pa.soft_binding carrying a perceptual fingerprint or watermark identifier. Soft bindings survive perceptually-equivalent transformations and are the foundation of durable Content Credentials. The two binding types can coexist in the same manifest, and a validator will use whichever is intact for a given received file.

The manifest store and chain traversal

A file can carry multiple manifests, one per editorial generation. The active manifest is the most recent; earlier manifests appear as referenced ingredients. When an editor opens an image with an existing manifest and saves a derivative, the editor adds its own manifest to the store and references the prior one via a c2pa.ingredient assertion. The ingredient assertion includes the hash of the prior manifest, binding the two cryptographically.

A validator traverses the chain by starting at the active manifest, validating its claim and bindings, then recursively validating each ingredient. The result is a tree (a single image can have multiple ingredients, as in a composite) that the validator walks bottom-up. The output indicates which nodes validated, which did not, and what reason codes apply to failures. The chain is intact only if every node validates against its referenced parent.

In practice A manifest with valid signatures but missing ingredients ("ingredient.hashedURI.mismatch" in spec language) is not a forgery. It usually indicates that an intermediate editing step did not preserve the prior manifest. Validators report this as a partial chain; consuming applications should not present it as a verification failure without explanation.

Embedded vs. remote manifests

Manifests are normally embedded inside the asset's JUMBF box. The C2PA spec also defines a remote-manifest pattern in which the asset carries a pointer to a manifest hosted on a server. Remote manifests are useful for very large files, for streaming media, and for cases where signing is performed asynchronously after distribution. The validator dereferences the pointer, fetches the manifest, and validates as if it had been embedded.

Remote manifests introduce a network dependency — a validator without internet access cannot verify a remote-manifest-only file — and a privacy consideration: the server hosting the manifest sees who is asking for it. Both factors have limited remote-manifest adoption to specialized use cases.

What this means for tooling

Implementers building C2PA support do not need to write JUMBF, CBOR, or COSE code from scratch. The CAI maintains open-source libraries — c2pa-rs (Rust), c2pa-node (Node.js), c2pa-python — that handle the structural work. The interesting work for an implementer is in the assertion vocabulary, the trust-list integration, and the UX for surfacing validation results to users. The lower-level structures are stable, well-specified, and well-supported.

Where the field is moving

The 2.4 release closed several known structural gaps, particularly around video binding granularity (per-segment vs. whole-file hashing) and around the redaction protocol used for editorially-required PII removal. The next major revision, likely 3.0 in the 2026–2027 window, is expected to formalize the integration with watermark-derived signals and to extend the binding grammar for live-stream and live-broadcast contexts. The structural decisions made now — particularly around what soft bindings can carry — will determine how much of the watermarking ecosystem ends up inside the C2PA tent versus outside it.