1"use strict";(globalThis.webpackChunkopensource_contentauth_org=globalThis.webpackChunkopensource_contentauth_org||[]).push([[2750],{8453:(e,s,t)=>{t.d(s,{R:()=>d,x:()=>i});var r=t(6540);const n={},a=r.createContext(n);function d(e){const s=r.useContext(a);return r.useMemo(function(){return"function"==typeof e?e(s):{...s,...e}},[s,e])}function i(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(n):e.components||n:d(e.components),r.createElement(a.Provider,{value:s},e.children)}},8891:(e,s,t)=>{t.r(s),t.d(s,{assets:()=>l,contentTitle:()=>i,default:()=>c,frontMatter:()=>d,metadata:()=>r,toc:()=>h});const r=JSON.parse('{"id":"sdk-repos/c2pa-rs/docs/embeddable-api","title":"Embeddable signing API","description":"[!WARNING]","source":"@site/docs/sdk-repos/c2pa-rs/docs/embeddable-api.md","sourceDirName":"sdk-repos/c2pa-rs/docs","slug":"/sdk-repos/c2pa-rs/docs/embeddable-api","permalink":"/docs/sdk-repos/c2pa-rs/docs/embeddable-api","draft":false,"unlisted":false,"editUrl":"https://github.com/contentauth/c2pa-rs/edit/main/docs/embeddable-api.md","tags":[],"version":"current","frontMatter":{},"sidebar":"docs","previous":{"title":"InstanceID behavior","permalink":"/docs/sdk-repos/c2pa-rs/docs/instanceid-behavior"},"next":{"title":"Release notes (Rust)","permalink":"/docs/sdk-repos/c2pa-rs/docs/release-notes"}}');var n=t(4848),a=t(8453);const d={},i="Embeddable signing API",l={},h=[{value:"Why use the embeddable API",id:"why-use-the-embeddable-api",level:2},{value:"Concepts",id:"concepts",level:2},{value:"Hard-binding modes",id:"hard-binding-modes",level:3},{value:"Placeholder sizing",id:"placeholder-sizing",level:3},{value:"API summary",id:"api-summary",level:2},{value:"Using the DataHash placeholder",id:"using-the-datahash-placeholder",level:2},{value:"Using the BmffHash placeholder",id:"using-the-bmffhash-placeholder",level:2}];function o(e){const s={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,a.R)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(s.header,{children:(0,n.jsx)(s.h1,{id:"embeddable-signing-api",children:"Embeddable signing API"})}),"\n",(0,n.jsx)(s.admonition,{type:"warning",children:(0,n.jsxs)(s.p,{children:["The embeddable signing API is for advanced use cases that require very low-level control. Most users won't need it and can instead use the standard ",(0,n.jsx)(s.code,{children:"Builder"})," methods."]})}),"\n",(0,n.jsxs)(s.p,{children:["The embeddable signing API provides direct 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 streams to ",(0,n.jsx)(s.code,{children:"Builder::sign()"}),", you perform each step explicitly:"]}),"\n",(0,n.jsxs)(s.ol,{children:["\n",(0,n.jsx)(s.li,{children:"Create a placeholder."}),"\n",(0,n.jsx)(s.li,{children:"Embed the placeholder yourself."}),"\n",(0,n.jsx)(s.li,{children:"Hash the asset."}),"\n",(0,n.jsx)(s.li,{children:"Sign the claim."}),"\n",(0,n.jsx)(s.li,{children:"Patch the manifest in place."}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:["This new, more generic API replaces the following ",(0,n.jsx)(s.code,{children:"Builder"})," methods that will soon be deprecated:"]}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:(0,n.jsx)(s.a,{href:"https://docs.rs/c2pa/latest/c2pa/struct.Builder.html#method.data_hashed_placeholder",children:(0,n.jsx)(s.code,{children:"data_hashed_placeholder()"})})}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"https://docs.rs/c2pa/latest/c2pa/struct.Builder.html#method.sign_data_hashed_embeddable",children:(0,n.jsx)(s.code,{children:"sign_data_hashed_embeddable()"})})," and ",(0,n.jsx)(s.a,{href:"https://docs.rs/c2pa/latest/c2pa/struct.Builder.html#method.sign_data_hashed_embeddable_async",children:(0,n.jsx)(s.code,{children:"sign_data_hashed_embeddable_async()"})})]}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:"why-use-the-embeddable-api",children:"Why use the embeddable API"}),"\n",(0,n.jsxs)(s.p,{children:["The original ",(0,n.jsx)(s.code,{children:"Builder::sign()"})," handles the full pipeline internally:"]}),"\n",(0,n.jsx)(s.pre,{children:(0,n.jsx)(s.code,{className:"language-rust",children:"// Old approach: SDK controls all I/O\nlet manifest_bytes = builder.sign(signer, format, &mut source, &mut dest)?;\n"})}),"\n",(0,n.jsx)(s.p,{children:"That works well for simple cases but be
1comes a problem when:"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"You control the I/O pipeline."})," Video transcoders, streaming ingest services, and other tools have their own asset-writing code. Transferring stream ownership to the SDK conflicts with that architecture."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"The asset is too large to buffer."})," The SDK's ",(0,n.jsx)(s.code,{children:"sign()"})," may re-read large files. With the embeddable API, you can hash chunks as you write them and pass the results directly to the builder."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.strong,{children:"You need in-place patching."})," Some formats store the manifest in a known location. After signing, only that location changes, allowing you to write only those bytes."]}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:"concepts",children:"Concepts"}),"\n",(0,n.jsx)(s.h3,{id:"hard-binding-modes",children:"Hard-binding modes"}),"\n",(0,n.jsx)(s.p,{children:"The embeddable API supports three hard-binding strategies, selected automatically based on format and settings:"}),"\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"Mode"}),(0,n.jsx)(s.th,{children:"Assertion"}),(0,n.jsx)(s.th,{children:"Formats"}),(0,n.jsx)(s.th,{children:"Requires placeholder"})]})}),(0,n.jsxs)(s.tbody,{children:[(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.a,{href:"#using-the-datahash-placeholder",children:"DataHash"})}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"DataHash"})}),(0,n.jsx)(s.td,{children:"JPEG, PNG, GIF, WebP, and others"}),(0,n.jsx)(s.td,{children:"Yes"})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.a,{href:"#using-the-bmffhash-placeholder",children:"BmffHash"})}),(0,n.jsx)(s.td,{children:(0,n.jsx)(s.code,{children:"BmffHash"})}),(0,n.jsx)(s.td,{children:"MP4, video (BMFF containers), AVIF, HEIF/HEIC"}),(0,n.jsx)(s.td,{children:"Yes"})]})]})]}),"\n",(0,n.jsx)(s.p,{children:"These formats support chunk-based hashing. This mode inserts the manifest as an independent chunk so byte offsets of existing data are never disturbed, which removes the need for a pre-sized placeholder."}),"\n",(0,n.jsx)(s.h3,{id:"placeholder-sizing",children:"Placeholder sizing"}),"\n",(0,n.jsxs)(s.p,{children:["When a placeholder is required, the SDK pre-sizes the JUMBF manifest based on its current state and records the target length internally. After signing, ",(0,n.jsx)(s.a,{href:"https://docs.rs/c2pa/latest/c2pa/struct.Builder.html#method.sign_embeddable",children:(0,n.jsx)(s.code,{children:"sign_embeddable()"})})," pads the compressed manifest to exactly that length so you can overwrite the placeholder bytes without shifting any other data in the file."]}),"\n",(0,n.jsx)(s.h2,{id:"api-summary",children:"API summary"}),"\n",(0,n.jsxs)(s.table,{children:[(0,n.jsx)(s.thead,{children:(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.th,{children:"Method"}),(0,n.jsx)(s.th,{children:"Description"})]})}),(0,n.jsxs)(s.tbody,{children:[(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.a,{href:"https://docs.rs/c2pa/latest/c2pa/struct.Builder.html#method.needs_placeholder",children:(0,n.jsx)(s.code,{children:"needs_placeholder"})})}),(0,n.jsxs)(s.td,{children:["Returns ",(0,n.jsx)(s.code,{children:"true"})," when the format requires a pre-embedded placeholder before hashing. Always ",(0,n.jsx)(s.code,{children:"true"})," for BMFF formats. Returns ",(0,n.jsx)(s.code,{children:"false"})," a ",(0,n.jsx)(s.code,{children:"BoxHash"})," assertion has already been added."]})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.a,{href:"https://docs.rs/c2pa/latest/c2pa/struct.Builder.html#method.placeholder",children:(0,n.jsx)(s.code,{children:"placeholder"})})}),(0,n.jsxs)(s.td,{children:["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 (",(0,n.jsx)(s.code,{children:"BmffHash"})," for BMFF formats, ",(0,n.jsx)(s.code,{children:"DataHash"})," for others). Stores the JUMBF length internally so ",(0,n.jsx)(s.code,{children:"sign_embeddable()"})," can pad to the same size."]})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.a,{href:"https://docs.rs/c2pa/latest/c2pa/struct.Builder.html#method.set_data_hash_exclusions",children:(0,n.jsx)(s.code,{children:"set_data_hash_exclusions"})})}),(0,n.jsxs)(s.td,{children:["Replaces the dummy exclusion ranges in the ",(0,n.jsx)(s.code,{children:"DataHash"})," assertion with the actual byte offset and length of the embedded placeholder. Call after embedding placeholder bytes and before ",(0,n.jsx)(s.code,{children:"update_hash_from_stream()"}),"."]})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.a,{href:"https://docs.rs/c2pa/latest/c2pa/struct.Builder.html#method.update_hash_from_stream",children:(0,n.jsx)(s.code,{children:"update_hash_from_stream"})})}),(0,n.jsxs)(s.td,{children:["Reads the asset and computes the hard-binding hash. Automatically selects the appropriate path based on format: ",(0,n.jsx)(s.code,{children:"BmffHash"})," for BMFF (skips manifest box), ",(0,n.jsx)(s.code,{children:"BoxHash"})," for chunk-based formats (creates assertion if needed), or ",(0,n.jsx)(s.code,{children:"DataHash"})," (skips exclusion ranges)."]})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.a,{href:"https://docs.rs/c2pa/latest/c2pa/struct.Builder.html#method.set_bmff_mdat_hashes",children:(0,n.jsx)(s.code,{children:"set_bmff_mdat_hashes"})})}),(0,n.jsxs)(s.td,{children:["Provides pre-computed Merkle leaf hashes for ",(0,n.jsx)(s.code,{children:"mdat"})," segments in BMFF assets. Use when your code already hashes ",(0,n.jsx)(s.code,{children:"mdat"})," chunks during writing/transcoding to avoid re-reading large files. Call before ",(0,n.jsx)(s.code,{children:"sign_embeddable()"}),"."]})]}),(0,n.jsxs)(s.tr,{children:[(0,n.jsx)(s.td,{children:(0,n.jsx)(s.a,{href:"https://docs.rs/c2pa/latest/c2pa/struct.Builder.html#method.sign_embeddable",children:(0,n.jsx)(s.code,{children:"sign_embeddable"})})}),(0,n.jsx)(s.td,{children:"Signs the manifest and returns bytes ready to embed. For placeholder workflows, pads to match placeholder size for in-place patching. For BoxHash/direct workflows, returns bytes at natural size for appending as a new chunk."})]})]})]}),"\n",(0,n.jsx)(s.h2,{id:"using-the-datahash-placeholder",children:"Using the DataHash placeholder"}),"\n",(0,n.jsx)(s.p,{children:"Use this workflow for JPEG, PNG, and other common image formats (not BMFF formats)."}),"\n",(0,n.jsxs)(s.p,{children:["For this workflow, make sure ",(0,n.jsx)(s.code,{children:"prefer_box_hash"})," in ",(0,n.jsx)(s.a,{href:"https://opensource.contentauthenticity.org/docs/manifest/json-ref/settings-schema#buildersettings",children:"Builder settings"})," is ",(0,n.jsx)(s.code,{children:"false"})," (the default)."]}),"\n",(0,n.jsx)(s.pre,{children:(0,n.jsx)(s.code,{className:"language-rust",children:'use std::io::{Cursor, Seek, Write};\nuse c2pa::{Builder, HashRange};\n\n// 1. Compose the placeholder \u2014 returns JPEG APP11 segments.\nlet placeholder_bytes = builder.placeholder("image/jpeg")?;\n\n// 2. Construct the output, inserting the placeholder after the JPEG SOI marker.\nlet source_bytes = std::fs::read("input.jpg")?;\nlet insert_offset: u64 = 2;\nlet mut output: Vec<u8> = Vec::new();\noutput.extend_from_slice(&source_bytes[..insert_offset as usize]);\noutput.extend_from_slice(&placeholder_bytes);\noutput.extend_from_slice(&source_bytes[insert_offset as usize..]);\nlet mut stream = Cursor::new(output);\n\n// 3. Tell the builder where the placeholder lives.\nbuilder.set_data_hash_exclusions(vec![\n HashRange::new(insert_offset, placeholder_bytes.len() as u64),\n])?;\n\n// 4. Hash the asset (placeholder bytes are excluded from the hash).\nbuilder.update_hash_from_stream("image/jpeg", &mut stream)?;\n\n// 5. Sign \u2014 returned bytes are the same size as placeholder_bytes.\nlet final_manifest = builder.sign_embeddable("image/jpeg")?;\n\n// 6. Overwrite the placeholder with the signed manifest.\nstream.seek(std::io::SeekFrom::Start(insert_offset))?;\nstream.write_all(&final_manifest)?;\n'})}),"\n",(0,n.jsx)(s.h2,{id:"using-the-bmffhash-placeholder",children:"Using the BmffHash placeholder"}),"\n",(0,n.jsx)(s.p,{children:"Use this workflow with MP4 and other BMFF formats, which always require a placeholder."}),"\n",(0,n.jsxs)(s.p,{children:["The SDK pre-allocates Merkle slots in the ",(0,n.jsxs)(s.a,{href:"https://docs.rs/c2pa/latest/c2pa/assertions/struct.BmffHash.html",children:[(0,n.jsx)(s.code,{children:"BmffHash"})," assertion"]}),"."]}),"\n",(0,n.jsx)(s.pre,{children:(0,n.jsx)(s.code,{className:"language-rust",children:'// 1. Compose the placeholder \u2014 returns a BMFF `uuid` box suitable for insertion.\nlet placeholder_bytes = builder.placeholder("video/mp4")?;\n\n// 2. Insert the placeholder box into the MP4 container at an appropriate location\n// (for example, before `mdat`). Your muxer/container writer controls this
1step.\nlet insert_offset = your_muxer.insert_manifest_box(&placeholder_bytes);\n\n// 3. Hash the asset. BmffHash handles exclusion of the manifest box automatically.\nbuilder.update_hash_from_stream("video/mp4", &mut your_stream)?;\n\n// 4. Sign and patch in place.\nlet final_manifest = builder.sign_embeddable("video/mp4")?;\nyour_stream.seek(std::io::SeekFrom::Start(insert_offset))?;\nyour_stream.write_all(&final_manifest)?;\n'})}),"\n",(0,n.jsxs)(s.p,{children:["If you hash ",(0,n.jsx)(s.code,{children:"mdat"})," segments at write time, pass the leaf hashes before signing:"]}),"\n",(0,n.jsx)(s.pre,{children:(0,n.jsx)(s.code,{className:"language-rust",children:'// leaf_hashes is Vec<Vec<Vec<u8>>>: outer = tracks, middle = chunks, inner = hash bytes\nbuilder.set_bmff_mdat_hashes(leaf_hashes)?;\nlet final_manifest = builder.sign_embeddable("video/mp4")?;\n'})})]})}function c(e={}){const{wrapper:s}={...(0,a.R)(),...e.components};return s?(0,n.jsx)(s,{...e,children:(0,n.jsx)(o,{...e})}):o(e)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.