Build complete audit trails for content lifecycle tracking
Provenance records the complete history of content from creation through all modifications to final consumption. It answers critical questions: Who created this? When? What tools were used? Has it been edited? By whom?
The Coalition for Content Provenance and Authenticity (C2PA) defines the standard format for provenance metadata.
// Example: C2PA Manifest Structure
import { createManifest, addAssertion, signManifest } from 'c2pa';
async function createC2PAManifest(content, metadata) {
const manifest = {
claim_generator: 'WIA-AI-017 Authenticator/1.0',
claim_generator_info: [{
name: 'WIA Content Auth',
version: '1.0.0'
}],
title: metadata.title,
format: metadata.format,
instance_id: generateUUID(),
assertions: [
// Creation assertion
{
label: 'c2pa.actions',
data: {
actions: [{
action: 'c2pa.created',
when: metadata.timestamp,
softwareAgent: metadata.ai_model,
parameters: {
model: metadata.ai_model,
prompt: metadata.prompt,
settings: metadata.settings
}
}]
}
},
// AI generation assertion
{
label: 'c2pa.ai_generative_training',
data: {
use: {
ai_generative: true,
ai_training: false
},
entries: [{
name: metadata.ai_model,
version: metadata.model_version
}]
}
},
// Creator assertion
{
label: 'stds.schema-org.CreativeWork',
data: {
author: [{
'@type': 'Person',
name: metadata.creator,
identifier: metadata.creator_did
}],
dateCreated: metadata.timestamp
}
},
// Hash assertion
{
label: 'c2pa.hash.data',
data: {
hash: computeSHA256(content),
alg: 'sha256'
}
}
],
signature_info: {
alg: 'es256',
issuer: 'WIA Certificate Authority'
}
};
// Sign the manifest
const signed = await signManifest(manifest, privateKey);
return signed;
}
Every content modification must be recorded in the provenance chain:
// Example: Tracking Content Actions
class ProvenanceTracker {
constructor() {
this.actions = [];
}
recordAction(action, params) {
const record = {
action: action,
timestamp: new Date().toISOString(),
actor: params.actor || 'system',
software: params.software,
parameters: params.parameters || {},
instanceId: generateUUID()
};
this.actions.push(record);
return record;
}
async recordCreation(metadata) {
return this.recordAction('c2pa.created', {
actor: metadata.creator,
software: metadata.tool,
parameters: {
ai_model: metadata.ai_model,
prompt: metadata.prompt,
seed: metadata.seed,
dimensions: metadata.dimensions
}
});
}
async recordEdit(editType, params) {
const actionMap = {
crop: 'c2pa.cropped',
resize: 'c2pa.resized',
filter: 'c2pa.filtered',
color: 'c2pa.color_adjustments',
retouch: 'c2pa.edited'
};
return this.recordAction(actionMap[editType], {
actor: params.user,
software: params.tool,
parameters: params.changes
});
}
async recordAIEnhancement(model, params) {
return this.recordAction('c2pa.ai_inference', {
software: model,
parameters: {
enhancement_type: params.type,
model_version: params.version,
confidence: params.confidence
}
});
}
getActionChain() {
return {
total_actions: this.actions.length,
first_action: this.actions[0],
last_action: this.actions[this.actions.length - 1],
complete_chain: this.actions
};
}
exportC2PA() {
return {
label: 'c2pa.actions',
data: {
actions: this.actions
}
};
}
}
// Usage
const tracker = new ProvenanceTracker();
await tracker.recordCreation({
creator: 'Alice Smith',
tool: 'DALL-E 3',
ai_model: 'dall-e-3',
prompt: 'A serene mountain landscape at sunset',
seed: 42,
dimensions: { width: 1024, height: 1024 }
});
await tracker.recordEdit('crop', {
user: 'Alice Smith',
tool: 'Photoshop 2025',
changes: {
x: 100,
y: 50,
width: 800,
height: 800
}
});
const chain = tracker.getActionChain();
console.log('Action chain:', chain);
Blockchain provides immutable, decentralized provenance records:
// Example: Blockchain Provenance Anchoring
import { createHash } from 'crypto';
class BlockchainProvenance {
constructor(networkUrl) {
this.network = networkUrl;
}
async anchorProvenance(manifest) {
// Create Merkle root of all assertions
const merkleRoot = this.computeMerkleRoot(manifest.assertions);
// Create blockchain transaction
const transaction = {
type: 'PROVENANCE_ANCHOR',
data: {
manifest_hash: computeSHA256(JSON.stringify(manifest)),
merkle_root: merkleRoot,
timestamp: new Date().toISOString(),
content_id: manifest.instance_id
}
};
// Submit to blockchain
const txHash = await this.submitTransaction(transaction);
// Create proof
const proof = {
blockchain: 'Ethereum',
network: 'mainnet',
transaction_hash: txHash,
block_number: await this.getBlockNumber(txHash),
merkle_root: merkleRoot,
timestamp: transaction.data.timestamp
};
return proof;
}
computeMerkleRoot(assertions) {
const leaves = assertions.map(a =>
createHash('sha256').update(JSON.stringify(a)).digest()
);
return this.buildMerkleTree(leaves);
}
buildMerkleTree(leaves) {
if (leaves.length === 1) return leaves[0].toString('hex');
const nextLevel = [];
for (let i = 0; i < leaves.length; i += 2) {
const left = leaves[i];
const right = leaves[i + 1] || left;
const combined = Buffer.concat([left, right]);
const hash = createHash('sha256').update(combined).digest();
nextLevel.push(hash);
}
return this.buildMerkleTree(nextLevel);
}
async verifyProof(manifest, proof) {
// Recompute Merkle root
const computedRoot = this.computeMerkleRoot(manifest.assertions);
// Verify on blockchain
const onChain = await this.queryBlockchain(proof.transaction_hash);
return {
valid: computedRoot === proof.merkle_root &&
onChain.merkle_root === proof.merkle_root,
merkle_match: computedRoot === proof.merkle_root,
blockchain_match: onChain.merkle_root === proof.merkle_root,
block_confirmed: onChain.confirmations > 6
};
}
}
C2PA supports tracking content ingredients - assets combined to create the final content:
// Example: Tracking Content Ingredients
class IngredientTracker {
constructor() {
this.ingredients = [];
}
addIngredient(ingredientManifest) {
const ingredient = {
instance_id: ingredientManifest.instance_id,
title: ingredientManifest.title,
format: ingredientManifest.format,
relationship: 'componentOf',
hash: ingredientManifest.assertions.find(
a => a.label === 'c2pa.hash.data'
)?.data.hash,
thumbnail: ingredientManifest.thumbnail
};
this.ingredients.push(ingredient);
return ingredient;
}
createIngredientAssertion() {
return {
label: 'c2pa.ingredient',
data: {
ingredients: this.ingredients.map(ing => ({
...ing,
validation_status: [{
code: 'claimSignature.validated',
url: ing.instance_id
}]
}))
}
};
}
}
// Example: Composite content with multiple ingredients
async function createCompositeContent(baseImage, overlays, metadata) {
const tracker = new IngredientTracker();
// Add base image as ingredient
const baseManifest = await loadC2PAManifest(baseImage);
tracker.addIngredient(baseManifest);
// Add overlays
for (const overlay of overlays) {
const overlayManifest = await loadC2PAManifest(overlay);
tracker.addIngredient(overlayManifest);
}
// Create new manifest for composite
const compositeManifest = {
claim_generator: 'WIA Compositor/1.0',
title: metadata.title,
assertions: [
{
label: 'c2pa.actions',
data: {
actions: [{
action: 'c2pa.edited',
when: new Date().toISOString(),
softwareAgent: 'Compositor 1.0',
parameters: {
ingredients: tracker.ingredients.length
}
}]
}
},
tracker.createIngredientAssertion()
]
};
return compositeManifest;
}
Provenance must balance transparency with privacy. WIA-AI-017 supports selective redaction:
// Example: Selective Redaction
class ProvenanceRedactor {
constructor(manifest) {
this.manifest = manifest;
this.redactions = [];
}
redactAssertion(label, reason) {
const assertion = this.manifest.assertions.find(a => a.label === label);
if (assertion) {
const hash = createHash('sha256')
.update(JSON.stringify(assertion))
.digest('hex');
this.redactions.push({
label: label,
hash: hash,
reason: reason,
redacted_at: new Date().toISOString()
});
// Replace assertion with redacted placeholder
const index = this.manifest.assertions.indexOf(assertion);
this.manifest.assertions[index] = {
label: 'c2pa.redacted',
data: {
original_label: label,
hash: hash,
reason: reason
}
};
}
}
redactPersonalData() {
// Remove GPS coordinates
this.redactAssertion('stds.exif', 'Privacy: Location removed');
// Remove creator identity
this.redactAssertion('stds.schema-org.CreativeWork', 'Privacy: Creator anonymous');
}
createRedactionProof() {
return {
redaction_count: this.redactions.length,
redactions: this.redactions,
proof: this.redactions.map(r => r.hash).join('|')
};
}
}
Content moves across platforms. Provenance must be preserved during transfers:
Timestamps must be trustworthy. RFC 3161 timestamp authorities provide cryptographic proof of when content was created:
// Example: Trusted Timestamping
async function addTrustedTimestamp(manifest) {
const manifestHash = createHash('sha256')
.update(JSON.stringify(manifest))
.digest();
// Request timestamp from TSA
const tsaResponse = await fetch('https://freetsa.org/tsr', {
method: 'POST',
headers: { 'Content-Type': 'application/timestamp-query' },
body: createTimestampRequest(manifestHash)
});
const timestampToken = await tsaResponse.arrayBuffer();
// Parse and verify timestamp
const timestamp = parseTimestampToken(timestampToken);
return {
label: 'c2pa.timestamp',
data: {
time: timestamp.genTime,
tsa: timestamp.tsa,
policy: timestamp.policy,
token: Buffer.from(timestampToken).toString('base64')
}
};
}
Complex provenance chains need clear visualization for users:
// Example: Provenance Graph Generation
class ProvenanceVisualizer {
constructor(manifest) {
this.manifest = manifest;
}
generateGraph() {
const nodes = [];
const edges = [];
// Root node (current content)
nodes.push({
id: this.manifest.instance_id,
label: this.manifest.title,
type: 'current',
timestamp: this.getCreationTime()
});
// Add ingredients as nodes
const ingredients = this.getIngredients();
for (const ing of ingredients) {
nodes.push({
id: ing.instance_id,
label: ing.title,
type: 'ingredient'
});
edges.push({
from: ing.instance_id,
to: this.manifest.instance_id,
label: 'used in'
});
}
// Add action nodes
const actions = this.getActions();
actions.forEach((action, idx) => {
const nodeId = `action_${idx}`;
nodes.push({
id: nodeId,
label: action.action,
type: 'action',
timestamp: action.when
});
if (idx === 0) {
edges.push({
from: nodeId,
to: this.manifest.instance_id,
label: 'created'
});
} else {
edges.push({
from: `action_${idx-1}`,
to: nodeId,
label: 'then'
});
}
});
return {
nodes: nodes,
edges: edges,
format: 'graphviz'
};
}
exportDOT() {
const graph = this.generateGraph();
let dot = 'digraph Provenance {\n';
for (const node of graph.nodes) {
const color = node.type === 'current' ? 'green' :
node.type === 'ingredient' ? 'blue' : 'orange';
dot += ` "${node.id}" [label="${node.label}" color="${color}"];\n`;
}
for (const edge of graph.edges) {
dot += ` "${edge.from}" -> "${edge.to}" [label="${edge.label}"];\n`;
}
dot += '}';
return dot;
}
}
εΌηδΊΊι (νμ΅μΈκ°) Β· Benefit All Humanity