Skip to content

C2PA manifest definition ​

A render can carry Content Credentials. Sealpixel does not sign them: the host's product is the claim generator, holds the key and attaches the original. What Sealpixel contributes is the part only it knows: which actions the edit log performed on the pixels, in the vocabulary of the C2PA 2.2 specification, with the redacted regions in coordinates of the output. @sealpixel/c2pa writes that as a manifest definition, the JSON that c2patool --manifest and the c2pa-rs Builder take as it is.

Write the definition ​

ts
import { manifestDefinition } from '@sealpixel/c2pa';
import { defaultPlugins } from '@sealpixel/plugins';

const { json } = await manifestDefinition({
  log, // getEditLog().log, or manifest.editLog.log from a render manifest
  plugins: defaultPlugins,
  title: 'result.jpg',
});
await writeFile('result.c2pa.json', json);

The native CLI writes the same definition for a plan:

bash
sealpixel render original.jpg plan.json result.jpg --c2pa result.c2pa.json

manifestDefinitionFor(manifest, options) takes a render manifest instead of a log: the log, the index and the renderer's version come from it, and the plugins must be the ones the render used.

Sign it ​

With c2patool, the host names the original as the parent and signs with its own certificate:

bash
c2patool result.jpg --manifest result.c2pa.json --parent original.jpg --output signed.jpg

With c2pa-rs, the host builds from the definition, adds the original with add_ingredient_from_stream as parentOf, and signs. @sealpixel/c2pa-sign does the same from Node, Deno, Bun or Lambda with a signer function you supply. Either way the signing certificate is the host's; c2pa-rs refuses a self-signed one, so the smallest working setup is a CA and a signing certificate under it.

The definition carries no claim_generator_info. That is the host's name and version, the product that signs.

What is in it ​

One c2pa.actions assertion with allActionsIncluded: true, in the order the stages run:

StageAction
The originalNothing from Sealpixel: the host attaches it as the parentOf ingredient, and the Builder adds c2pa.opened for it, tied to the ingredient as the validator demands
redact, one per regionc2pa.edited with the region in changes, a description that names the mode, and the mode and amount as dev.sealpixel.redact parameters
annotate, one per shapec2pa.addedText for text, c2pa.drawing for a rectangle, ellipse, line, arrow or path
cropc2pa.cropped, plus c2pa.orientation when the crop rotates or flips; c2pa.edited for a background fill or an alpha mask
finetunec2pa.adjustedColor
filterc2pa.filtered
sticker, one per itemc2pa.edited with the asset's name as dev.sealpixel.asset; see below for c2pa.placed
framec2pa.edited
watermarkc2pa.addedText for text, c2pa.edited with the asset's name for an image
resizec2pa.resized
A plugin kernel outside the corec2pa.edited with the plugin's name. The regions of the stages after it assume the kernel kept the buffer's size

Never c2pa.redacted: in C2PA that means assertions removed from a manifest. Never c2pa.watermarked: that is an invisible soft-binding watermark. Not c2pa.placed either, although a sticker is a placed asset: the validator demands that c2pa.placed point at a componentOf ingredient with the asset's bytes, which only the signer holds. The definition signs as it is; a host that attaches the assets as ingredients may rewrite those actions.

Every action carries a digitalSourceType. The default is IPTC's humanEdits, because the log holds decisions a person made. A stage the host configured rather than a person chose, such as a resize from initialOps, is algorithmicallyEnhanced under the conformance rubric; set it with digitalSourceTypeByStage, keyed by the plugin's name (crop, fill, finetune, filter, resize, a kernel's name, ...) or by the stage kind (color covers finetune and filter). softwareAgent names the renderer; the default is this core's version.

The labels are those of the C2PA 2.2 specification, which added c2pa.adjustedColor and c2pa.addedText and deprecated c2pa.color_adjustments. A reader built on an older vocabulary, c2pa-rs 0.91 among them, shows the two as actions it has no name for; it signs and validates them all the same, which is what pnpm test:c2pa checks.

Regions ​

A redact region is in the source's image space. A C2PA region refers to the signed asset, the output. The definition moves every region through the crop stage's transform and through the rectangle a cover resize keeps, clips it to the frame and writes it in percent of the output: a rectangle when the crop did not rotate, a polygon when it did, nothing when the crop cut the region away. A contain or fill resize changes no percentage. The mapping lives in the core, so the browser, Node and the native CLI write the same numbers.

What is checked ​

The parity script writes the definition of every golden log twice, through the wasm core and through the native CLI, compares them, compares them with fixtures/c2pa-goldens.json, then signs every one with c2patool and a certificate chain made on the spot, validates the file and reads the actions back. It runs in CI on every commit.