Skip to main content

crates.io docs.rs CI OpenSSF Scorecard License

Overview

Implements the Embedding Manifests into Unstructured Text section of the C2PA Technical Specification, which carries a C2PA Manifest Store inside a Unicode text stream as a run of non-rendering variation selectors, so provenance survives copy and paste between systems that have no file.

Hello world.<U+FEFF><variation-selector run carrying magic|version|length|manifest>

The specification describes this method as one that should only be used where no other embedding method is feasible. For source code, configuration, and markup, prefer c2pa-structured-text. This crate exists for the case the spec carves out: text with no container.

This crate owns two things:

  1. The frame — encode, embed, locate, and strip the C2PATextManifestWrapper, including the specified deterministic padding.
  2. The hard binding — the exact c2pa.hash.data coverage for unstructured text, with compute and verify.

Signature verification, certificate trust, and assertion validation are not reimplemented here.

Not certified or conformance-tested by the C2PA. It implements the embedding and hard binding as specified.

Zero dependencies by default

[dependencies]
c2pa-unstructured-text = "0.1"

The frame and the binding algorithm pull nothing in. Hashing and NFC are injected through two traits, so a host that already provides them supplies its own:

use c2pa_unstructured_text::hardbinding::{Algorithm, Hasher, Normalizer};

struct HostCrypto;
impl Hasher for HostCrypto {
    fn digest(&self, alg: Algorithm, data: &[u8]) -> Vec<u8> { todo!("call the runtime") }
}
struct HostNfc;
impl Normalizer for HostNfc {
    fn nfc(&self, text: &str) -> String { todo!("call the runtime") }
}

That matters at the edge. A Cloudflare Worker or a browser already has SHA-2 and String.prototype.normalize, so shipping Unicode tables into the bundle is pure waste. Enable hard-binding to get ready-made implementations instead; it adds convenience, never capability.

Embed and extract

use c2pa_unstructured_text::wrapper;

let asset = wrapper::embed("Hello world.", b"manifest-bytes").unwrap();

let found = wrapper::extract(&asset).unwrap();
assert_eq!(found.payload, b"manifest-bytes");
assert_eq!(wrapper::strip(&asset, found.range()).unwrap(), "Hello world.");

The hard binding

The exclusion range covers the U+FEFF marker together with the selector run, including any trailing padding. Offsets are into the text as stored, before normalization. A validator removes the excluded bytes first, normalizes what remains to NFC, then hashes.

# #[cfg(feature = "hard-binding")] {
use c2pa_unstructured_text::hardbinding::{
    compute_data_hash, verify_data_hash, Algorithm, RustCrypto, UnicodeNfc,
};
use c2pa_unstructured_text::wrapper;

let asset = wrapper::embed("Hello world.", b"manifest-bytes").unwrap();
let binding =
    compute_data_hash(&asset, Algorithm::Sha256, &RustCrypto, &UnicodeNfc).unwrap();

assert!(verify_data_hash(&asset, &binding, &RustCrypto, &UnicodeNfc).is_ok());
# }

Normalizing before computing offsets would shift every one of them whenever the stored text is not already NFC, which is why the order is fixed this way.

Deterministic padding

The wrapper's byte length depends on the manifest's byte distribution, since low bytes encode to three UTF-8 bytes and the rest to four. That is circular when the exclusion length has to go inside the manifest being measured. The specification breaks the cycle with a target that depends only on the manifest size, and fixes the padding byte values so compliant generators emit byte-identical wrappers:

use c2pa_unstructured_text::wrapper;

assert_eq!(wrapper::target_length(16), 125);
assert_eq!(wrapper::encode(b"c2pa-manifest-01").unwrap().len(), 114);
// The 11-byte gap decomposes as one 0x00 then two 0x10.
assert_eq!(wrapper::padding(11).unwrap(), vec![0x00, 0x10, 0x10]);
assert_eq!(wrapper::encode_padded(b"c2pa-manifest-01").unwrap().len(), 125);

This is the part of the method implementations most easily diverge on, so it is covered by vectors cross-checked against the C2PA public test corpus.

Locating

The specification defines two failure codes here, and the crate reports both:

outcome code meaning
no wrapper, no candidate (none) the text is unsigned
candidate detected, none decodes manifest.text.corruptedWrapper magic found, frame malformed
more than one valid wrapper manifest.text.multipleWrappers rejected

Only the first means the text carries no provenance, which is what is_no_manifest_located() reports — an integrator needs to tell "carries no provenance" from "carried provenance that was rejected":

use c2pa_unstructured_text::{wrapper, Error};

let err = wrapper::extract("no wrapper here").unwrap_err();
assert_eq!(err, Error::NotFound);
assert!(err.is_no_manifest_located());
assert_eq!(err.code(), None);

A candidate that fails to decode beside a valid wrapper is skipped, not reported: letting stray bytes carrying the magic invalidate an otherwise good wrapper would hand anyone who can append to the text a denial of service.

The specification is in tension here. Placement rule 5 says a validator "may encounter multiple wrappers" and that selection "is governed by the exclusions field", while the status-code section makes more than one valid wrapper a failure. This crate follows the explicit failure code.

Comparison with the structured-text binding

Both crates expose the same shape, so a dispatcher can treat them alike, but coverage differs deliberately.

unstructured (this crate) structured
carrier variation selectors ASCII armour comment
normalization NFC, after exclusion none
exclusion marker + selector run + padding the manifest block line
default deps none none

Features

  • hard-bindingRustCrypto and UnicodeNfc implementations (pulls sha2 and unicode-normalization).
  • checksum-v2 — a v2 frame carrying a truncated hash over the header and payload, so a mangled carrier is rejected rather than decoded to wrong bytes. A WritersLogic extension, not part of the specified frame.

No feature is enabled by default.

Related Crates

Part of a family of single-purpose crates, one per C2PA embedding method. Each is standalone and independently versioned.

Crate Description
c2pa-structured-text Structured text: ASCII-armoured manifest in a comment or front matter
c2pa-html HTML: script and link elements in the document head
c2pa-http HTTP: the c2pa-manifest Link header, with a Tower middleware
c2pa-text-binding Soft binding and content fingerprinting for text assets
c2pa-vtt WebVTT caption and subtitle embedding
c2pa-zip ZIP-based documents: EPUB, DOCX, ODT, OXPS
c2pa-warc WARC web archive embedding (ISO 28500)
c2pa-fonts OpenType/TrueType (SFNT) font embedding
c2pa-ml ML model containers: GGUF, SafeTensors, ONNX
c2pa Official C2PA SDK

Security

Found a vulnerability? Please report it privately — see SECURITY.md.

License

Licensed under either of Apache License, Version 2.0 or MIT License at your option.

Built by WritersLogic

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

c2pa_unstructured_text-0.2.0.tar.gz (42.2 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

c2pa_unstructured_text-0.2.0-cp39-abi3-win_amd64.whl (215.3 kB view details)

Uploaded CPython 3.9+Windows x86-64

c2pa_unstructured_text-0.2.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (348.5 kB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ x86-64

c2pa_unstructured_text-0.2.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (342.0 kB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ ARM64

c2pa_unstructured_text-0.2.0-cp39-abi3-macosx_11_0_arm64.whl (313.7 kB view details)

Uploaded CPython 3.9+macOS 11.0+ ARM64

c2pa_unstructured_text-0.2.0-cp39-abi3-macosx_10_12_x86_64.whl (321.7 kB view details)

Uploaded CPython 3.9+macOS 10.12+ x86-64

File details

Details for the file c2pa_unstructured_text-0.2.0.tar.gz.

File metadata

  • Download URL: c2pa_unstructured_text-0.2.0.tar.gz
  • Upload date:
  • Size: 42.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for c2pa_unstructured_text-0.2.0.tar.gz
Algorithm Hash digest
SHA256 33967c2364444b2a38406b4eaedaa8590465464bf8d03656cff42e2a57583170
MD5 c6506d14004ca68ae527e7781a21ce17
BLAKE2b-256 d2139573de339a145fb6eb2a84a66b2df9b8285d7cc2ebd8330a4012b3c8d76f

See more details on using hashes here.

Provenance

The following attestation bundles were made for c2pa_unstructured_text-0.2.0.tar.gz:

Publisher: release.yml on writerslogic/c2pa-unstructured-text

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file c2pa_unstructured_text-0.2.0-cp39-abi3-win_amd64.whl.

File metadata

File hashes

Hashes for c2pa_unstructured_text-0.2.0-cp39-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 8ed76f30b833ab612555cf87614c14f33eed89ae0d36fb284a31ae83e46b0556
MD5 d7135e4cf6299cea3d63ea87026a1fb8
BLAKE2b-256 24efc47e03176d4887140dcbbf2363f5568de52a2ff14e24418732b50ec5627d

See more details on using hashes here.

Provenance

The following attestation bundles were made for c2pa_unstructured_text-0.2.0-cp39-abi3-win_amd64.whl:

Publisher: release.yml on writerslogic/c2pa-unstructured-text

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file c2pa_unstructured_text-0.2.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for c2pa_unstructured_text-0.2.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 286650cc7c02209e82212cf9be5734263e1e164067e1372fd6d92d6a546e7470
MD5 657b6339b8dedeed2f7f2a94d377c939
BLAKE2b-256 c856eb28f730c1b18222678679e0950a0fe61d086aa2e9176cc58622236ced8b

See more details on using hashes here.

Provenance

The following attestation bundles were made for c2pa_unstructured_text-0.2.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release.yml on writerslogic/c2pa-unstructured-text

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file c2pa_unstructured_text-0.2.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for c2pa_unstructured_text-0.2.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 52c4ae17816de93e302dbbaed14b4d27a9ffb1882bab0b2174c2cd3385843b47
MD5 ee40b68482d405a94f7ac3df2f08548d
BLAKE2b-256 1252bcc91780909e644c87bb6f534ec2b8e6047bd765f74f7aff17381ab8fdab

See more details on using hashes here.

Provenance

The following attestation bundles were made for c2pa_unstructured_text-0.2.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: release.yml on writerslogic/c2pa-unstructured-text

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file c2pa_unstructured_text-0.2.0-cp39-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for c2pa_unstructured_text-0.2.0-cp39-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 130711a72ef1f3dc6aa93c75ebe38ae8b83cf1b437bedb088bc906906f633196
MD5 23b9911d74652e420a320fa62d29ebb1
BLAKE2b-256 50a595656f39106049b30c48a3adaf42ab76cf2fd9edc30d14753f563619e10d

See more details on using hashes here.

Provenance

The following attestation bundles were made for c2pa_unstructured_text-0.2.0-cp39-abi3-macosx_11_0_arm64.whl:

Publisher: release.yml on writerslogic/c2pa-unstructured-text

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file c2pa_unstructured_text-0.2.0-cp39-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for c2pa_unstructured_text-0.2.0-cp39-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 19ab781a74e25177de58ce538aa70dd91e4f21deef0e9c7b5db38c56bb0bfe2d
MD5 f6f902b8b1dfa812e8e3597c63db5714
BLAKE2b-256 4928fdb2c67c330acaa71a9e2ae300431cccca634fa89a3dc2f91f57e4dde175

See more details on using hashes here.

Provenance

The following attestation bundles were made for c2pa_unstructured_text-0.2.0-cp39-abi3-macosx_10_12_x86_64.whl:

Publisher: release.yml on writerslogic/c2pa-unstructured-text

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.2.0 This release

6 files

0.1.0

6 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page