fix(identify): attribute C2PA by claim_generator, not incidental issuer tokens (v0.6.1)

Verified on real signed files that the issuer byte-scan mis-attributes
multi-entity manifests: Leica read as "Truepic" (timestamp authority in the
chain), Nikon as "Adobe Firefly" (XMP-toolkit "Adobe" + the sample's
"Adobe_MAX" name), Truepic as "Google". Platform attribution now prefers the
claim generator (what produced the asset) and falls back to the issuer scan.

- New _CLAIM_GENERATOR_PLATFORM map + _platform_from_generator; claim generator
  read for non-PNG via the now-public c2pa.cbor_text_after.
- Device tokens listed only where verified against a real C2PA file (Leica
  lc_c2pa, Nikon, Truepic Lens); Pixel/Samsung/Sony/Canon/Bria deferred until a
  real sample confirms the in-manifest string. Camera C2PA marks capture
  authenticity, so these never set is_ai.
- cbor_text_after made public (was _cbor_text_after); call sites + tests updated.
- Regression test: claim_generator beats incidental Adobe/Google/Truepic tokens.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
test-user
2026-05-26 20:10:07 -07:00
co-authored by Claude Opus 4.7
parent 2676325184
commit dda2ee7fbb
8 changed files with 76 additions and 20 deletions
+19
View File
@@ -337,6 +337,25 @@ class TestIdentifyIptcAi:
assert "Gemini" in r.platform
class TestIdentifyC2paClaimGenerator:
"""C2PA attribution prefers claim_generator over incidental issuer tokens."""
def test_claim_generator_beats_incidental_tokens(self, tmp_path: Path):
# Real manifests mention timestamp authorities / XMP toolkits, so
# "Adobe"/"Google"/"Truepic" appear incidentally; the claim generator
# (a Leica camera) must win the platform attribution. Regression guard
# for the real-sample mis-attribution (Leica->Truepic, Nikon->Adobe).
gen = b"M11-P/2.0.1 lc_c2pa"
blob = (
b"\xff\xd8\xff\xe1 c2pa.claim jumbf Adobe Google Truepic "
b"claim_generator" + bytes([0x60 + len(gen)]) + gen + b" \xff\xd9"
)
p = tmp_path / "leica_like.jpg"
p.write_bytes(blob)
r = identify(p, check_visible=False, check_invisible=False)
assert r.platform == "Leica (camera, C2PA capture)"
# ── Open invisible watermark (SD/SDXL/FLUX) integration ─────────────
from remove_ai_watermarks.invisible_watermark import is_available as _wm_available # noqa: E402