Skip to content

Latest commit

 

History

History
1014 lines (748 loc) · 36.4 KB

File metadata and controls

1014 lines (748 loc) · 36.4 KB

Configuring the SDK with Context and Settings

This guide explains how to configure the C2PA SDK using Context and Settings. The configuration controls SDK behavior including verification, trust anchors, thumbnails, signing, and more.

See also:

Quick start

The simplest way to configure the SDK is to create a Context with inline JSON and pass it to Reader or Builder:

#include "c2pa.hpp"

// Create a Context with settings
c2pa::Context context(R"({
  "version": 1,
  "builder": {
    "claim_generator_info": {"name": "My App", "version": "1.0"},
    "thumbnail": {"enabled": false}
  },
  "verify": {
    "remote_manifest_fetch": false
  }
})");

// Use with Reader or Builder
c2pa::Reader reader(context, "image.jpg");
c2pa::Builder builder(context, manifest_json);

For default SDK configuration, just create an empty Context:

c2pa::Context context;  // Uses SDK defaults
c2pa::Reader reader(context, "image.jpg");

Understanding Context and Settings

What is Context?

Context encapsulates SDK configuration that controls how Reader, Builder, and other components operate:

  • Settings: Trust configuration, builder behavior, thumbnails, and more
  • Signer configuration: Optional signing credentials that can be stored for reuse
  • State isolation: Each Context is independent, allowing multiple configurations to coexist

Why use Context?

Context provides explicit, isolated configuration:

  • Makes dependencies explicit: Configuration is passed directly to Reader and Builder, not hidden in global state
  • Enables multiple configurations: Run different configurations simultaneously (e.g., development with test certificates, production with strict validation)
  • Eliminates thread-local state: No subtle bugs from shared state
  • Simplifies testing: Isolated configurations without cleanup or interference
  • Improves code clarity: Builder(context, manifest) shows configuration is being used

Note

The deprecated c2pa::load_settings(data, format) still works but you should migrate to Context. Never mix the two approaches. See Migrating from deprecated APIS.

Context lifecycle

  • Non-copyable, moveable: Context can be moved but not copied. After moving, is_valid() returns false on the source
  • Used at construction: Reader and Builder copy configuration from the context at construction time. The Context doesn't need to outlive them
  • Reusable: Use the same Context to create multiple readers and builders
c2pa::Context context(settings);

// All three use the same configuration
c2pa::Builder builder1(context, manifest1);
c2pa::Builder builder2(context, manifest2);
c2pa::Reader reader(context, "image.jpg");

// Context can go out of scope, readers/builders still work

Settings format

Settings use JSON or TOML format. The schema is shared across all SDK languages (Rust, C/C++, Python). JSON is preferred for C++.

// JSON (preferred)
c2pa::Context context(R"({"verify": {"verify_after_sign": true}})");

// TOML
c2pa::Settings settings(R"([verify]
verify_after_sign = true)", "toml");
c2pa::Context context(settings);

Creating a Context

There are several ways to create a Context:

Use SDK defaults

For quick prototyping and simple use cases, you can use the SDK defaults like this:

c2pa::Context context;  // Uses SDK defaults

For information on the defaults, see Configuring SDK settings - Default configuration.

Create from inline JSON

To specify a simple configuration that doesn't need to be shared across the codebase, you can use inline JSON like this:

c2pa::Context context(R"({
  "version": 1,
  "verify": {"verify_after_sign": true},
  "builder": {"claim_generator_info": {"name": "My App"}}
})");

Create from a Settings object

To specify a configuration that needs runtime logic or incremental construction, use a Settings object like this:

c2pa::Settings settings;
settings.set("builder.thumbnail.enabled", "false");
settings.set("verify.verify_after_sign", "true");
settings.update(R"({"builder": {"claim_generator_info": {"name": "My App"}}})");

c2pa::Context context(settings);

Create using ContextBuilder

To load a configuration from files or combine multiple configuration sources, use ContextBuilder. Don't use if you have a single configuration source, since direct construction is simpler.

c2pa::Settings base_settings;
base_settings.set("builder.thumbnail.enabled", "true");

auto context = c2pa::Context::ContextBuilder()
    .with_settings(base_settings)
    .with_json(R"({"verify": {"verify_after_sign": true}})")
    .with_json_settings_file("config/overrides.json")
    .create_context();

Important

Later configuration overrides earlier configuration. In the example above, overrides.json will override values from base_settings and the inline JSON.

ContextBuilder methods:

Method Description
with_settings(settings) Apply a Settings object
with_json(json_string) Apply settings from a JSON string
with_json_settings_file(path) Load and apply settings from a JSON file
with_signer(signer) Store a Signer in the context (consumed; used by Builder::sign with no explicit signer)
with_progress_callback(callback) Register a progress/cancel callback (see Progress callbacks and cancellation)
create_context() Build and return the Context (consumes the builder)

Progress callbacks and cancellation

You can register a callback on a Context to receive progress notifications during signing and reading operations, and to cancel an operation in flight.

Registering a callback

Use ContextBuilder::with_progress_callback to attach a callback before building the context:

#include <atomic>

std::atomic<int> phase_count{0};

auto context = c2pa::Context::ContextBuilder()
    .with_progress_callback([&](c2pa::ProgressPhase phase, uint32_t step, uint32_t total) {
        ++phase_count;
        // Return true to continue, false to cancel.
        return true;
    })
    .create_context();

// Use the context normally — the callback fires automatically.
c2pa::Builder builder(context, manifest_json);
builder.sign("source.jpg", "output.jpg", signer);

The callback signature is:

bool callback(c2pa::ProgressPhase phase, uint32_t step, uint32_t total);
  • phase — which stage the SDK is in (see ProgressPhase values below).
  • step — monotonically increasing counter within the current phase, starting at 1. Resets to 1 at the start of each new phase. Use as a liveness signal: a rising step means the SDK is making forward progress.
  • total0 = indeterminate (show a spinner); 1 = single-shot phase; > 1 = determinate (step / total gives a completion fraction).
  • Return value — return true to continue, false to request cancellation (same effect as calling context.cancel()).

Do not throw from the progress callback. Exceptions cannot cross the C/Rust boundary safely; if your callback throws, the wrapper catches it and the operation is aborted as a cancellation (you do not get your exception back at the call site). Use return false, context.cancel(), or application-side state instead.

Cancelling from another thread

You may call Context::cancel() from another thread while the same Context remains valid and is not being destroyed or moved concurrently with that call. The SDK returns a C2paException with an OperationCancelled error at the next progress checkpoint:

#include <thread>

auto context = c2pa::Context::ContextBuilder()
    .with_progress_callback([](c2pa::ProgressPhase, uint32_t, uint32_t) {
        return true;  // Don't cancel from the callback — use cancel() instead.
    })
    .create_context();

// Kick off a cancel after 500 ms from a background thread.
std::thread cancel_thread([&context]() {
    std::this_thread::sleep_for(std::chrono::milliseconds(500));
    context.cancel();
});

try {
    c2pa::Builder builder(context, manifest_json);
    builder.sign("large_file.jpg", "output.jpg", signer);
} catch (const c2pa::C2paException& e) {
    // "OperationCancelled" if cancel() fired before signing completed.
}

cancel_thread.join();

cancel() is safe to call when no operation is in progress — it is a no-op in that case (and a no-op if the Context is moved-from).

ProgressPhase values

Phase When emitted
Reading Start of a read/verification pass
VerifyingManifest Manifest structure is being validated
VerifyingSignature COSE signature is being verified
VerifyingIngredient An ingredient manifest is being verified
VerifyingAssetHash Asset hash is being computed and checked
AddingIngredient An ingredient is being embedded
Thumbnail A thumbnail is being generated
Hashing Asset data is being hashed for signing
Signing Claim is being signed
Embedding Signed manifest is being embedded into the asset
FetchingRemoteManifest A remote manifest URL is being fetched
Writing Output is being written
FetchingOCSP OCSP certificate status is being fetched
FetchingTimestamp A timestamp is being fetched from a TSA

Typical phase sequence during signing:

AddingIngredient → Thumbnail → Hashing → Signing → Embedding

If verify_after_sign is enabled, verification phases follow:

→ VerifyingManifest → VerifyingSignature → VerifyingAssetHash → VerifyingIngredient

Typical phase sequence during reading:

Reading → VerifyingManifest → VerifyingSignature → VerifyingAssetHash → VerifyingIngredient

Combining with other settings

with_progress_callback chains with other ContextBuilder methods:

auto context = c2pa::Context::ContextBuilder()
    .with_settings(settings)
    .with_signer(std::move(signer))
    .with_progress_callback([](c2pa::ProgressPhase phase, uint32_t step, uint32_t total) {
        // Update a UI progress bar, log phases, etc.
        return true;
    })
    .create_context();

Common configuration patterns

Common configurations include:

Development with test certificates

Trust self-signed or custom CA certificates during development:

std::string test_ca = read_file("test-ca.pem");

c2pa::Context dev_context(R"({
  "version": 1,
  "trust": {
    "user_anchors": ")" + test_ca + R"("
  },
  "verify": {
    "remote_manifest_fetch": false,
    "ocsp_fetch": false
  },
  "builder": {
    "claim_generator_info": {"name": "Dev Build", "version": "dev"},
    "thumbnail": {"enabled": false}
  }
})");

Layered configuration

Load base configuration and apply environment-specific overrides:

auto context = c2pa::Context::ContextBuilder()
    .with_json_settings_file("config/base.json")
    .with_json_settings_file("config/" + environment + ".json")
    .with_json(R"({
      "builder": {
        "claim_generator_info": {"version": ")" + app_version + R"("}
      }
    })")
    .create_context();

Configuration from environment variables

Adapt configuration based on runtime environment:

std::string env = std::getenv("ENVIRONMENT") ? std::getenv("ENVIRONMENT") : "dev";

c2pa::Settings settings;
if (env == "production") {
    settings.update(read_file("config/production.json"), "json");
    settings.set("verify.strict_v1_validation", "true");
} else {
    settings.update(read_file("config/development.json"), "json");
    settings.set("verify.remote_manifest_fetch", "false");
}

c2pa::Context context(settings);

Multiple contexts

Use different Context objects for different purposes:

c2pa::Context dev_context(dev_settings);
c2pa::Context prod_context(prod_settings);

c2pa::Builder dev_builder(dev_context, manifest);
c2pa::Builder prod_builder(prod_context, manifest);

Temporary contexts

Since configuration is copied at construction, you can use temporary contexts:

c2pa::Reader reader(
    c2pa::Context(R"({"verify": {"remote_manifest_fetch": false}})"),
    "image.jpg"
);

Using Context with Reader

Reader uses Context to control validation, trust configuration, network access, and performance.

Important

Context is used only at construction. Reader copies the configuration it needs internally, so the Context doesn't need to outlive the Reader.

Reading from a file

c2pa::Context context(R"({
  "version": 1,
  "verify": {
    "remote_manifest_fetch": false,
    "ocsp_fetch": false
  }
})");

c2pa::Reader reader(context, "image.jpg");
std::cout << reader.json() << std::endl;

Reading from a stream

std::ifstream stream("image.jpg", std::ios::binary);
c2pa::Reader reader(context, "image/jpeg", stream);
std::cout << reader.json() << std::endl;

Using Context with Builder

Builder uses Context to control manifest creation, signing, thumbnails, and more.

Important

The Context is used only when constructing the Builder. It copies the configuration internally, so the Context doesn't need to outlive the Builder.

c2pa::Context context(R"({
  "version": 1,
  "builder": {
    "claim_generator_info": {"name": "My App", "version": "1.0"},
    "intent": {"Create": "digitalCapture"}
  }
})");

c2pa::Builder builder(context, manifest_json);

// Pass signer explicitly at signing time
c2pa::Signer signer("es256", certs, private_key);
builder.sign(source_path, output_path, signer);

Settings reference

Settings API

The Settings class provides methods for creating and manipulating configuration:

Method Description
Settings() Create default settings
Settings(data, format) Parse settings from a string. Format is "json" or "toml"
set(path, json_value) Set a value by dot-separated path (e.g., "verify.verify_after_sign"). Value must be JSON-encoded. Returns *this for chaining
update(data) Merge JSON configuration (same as update(data, "json"))
update(data, format) Merge configuration from a string with specified format
is_valid() Returns true if the object is valid (not moved-from)

Note

  • Settings are moveable, not copyable. After moving, is_valid() returns false on the source.
  • set() and update() can be chained for sequential configuration.
  • Later calls override earlier ones (last wins).

Settings object structure

Tip

For the complete reference to the Settings object, see SDK object reference - Settings.

Settings JSON has this top-level structure:

{
  "version": 1,
  "trust": { ... },
  "cawg_trust": { ... },
  "core": { ... },
  "verify": { ... },
  "builder": { ... },
  "signer": { ... },
  "cawg_x509_signer": { ... }
}
Property Description
version Settings format version (must be 1)
trust Certificate trust configuration for C2PA validation
cawg_trust Certificate trust configuration for CAWG identity assertions
core Core SDK behavior and performance tuning
verify Validation and verification behavior
builder Manifest creation and embedding behavior
signer C2PA signer configuration
cawg_x509_signer CAWG identity assertion signer configuration

The version property must be 1. All other properties are optional.

Important

If you don't specify a property, the SDK uses the default value. If you specify null, the property is explicitly set to null (not the default). This distinction matters when overriding default behavior.

For Boolean values, use JSON true and false, not the strings "true" and "false".

Tip

For the complete default settings configuration, see Rust library - Configuring SDK settings - Default configuration.

Trust configuration

The trust properties control which certificates are trusted when validating C2PA manifests.

Property Description Default
trust.user_anchors Additional root certificates (PEM format). Adds custom CAs without replacing built-in trust anchors. Recommended for development.
trust.allowed_list Explicitly allowed certificates (PEM format). Trusted regardless of chain validation. Use for development/testing to bypass validation.
trust.trust_anchors Default trust anchor root certificates (PEM format). Replaces the SDK's built-in trust anchors entirely.
trust.trust_config Allowed Extended Key Usage (EKU) OIDs for certificate purposes (e.g., document signing: 1.3.6.1.4.1.311.76.59.1.9).

Using user_anchors

For development, add your test root CA without replacing the SDK's default trust store:

std::string test_root_ca = R"(-----BEGIN CERTIFICATE-----
MIICEzCCAcWgAwIBAgIUW4fUnS38162x10PCnB8qFsrQuZgwBQYDK2VwMHcxCzAJ
...
-----END CERTIFICATE-----)";

c2pa::Context context(R"({
  "version": 1,
  "trust": {
    "user_anchors": ")" + test_root_ca + R"("
  }
})");

c2pa::Reader reader(context, "signed_asset.jpg");

Using allowed_list

For quick testing, bypass chain validation by explicitly allowing a specific certificate:

std::string test_cert = read_file("test_cert.pem");

c2pa::Settings settings;
settings.update(R"({
  "version": 1,
  "trust": {
    "allowed_list": ")" + test_cert + R"("
  }
})");

c2pa::Context context(settings);
c2pa::Reader reader(context, "signed_asset.jpg");

Loading trust from a file

Suppose dev_trust_config.json looks like this:

{
  "version": 1,
  "trust": {
    "user_anchors": "-----BEGIN CERTIFICATE-----\nMIICEzCCA...\n-----END CERTIFICATE-----",
    "trust_config": "1.3.6.1.4.1.311.76.59.1.9\n1.3.6.1.4.1.62558.2.1"
  }
}

For the PEM string (for example in user_anchors in above example):

  • Use literal \n (as two-character strings) in JSON for line breaks
  • Include the full certificate chain if needed
  • Concatenate multiple certificates into a single string

Load in your application:

auto context = c2pa::Context::ContextBuilder()
    .with_json_settings_file("dev_trust_config.json")
    .create_context();

c2pa::Reader reader(context, "signed_asset.jpg");

CAWG trust configuration

The cawg_trust properties configure CAWG (Creator Assertions Working Group) validation of identity assertions in C2PA manifests. The structure is identical to trust.

Note

CAWG trust settings only apply when processing identity assertions with X.509 certificates. If your workflow doesn't use CAWG identity assertions, these settings have no effect.

Core settings

The core properties control SDK behavior and performance tuning:

Property Description Default
merkle_tree_chunk_size_in_kb Merkle tree chunk size null
merkle_tree_max_proofs Maximum merkle tree proofs 5
backing_store_memory_threshold_in_mb Memory threshold for backing store 512
decode_identity_assertions Decode identity assertions true
allowed_network_hosts Allowed network hosts for SDK requests null

Use cases:

  • Performance tuning for large files: Set backing_store_memory_threshold_in_mb to 2048 or higher for large video files with sufficient RAM
  • Restricted network environments: Set allowed_network_hosts to limit which domains the SDK can contact

Verify settings

The verify properties control how the SDK validates C2PA manifests.

The following table lists the key properties (all default to true):

Property Description Default
verify_after_reading Automatically verify manifests when reading assets true
verify_after_sign Automatically verify manifests after signing (recommended) true
verify_trust Verify signing certificates against trust anchors true
verify_timestamp_trust Verify timestamp authority (TSA) certificates true
remote_manifest_fetch Fetch remote manifests referenced in the asset true
ocsp_fetch Fetch OCSP responses for certificate validation false
skip_ingredient_conflict_resolution Skip ingredient conflict resolution false
strict_v1_validation Enable strict C2PA v1 validation false

Warning

Disabling verify_trust or verify_timestamp_trust makes verification non-compliant with the C2PA specification. Only modify in controlled environments or with specific requirements.

Offline or air-gapped environments

Disable network-dependent features:

c2pa::Context context(R"({
  "version": 1,
  "verify": {
    "remote_manifest_fetch": false,
    "ocsp_fetch": false
  }
})");

c2pa::Reader reader(context, "signed_asset.jpg");

Strict validation

Enable all validation features for certification or compliance testing:

c2pa::Context context(R"({
  "version": 1,
  "verify": {
    "strict_v1_validation": true,
    "ocsp_fetch": true,
    "verify_trust": true,
    "verify_timestamp_trust": true
  }
})");

c2pa::Reader reader(context, "asset_to_validate.jpg");
auto validation_result = reader.json();

Builder settings

The builder settings control how the SDK creates and embeds C2PA manifests:

Builder intent

Use the builder.intent setting to specify the purpose of the claim, one of:

  • {"Create": <TYPE>}: Specifies a new digital creation, where <TYPE> is one of the DigitalSourceType.
  • {"Edit": null}: An edit of a pre-existing parent asset.
  • {"Update": null}: An restricted version of Edit type for non-editorial changes.

Tip

For more information on intents, see Intents and BuilderIntent in the SDK object reference..

Example: Original digital capture (photos from camera)

c2pa::Context camera_context(R"({
  "version": 1,
  "builder": {
    "intent": {"Create": "digitalCapture"},
    "claim_generator_info": {"name": "Camera App", "version": "1.0"}
  }
})");

Example: Editing existing content

c2pa::Context editor_context(R"({
  "version": 1,
  "builder": {
    "intent": {"Edit": null},
    "claim_generator_info": {"name": "Photo Editor", "version": "2.0"}
  }
})");

Claim generator information

Identifies your application in the C2PA manifest:

Property Description
claim_generator_info.name Application name (required, e.g., "My Photo Editor")
claim_generator_info.version Application version (recommended, e.g., "2.1.0")
claim_generator_info.icon Icon in C2PA format (optional)
claim_generator_info.operating_system OS identifier or "auto" to auto-detect (optional)

See ClaimGeneratorInfoSettings in the SDK object reference.

Example:

c2pa::Context context(R"({
  "version": 1,
  "builder": {
    "claim_generator_info": {
      "name": "My Photo Editor",
      "version": "2.1.0",
      "operating_system": "auto"
    }
  }
})");

c2pa::Builder builder(context, manifest_json);

Thumbnail settings

The builder.thumbnail properties control automatic thumbnail generation:

Property Description Default
enabled Enable/disable thumbnail generation true
long_edge Maximum size in pixels for the long edge 1024
quality Quality level: "low", "medium", or "high" "medium"
format Output format (null for auto-detect) null
prefer_smallest_format Prefer smallest format by file size true
ignore_errors Continue if thumbnail generation fails true

Examples:

// Disable thumbnails for batch processing
c2pa::Context no_thumbnails(R"({
  "builder": {
    "claim_generator_info": {"name": "Batch Processor"},
    "thumbnail": {"enabled": false}
  }
})");

// Customize for mobile (smaller size, lower quality)
c2pa::Context mobile_thumbnails(R"({
  "builder": {
    "thumbnail": {
      "long_edge": 512,
      "quality": "low",
      "prefer_smallest_format": true
    }
  }
})");

Signer settings

The signer properties configure the C2PA signer. Set to null if you provide the signer at runtime, or configure as local or remote in settings.

Note

In C++, you typically create a c2pa::Signer explicitly and pass it to Builder::sign(). Settings-based signing is useful when you need the same configuration across multiple operations or when loading from files.

Local signer

Use when you have direct access to the private key and certificate. See signer.local for all properties.

Example: Local signer with ES256

std::string config = R"({
  "version": 1,
  "signer": {
    "local": {
      "alg": "es256",
      "sign_cert": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
      "private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----",
      "tsa_url": "http://timestamp.digicert.com"
    }
  }
})";

c2pa::Context context(config);
c2pa::Builder builder(context, manifest_json);
builder.sign(source_path, dest_path);  // Uses signer from context

Remote signer

Use when the private key is on a secure signing service (HSM, cloud KMS). See signer.remote for all properties.

The signing service receives a POST request with data to sign and must return the signature:

c2pa::Context context(R"({
  "version": 1,
  "signer": {
    "remote": {
      "url": "https://signing-service.example.com/sign",
      "alg": "ps256",
      "sign_cert": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
      "tsa_url": "http://timestamp.digicert.com"
    }
  }
})");

Explicit signer

For full programmatic control, create a Signer and pass it to Builder::sign(). This is the typical C++ approach:

c2pa::Signer signer("es256", certs_pem, private_key_pem, "http://timestamp.digicert.com");
c2pa::Builder builder(context, manifest_json);
builder.sign(source_path, dest_path, signer);

The Context controls verification and builder options. The signer is used only for the cryptographic signature.

CAWG X.509 signer configuration

The cawg_x509_signer property configures signing for identity assertions. It has the same structure as signer (local or remote).

When to use: To sign identity assertions separately from the main C2PA claim. When both signer and cawg_x509_signer are configured, the SDK uses a dual signer:

  • Main claim signature from signer
  • Identity assertions signed with cawg_x509_signer

Example: Dual signer configuration

c2pa::Context context(R"({
  "version": 1,
  "signer": {
    "local": {
      "alg": "es256",
      "sign_cert": "...",
      "private_key": "..."
    }
  },
  "cawg_x509_signer": {
    "local": {
      "alg": "ps256",
      "sign_cert": "...",
      "private_key": "..."
    }
  }
})");

Migrating from deprecated APIs

The SDK introduced Context-based APIs to replace constructors and functions that relied on thread-local state. The older APIs still compile but produce deprecation warnings. This section covers each deprecation that involves passing a Context, and explains the Builder::sign overloads.

Quick reference

Deprecated API Replacement
load_settings(data, format) Context constructors or ContextBuilder
Reader(format, stream) Reader(context, format, stream)
Reader(source_path) Reader(context, source_path)
Builder(manifest_json) Builder(context, manifest_json)
Builder::sign(..., ostream, ...) Builder::sign(..., iostream, ...)

Replacing load_settings

c2pa::load_settings(data, format) sets thread-local settings that Reader and Builder pick up implicitly. Replace it with a Context that you pass explicitly.

Aspect load_settings (legacy) Context
Scope Global / thread-local Per Reader/Builder, passed explicitly
Multiple configs Awkward (per-thread) One context per configuration
Testing Shared global state Isolated contexts per test

Deprecated:

std::ifstream config_file("settings.json");
std::string config((std::istreambuf_iterator<char>(config_file)), std::istreambuf_iterator<char>());
c2pa::load_settings(config, "json");
c2pa::Reader reader("image/jpeg", stream);  // uses thread-local settings

With context API:

c2pa::Context context(settings_json_string);  // or Context(Settings(...))
c2pa::Reader reader(context, "image/jpeg", stream);

If you still use load_settings, construct Reader or Builder without a context parameter to continue using the thread-local settings. Prefer passing a context for new code.

Adding a context parameter to Reader and Builder

The following constructors are deprecated because they rely on thread-local settings:

  • Reader(const std::string& format, std::istream& stream)
  • Reader(const std::filesystem::path& source_path)
  • Builder(const std::string& manifest_json)

The migration path for each is to create a Context and pass it as the first argument.

Deprecated:

c2pa::Reader reader("image/jpeg", stream);
c2pa::Reader reader("image.jpg");
c2pa::Builder builder(manifest_json);

With context API:

c2pa::Context context;  // or Context(settings) or Context(json_string)
c2pa::Reader reader(context, "image/jpeg", stream);
c2pa::Reader reader(context, "image.jpg");
c2pa::Builder builder(context, manifest_json);

If you need default SDK behavior and have no custom settings, c2pa::Context context; with no arguments is sufficient.

About IContextProvider

The deprecation warnings reference IContextProvider in their suggested fix (e.g., "Use Reader(IContextProvider& context, ...)"). IContextProvider is the interface that Reader and Builder constructors accept. Context is the SDK's built-in implementation of this interface.

When the deprecation warning says "Use Reader(IContextProvider& context, ...)", passing a Context object satisfies that parameter.

External libraries can also implement IContextProvider to provide their own context objects (for example, wrapping a platform-specific configuration system). The interface is minimal: any class that can produce a valid C2paContext* pointer and report its validity can serve as a context provider. This becomes relevant when building integrations that need to manage context lifetime or initialization differently than the SDK's Context class does.

Builder::sign overloads

Builder::sign has two kinds of overloads: those that take an explicit Signer argument, and those that use the signer stored in the Builder's Context.

Signing with an explicit Signer

Pass a Signer directly when you create signers at runtime or use different signers for different signing operations:

c2pa::Signer signer("es256", certs, key, tsa_url);

// Stream-based (preferred)
std::fstream dest("output.jpg", std::ios::in | std::ios::out | std::ios::binary);
builder.sign("image/jpeg", source, dest, signer);

// File-based
builder.sign("source.jpg", "output.jpg", signer);

Signing with the Context's signer

If a signer is configured in the Context (through settings JSON or ContextBuilder::with_signer()), you can call sign without a Signer argument. The context's signer is used automatically. If both a programmatic signer (via with_signer()) and a settings-based signer exist, the programmatic signer takes priority.

c2pa::Signer signer("es256", certs, key, tsa_url);

auto context = c2pa::Context::ContextBuilder()
    .with_json(settings_json)
    .with_signer(std::move(signer))  // signer is consumed here
    .create_context();

c2pa::Builder builder(context, manifest_json);

// Stream-based (preferred)
std::fstream dest("output.jpg", std::ios::in | std::ios::out | std::ios::binary);
builder.sign("image/jpeg", source, dest);

// File-based
builder.sign("source.jpg", "output.jpg");

This is useful when you want to configure signing once and reuse the same context across multiple builders without passing the signer to each sign call.

Deprecated:

std::ofstream out("output.jpg", std::ios::binary);
builder.sign("image/jpeg", source, out, signer);

With context API:

std::fstream dest("output.jpg", std::ios::in | std::ios::out | std::ios::binary);
builder.sign("image/jpeg", source, dest, signer);