Work with Coalition for Content Provenance and Authenticity standards for complete content verification
The Coalition for Content Provenance and Authenticity (C2PA) represents a landmark collaboration between technology companies, media organizations, and standards bodies to establish a universal framework for content authentication. Founded by Adobe, Microsoft, Intel, BBC, and Truepic, C2PA has quickly become the de facto standard for embedding provenance information in digital media.
C2PA addresses a critical gap: while cryptographic signatures prove content integrity and watermarks enable tracking, neither provides a comprehensive, standardized way to communicate "who created this, how, and what happened to it since." C2PA manifests fill this need by embedding rich, structured metadata directly into media files in a tamper-evident format.
The C2PA specification builds on several fundamental concepts that work together to provide comprehensive authentication:
A C2PA manifest follows a well-defined schema that ensures consistency across implementations. Understanding this structure is essential for creating compliant authentication systems.
// Complete C2PA Manifest Example
{
"@context": "https://c2pa.org/context/2.0",
"type": "Claim",
"dc:title": "Mountain Landscape Photography",
"dc:format": "image/jpeg",
"claim_generator": "WIA-AI-017 Authenticator/1.0",
"instance_id": "xmp:iid:7a9f8b2c-1e3d-4f5a-9b8c-2d1e3f4a5b6c",
"claim_generator_info": [{
"name": "WIA Content Authentication System",
"version": "1.0.0",
"icon": "data:image/svg+xml;base64,..."
}],
"assertions": [
{
"label": "c2pa.actions",
"data": {
"actions": [
{
"action": "c2pa.created",
"when": "2025-12-25T10:30:00Z",
"softwareAgent": {
"name": "Canon EOS R5",
"version": "1.8.1"
},
"parameters": {
"exposure": "1/500",
"aperture": "f/8",
"iso": 100,
"focal_length": "85mm"
}
},
{
"action": "c2pa.color_adjustments",
"when": "2025-12-25T11:15:00Z",
"softwareAgent": {
"name": "Adobe Lightroom",
"version": "13.1"
},
"changes": {
"exposure": "+0.5",
"contrast": "+15",
"saturation": "+10"
}
},
{
"action": "c2pa.cropped",
"when": "2025-12-25T11:20:00Z",
"parameters": {
"crop_region": {
"x": 100, "y": 150,
"width": 4000, "height": 3000
}
}
}
]
}
},
{
"label": "c2pa.hash.data",
"data": {
"name": "jumbf manifest",
"alg": "sha256",
"hash": "9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f"
}
},
{
"label": "stds.schema-org.CreativeWork",
"data": {
"@context": "https://schema.org",
"@type": "Photograph",
"author": {
"@type": "Person",
"name": "Jane Smith",
"identifier": "did:web:photographer.example.com"
},
"copyrightHolder": {
"@type": "Organization",
"name": "Acme Photography Studio"
},
"license": "https://creativecommons.org/licenses/by-nc/4.0/"
}
},
{
"label": "c2pa.ai.generative",
"data": {
"ai_used": false
}
},
{
"label": "c2pa.thumbnail.claim.jpeg",
"data": {
"format": "image/jpeg",
"identifier": "self#jumbf=/c2pa/thumbnail.jpg"
}
}
],
"signature_info": {
"alg": "es256",
"issuer": "WIA Certificate Authority",
"cert_serial_number": "0x1A2B3C4D",
"time": "2025-12-25T11:25:00Z"
},
"ingredients": []
}
Integrating C2PA support into your content authentication system requires several components working in harmony. The WIA-AI-017 standard provides reference implementations and best practices.
The first step is generating a compliant C2PA manifest when content is created or modified:
import { createC2PA, ManifestBuilder } from '@c2pa/node';
import { sign } from 'crypto';
class WIA_C2PA_Integrator {
constructor(config) {
this.c2pa = createC2PA();
this.privateKey = config.privateKey;
this.certificate = config.certificate;
}
async createAuthenticatedContent(file, metadata) {
// Build the manifest
const manifest = new ManifestBuilder()
.setTitle(metadata.title)
.setFormat(this.getMediaType(file))
.setClaimGenerator('WIA-AI-017/1.0')
.setInstanceId(this.generateUUID());
// Add creation action
manifest.addAssertion('c2pa.actions', {
actions: [{
action: 'c2pa.created',
when: new Date().toISOString(),
softwareAgent: metadata.creator || 'Unknown',
parameters: metadata.parameters || {}
}]
});
// Add AI usage declaration
manifest.addAssertion('c2pa.ai.generative', {
ai_used: metadata.ai_generated === true,
ai_model: metadata.ai_model || null,
ai_version: metadata.ai_version || null
});
// Add content hash
const contentHash = await this.hashContent(file);
manifest.addAssertion('c2pa.hash.data', {
name: 'jumbf manifest',
alg: 'sha256',
hash: contentHash
});
// Add watermark reference
if (metadata.watermark_id) {
manifest.addAssertion('wia.watermark', {
watermark_id: metadata.watermark_id,
embedding_method: 'DCT',
strength: metadata.watermark_strength
});
}
// Add thumbnail
const thumbnail = await this.generateThumbnail(file);
manifest.addThumbnail(thumbnail);
// Sign the manifest
const signedManifest = await this.signManifest(
manifest.build(),
this.privateKey,
this.certificate
);
// Embed in the file
const authenticatedFile = await this.c2pa.embed(
file,
signedManifest
);
return authenticatedFile;
}
async signManifest(manifest, privateKey, certificate) {
const manifestBytes = JSON.stringify(manifest);
const signature = sign('sha256', Buffer.from(manifestBytes), {
key: privateKey,
dsaEncoding: 'ieee-p1363'
});
return {
...manifest,
signature_info: {
alg: 'es256',
signature: signature.toString('base64'),
cert_chain: [certificate]
}
};
}
generateUUID() {
return `xmp:iid:${crypto.randomUUID()}`;
}
}
Equally important is the ability to read and verify C2PA manifests from authenticated content. Verification ensures the manifest hasn't been tampered with and the signatures are valid.
class C2PA_Verifier {
constructor() {
this.c2pa = createC2PA();
this.trustedCAs = this.loadTrustedCAs();
}
async verifyContent(file) {
// Extract the manifest
const manifest = await this.c2pa.read(file);
if (!manifest) {
return {
verified: false,
error: 'No C2PA manifest found'
};
}
// Verify signature
const signatureValid = await this.verifySignature(
manifest,
this.trustedCAs
);
if (!signatureValid) {
return {
verified: false,
error: 'Invalid signature'
};
}
// Verify content binding
const bindingValid = await this.verifyContentBinding(
file,
manifest
);
if (!bindingValid) {
return {
verified: false,
error: 'Content has been modified'
};
}
// Verify ingredient chain
const ingredientsValid = await this.verifyIngredients(
manifest
);
// Extract key information
const actions = this.extractActions(manifest);
const aiUsage = this.extractAIUsage(manifest);
const author = this.extractAuthor(manifest);
return {
verified: true,
manifest: manifest,
actions: actions,
ai_used: aiUsage.ai_used,
ai_model: aiUsage.ai_model,
author: author,
ingredients_verified: ingredientsValid,
signature_time: manifest.signature_info.time
};
}
async verifySignature(manifest, trustedCAs) {
const { signature_info } = manifest;
// Extract certificate
const cert = signature_info.cert_chain[0];
// Verify certificate chain
const certValid = await this.verifyCertificateChain(
cert,
trustedCAs
);
if (!certValid) return false;
// Verify signature
const manifestBytes = this.serializeManifest(manifest);
const signature = Buffer.from(signature_info.signature, 'base64');
return verify(
'sha256',
Buffer.from(manifestBytes),
{
key: cert.publicKey,
dsaEncoding: 'ieee-p1363'
},
signature
);
}
async verifyContentBinding(file, manifest) {
// Compute current content hash
const currentHash = await this.hashContent(file);
// Extract hash from manifest
const hashAssertion = manifest.assertions.find(
a => a.label === 'c2pa.hash.data'
);
if (!hashAssertion) return false;
return currentHash === hashAssertion.data.hash;
}
extractActions(manifest) {
const actionsAssertion = manifest.assertions.find(
a => a.label === 'c2pa.actions'
);
return actionsAssertion?.data.actions || [];
}
extractAIUsage(manifest) {
const aiAssertion = manifest.assertions.find(
a => a.label === 'c2pa.ai.generative'
);
return aiAssertion?.data || { ai_used: false };
}
}
One of C2PA's most powerful features is the ability to track content through multiple editing steps. Each edit creates a new manifest that references the previous version as an ingredient.
async function updateAuthenticatedContent(file, edits, metadata) {
// Read existing manifest
const existingManifest = await c2pa.read(file);
// Apply edits to content
const editedFile = await applyEdits(file, edits);
// Create new manifest
const newManifest = new ManifestBuilder()
.setTitle(metadata.title || existingManifest['dc:title'])
.setFormat(getMediaType(editedFile))
.setClaimGenerator('WIA-AI-017/1.0');
// Add the original as an ingredient
newManifest.addIngredient({
title: existingManifest['dc:title'],
format: existingManifest['dc:format'],
instance_id: existingManifest.instance_id,
document_id: existingManifest.instance_id,
relationship: 'parentOf',
manifest: existingManifest
});
// Add edit actions
edits.forEach(edit => {
newManifest.addAction({
action: edit.action,
when: new Date().toISOString(),
softwareAgent: edit.software,
parameters: edit.parameters
});
});
// Add content hash
const contentHash = await hashContent(editedFile);
newManifest.addAssertion('c2pa.hash.data', {
name: 'jumbf manifest',
alg: 'sha256',
hash: contentHash
});
// Sign and embed
const signed = await signManifest(newManifest.build());
return await c2pa.embed(editedFile, signed);
}
A critical challenge for content authentication is maintaining manifests across different platforms and formats. C2PA addresses this through standardized embedding methods for various media types.
| Media Type | Container Format | Embedding Method |
|---|---|---|
| JPEG Images | JUMBF (JPEG Universal Metadata Box Format) | APP11 marker segment |
| PNG Images | JUMBF | iTXt chunk |
| MP4 Video | JUMBF | uuid box in moov atom |
| WebP | JUMBF | EXIF chunk |
| WAV Audio | RIFF | LIST INFO chunk |
| PDF Documents | PDF Objects | Metadata stream |
Beyond basic provenance tracking, C2PA supports several advanced features for specific use cases:
Privacy-sensitive information can be redacted from public manifests while maintaining verifiability:
// Create manifest with redactable assertions
const manifest = new ManifestBuilder()
.addAssertion('c2pa.location', {
latitude: 37.7749,
longitude: -122.4194
}, { redactable: true })
.addAssertion('c2pa.actions', actions);
// Later, create redacted version
const redacted = manifest.redact(['c2pa.location']);
// Verification still works, but location is hidden
const verified = await verifier.verify(redacted);
// verified.redacted_assertions = ['c2pa.location']
Complex content creation often involves multiple source materials. C2PA supports rich relationship modeling:
C2PA's security relies on a web of trust built from certificate authorities, hardware attestation, and organizational reputation.
While WIA-AI-017 fully supports standard C2PA manifests, it also defines extensions for AI-specific use cases:
// WIA-AI-017 specific assertions
{
"label": "wia.ai.training",
"data": {
"model_name": "GPT-4",
"training_data_sources": [
"Common Crawl",
"WebText",
"Books3"
],
"training_cutoff": "2023-04",
"fine_tuning": "RLHF"
}
}
{
"label": "wia.ai.inference",
"data": {
"prompt": "A mountain landscape at sunset",
"seed": 42,
"guidance_scale": 7.5,
"steps": 50,
"model_version": "2.1"
}
}
{
"label": "wia.deepfake_detection",
"data": {
"detector": "WIA-Deepfake-Detector-v3",
"confidence": 0.98,
"is_synthetic": false,
"detection_time": "2025-12-25T12:00:00Z"
}
}
Thorough testing is essential to ensure C2PA implementation correctness:
import { expect } from 'chai';
import { C2PA_Integrator } from './c2pa';
describe('C2PA Integration Tests', () => {
it('creates valid manifest for image', async () => {
const image = readFileSync('test.jpg');
const metadata = {
title: 'Test Image',
creator: 'Test Suite'
};
const authenticated = await integrator.createAuthenticatedContent(
image,
metadata
);
const manifest = await c2pa.read(authenticated);
expect(manifest).to.exist;
expect(manifest['dc:title']).to.equal('Test Image');
});
it('verifies signature correctly', async () => {
const result = await verifier.verifyContent(authenticatedImage);
expect(result.verified).to.be.true;
expect(result.error).to.be.undefined;
});
it('detects manifest tampering', async () => {
const tampered = await modifyManifest(authenticatedImage);
const result = await verifier.verifyContent(tampered);
expect(result.verified).to.be.false;
});
it('preserves manifests through edits', async () => {
const edited = await updateAuthenticatedContent(
authenticatedImage,
[{ action: 'c2pa.cropped', parameters: {} }]
);
const manifest = await c2pa.read(edited);
expect(manifest.ingredients).to.have.length(1);
expect(manifest.assertions.find(
a => a.label === 'c2pa.actions'
).data.actions).to.have.length.at.least(2);
});
});
弘益人間 (홍익인간) · Benefit All Humanity