Adding and signing a manifest
- JavaScript
- Python
- Node.js
- C++
- Rust
Notes:
- You provide the
Signer. In production, this object wraps a service/HSM that returns a proper signature for your algorithm (es256,ps256,ed25519, etc.). SetreserveSizeto a value large enough for timestamps/countersignatures your signer adds. - To attach a remote manifest instead of embedding, use
builder.setRemoteUrl(url)andbuilder.setNoEmbed(true)before signing.
import { createC2pa, type Signer } from '@contentauth/c2pa-web';
import wasmSrc from '@contentauth/c2pa-web/resources/c2pa.wasm?url';
// 1) Create a Signer that calls your backend (which returns the signature bytes)
function createRemoteSigner(): Signer {
return {
alg: 'es256',
reserveSize: async () => 4096, // bytes to reserve for TSA/countersignature (tune as needed)
sign: async (toBeSigned: Uint8Array, _reserveSize: number) => {
const res = await fetch('/api/sign', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
alg: 'es256',
payload: Array.from(toBeSigned),
}),
});
if (!res.ok) throw new Error('Signing failed');
const sigBytes = new Uint8Array(await res.arrayBuffer());
return sigBytes;
},
};
}
async function run() {
// 2) Initialize the SDK
const c2pa = await createC2pa({ wasmSrc });
// 3) Fetch the asset to sign
const imgUrl = 'https://contentauth.github.io/example-assets/images/cloudscape-ACA-Cr.jpeg';
const resp = await fetch(imgUrl);
const assetBlob = await resp.blob();
// 4) Build a simple manifest (add a created action and optional thumbnail)
const builder = await c2pa.builder.new();
await builder.addAction({
action: 'c2pa.created',
digitalSourceType: 'http://cv.iptc.org/newscodes/digitalsourcetype/digitalCapture',
});
// Optional: include a thumbnail that represents the asset
await builder.setThumbnailFromBlob('image/jpeg', assetBlob);
// 5) Sign and get a new asset with the manifest embedded
const signer = createRemoteSigner();
const signedBytes = await builder.sign(signer, assetBlob.type, assetBlob);
// 6) Use/save the signed asset
const signedBlob = new Blob([signedBytes], { type: assetBlob.type });
const url = URL.createObjectURL(signedBlob);
// e.g., download
const a = document.createElement('a');
a.href = url;
a.download = 'signed.jpg';
a.click();
URL.revokeObjectURL(url);
// Cleanup
await builder.free();
c2pa.dispose();
}
run().catch(console.error);
To retrieve manifest bytes alongside the signed asset:
const { asset, manifest } = await builder.signAndGetManifestBytes(
signer,
assetBlob.type,
assetBlob
);
// asset -> signed asset bytes
// manifest -> embedded manifest bytes
This is an example of how to assign a manifest to an asset and sign the claim using Python.
Use a Builder object to add a manifest to an asset.
# Import the C2PA Python package.
from c2pa import *
# Import standard general-purpose packages.
import os
import io
import logging
import json
import base64
try:
# Define a function to sign the claim bytes.
# In this case we are using a pre-defined sign_ps256 method, passing in our private cert.
# Normally this cert would be kept safe in some other location.
def private_sign(data: bytes) -> bytes:
return sign_ps256(data, "tests/fixtures/ps256.pem")
# Read our public certs into memory.
certs = open(data_dir + "ps256.pub", "rb").read()
# Create a signer from the private signer, certs and a time stamp service URL.
signer = create_signer(private_sign, SigningAlg.PS256, certs, "http://timestamp.digicert.com")
# Create a builder add a thumbnail resource and an ingredient file.
builder = Builder(manifest_json)
# Add the resource from a stream.
a_thumbnail_jpg_stream = open("tests/fixtures/A_thumbnail.jpg", "rb")
builder.add_resource("image/jpeg", a_thumbnail_jpg_stream)
# Add the resource from a file.
# The URI provided here, "thumbnail", must match an identifier in the manifest definition.
builder.add_resource_file("thumbnail", "tests/fixtures/A_thumbnail.jpg")
# Define an ingredient, in this case a parent ingredient named A.jpg, with a thumbnail.
ingredient_json = {
"title": "A.jpg",
"relationship": "parentOf", # "parentOf", "componentOf" or "inputTo"
"thumbnail": {
"identifier": "thumbnail",
"format": "image/jpeg"
}
}
# Add the ingredient from a stream.
a_jpg_stream = open("tests/fixtures/A.jpg", "rb")
builder.add_ingredient("image/jpeg", a_jpg_stream)
# At this point archive or unarchive Builder to continue later.
# This example uses a bytearray for the archive stream.
# All ingredients and resources are saved in the archive.
archive = io.BytesIO(bytearray())
builder.to_archive(archive)
archive.seek()
builder = builder.from_archive(archive)
# Sign the builder with a stream and output it to a stream.
# This returns the binary manifest data that could be uploaded to cloud storage.
input_stream = open("tests/fixtures/A.jpg", "rb")
output_stream = open("target/out.jpg", "wb")
c2pa_data = builder.sign(signer, "image/jpeg", input_stream, output_stream)
except Exception as err:
print(err)
This is an example of how to assign a manifest to an asset and sign the claim using Node.js:
import { Builder } from '@contentauth/c2pa-node';
// Create a new builder
const builder = Builder.new();
// Create with custom settings
const settings = {
builder: {
generate_c2pa_archive: true
}
};
const builder = Builder.new(settings);
// Or create from an existing manifest definition
const builder = Builder.withJson(manifestDefinition);
// Or create with both manifest and settings
const builder = Builder.withJson(manifestDefinition, settings);
// Add assertions to the manifest
builder.addAssertion('c2pa.actions', actionsAssertion);
// Add resources
await builder.addResource('resource://example', resourceAsset);
// Sign the manifest
const manifest = builder.sign(signer, inputAsset, outputAsset);
Use the c2pa.sign() method to sign an ingredient, either locally if you have a signing certificate and key available, or by using a remote signing API.
Signing a stream
If you have an asset file's data loaded into a stream, you can use it to sign the asset
NOTE: Signing using a stream is currently supported only for image/jpeg and image/png data. For all other file types, use the file-based approach .
import { readFile } from 'node:fs/promises';
import { createC2pa, createTestSigner } from 'c2pa-node';
// read an asset into a buffer
const buffer = await readFile('to-be-signed.jpg');
const asset: Asset = { buffer, mimeType: 'image/jpeg' };
// build a manifest to use for signing
const manifest = new ManifestBuilder(
{
claim_generator: 'my-app/1.0.0',
format: 'image/jpeg',
title: 'buffer_signer.jpg',
assertions: [
{
label: 'c2pa.actions',
data: {
actions: [
{
action: 'c2pa.created',
},
],
},
},
{
label: 'com.custom.my-assertion',
data: {
description: 'My custom test assertion',
version: '1.0.0',
},
},
],
},
{ vendor: 'cai' },
);
// create a signing function
async function sign(asset, manifest) {
const signer = await createTestSigner();
const c2pa = createC2pa({
signer,
});
const { signedAsset, signedManifest } = await c2pa.sign({
asset,
manifest,
});
}
// sign
await sign(asset, manifest);
Remote signing
If you have access to a web service that performs signing, you can use it to sign remotely; for example:
import { readFile } from 'node:fs/promises';
import { fetch, Headers } from 'node-fetch';
import { createC2pa, SigningAlgorithm } from 'c2pa-node';
function createRemoteSigner() {
return {
type: 'remote',
async reserveSize() {
const url = `https://my.signing.service/box-size`;
const res = await fetch(url);
const data = (await res.json()) as { boxSize: number };
return data.boxSize;
},
async sign({ reserveSize, toBeSigned }) {
const url = `https://my.signing.service/sign?boxSize=${reserveSize}`;
const res = await fetch(url, {
method: 'POST',
headers: new Headers({
'Content-Type': 'application/octet-stream',
}),
body: toBeSigned,
});
return res.buffer();
},
};
}
async function sign(asset, manifest) {
const signer = createRemoteSigner();
const c2pa = createC2pa({
signer,
});
const { signedAsset, signedManifest } = await c2pa.sign({
asset,
manifest,
});
}
const buffer = await readFile('to-be-signed.jpg');
const asset: Asset = { buffer, mimeType: 'image/jpeg' };
const manifest = new ManifestBuilder(
{
claim_generator: 'my-app/1.0.0',
format: 'image/jpeg',
title: 'buffer_signer.jpg',
assertions: [
{
label: 'c2pa.actions',
data: {
actions: [
{
action: 'c2pa.created',
},
],
},
},
{
label: 'com.custom.my-assertion',
data: {
description: 'My custom test assertion',
version: '1.0.0',
},
},
],
},
{ vendor: 'cai' },
);
await sign(asset, manifest);
This is an example of how to assign a manifest to an asset and sign the claim using C++:
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <stdexcept>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include "c2pa.hpp"
#include "test_signer.hpp"
using namespace std;
namespace fs = std::filesystem;
using namespace c2pa;
const std::string manifest_json = R"{
"claim_generator": "c2pa_c_test/0.1",
"claim_generator_info": [
{
"name": "c2pa-c example",
"version": "0.1"
}
],
"assertions": [
{
"label": "cawg.training-mining",
"data": {
"entries": {
"cawg.ai_generative_training": { "use": "notAllowed" },
"cawg.ai_inference": { "use": "notAllowed" },
"cawg.ai_training": { "use": "notAllowed" },
"cawg.data_mining": { "use": "notAllowed" }
}
}
}
]
};
auto builder = Builder(manifest_json);
This is an example of how to assign a manifest to an asset and sign the claim using Rust.
This example is from c2pa-rs/sdk/examples/v2api.rs:
use std::io::{Cursor, Seek};
use anyhow::Result;
use c2pa::{
crypto::raw_signature::SigningAlg, settings::Settings, validation_results::ValidationState,
Builder, CallbackSigner, Reader,
};
use serde_json::json;
let json = manifest_def(title, format);
let mut builder = Builder::from_json(&json)?;
builder.add_ingredient_from_stream(
json!({
"title": parent_name,
"relationship": "parentOf"
})
.to_string(),
format,
&mut source,
)?;
let thumb_uri = builder
.definition
.thumbnail
.as_ref()
.map(|t| t.identifier.clone());
// add a manifest thumbnail ( just reuse the image for now )
if let Some(uri) = thumb_uri {
if !uri.starts_with("self#jumbf") {
source.rewind()?;
builder.add_resource(&uri, &mut source)?;
}
}
// write the manifest builder to a zipped stream
let mut zipped = Cursor::new(Vec::new());
builder.to_archive(&mut zipped)?;
// unzip the manifest builder from the zipped stream
zipped.rewind()?;
let ed_signer =
|_context: *const (), data: &[u8]| CallbackSigner::ed25519_sign(data, PRIVATE_KEY);
let signer = CallbackSigner::new(ed_signer, SigningAlg::Ed25519, CERTS);
let mut builder = Builder::from_archive(&mut zipped)?;
// sign the ManifestStoreBuilder and write it to the output stream
let mut dest = Cursor::new(Vec::new());
builder.sign(&signer, format, &mut source, &mut dest)?;