feat(png): carry the C2PA manifest store in the caBX chunk - #511
Open
justin13888 wants to merge 1 commit into
Open
feat(png): carry the C2PA manifest store in the caBX chunk#511justin13888 wants to merge 1 commit into
justin13888 wants to merge 1 commit into
Conversation
Read and write the C2PA manifest store (C2PA 2.4 §A.3.2) as one more raw, MetadataBlock-ready ancillary payload, the way eXIf/iCCP/XMP already travel. Decode: `DecodedPng::c2pa` / `PngMetadata::c2pa` carry the first `caBX` verbatim and uncompressed; a later `caBX` is counted in `c2pa_duplicates` (saturating), never concatenated, since PNG has no multi-chunk store. The store is charged to the cumulative `with_max_metadata_bytes` budget like every other attacker-sized payload — skipped past the remainder, not an error. Encode: `with_c2pa(store)` embeds a caller-computed store and `with_c2pa_reserved(len)` writes `len` zero bytes in its place, as the last chunk before the first IDAT (after PLTE/tRNS and every other ancillary chunk) so a reservation is filled by a second equal-length encode that changes no byte outside the chunk. `encode_with_report` and `PngReport::c2pa` name the chunk's whole span — length, type, payload and CRC — as `C2paSpan`, the `c2pa.hash.data` exclusion §18.5.4 asks for. `EncodeImage` is untouched. The chunk type is spelled once, `chunk::CABX`, with its property bits asserted per PNG §5.4 Table 6: ancillary and private set, reserved clear, and unsafe-to-copy *clear* on the fourth byte — the polarity the issue's prose had backwards. That bit is the container's own enforcement of the facade's no-copy-forward law (`C2paPolicy`). libpng carries `caBX` as an unknown chunk, which is the framing proof: for the same payload it frames the same length/type/CRC bytes as gamut, decodes gamut's file pixel-exact with the chunk in place, and gamut reads the store back from libpng's file. The behavioural oracle (c2pa-rs) is #447. Refs #440
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #485 (
feat/224-png-encoder-efficiency, basefac39dc): this PR is opened against that branch and must merge after it. Nothing #485 did is modified; this adds thecaBXslot to the ancillary pass #485 already plumbs.This is an unattended run: no human approved the plan. The decision record below is what a human reads afterwards.
Summary
gamut-pngnow reads and writes the C2PA manifest store in thecaBXchunk (C2PA 2.4 §A.3.2) as one more raw,MetadataBlock-ready ancillary payload — the same mechanismeXIf/iCCP/XMP already use, one chunk type added to both sides. Issue #440, part of epic #239.DecodedPng::c2pa/PngMetadata::c2pacarry the firstcaBXverbatim and uncompressed. Exactly one store per file: a latercaBXis counted inc2pa_duplicates(saturatingu8), never concatenated. The store is charged to the cumulativewith_max_metadata_bytesbudget like every other attacker-sized payload — past the remainder it is skipped, not an error, and stays "the first" so a smaller one after it is a duplicate, not a substitute.PngEncoder::with_c2pa(store)embeds a caller-computed store;with_c2pa_reserved(len)writeslenzero bytes in its place. Emitted as the last chunk before the firstIDAT(afterPLTE/tRNSand every other ancillary chunk), so a reservation is filled by a second equal-length encode that changes no byte outside the chunk — pinned as an exact-byte diff.EncodeImageis untouched.PngEncoder::encode_with_reportandPngReport::c2pa()name the chunk's whole span — length, type, payload and CRC — asC2paSpan(with the payload bracketed inside), thec2pa.hash.dataexclusion of §18.5.4; derived from the same chunk walk the byte accounting uses, so it is always a claimed segment. The first CRC-validcaBX, so it names the chunk decode surfaces.chunk::CABXis the one place the type is spelled; a test asserts its property bits per PNG §5.4 Table 6 — ancillary and private set, reserved clear, and unsafe-to-copy clear on byte 3 (Table 6's polarity; the issue's prose had it backwards, its bytes did not). The bit is documented as the container's own enforcement of the facade's no-copy-forward law.caBXas an unknown chunk: for the same payload it frames the same length/type/CRC bytes as gamut, decodes gamut's file pixel-exact with the chunk in place, and gamut reads the store back from libpng's file.c2pa-rs(behavioural) is tooling/c2pa-oracle: differential oracle against c2pa-rs, both directions #447.Semver: minor (
#[non_exhaustive]structs gain fields; newpubitemsC2paSpan,PngEncodeReport, three encoder methods, one report method).Validation
Run in the lane worktree (
.claude/worktrees/agent-a3ce3726ec4e209c9, branchfeat/440-png-cabx).mise run fmt/fmt-checkneed the nested-worktree prefix__CARGO_TEST_ROOT=$(git rev-parse --show-toplevel).CARGO_BUILD_JOBS=2 cargo test -p gamut-png --all-features__CARGO_TEST_ROOT=… mise run fmtthenmise run fmt-checkmise run check-testsCARGO_BUILD_JOBS=2 cargo clippy -p gamut-png --all-targets --all-features -- -D warningsmise run lint(workspace, in the capped scope)mise run test(workspace, in the capped scope)convco check origin/feat/224-png-encoder-efficiency..HEADmise run mutants-crate gamut-png --shard i/nover the diffNo
Cargo.tomltouched, socheck-release-deps/check-ffi-featuresare not triggered; no C-surface type changed.Risks and rollout
caBXare byte-identical to before on both sides (the size-contract and libpng oracle suites pass unchanged); a file withcaBXpreviously had it ignored and now has it copied into memory under the metadata budget.c2pa_duplicatesis a new field on two#[non_exhaustive]structs: additive.Issue
Closes #440.
Decisions taken
Appended by the lane, in the record's shape:
Unresolved review notes
Level::Best+BruteForcethat is the whole brute-force set again). An in-placefill_c2pa(&mut [u8], &C2paSpan, &[u8])that overwrites the payload and recomputes the chunk CRC would make the flow O(store) instead of O(encode); not in the record, so not added here — a reviewer may want it as a follow-up under Integrate C2PA #239.c2pa_duplicatesis reported but no other malformed-file signal is; if a decoder-wide anomalies channel is ever added, this field is the first candidate to fold into it.