@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-c2paNote: This library uses the Web Crypto API (
crypto.subtle) to verify COSE signatures and compute BMFF hashes. In Node.js 20+,crypto.subtleis available globally. In browsers, it requires a secure context (HTTPS orlocalhost).
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
- VSI/EMSG Validation (§19.4)
- Manifest Box Validation (§19.3)
- VOD Merkle Validation (§15.12.2 / §18.6)
- Results and Error Codes
References
Peer dependencies
npm 7 and later installs peer dependencies with the package.