CHAPTER 05

Provenance and Chain of Custody

Build complete audit trails for content lifecycle tracking

Understanding Content Provenance

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?

Provenance Data Elements

C2PA Manifests

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;
}
            

Action History Tracking

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-Based Provenance

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
        };
    }
}
            

Ingredient Tracking

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;
}
            

Redaction and Privacy

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('|')
        };
    }
}
            

Cross-Platform Provenance

Content moves across platforms. Provenance must be preserved during transfers:

ℹ️ Platform Challenges
Social media platforms often strip metadata. WIA-AI-017 recommends: (1) Embed watermarks alongside manifests, (2) Link to external provenance databases, (3) Use platform APIs to preserve metadata when available.

Temporal Integrity

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')
        }
    };
}
            

Provenance Visualization

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;
    }
}
            

πŸ“Œ Key Takeaways

πŸ“ Review Questions

  1. What are the five essential data elements in content provenance?
  2. How does C2PA structure provenance information?
  3. Why is blockchain useful for provenance anchoring?
  4. Explain the concept of "ingredients" in C2PA manifests.
  5. How does selective redaction preserve privacy while maintaining verifiability?
  6. What role do RFC 3161 timestamp authorities play?
  7. Why is cross-platform provenance preservation challenging?
  8. How can provenance graphs help users understand content history?

εΌ˜η›ŠδΊΊι–“ (홍읡인간) Β· Benefit All Humanity