From a65b0b9b01fa4fdd9c58b792ba52c8e7a991eec1 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Thu, 23 Jul 2026 13:20:20 -0700 Subject: [PATCH 1/3] docs: use github-only CSS class instead of inline display:none style Switches the GitHub-only content divs to class="github-only" (already defined in opensource.contentauth.org's custom.css) instead of the style={{display: 'none'}} JSX trick, which only works because it's invalid CSS on GitHub. The class-based approach is plain HTML and renders correctly everywhere. --- README.md | 2 +- docs/working-stores.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4424e565..9c3dc092 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ The [c2pa-cpp repository](https://github.com/contentauth/c2pa-cpp) implements C+ Although this library works for plain C applications, the documentation assumes you're using C++, since that's most common for modern applications.
-
+
For the best experience, read the docs on the [CAI Open Source SDK documentation website](https://opensource.contentauthenticity.org/docs/c2pa-c). diff --git a/docs/working-stores.md b/docs/working-stores.md index c8446e5e..05fb3b72 100644 --- a/docs/working-stores.md +++ b/docs/working-stores.md @@ -1282,7 +1282,7 @@ const std::string ingredient_json = R"({ builder.add_ingredient(ingredient_json, "original.jpg"); builder.sign("edited.jpg", "signed.jpg", signer); ``` -
+
## Additional resources From db5b835781ffdf7ba03b792109a5f254a011a173 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Fri, 24 Jul 2026 14:31:43 -0700 Subject: [PATCH 2/3] Copy edit --- README.md | 17 ++++++------ docs/context-settings.md | 26 +++++++++--------- docs/embeddable-api.md | 36 ++++++++++++------------- docs/faqs.md | 20 +++++++------- docs/intents.md | 40 ++++++++++++++-------------- docs/release-notes.md | 4 +-- docs/selective-manifests.md | 53 +++++++++++++++---------------------- docs/usage.md | 39 ++++++++++++++------------- docs/working-stores.md | 36 +++++++++++++------------ 9 files changed, 132 insertions(+), 139 deletions(-) diff --git a/README.md b/README.md index 9c3dc092..cd2d471e 100644 --- a/README.md +++ b/README.md @@ -9,17 +9,17 @@ Although this library works for plain C applications, the documentation assumes
-For the best experience, read the docs on the [CAI Open Source SDK documentation website](https://opensource.contentauthenticity.org/docs/c2pa-c). +For the best experience, read the docs on the [CAI Open Source SDK documentation website](https://opensource.contentauthenticity.org/docs/c2pa-cpp). If you want to view the documentation in GitHub, see: - [Using the C++ library](docs/usage.md) - [Supported formats](https://github.com/contentauth/c2pa-rs/blob/main/docs/supported-formats.md) - [Configuring the SDK using `Context` and `Settings`](docs/context-settings.md) - [Using Builder intents](docs/intents.md) to ensure spec-compliant manifests -- Using [working stores and archvies](docs/working-stores.md) +- Using [working stores and archives](docs/working-stores.md) - Selectively constructing manifests by [filtering actions and ingredients](docs/selective-manifests.md) - Using the [embeddable API for low-level control over embedding manifests](docs/embeddable-api.md) -- [Frequently-asked questions (FAQs)](docs/faqs.md) +- [Frequently asked questions (FAQs)](docs/faqs.md) - [Release notes](docs/release-notes.md)
@@ -45,7 +45,7 @@ target_link_libraries(myapp PRIVATE c2pa_cpp) This will automatically fetch, build, and link the `c2pa_cpp` library and its dependencies. -> **Note:** +> [!NOTE] > This project uses pre-built dynamic libraries from the [c2pa-rs](https://github.com/contentauth/c2pa-rs) repository. It should select the correct library for your platform. If your platform is not supported, you can build your own library using the c2pa_rs repo. ### Example usage @@ -68,7 +68,7 @@ Enter this command to build the SDK: make release ``` -This will download the [pre-build libraries published with c2pa releases](https://github.com/contentauth/c2pa-rs/releases), build and link the C++ code. +This will download the [prebuilt libraries published with c2pa releases](https://github.com/contentauth/c2pa-rs/releases), build and link the C++ code. The Makefile has a number of other targets; for example: - `test` to run unit tests @@ -80,7 +80,7 @@ Results are saved in the `build` directory. ### Building using local sources -This project can also be built entirely from source (without pre-built library download), with the pre-requisite that you will also need [c2pa-rs](https://github.com/contentauth/c2pa-rs) on the local machine, as well as the [Rust toolchain](https://rust-lang.org/tools/install/). +This project can also be built entirely from source (without pre-built library download), with the prerequisite that you will also need [c2pa-rs](https://github.com/contentauth/c2pa-rs) on the local machine, as well as the [Rust toolchain](https://rust-lang.org/tools/install/). To build in this case, the build scripts need to be able to locate the `c2pa-rs` sources as well as the library this builds for linking. This is done by setting environment variables in the terminal where the builds will run. @@ -145,7 +145,7 @@ AddressSanitizer: CHECK failed: sanitizer_malloc_mac.inc:189 "((!asan_init_is_ru This is an incompatibility between an older AppleClang sanitizer runtime and the host malloc. The fix is to build the sanitizer target with a newer LLVM/Clang (for example, Homebrew's `llvm`). -To do so, install (or update) an `llvm` version. For instance, using homebrew:: +To do so, install (or update) an `llvm` version. For instance, using homebrew: ```sh brew install llvm @@ -173,10 +173,9 @@ And finally run the test executable: ctest --test-dir build/debug --output-on-failure ``` - ### Building API documentation -API documentation generated by Doxygen is automatically built on each PR. +Doxygen automatically builds API documentation on each PR. To generate API docs locally, these are the main files: diff --git a/docs/context-settings.md b/docs/context-settings.md index a10fb2c0..ef4650a7 100644 --- a/docs/context-settings.md +++ b/docs/context-settings.md @@ -61,12 +61,12 @@ c2pa::Reader reader(context, "image.jpg"); > [!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](#migrating-from-deprecated-apis). +> See [Migrating from deprecated APIs](#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 -- **Pass as `shared_ptr`**: `Reader` and `Builder` retain a shared reference to the context, keeping it alive for their lifetime. This is required when using progress callbacks — without it, the callback can fire after the context is destroyed, causing a crash +- **Pass as `shared_ptr`**: `Reader` and `Builder` retain a shared reference to the context, keeping it alive for their lifetime. This is required when using progress callbacks: without it, the callback can fire after the context is destroyed, causing a crash - **Reusable**: Use the same `shared_ptr` to create multiple readers and builders ```cpp @@ -199,10 +199,10 @@ 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](#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. -- **`total`** — `0` = 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()`). +- **`phase`**: which stage the SDK is in (see [ProgressPhase values](#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. +- **`total`**: `0` = 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. @@ -238,9 +238,9 @@ try { 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). +`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 +### ProgressPhase values | Phase | When emitted | |-------|-------------| @@ -509,7 +509,7 @@ The [`trust` properties](https://opensource.contentauthenticity.org/docs/manifes | `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` +#### Using user_anchors For development, add your test root CA without replacing the SDK's default trust store: @@ -529,7 +529,7 @@ auto context = std::make_shared(R"({ c2pa::Reader reader(context, "signed_asset.jpg"); ``` -#### Using `allowed_list` +#### Using allowed_list For quick testing, bypass chain validation by explicitly allowing a specific certificate: @@ -690,9 +690,9 @@ The [`builder` settings](https://opensource.contentauthenticity.org/docs/manifes #### Builder intent Use the [`builder.intent` setting](https://opensource.contentauthenticity.org/docs/manifest/json-ref/settings-schema/#builderintent) to specify the purpose of the claim, one of: -- `{"Create": }`: Specifies a new digital creation, where `` is one of the [DigitalSourceType](https://opensource.contentauthenticity.org/docs/manifest/json-ref/settings-schema/#digitalsourcetype). +- `{"Create": }`: Specifies a new digital creation, where `` is one of the [DigitalSourceType](https://opensource.contentauthenticity.org/docs/manifest/json-ref/settings-schema/#digitalsourcetype). - `{"Edit": null}`: An edit of a pre-existing parent asset. -- `{"Update": null}`: An restricted version of `Edit` type for non-editorial changes. +- `{"Update": null}`: A restricted version of the `Edit` type for non-editorial changes. > [!TIP] > For more information on intents, see [Intents](https://opensource.contentauthenticity.org/docs/rust-sdk/docs/intents) and [BuilderIntent in the SDK object reference.](https://opensource.contentauthenticity.org/docs/manifest/json-ref/settings-schema/#builderintent). @@ -974,7 +974,7 @@ c2pa::Builder builder(context, manifest_json); c2pa::Reader::from_asset(context, "image.jpg"); ``` -The `shared_ptr` overloads accept any `shared_ptr`, so custom `IContextProvider` implementations work the same way, wrap them in a `shared_ptr` before passing. +The `shared_ptr` overloads accept any `shared_ptr`, so custom `IContextProvider` implementations work the same way: wrap them in a `shared_ptr` before passing. #### About IContextProvider diff --git a/docs/embeddable-api.md b/docs/embeddable-api.md index 956192d1..680d697c 100644 --- a/docs/embeddable-api.md +++ b/docs/embeddable-api.md @@ -1,9 +1,9 @@ -# Embeddable Signing API +# Embeddable signing API > [!WARNING] -> The embeddable signing API is for advanced use cases that require fine-grained, low-level control over manifest embedding. The standard `Builder::sign()` method handles the full signing and embedding pipeline automatically and is the recommended approach for most use cases. The embeddable API should only be used when the application needs to manage the embedding process directly. With this level of control comes additional responsibility: callers must ensure that each step is performed correctly and in the right order. +> The embeddable signing API is for advanced use cases that require fine-grained, low-level control over manifest embedding. The standard `Builder::sign()` method handles the full signing and embedding pipeline automatically and is the recommended approach for most use cases. Use the embeddable API only when the application needs to manage the embedding process directly. With this level of control comes additional responsibility: callers must ensure they perform each step correctly and in the right order. -The embeddable signing API provides direct, fine-grained control over how a C2PA manifest is embedded into an asset. Instead of letting the SDK manage everything by providing both the source and destination to `Builder::sign()`, the caller performs each step explicitly, in the following order: +The embeddable signing API provides direct, fine-grained control over embedding a C2PA manifest into an asset. Instead of letting the SDK manage everything by providing both the source and destination to `Builder::sign()`, the caller performs each step explicitly, in the following order: 1. Create a placeholder. 2. Embed the placeholder into the asset. @@ -41,10 +41,10 @@ The embeddable API supports these hard-binding strategies, selected automaticall | [BoxHash](#using-boxhash-directly) | `BoxHash` | JPEG, PNG, GIF, WebP, and others | No | `prefer_box_hash` is `true` | --> -Call `needs_placeholder()` on the `Builder` to decide which workflow to follow. It returns `true` when the format requires a placeholder step (DataHash or BmffHash) and `false` when the format supports BoxHash. +Call `needs_placeholder()` on the `Builder` to decide which workflow to follow. It returns `true` when the format requires a placeholder step (`DataHash` or `BmffHash`) and `false` when the format supports `BoxHash`. > [!NOTE] -> The same format can require a placeholder or not, depending on the hashing strategy. For example, `needs_placeholder("image/jpeg")` returns `true` by default (DataHash). Always call `needs_placeholder()` rather than assuming based on format alone. +> The same format can require a placeholder or not, depending on the hashing strategy. For example, `needs_placeholder("image/jpeg")` returns `true` by default (`DataHash`). Always call `needs_placeholder()` rather than assuming based on format alone. Use the following decision tree to select the correct workflow. @@ -80,7 +80,7 @@ When `needs_placeholder()` returns `false`, the standard `Builder::sign()` flow - The application controls its own write pipeline (e.g., a video transcoder or streaming ingest service) and wants to hash the asset during its own write pass rather than having the SDK re-read the finished file. - The application needs to receive the raw signed manifest bytes and decide where and how to append them as a new chunk, rather than letting the SDK write to a destination path. -- The asset is being produced incrementally, so the application can pass the stream to `update_hash_from_stream()` at the right moment, then call `sign_embeddable()` to get the manifest without a second full read. +- The application produces the asset incrementally, so it can pass the stream to `update_hash_from_stream()` at the right moment, then call `sign_embeddable()` to get the manifest without a second full read. a `BoxHash` assertion has already been added. +Use `Builder::needs_placeholder(format)` to determine if the asset format requires a pre-embedded placeholder before hashing. It always returns `true` for BMFF formats and `false` if the caller has already added a `BoxHash` assertion. ### Signing and embedding @@ -187,7 +187,7 @@ These methods perform the signing workflow: placeholder creation, hashing, and s | `Builder::placeholder(format)` | Composes a placeholder manifest and returns it as format-specific bytes ready to embed (e.g., JPEG APP11 segments). Automatically adds the appropriate hash assertion (`BmffHash` for BMFF formats, `DataHash` for others). Stores the JUMBF length internally so `sign_embeddable()` can pad to the same size. | | `Builder::set_data_hash_exclusions(exclusions)` | Replaces the dummy exclusion ranges in the `DataHash` assertion with the actual byte offset and length of the embedded placeholder. Call after embedding placeholder bytes and before `update_hash_from_stream()`. Takes a `std::vector>` of (start, length) pairs. | | `Builder::update_hash_from_stream(format, stream)` | Reads the asset stream and computes the hard-binding hash. Automatically selects the appropriate path based on format: `BmffHash` for BMFF (skips manifest box), `BoxHash` for chunk-based formats (creates assertion if needed), or `DataHash` (skips exclusion ranges). Takes a `std::istream&`. | -| `Builder::sign_embeddable(format)` | Signs the manifest and returns bytes ready to embed. For placeholder workflows, the output is padded to match the placeholder size for in-place patching. For BoxHash workflows, the output is the actual signed manifest size (not padded), suitable for appending as a new chunk. | +| `Builder::sign_embeddable(format)` | Signs the manifest and returns bytes ready to embed. For placeholder workflows, it pads the output to match the placeholder size for in-place patching. For BoxHash workflows, the output is the actual signed manifest size (not padded), suitable for appending as a new chunk. | ## Using the DataHash placeholder @@ -220,8 +220,8 @@ auto context = c2pa::Context::ContextBuilder() --> - > [!IMPORTANT] -> The embeddable APIs require the **Signer to be attached to the Context** via `Context::ContextBuilder::with_signer()` or through a signer configuration in the JSON settings. Calling `placeholder()` or `sign_embeddable()` without a signer on the Context will throw a `C2paException`. +> [!IMPORTANT] +> The embeddable APIs require the **signer to be attached to the Context** via `Context::ContextBuilder::with_signer()` or through a signer configuration in the JSON settings. Calling `placeholder()` or `sign_embeddable()` without a signer on the Context will throw a `C2paException`. ### DataHash flow @@ -315,14 +315,14 @@ if (builder.needs_placeholder("image/jpeg")) { ## Using the BmffHash placeholder -Use this workflow with BMFF (ISO Base Media File Format) formats (like MP4), which always require a placeholder. They always use `BmffHash` regardless of the setting. Therefore, no special Builder settings are required as the SDK selects `BmffHash` automatically based on the format. +Use this workflow with BMFF (ISO Base Media File Format) formats (like MP4), which always require a placeholder. They always use `BmffHash` regardless of the setting. Therefore, no special `Builder` settings are required as the SDK selects `BmffHash` automatically based on the format. BMFF containers store media data in `mdat` (media data) boxes, which hold the raw audio, video, and other media samples. The SDK uses a `BmffHash` assertion for BMFF formats that excludes the manifest UUID box when computing the asset hash. The `placeholder()` method creates a `BmffHash` assertion with a placeholder hash and default exclusions. After embedding the placeholder, call `update_hash_from_stream()` to read and hash the asset, then `sign_embeddable()` to produce a signed manifest of the same size as the placeholder for in-place patching. > [!IMPORTANT] -> The embeddable APIs require the **Signer to be attached to the Context** via `Context::ContextBuilder::with_signer()` or through a signer configuration in the JSON settings. Calling `placeholder()` or `sign_embeddable()` without a signer on the Context will throw a `C2paException`. +> The embeddable APIs require the **signer to be attached to the Context** via `Context::ContextBuilder::with_signer()` or through a signer configuration in the JSON settings. Calling `placeholder()` or `sign_embeddable()` without a signer on the Context will throw a `C2paException`. ### BmffHash flow @@ -505,7 +505,7 @@ auto manifest_bytes = builder.sign_embeddable("image/jpeg"); ## Class relationships -This is a partial class diagram showing only the classes and methods relevant to the embeddable APIs. For the full API reference, see the [c2pa.hpp](../include/c2pa.hpp) header file. +This is a partial class diagram showing only the classes and methods relevant to the embeddable APIs. For the full API reference, see the [`c2pa.hpp`](../include/c2pa.hpp) header file. ```mermaid classDiagram diff --git a/docs/faqs.md b/docs/faqs.md index b1023d18..25a0817a 100644 --- a/docs/faqs.md +++ b/docs/faqs.md @@ -1,6 +1,6 @@ -# Frequently-asked questions (FAQs) +# Frequently asked questions -## When do I use `Reader` versus `Builder`? +## When do I use Reader versus Builder? **Use a `Reader` when the goal is only to inspect or extract data without creating a new manifest.** @@ -32,7 +32,7 @@ builder.sign(source_path, output_path, signer); Every call to the `Builder` constructor or `Builder::from_archive()` creates a new `Builder`. There is no way to modify an existing signed manifest directly. -### When to use both `Reader` and `Builder` together +### When to use both Reader and Builder together **Use both when filtering content from an existing manifest into a new one. The `Reader` extracts data, application code filters it, and a new `Builder` receives only the selected parts.** @@ -74,7 +74,7 @@ flowchart TD ## How should I add ingredients? -There are two ways: using `add_ingredient()` and injecting ingredient JSON via `with_definition()`. The table below summarizes these options. +There are two ways: using `add_ingredient()` and injecting ingredient JSON via `with_definition()`. The table below summarizes these options. | Approach | What it does | When to use | | --- | --- | --- | @@ -90,14 +90,14 @@ There are two distinct archive concepts: - Checkpointing work-in-progress before signing - Transmitting a `Builder` state across a network boundary -- **Ingredient archives** contain the manifest store data (`.c2pa` binary) from ingredients that were added to a `Builder`. When a signed asset is added as an ingredient via `add_ingredient()`, the library extracts and stores its manifest store as `manifest_data` within the ingredient record. When the `Builder` is then serialized via `to_archive()`, these ingredient manifest stores are included. Use ingredient archives when: +- **Ingredient archives** contain the manifest store data (`.c2pa` binary) from ingredients added to a `Builder`. When you add a signed asset as an ingredient via `add_ingredient()`, the library extracts and stores its manifest store as `manifest_data` within the ingredient record. When you then serialize the `Builder` via `to_archive()`, it includes these ingredient manifest stores. Use ingredient archives when: - Building an ingredients catalog for pick-and-choose workflows - Preserving provenance history from source assets - Transferring ingredient data between `Reader` and `Builder` See also [Working stores](working-stores.md). -Key consideration for builder archives: `from_archive()` creates a new `Builder` with **default** context settings. If specific settings are needed (e.g., thumbnails disabled), use `with_archive()` on a `Builder` that already has the desired context: +Key consideration for builder archives: `from_archive()` creates a new `Builder` with default context settings. If you need specific settings (e.g., thumbnails disabled), use `with_archive()` on a `Builder` that already has the desired context: ```cpp // Preserves the caller's context settings @@ -106,7 +106,7 @@ builder.with_archive(archive_stream); builder.sign(source, output, signer); ``` -## Can a manifest be modified in place? +## Can you modify a manifest in place? **No.** C2PA manifests are cryptographically signed. Any modification invalidates the signature. The only way to "modify" a manifest is to create a new `Builder` with the desired changes and sign it. This is by design: it ensures the integrity of the provenance chain. @@ -114,12 +114,12 @@ builder.sign(source, output, signer); When creating a new manifest, the chain is preserved once the original asset is added as an ingredient. The ingredient carries the original asset's manifest data, so validators can trace the full history. If the original is not added as an ingredient, the provenance chain is broken: the new manifest has no link to the original. This might be intentional (starting fresh) or a mistake (losing provenance). -## What replaced `read_file`, `read_ingredient_file`, and `sign_file`? +## What replaced read_file, read_ingredient_file, and sign_file? -These file-based functions not attached to an API-exposed object, `read_file`, `read_ingredient_file`, and `sign_file`, were deprecated, then removed. Each legacy function maps to another implementation path using existing APIs on the `Reader` or `Builder` objects: +The C++ library deprecated, then removed, these file-based functions that were not attached to an API-exposed object: `read_file`, `read_ingredient_file`, and `sign_file`. Each legacy function maps to another implementation path using existing APIs on the `Reader` or `Builder` objects: | Removed function | Equivalent | | --- | --- | | `read_file(path)` | `Reader::from_asset(ctx, format, stream)` then `Reader::json()`. Pull binary resources with `Reader::get_resource(uri, dest)`. | -| `read_ingredient_file(path, data_dir)` | `Builder::add_ingredient(ingredient_json, source_path)`, which adds ingredients to the active Builder. To recover the formed ingredient JSON (if needed) and its resources, archive the working store and read it back, or move the ingredient with the dedicated `write_ingredient_archive` / `add_ingredient_from_archive` APIs. There is no single-call replacement; but the full behavior (extract the ingredient JSON, thumbnail, and manifest_data to a directory, then reuse that directory to sign an asset using a Builder) can be reimplemented as shown in examples from [Migrating from `read_ingredient_file`](selective-manifests.md#migrating-from-read_ingredient_file). See also [Extracting ingredients from a working store](selective-manifests.md#extracting-ingredients-from-a-working-store). | +| `read_ingredient_file(path, data_dir)` | `Builder::add_ingredient(ingredient_json, source_path)`, which adds ingredients to the active `Builder`. To recover the formed ingredient JSON (if needed) and its resources, archive the working store and read it back, or move the ingredient with the dedicated `write_ingredient_archive` / `add_ingredient_from_archive` APIs. There is no single-call replacement, but you can reimplement the full behavior (extract the ingredient JSON, thumbnail, and `manifest_data` to a directory, then reuse that directory to sign an asset using a `Builder`) as shown in examples from [Migrating from `read_ingredient_file`](selective-manifests.md#migrating-from-read_ingredient_file). See also [Extracting ingredients from a working store](selective-manifests.md#extracting-ingredients-from-a-working-store). | | `sign_file(src, dst, manifest, SignerInfo*, data_dir)` | `Builder::sign(source_path, dest_path, signer)` with a `Signer`. | diff --git a/docs/intents.md b/docs/intents.md index 481cd5ac..4e9eae81 100644 --- a/docs/intents.md +++ b/docs/intents.md @@ -1,12 +1,12 @@ # Using Builder intents -_Intents_ enable validation, add required actions that are required by the C2PA specification, and help prevent invalid operations when using a `Builder`. Intents are about the operation (create, edit, update) executed on the source asset. +_Intents_ enable validation, add actions required by the C2PA specification, and help prevent invalid operations when using a `Builder`. Intents are about the operation (create, edit, or update) executed on the source asset. ## Why use intents? Without intents, you have to manually construct the correct manifest structure: adding the required actions (`c2pa.created` or `c2pa.opened` as the first action per the specification), setting digital source types, managing ingredients, and linking actions to ingredients. Getting any of this wrong produces a non-compliant manifest. -With intents, the caller declares *what is being done* and `Builder` handles the rest. +With intents, the caller declares *what to do* and `Builder` handles the rest. For example, without intents you have to manually wire up actions and make sure ingredients are properly linked to actions. This is especially important for `parentOf` ingredient relationships with the `c2pa.opened` action. @@ -88,9 +88,9 @@ for (const auto& path : image_paths) { } ``` -### Using `set_intent` on Builder +### Using set_intent on Builder -Call `set_intent` directly on a `Builder` instance for one-off operations or when the intent is determined at runtime. For example: +Call `set_intent` directly on a `Builder` instance for one-off operations or when you determine the intent at runtime. For example: ```cpp c2pa::Context context; @@ -101,8 +101,8 @@ builder.sign(source_path, output_path, signer); ### Intent precedence -When an intent is configured in multiple places, the most specific setting takes precedence. -If `set_intent` is called on a `Builder` instance, it takes precedence over all other sources. +When you configure an intent in multiple places, the most specific setting takes precedence. +Calling `set_intent` on a `Builder` instance takes precedence over all other sources. ```mermaid flowchart TD @@ -162,11 +162,11 @@ flowchart LR end ``` -For `Edit` and `Update` intents, `Builder` looks at the source stream, and if no `parentOf` ingredient has been added manually, it automatically creates one from that stream (and adds the needed action). The source stream *becomes* the parent ingredient. If a `parentOf` ingredient has already been added manually (via `add_ingredient`), `Builder` uses that one instead and does not automatically create one from the source. +For `Edit` and `Update` intents, `Builder` looks at the source stream. If the caller has not manually added a `parentOf` ingredient, `Builder` automatically creates one from that stream (and adds the needed action). The source stream *becomes* the parent ingredient. If the caller has already manually added a `parentOf` ingredient (via `add_ingredient`), `Builder` uses that one instead and does not automatically create one from the source. -### How intent relates to `add_ingredient` +### How intent relates to add_ingredient -The `Builder` intent controls what the Builder does with the source stream (source asset) at sign time. The `add_ingredient` method adds other ingredients explicitly. These are separate concerns. +The `Builder` intent controls what `Builder` does with the source stream (source asset) at sign time. The `add_ingredient` method adds other ingredients explicitly. These are separate concerns. ```mermaid flowchart TD @@ -205,9 +205,9 @@ The `C2paBuilderIntent` and `C2paDigitalSourceType` enums are available through Enum values are in the global namespace: - `C2paBuilderIntent`: Create, Edit, or Update -- `C2paDigitalSourceType`: Empty, DigitalCapture, TrainedAlgorithmicMedia, and so on... +- `C2paDigitalSourceType`: Empty, DigitalCapture, TrainedAlgorithmicMedia, and so on. -### Using `set_intent` +### Using set_intent Use the `Builder` [`set_intent`](https://contentauth.github.io/c2pa-cpp/da/db7/classc2pa_1_1Builder.html#ac3ca980a43f44c9349ac0d6de50a088c) method to specify the intent: @@ -220,7 +220,7 @@ void Builder::set_intent( Where: - `intent` is one of the [intent types](#intent-types). -- `digital_source_type` is one of the [`C2paDigitalSourceType` values](#c2padigitalsourcetype) values that describes how the asset was made. Required for the `Create` intent. Defaults to `Empty`. +- `digital_source_type` is one of the [`C2paDigitalSourceType` values](#c2padigitalsourcetype) that describe how the asset was made. Required for the `Create` intent. Defaults to `Empty`. ### Intent types @@ -229,9 +229,9 @@ Intent types can be any `C2paBuilderIntent` values: | Intent | Operation | Parent ingredient | Auto-generated action | |----------|-----------|--------------------|------------------------| -| `Create` | Brand-new content | Must NOT have one. | `c2pa.created` | -| `Edit` | Modifying existing content| Automatically created from the source stream if not provided | `c2pa.opened` (linked to parent) | -| `Update` | Metadata-only changes | Automatically created from the source stream if not provided. | `c2pa.opened` (linked to parent) | +| `Create` | Brand-new content | Must *not* have one | `c2pa.created` | +| `Edit` | Modifying existing content | Automatically created from the source stream if not provided | `c2pa.opened` (linked to parent) | +| `Update` | Metadata-only changes | Automatically created from the source stream if not provided | `c2pa.opened` (linked to parent) | ### C2paDigitalSourceType @@ -277,7 +277,7 @@ flowchart TD ## Create intent -Use the `Create` intent when the asset has no prior history. A `C2paDigitalSourceType` is required to describe how the asset was produced. `Builder` will: +Use the `Create` intent when the asset has no prior history. Specify a `C2paDigitalSourceType` to describe how the asset was produced. `Builder` will: - Add a `c2pa.created` action with the specified digital source type. - Reject the operation if a `parentOf` ingredient exists. @@ -351,9 +351,9 @@ builder.sign(source_path, output_path, signer); ## Edit intent -Use the `Edit` intent when an existing asset is modified. With this intent, `Builder`: +Use the `Edit` intent when you modify an existing asset. With this intent, `Builder`: -1. Checks if a `parentOf` ingredient has already been added. If not, it automatically creates one from the source stream passed to `sign()`. +1. Checks whether the caller has already added a `parentOf` ingredient. If not, it automatically creates one from the source stream passed to `sign()`. 2. Adds a `c2pa.opened` action linked to the parent ingredient. No `digital_source_type` parameter is needed. @@ -455,13 +455,13 @@ builder.sign("original.jpg", "composite.jpg", signer); ## Update intent -Use the `Update` intent for metadata-only changes where the asset content itself is not modified. This is a restricted form of the `Edit` intent that: +Use the `Update` intent for metadata-only changes, where the asset content itself does not change. This is a restricted form of the `Edit` intent that: - Allows exactly one ingredient (the parent). - Does not allow changes to the parent's hashed content. - Produces a more compact manifest than `Edit`. -As with `Edit` intent, `Builder` automatically creates a parent ingredient from the source stream if one is not provided. +As with the `Edit` intent, `Builder` automatically creates a parent ingredient from the source stream if the caller has not provided one. ### Example: Adding metadata to a signed asset diff --git a/docs/release-notes.md b/docs/release-notes.md index f0793439..fbb7b422 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,4 +1,4 @@ -# Release Notes +# Release notes ## 3 Mar 2026 @@ -9,5 +9,5 @@ Additionally, this release adds support for: - Using [working stores and archives](working-stores.md). - Using `Builder` and `Reader` together to [selectively construct a manifest by filtering actions and ingredients](selective-manifests.md). -For answers to frequently-asked questions about these features, see the [FAQs](faqs.md). +For answers to frequently asked questions about these features, see the [FAQs](faqs.md). diff --git a/docs/selective-manifests.md b/docs/selective-manifests.md index ef527ef8..1d4de33d 100644 --- a/docs/selective-manifests.md +++ b/docs/selective-manifests.md @@ -1,6 +1,6 @@ # Selective manifest construction -You can use `Builder` and `Reader` together to selectively construct manifests—keeping only the parts you need and omitting the rest. This is useful when you don't want to include all ingredients in a working store (for example, when some ingredient assets are not visible). +You can use `Builder` and `Reader` together to selectively construct manifests, keeping only the parts you need and omitting the rest. This is useful when you don't want to include all ingredients in a working store (for example, when some ingredient assets are not visible). This process is best described as *filtering* or *rebuilding* a working store: @@ -10,7 +10,7 @@ This process is best described as *filtering* or *rebuilding* a working store: A manifest is a signed data structure attached to an asset that records provenance and which source assets (ingredients) contributed to it. It contains assertions (statements about the asset), ingredients (references to other assets), and references to binary resources (such as thumbnails). -Since both `Reader` and `Builder` are **read-only** by design (neither has a `remove()` method), to exclude content you must **read what exists, filter to keep what you need, and create a new** `Builder` **with only that information**. This produces a new `Builder` instance—a "rebuild." +Since both `Reader` and `Builder` are **read-only** by design (neither has a `remove()` method), to exclude content you must **read what exists, filter to keep what you need, and create a new** `Builder` **with only that information**. This produces a new `Builder` instance: a "rebuild." > [!IMPORTANT] > This process always creates a new `Builder`. The original signed asset and its manifest are never modified, neither is the starting working store. The `Reader` extracts data without side effects, and the `Builder` constructs a new manifest based on extracted data. @@ -25,8 +25,6 @@ flowchart LR D -->|sign| E[New Asset] ``` - - The fundamental workflow is: 1. **Read** the existing manifest with `Reader` to get JSON and binary resources @@ -75,7 +73,8 @@ Each example below creates a **new `Builder`** from filtered data. The original When transferring ingredients from a `Reader` to a new `Builder`, you must transfer both the JSON metadata and the associated binary resources (thumbnails, manifest data). The JSON contains identifiers that reference those resources; the same identifiers must be used when calling `builder.add_resource()`. -> **Transferring binary resources:** For each kept ingredient, call `reader.get_resource(id, stream)` for any `thumbnail` or `manifest_data` it contains, then `builder.add_resource(id, stream)` with the same identifier. +> [!IMPORTANT] +> For each kept ingredient, call `reader.get_resource(id, stream)` for any `thumbnail` or `manifest_data` it contains, then `builder.add_resource(id, stream)` with the same identifier. ### Keep only specific ingredients @@ -172,8 +171,6 @@ flowchart TD style NI fill:#efe,stroke:#090 ``` - - ```cpp // Create a new Builder with a new definition c2pa::Builder builder(context); @@ -209,7 +206,6 @@ builder.add_action(R"({ ### Action JSON fields - | Field | Required | Description | | --- | --- | --- | | `action` | Yes | Action identifier, e.g. `"c2pa.created"`, `"c2pa.opened"`, `"c2pa.placed"`, `"c2pa.color_adjustments"`, `"c2pa.filtered"` | @@ -217,19 +213,18 @@ builder.add_action(R"({ | `description` | No | Human-readable description of what happened | | `digitalSourceType` | Sometimes, depending on action | URI describing the digital source type (typically for `c2pa.created`) | - ### Linking actions to ingredients When an action involves a specific ingredient, the ingredient is linked to the action using `ingredientIds` (in the action's `parameters`), referencing a matching key in the ingredient. -#### How `ingredientIds` resolution works +#### How ingredientIds resolution works The SDK matches each value in `ingredientIds` against ingredients using this priority: 1. `label` on the ingredient (primary): if set and non-empty, this is used as the linking key. 2. `instance_id` on the ingredient (fallback): used when `label` is absent or empty. -#### Linking with `label` +#### Linking with label The `label` field on an ingredient is the **primary** linking key. Set a `label` on the ingredient and reference it in the action's `ingredientIds`. The label can be any string: it acts as a linking key between the ingredient and the action. @@ -334,7 +329,7 @@ builder.add_ingredient(R"({ builder.sign(source_path, output_path, signer); ``` -#### Linking with `instance_id` +#### Linking with instance_id When no `label` is set on an ingredient, the SDK matches `ingredientIds` against `instance_id`. @@ -413,7 +408,7 @@ for (auto& assertion : manifest["assertions"]) { } ``` -#### When to use `label` vs `instance_id` +#### When to use label vs. instance_id | Property | `label` | `instance_id` | | --- | --- | --- | @@ -423,7 +418,6 @@ for (auto& assertion : manifest["assertions"]) { | **Survives signing** | SDK may reassign the actual assertion label | Unchanged | | **Stable across rebuilds** | The caller controls the build-time value; the post-signing label may change | Yes, always the same set value | - Use `label` when defining manifests in JSON. Use `instance_id` when working programmatically with ingredients whose identity comes from other sources, or when a stable identifier that persists unchanged across rebuilds is needed. ## Working with archives @@ -434,7 +428,7 @@ There are two distinct types of archives, sharing the same binary format but bei ### Builder archives vs. ingredient archives -A **builder archive** is a serialized snapshot of a `Builder` (i.e. of a working store). The term *working store* refers to the unsigned `Builder` itself; the *builder archive* is its serialized `.c2pa` form. The archive contains the manifest definition, all resources, and any ingredients that were added. It is created by `builder.to_archive()` and restored with `Builder::from_archive()` or `builder.with_archive()`. +A **builder archive** is a serialized snapshot of a `Builder` (i.e. of a working store). The term *working store* refers to the unsigned `Builder` itself; the *builder archive* is its serialized `.c2pa` form. The archive contains the manifest definition, all resources, and any ingredients that were added. `builder.to_archive()` creates it, and `Builder::from_archive()` or `builder.with_archive()` restores it. An **ingredient archive** contains the manifest store from an asset that was added as an ingredient. @@ -688,7 +682,7 @@ consumer.add_ingredient_from_archive(archive_a); consumer.sign(source_path, output_path, signer); ``` -#### Legacy: `to_archive` + Reader loop +#### Legacy: to_archive + Reader loop > [!NOTE] > **Legacy approach.** The pattern below archives a multi-ingredient builder and uses a `Reader` loop to find ingredients by `instance_id`. @@ -799,7 +793,8 @@ for (auto& action : actions) { } ``` -> **Naming convention:** Vendor parameters must use reverse domain notation with period-separated components (e.g., `com.mycompany.tool`, `net.example.session_id`). Some namespaces (e.g., `c2pa` or `cawg`) may be reserved. +> [!NOTE] +> Vendor parameters must use reverse domain notation with period-separated components (e.g., `com.mycompany.tool`, `net.example.session_id`). Some namespaces (e.g., `c2pa` or `cawg`) may be reserved. ### Extracting ingredients from a working store @@ -1186,7 +1181,7 @@ For the linking rules, see [Linking an archived ingredient to an action](#linkin ### Merging multiple working stores -In some cases you may need to merge ingredients from multiple working stores (builder archives) into a single `Builder`. This should be a **fallback strategy**—the recommended practice is to maintain a single active working store and add ingredients incrementally (archived ingredient catalogs help with this). Merging is available when multiple working stores must be consolidated. +In some cases you may need to merge ingredients from multiple working stores (builder archives) into a single `Builder`. This should be a **fallback strategy**: the recommended practice is to maintain a single active working store and add ingredients incrementally (archived ingredient catalogs help with this). Merging is available when multiple working stores must be consolidated. When merging from multiple sources, resource identifier URIs can collide. Rename identifiers with a unique suffix when needed. Use two passes: (1) collect ingredients with collision handling, build the manifest, create the builder; (2) re-read each archive and transfer resources (use original ID for `get_resource()`, renamed ID for `add_resource()` when collisions occurred). @@ -1251,7 +1246,7 @@ builder.sign(source_path, output_path, signer); ### Migrations -#### Using `add_resource` instead of `set_base_path` +#### Using add_resource instead of set_base_path `set_base_path` carries a `@deprecated` note in the C++ header. `add_resource` is its replacement, registering each resource directly on a `Builder` instance. @@ -1315,7 +1310,7 @@ The asset itself is not in the directory: the directory holds metadata and a man } ``` -Two things decide how to re-add the ingredient: whether its asset is still available, and what the directory contains. The asset is checked first — `add_ingredient` needs an asset stream, so without one the ingredient is injected straight into the definition. +Two things decide how to re-add the ingredient: whether its asset is still available, and what the directory contains. The asset is checked first, since `add_ingredient` needs an asset stream, so without one the ingredient is injected straight into the definition. ```mermaid flowchart TD @@ -1520,7 +1515,7 @@ builder.sign(source_asset, output_path, signer); When the asset is gone, swap the `add_ingredient` + `set_base_path` step for the [no-asset route](#loading-an-ingredient-directory-without-using-an-asset-stream) above and still add the modern archive with `add_ingredient_from_archive`. -##### Re-archiving an ingredient directory into the modern `.c2pa` format +##### Re-archiving an ingredient directory into the modern .c2pa format The on-disk directory format is awkward to carry around: multiple files, on-disk relative references, a Builder-global `base_path`. If you still have the ingredients, convert each one to a modern dedicated ingredient archive once, then use only the archives. Loading a directory and calling `write_ingredient_archive` produces that archive, which bundles the manifest store and no longer depends on `base_path` or on the asset file. @@ -1583,7 +1578,7 @@ builder.sign(source_asset, output_path, signer); An unknown id in `ingredientIds` is rejected at sign with `"Action ingredientId not found"`. -#### Migrating from `read_ingredient_file` +#### Migrating from read_ingredient_file The deprecated and removed function `read_ingredient_file(source_path, data_dir)` read an asset, returned a fully formed ingredient JSON, and wrote the ingredient's binary resources (thumbnail and manifest data) to `data_dir`, so a later signing step could load that directory and embed the ingredient. The behavior can be reimplemented with `Builder` and `Reader` objects: form the ingredient, archive it, read it back, write the resources to disk under stable non-colliding names, then reuse the directory on a Builder to sign. @@ -1650,7 +1645,7 @@ An ingredient formed through `add_ingredient` from an asset stream or path alway ## Retrieving actions from a working store -Actions are stored in the `c2pa.actions.v2` assertion. Use `Reader` to extract them from a signed asset or an archived Builder. +The `c2pa.actions.v2` assertion stores actions. Use `Reader` to extract them from a signed asset or an archived Builder. ### Reading actions @@ -1687,7 +1682,7 @@ c2pa::Reader reader(context, "application/c2pa", archive_file); ### Understanding the manifest tree -The `Reader` returns a manifest store—a dictionary of manifests keyed by label (a URN like `contentauth:urn:uuid:...`). Conceptually it forms a tree: each manifest has assertions and ingredients; ingredients with `manifest_data` carry their own manifest store, which can have its own ingredients and assertions recursively. The `active_manifest` key indicates the root. +The `Reader` returns a manifest store: a dictionary of manifests keyed by label (a URN like `contentauth:urn:uuid:...`). Conceptually it forms a tree: each manifest has assertions and ingredients; ingredients with `manifest_data` carry their own manifest store, which can have its own ingredients and assertions recursively. The `active_manifest` key indicates the root. ```mermaid flowchart TD @@ -1757,7 +1752,7 @@ for (auto& ingredient : active_manifest["ingredients"]) { ## Filtering actions -To remove actions, use the same read–filter–rebuild pattern: **read, pick the ones to keep, create a new Builder**. +To remove actions, use the same read-filter-rebuild pattern: **read, pick the ones to keep, create a new Builder**. ```mermaid flowchart TD @@ -1767,8 +1762,6 @@ flowchart TD NB -->|sign| OUT["New with 2 actions only: opened, placed"] ``` - - ### Basic action filtering When filtering, remember that the first action must remain `c2pa.created` or `c2pa.opened` for the manifest to be valid. If the first action is removed, a new one must be added. @@ -1817,7 +1810,7 @@ builder.sign(source_path, output_path, signer); Some actions reference ingredients (via `parameters.ingredients[].url` after signing). If keeping an action that references an ingredient, **the corresponding ingredient and its binary resources must also be kept**. If an ingredient is dropped, any actions that reference it must also be dropped (or updated). -#### `c2pa.opened` action +#### c2pa.opened action The `c2pa.opened` action is special because it must be the first action and it references the asset that was opened (the `parentOf` ingredient). When filtering: @@ -1825,7 +1818,7 @@ The `c2pa.opened` action is special because it must be the first action and it r - **Keep the ingredient it references**: the `parentOf` ingredient linked via its `parameters.ingredients[].url` - Removing the ingredient that `c2pa.opened` points to will make the manifest invalid -#### `c2pa.placed` action +#### c2pa.placed action The `c2pa.placed` action references a `componentOf` ingredient that was composited into the asset. When filtering: @@ -1945,8 +1938,6 @@ flowchart LR end ``` - - ```cpp c2pa::Builder builder(context, manifest_json); builder.set_no_embed(); diff --git a/docs/usage.md b/docs/usage.md index 57b72cff..b944d139 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -8,7 +8,7 @@ To use this library, include the header file in your code as follows: ## Read and validate an istream -Use the `Reader` constructor to read C2PA data from a stream. This constructor examines the specified stream for C2PA data in the given format and its return value is a Reader that can be used to extract more information. Exceptions are thrown on errors. +Use the `Reader` constructor to read C2PA data from a stream. This constructor examines the specified stream for C2PA data in the given format, and its return value is a `Reader` that you can use to extract more information. The constructor throws exceptions on errors. ```cpp c2pa::Context context; // or Context(settings) or Context(json_string) @@ -43,7 +43,7 @@ ifs.close(); The manifest JSON string defines the C2PA manifest to add to the file. -A sample JSON manifest is provided in [tests/fixtures/training.json](https://github.com/contentauth/c2pa-cpp/blob/main/tests/fixtures/training.json). +A sample JSON manifest is provided in [`tests/fixtures/training.json`](https://github.com/contentauth/c2pa-cpp/blob/main/tests/fixtures/training.json). For example: @@ -73,13 +73,14 @@ const std::string manifest_json = R"{ ## Using settings -The behavior of the SDK can be configured through various [settings](https://opensource.contentauthenticity.org/docs/manifest/json-ref/settings-schema/) loaded from JSON config files or JSON strings directly in the code. +You can configure the behavior of the SDK through various [settings](https://opensource.contentauthenticity.org/docs/manifest/json-ref/settings-schema/) loaded from JSON config files or JSON strings directly in the code. -SDK settings are set on the `Context` objects used by the Builder and Reader objects. -For full details see [Configuring the SDK with Context and Settings](context-settings.md). +You set SDK settings on the `Context` objects used by the `Builder` and `Reader` objects. +For full details, see [Configuring the SDK with Context and Settings](context-settings.md). -> [!NOTE] If you don't specify a value for a property, then the SDK will use the default value. -> If you specify a value of null, then the property will be set to null, not the default. +> [!NOTE] +> If you don't specify a value for a property, the SDK uses the default value. +> If you specify a value of null, the property is set to null, not the default. ### Creating a Context @@ -108,7 +109,7 @@ c2pa::Context context(R"({ #### Using a Context -Contexts are passed by reference to `Builder` and `Reader` constructors. The context is used only at construction; the implementation copies context state into the reader/builder, so the context does not need to outlive them. +You pass contexts by reference to `Builder` and `Reader` constructors. The constructor uses the context only at construction; the implementation copies context state into the `Reader`/`Builder`, so the context does not need to outlive them. ```cpp c2pa::Context context; @@ -139,7 +140,7 @@ For example: ## Creating a Signer -For testing, you can create a signer using any supported algorithm by a `Signer` constructor. For the list of supported signing algorithms, see [Creating and using an X.509 certificate](https://opensource.contentauthenticity.org/docs/c2patool/x_509). +For testing, you can create a signer with any supported algorithm using a `Signer` constructor. For the list of supported signing algorithms, see [Creating and using an X.509 certificate](https://opensource.contentauthenticity.org/docs/c2patool/x_509). There are multiple constructor forms; this example shows how to create a signer with a public/private key pair. @@ -148,10 +149,10 @@ There are multiple constructor forms; this example shows how to create a signer ``` The parameters are: -- ``- The `C2paSigningAlg` from `c2pa.h` associated with the signing function. -- ``- A buffer containing the public cert chain in PEM format. -- ``- A buffer containing the private_key in PEM format. -- ``- An optional parameter containing a URL to a public Time Stamp Authority service. +- `` - The `C2paSigningAlg` from `c2pa.h` associated with the signing function. +- `` - A buffer containing the public cert chain in PEM format. +- `` - A buffer containing the `private_key` in PEM format. +- `` - An optional parameter containing a URL to a public time stamp authority service. For example: @@ -160,11 +161,11 @@ Signer signer = c2pa::Signer("Es256", certs, private_key, "http://timestamp.digi ``` > [!WARNING] -> Do not access a private key and certificate directly like this in production because it's not secure. Instead use a hardware security module (HSM) and optionally a Key Management Service (KMS) to access the key; for example as shown in the [C2PA Python Example](https://github.com/contentauth/c2pa-python-example). +> Do not access a private key and certificate directly like this in production because it's not secure. Instead, use a hardware security module (HSM) and optionally a key management service (KMS) to access the key, for example as shown in the [C2PA Python Example](https://github.com/contentauth/c2pa-python-example). ## Signing and embedding a manifest -A media file may contain many manifests in a manifest store. The `active_manifest` property in the manifest store identifies the most recently-added manifest. For a comprehensive reference to the JSON manifest structure, see the [CAI manifest store reference](https://opensource.contentauthenticity.org/docs/manifest/manifest-ref). +A media file may contain many manifests in a manifest store. The `active_manifest` property in the manifest store identifies the most recently added manifest. For a comprehensive reference to the JSON manifest structure, see the [CAI manifest store reference](https://opensource.contentauthenticity.org/docs/manifest/manifest-ref). ```cpp auto manifest_data = builder.sign(image_path, output_path, signer); @@ -172,8 +173,8 @@ A media file may contain many manifests in a manifest store. The `active_manifes The parameters are: -- ``- A file path or an istream referencing the asset to sign. -- ``- A file path or an iostream referencing the asset to generate. +- `` - A file path or an istream referencing the asset to sign. +- `` - A file path or an iostream referencing the asset to generate. - `` - A `Signer` instance. For example: @@ -185,14 +186,14 @@ For example: The C++ library can validate [CAWG identity assertions](https://cawg.io/identity/). -## On trust configurations +## Trust configurations C2PA maintains two [trust lists](https://opensource.contentauthenticity.org/docs/conformance/trust-lists/) to verify the authenticity and integrity of Content Credentials attached to digital media: the C2PA trust list and the C2PA time-stamping authority (TSA) trust list. The C2PA trust list is a list of X.509 certificate trust anchors (either root or subordinate certification authorities) that issue certificates to conforming generator products under the C2PA Certificate Policy. The C2PA time-stamping authority (TSA) trust list is a list of X.509 certificate trust anchors (either root or subordinate certification authorities) that issue time-stamp signing certificates to TSAs. Configure trust lists using the `Settings` and `Context` APIs. Using the `Context` API ensures proper propagation of settings (and trust) to `Builder` and `Reader` objects. -Trust affects manifest validation status: a manifest whose trust chain was verified will be flagged as `Trusted`. +Trust affects manifest validation status: if validation verifies a manifest's trust chain, it flags the manifest as `Trusted`. ## More examples diff --git a/docs/working-stores.md b/docs/working-stores.md index 05fb3b72..f87e41ec 100644 --- a/docs/working-stores.md +++ b/docs/working-stores.md @@ -8,7 +8,7 @@ This table summarizes the fundamental entities that you work with when using the | [**Working store**](#working-store) | Editable in-progress manifest. | `Builder` object | [`Builder`](https://contentauth.github.io/c2pa-cpp/da/db7/classc2pa_1_1Builder.html) class | | [**Archive**](#archive) | Serialized working store | `.c2pa` file/stream | [`Builder::to_archive()`](https://contentauth.github.io/c2pa-cpp/da/db7/classc2pa_1_1Builder.html#a68074eac71b7fc57d338019220101db3)
[`Builder::from_archive()`](https://contentauth.github.io/c2pa-cpp/da/db7/classc2pa_1_1Builder.html#a913c64f6b5ec978322ef0edc89e407b3) | | [**Resources**](#working-with-resources) | Binary assets referenced by manifest assertions, such as thumbnails or ingredient thumbnails. | In manifest. | [`Builder::add_resource()`](https://contentauth.github.io/c2pa-cpp/da/db7/classc2pa_1_1Builder.html#a45bf6fc8163b0194b334aa21f73f8476)
[`Reader::get_resource`](https://contentauth.github.io/c2pa-cpp/d9/dbb/classc2pa_1_1Reader.html#a308939c990cab98bf8435c699bc96096) | -| [**Ingredients**](#working-with-ingredients) | Source materials used to create an asset. | In manifest. | [`builder.add_ingredient`](https://contentauth.github.io/c2pa-cpp/da/db7/classc2pa_1_1Builder.html#a49407f9604a53b5b68bcfa699cba05f5) +| [**Ingredients**](#working-with-ingredients) | Source materials used to create an asset. | In manifest. | [`builder.add_ingredient`](https://contentauth.github.io/c2pa-cpp/da/db7/classc2pa_1_1Builder.html#a49407f9604a53b5b68bcfa699cba05f5) | This diagram summarizes the relationships among these entities. @@ -216,7 +216,9 @@ std::string manifest_store_json = reader.json(); // This is the manifest store ## Creating and signing manifests -### Creating a Builder (working store) +### Creating a Builder + +A `Builder` object represents the working store. ```cpp // Create with default settings @@ -285,7 +287,7 @@ try { } ``` -### Signing with streams (recommended) +### Signing with streams The stream API is recommended as it provides better control and memory efficiency: @@ -451,7 +453,7 @@ An **ingredient archive** (in c2pa-archive-format) is a `.c2pa` file that alread Once an ingredient is archived, the original ingredient asset is no longer needed: the `.c2pa` ingredient archive stands in for it and carries the ingredient's provenance. > [!NOTE] -> The relationship is one-directional. For legacy support you can _read_ an ingredient out of a builder archive, but you should not try to restore a `Builder` from an ingredient archive — consume it as an ingredient with `add_ingredient_from_archive` (or the legacy `add_ingredient(json, "application/c2pa", archive)` path) instead. +> The relationship is one-directional. For legacy support you can _read_ an ingredient out of a builder archive, but you should not try to restore a `Builder` from an ingredient archive: consume it as an ingredient with `add_ingredient_from_archive` (or the legacy `add_ingredient(json, "application/c2pa", archive)` path) instead. For the dedicated ingredient archive APIs, see [Single-ingredient archive APIs](#single-ingredient-archive-apis). @@ -459,13 +461,13 @@ This difference governs how each can be linked to an action via `ingredientIds`. | Aspect | Ingredient | Ingredient archive (dedicated APIs: `write_ingredient_archive` + `add_ingredient_from_archive`) | Ingredient archive (legacy load via `add_ingredient(json, "application/c2pa", archive)`) | | --- | --- | --- | --- | -| Source format passed to `add_ingredient` | Asset MIME type (`image/jpeg`, `video/mp4`, ...) or asset path | N/A — loaded via `add_ingredient_from_archive(stream)` | `application/c2pa` or path to a `.c2pa` ingredient archive file | +| Source format passed to `add_ingredient` | Asset MIME type (`image/jpeg`, `video/mp4`, ...) or asset path | N/A: loaded via `add_ingredient_from_archive(stream)` | `application/c2pa` or path to a `.c2pa` ingredient archive file | | What it is | "Live" asset | A serialized single-ingredient archive (opaque provenance) | A serialized manifest store (opaque provenance) | | Linking via `label` | Primary linking key, set on the signing builder's `add_ingredient` JSON parameter | Pass `label` value as archive key to `write_ingredient_archive`; flows through as `ingredientIds` value | Only linking key that works, set on the signing builder's `add_ingredient` JSON | | Linking via `instance_id` | Alternative to using `label` | Pass `instance_id` value as archive key to `write_ingredient_archive`; flows through as `ingredientIds` value | Does not link, signing-time error | -| Linking via a `label` baked in at archive-creation time | N/A (not an archive) | N/A — archive key is set explicitly at `write_ingredient_archive` call time | Does not carry through, must be re-asserted on the signing builder's `add_ingredient` JSON parameter | +| Linking via a `label` baked in at archive-creation time | N/A (not an archive) | N/A: archive key is set explicitly at `write_ingredient_archive` call time | Does not carry through, must be re-asserted on the signing builder's `add_ingredient` JSON parameter | -#### When to use `label` vs `instance_id` +#### When to use label vs instance_id | Property | `label` | `instance_id` | | --- | --- | --- | @@ -518,7 +520,7 @@ Two paths exist for loading an ingredient archive and linking it to an action. T The archive key passed to `write_ingredient_archive` (either a `label` or `instance_id` value) flows through automatically and becomes the `ingredientIds` value on the signing builder. No re-assertion is needed. -Producer — register the ingredient and write the archive, keyed by `instance_id`: +Producer: register the ingredient and write the archive, keyed by `instance_id`. ```cpp auto settings = c2pa::Settings(); @@ -536,7 +538,7 @@ std::stringstream archive(std::ios::in | std::ios::out | std::ios::binary); producer.write_ingredient_archive("my-ingredient", archive); ``` -Signing builder — use the same archive key string in `ingredientIds`, then load the archive: +Signing builder: use the same archive key string in `ingredientIds`, then load the archive. ```cpp auto signing_manifest = R"({ @@ -564,7 +566,7 @@ For the full table of linking outcomes when `label`, `instance_id`, or both are When linking multiple ingredient archives, write one archive per ingredient with a distinct key and load each with `add_ingredient_from_archive`. List all keys in the appropriate `ingredientIds` arrays. -#### Using the legacy load path (deprecated) +#### Using the legacy load path > [!IMPORTANT] > **For the legacy load path, linking an ingredient archive is `label`-driven only.** @@ -867,7 +869,7 @@ The `Builder` class exposes two dedicated APIs for moving a single ingredient be - `Builder::write_ingredient_archive(id, stream)` writes one already-registered ingredient out as a single-ingredient JUMBF archive. - `Builder::add_ingredient_from_archive(stream)` loads one such archive into a builder. -#### How `add_ingredient` and `write_ingredient_archive` interact +#### How add_ingredient and write_ingredient_archive interact `add_ingredient(json, source)` is the registration step. It hashes the source asset, builds the ingredient assertion, and stores the ingredient in the builder under an id read from the JSON. The id is the `label` field if present, otherwise `instance_id`. @@ -947,7 +949,7 @@ builder.write_ingredient_archive("wrong-id", stream); For a multi-archive use case (one catalog, many ingredients picked at build time), see [The ingredients catalog pattern](./selective-manifests.md#the-ingredients-catalog-pattern) in the selective manifests guide. -#### Migration guide: from `to_archive` / `from_archive` to single-ingredient APIs +#### Migrating from to_archive and from_archive to single-ingredient APIs The legacy approach wrapped one ingredient in a full builder archive, then restored it with `from_archive`: @@ -992,7 +994,7 @@ Key differences: no JSON parsing, no `add_resource` loops, each archive holds ex Action linking also changes between the two APIs. The legacy load path (`add_ingredient(json, "application/c2pa", archive)`) accepts only `label` as the linking key on the signing builder's `add_ingredient` JSON. See [Linking an ingredient archive to an action](#linking-an-ingredient-archive-to-an-action). The dedicated ingredient archive APIs (`write_ingredient_archive` + `add_ingredient_from_archive`) accept the archive key, which can be either `label` or `instance_id`. See [Lookup keys and action linking](#lookup-keys-and-action-linking). When migrating code that linked by label, pass that same label as the archive key to keep `ingredientIds` unchanged. -## How `instance_id` survives archiving and signing +## How instance_id survives archiving and signing ### What is an instance_id? @@ -1031,7 +1033,7 @@ The linking label is a builder-only concept. It does not appear in `Reader::json If the archive key matches neither `label` nor `instance_id` of any ingredient on the producer builder, `write_ingredient_archive` throws immediately with `C2paException`. -#### Linking with `instance_id` only +#### Linking with instance_id only When no `label` is set, pass the `instance_id` value to `write_ingredient_archive`. Use that same string in `ingredientIds` on the signing builder. @@ -1068,7 +1070,7 @@ consumer.add_ingredient_from_archive(archive); consumer.sign(source_path, output_path, signer); ``` -#### Linking with `label` only +#### Linking with label only When only `label` is set, pass the `label` value to `write_ingredient_archive`. Use that same string in `ingredientIds`. @@ -1105,7 +1107,7 @@ consumer.add_ingredient_from_archive(archive); consumer.sign(source_path, output_path, signer); ``` -#### Linking when both `label` and `instance_id` are set +#### Linking when both label and instance_id are set If both `label` and `instance_id` are set on an ingredient, pass whichever value is to be used as the linking key to `write_ingredient_archive`. That string, and only that string, is what `ingredientIds` must reference on the signing builder. @@ -1180,7 +1182,7 @@ auto reader = c2pa::Reader("signed.jpg"); assert(reader.is_embedded() == true); // Manifest store is embedded ``` -### External manifest stores (no embed) +### External manifest stores Prevent embedding the manifest store in the asset: From 2f7c9e74748229d342808f778ca8c605761e76e6 Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Fri, 24 Jul 2026 14:33:51 -0700 Subject: [PATCH 3/3] Remove Rust-specific stuff from CONTRIBUTING.md --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b97ecbdb..77ecca30 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -52,9 +52,9 @@ by project committers. Read [GitHub's pull request documentation](https://help.g for more information on sending pull requests. Code submissions will need to pass all automated tests in place at the time of submission. -These include such things as Rust code format, Clippy/lint checks, and unit test coverage. +These include such things as code format, lint checks, and unit test coverage. -We encourage you to raise an issue in GitHub before starting work on a major addition to the crate. +We encourage you to raise an issue in GitHub before starting work on a major addition to the library. This will give us an opportunity to discuss API design and avoid duplicate efforts. ### Pull request titles