Skip to content

Provenance & C2PA

Provenance & C2PA

Layer 1 of the Verbitas stack implements the C2PA 2.4 specification. A C2PA manifest is a JUMBF-encoded, COSE-signed record embedded in an asset or stored alongside it. It answers: who signed this, when, and what claims did they make about its origin.

What goes in a manifest

A manifest contains one or more assertions — typed key-value claims about the asset. Verbitas supports the following assertion types:

AssertionDescription
ai_generatedBoolean — content is fully AI-generated
ai_assistedBoolean — human creator with AI edits
generatorName of the generating system (e.g. stable-diffusion-xl)
modelModel identifier
prompt_hashSHA-256 of the generation prompt. Never the prompt text itself.
created_atISO 8601 creation timestamp
creatorCreator identity (editorial recipes)
location_hintApproximate location (editorial recipes)
legal_entityLegal entity name (enterprise recipes)
legal_case_refCase reference string (legal-evidence recipe)
deepfake_disclosureRequired for image-deepfake-v1 and audio-voiceclone-v1

The assertions included in a given sign call are determined by the recipe.

Signing architecture

Asset + assertions
apps/worker: builds C2PA claim
▼ POST /internal/sign
apps/signer (Tailscale-internal only)
AWS KMS eu-central-1
COSE_Sign1 signature + X.509 cert chain + RFC 3161 timestamp
apps/worker: assembles JUMBF manifest, embeds in asset

The signer (apps/signer) is the only service that calls AWS KMS. No other service has the IAM permission. The signer accepts only 32-byte canonical C2PA claim digests — it is not a generic signing oracle.

Manifest embedding

FormatMethod
JPEGAPP11 segment
PNGcaBX chunk
WebPXMP metadata
Audio / VideoSidecar .c2pa file
PDFSidecar .c2pa file

The embedded or sidecar manifest URI is also stored in Verbitas object storage at https://m.verbitas.io/manifests/<asset_id>/manifest.c2pa.

Ingredient chain

When an asset is edited or derived from another Verbitas-signed asset, the new manifest can reference the parent manifest as an ingredient. This creates a chain of custody:

Original image (manifest A)
└─ Cropped + color-corrected (manifest B, references A as ingredient)
└─ Published to wire (manifest C, references B)

Recipients verifying manifest C can traverse the entire chain. The recipe field require_ingredient_chain: true enforces that all edits create new manifest nodes.

OCSP and certificate revocation

Every Verbitas signing certificate is checked against its OCSP responder at verification time.

  • Production (VB_ENV=prod): OCSP status must be good. Revoked or unreachable OCSP returns a revoked or ocsp_unavailable verification state.
  • Development (VB_ENV=dev): OCSP failure downgrades to a warning; it does not fail verification. A loud log line is emitted.

Enterprise recipes with ocsp_mode: required never degrade — the check is mandatory regardless of environment.

RFC 3161 timestamps

Every manifest includes an RFC 3161 timestamp token from a trusted timestamping authority. The timestamp proves the asset existed at a specific point in time, independent of the signing certificate’s validity period. This is particularly important for legal-evidence use cases where certificates may have been rotated since signing.

Anchoring (blockchain timestamps)

Beyond the RFC 3161 timestamp, Verbitas optionally anchors a SHA-256 Merkle root of manifest digests to:

  • Tier 1 (OpenTimestamps / Bitcoin): hourly batch, ~60 min to Bitcoin block inclusion
  • Tier 2 (Arbitrum One): hourly batch, near-instant L2 finality, periodic L1 anchor

The anchor proves that the manifest existed before a specific blockchain block. The payload on-chain is exactly 32 bytes — the Merkle root. No PII, no manifest content, no asset bytes.

See concepts: soft-binding for how anchor matches affect the verification state.

Parser security limits

The C2PA parser enforces hard limits to prevent denial-of-service attacks:

  • 32 MiB maximum manifest size
  • CBOR nesting depth ≤ 32
  • Depth-bomb protection
  • Magic-byte file type validation (file extensions are never trusted)

What the manifest does not prove

A valid manifest proves:

  • The signing certificate was valid at sign time
  • The claim digest matches the manifest content (tamper-evident)
  • The assertions in the manifest were submitted by the API caller with that key

A valid manifest does not prove:

  • The assertions are accurate (e.g. ai_generated: false could be a lie by the submitter)
  • The asset has not been edited in ways that preserve the manifest (sidecar manifests are detachable)
  • Legal compliance with any specific regulation

See Compliance: Positioning for approved language.