Skip to content

@svta/cml-c2pa ​

C2PA (Coalition for Content Provenance and Authenticity) live video validation for BMFF and MP4 containers.

Supported C2PA segment validation methods:

  • §19.3 Per-segment C2PA Manifest Box: each segment embeds a full C2PA manifest with a COSE signature
  • §19.4 Verifiable Segment Info (VSI/EMSG): the init segment contains the manifest and the session keys. Each media segment contains a small signed EMSG box
  • §15.12.2 / §18.6 VOD Merkle: for VOD assets in fragmented MP4, the init manifest stores one Merkle tree row per track. Each media segment contains its own leaf hash and proof

When to use ​

  • Validate the C2PA manifest in an init segment: validateC2paInitSegment
  • Validate a live segment with the VSI and EMSG method and the session keys of the init segment: validateC2paSegment
  • Validate a per-segment C2PA manifest box: validateC2paManifestBoxSegment
  • Validate the Merkle leaf and proof in a VOD segment: validateC2paMerkleSegment
  • Map a validation result to a status code: C2paStatusCode, LiveVideoStatusCode

Do not write your own COSE, CBOR, or manifest box parsing for C2PA. Open an issue for a validation method that is missing.

Installation ​

bash
npm i @svta/cml-c2pa

Note: This library uses the Web Crypto API (crypto.subtle) to verify COSE signatures and compute BMFF hashes. In Node.js 20+, crypto.subtle is available globally. In browsers, it requires a secure context (HTTPS or localhost).

Usage ​

Manifest Box method (per-segment manifests) ​

typescript
import { validateC2paManifestBoxSegment } from '@svta/cml-c2pa'
import type { ManifestBoxValidationState } from '@svta/cml-c2pa'

async function validateManifestBoxStream(segmentUrls: string[]): Promise<void> {
  let lastManifestId: string | null = null
  let state: ManifestBoxValidationState | undefined

  for (const segmentUrl of segmentUrls) {
    const response = await fetch(segmentUrl)
    const bytes = new Uint8Array(await response.arrayBuffer())
    const { result, nextManifestId, nextState } = await validateC2paManifestBoxSegment(
      bytes,
      lastManifestId,
      state,
    )
    lastManifestId = nextManifestId
    state = nextState

    console.log(result.isValid, result.errorCodes)
  }
}

VSI/EMSG method (init segment + media segments) ​

typescript
import { validateC2paInitSegment, validateC2paSegment } from '@svta/cml-c2pa'

async function validateVsiSegment(initUrl: string, segmentUrl: string): Promise<void> {
  const initResponse = await fetch(initUrl)
  const init = await validateC2paInitSegment(new Uint8Array(await initResponse.arrayBuffer()))
  if (!init.isValid) {
    console.error(init.errorCodes)
    return
  }

  const segmentResponse = await fetch(segmentUrl)
  const segmentBytes = new Uint8Array(await segmentResponse.arrayBuffer())
  const validated = await validateC2paSegment(segmentBytes, init.sessionKeys)
  console.log(validated?.result.isValid)
}

VOD Merkle method (init segment + media segments) ​

typescript
import { validateC2paInitSegment, validateC2paMerkleSegment } from '@svta/cml-c2pa'

async function validateMerkleSegment(initUrl: string, segmentUrl: string): Promise<void> {
  const initResponse = await fetch(initUrl)
  const init = await validateC2paInitSegment(new Uint8Array(await initResponse.arrayBuffer()))
  if (!init.isValid) {
    console.error(init.errorCodes)
    return
  }

  const segmentResponse = await fetch(segmentUrl)
  const segmentBytes = new Uint8Array(await segmentResponse.arrayBuffer())
  const { result } = await validateC2paMerkleSegment(segmentBytes, init.merkleMaps)
  console.log(result.isValid, result.errorCodes)
}

Docs ​

References ​

Peer dependencies ​

npm 7 and later installs peer dependencies with the package.

A Streaming Video Technology Alliance project.