Skip to main content

Verify an asset

Use AssetSdk to verify finished files (images, audio clips, static MP4 video, PDFs). Pass the file bytes — or a RangeSource for a large file streamed from disk — to verify; it returns every credential for the asset as an array of { manifest, binding } entries: one for an embedded manifest, possibly several when recovered from the registry, and an empty array when the asset is unsigned. Each entry's binding records how the manifest was located — null when it was embedded, { kind: 'hard', hash } when recovered by exact content hash, or { kind: 'soft', alg, value } when recovered by a durable soft binding (e.g. an ISCC code).

AssetSdk.create({ trustAnchors, registry?, trustC2paAnchors? }) takes the PEM trust anchors and an optional registry URL (e.g. https://api.trylimbo.com); omit the registry for embedded-only verification with no recovery. The official C2PA conformance trust list is trusted by default; trustC2paAnchors: false restricts trust to your anchors. verify(input, binders) returns { manifest, binding }[]: input is a Uint8Array (in-memory bytes) or a Node RangeSource (a large file streamed without loading it whole — see File streaming), and binders is the array of soft-binding strategies to try when neither an embedded manifest nor an exact-hash copy is found. Pass [] for embedded + exact-hash recovery only. Soft recovery is explicit: to recover a re-encoded image, pass [Binder.iscc()]; for a watermark or an out-of-band video ISCC, pass Binder.watermark(...) or Binder.held(...). See Recovery.

Full example

import { AssetSdk, DEV_CA } from '@limboai/verifox-sdk-verifier';

const verifier = AssetSdk.create({ trustAnchors: [DEV_CA()] });

async function verifyUrl(url: string) {
const response = await fetch(url);
const bytes = new Uint8Array(await response.arrayBuffer());
return verifier.verify(bytes, []);
}

const [result] = await verifyUrl('/photos/cover.jpg');

For the common single-credential case, destructure the array with const [result] = …; result is undefined when the asset is unsigned (empty array), otherwise it's a { manifest, binding } pair.

To verify a Blob from a file input, get the bytes with new Uint8Array(await blob.arrayBuffer()) and pass them to verifier.verify(bytes, []). The rest of the flow is unchanged.

In-memory bytes are passed as a Uint8Array — read the file with fs.readFile on Node, or fetcharrayBuffer in the browser. A file too large to hold in memory streams instead: pass a Node RangeSource and the SDK reads only the ranges it needs, without loading the whole file. See File streaming.

The AssetSdk handle is garbage-collected when it goes out of scope; there's nothing to release manually.

Reading the result

Each entry pairs the c2pa ManifestStore (result.manifest) with the binding that located it. A viewer-facing badge typically requires three fields off result.manifest:

  • manifest.claim_generator is the Signer name string.
  • manifest.signature_info?.time is the signing timestamp.
  • manifest.validation_state is 'Trusted', 'Valid', or 'Invalid'.
const [result] = await verifier.verify(bytes, []);

if (!result?.manifest.active_manifest) {
return { signed: false }; // empty array (unsigned), or no active manifest
}

const store = result.manifest;
const manifest = store.manifests![store.active_manifest];

return {
signed: true,
signer: manifest.claim_generator,
signedAt: manifest.signature_info?.time,
state: store.validation_state,
recoveredBy: result.binding, // null when embedded, else the hard/soft binding
};

When recovery may return more than one credential, map over the array (or filter by c.manifest.validation_state) instead of taking the first.

See also