Inspiration

Fewer than one percent of images online carry C2PA provenance, and the credentials that do exist are stripped the moment an image is screenshotted or re-encoded by a platform. The C2PA standard already answers this with Soft Binding Resolution: recover the stripped manifest from a repository by matching a watermark or a perceptual-hash fingerprint. The only production recovery service today is Adobe's, and it is Adobe-only. We wanted the open, vendor-neutral version, on storage anyone can control.

What it does

Rooted closes the full provenance loop and lets you watch it live. An asset is generated, watermarked (TrustMark variant P), stored on Backblaze B2 content-addressably, and signed (Ed25519 over a COSE_Sign1 manifest, plus a real C2PA claim via c2pa-python). It is indexed by its watermark id and a 256-bit PDQ perceptual hash, and appended to a Merkle transparency log with signed checkpoints. When the asset later shows up stripped, Rooted recovers the signed manifest: watermark first, PDQ fallback within Hamming distance 31, with a cross-layer integrity check, and returns it with an independently verifiable inclusion proof after an SB 942-style redaction split. The recovery API implements the real C2PA v2.4 Soft Binding Resolution routes and is contract-tested with schemathesis. Rooted also exposes its own MCP server so an AI agent can verify provenance conversationally.

Beyond the core image loop, the same recovery closes for audio (a spectral fingerprint), video (per-keyframe PDQ), and a Genblaze speech-to-text transcript that the live site reconciles against Rooted's signature. The site shows the green C2PA Trusted state via the conformance test trust list (labeled FOR TESTING ONLY), a side-by-side against the official C2PA reader on the same bytes, C2PA ingredient-DAG lineage, and tamper-diff forensics that name which signed field changed. The transparency log carries an append-only consistency proof bound to the WORM B2 checkpoint, a checkpoint-history chain, and a nuke-and-rebuild that reconstructs the recovery index from B2 content-addressed objects alone (no database) and re-proves recovery; an adversarial robustness grid reports which transforms a recovery survives (JPEG, downscale, and a screenshot recover; crop and rotation do not), and a watermark-failover run destroys the watermark outright (decode on Modal) and still recovers by fingerprint at Hamming distance 6 of 31. Rooted ships seven surfaces: the web app, the SBR API, an MCP server, a CLI published on PyPI (pip install rooted-sbr), an MV3 browser extension that recovers provenance for any image from a right-click, and native iOS (SwiftUI) and Android (Jetpack Compose) apps that verify against the same live API, plus a shareable provenance-receipt permalink at /r/, an embeddable verify badge, and a wired federated peer-forward across an open, vendor-neutral network.

How we built it

FastAPI in a uv workspace; TrustMark variant P watermarking; PDQ stored as bit(256) and queried over an HNSW bit_hamming_ops index (pgvector, the live production path); Ed25519 / COSE signing and c2pa-python for the standards claim; pymerkle for the transparency log; Backblaze B2 via b2sdk; Genblaze for generation; a Next.js 15 front end with react-three-fiber (a bloomed galaxy hero with a live metrics ribbon, an in-browser strip-and-recover reveal, a scroll-spy nav over six acts, and a 3D Merkle explorer), a typed client generated from the backend OpenAPI schema, and @contentauth/c2pa-web to read Content Credentials in the browser; native apps in SwiftUI and Jetpack Compose. Deployed on Render (API) and Vercel (web), same-origin through a rewrite, with a keepalive, and the TrustMark decode tail on Modal serverless.

Challenges we ran into

The two trust layers have opposite carrier needs: TrustMark embeds in smooth content, PDQ needs structure for a stable hash. Reading C2PA in Next.js meant a WASM module with a worker that Turbopack cannot resolve, so the reader loads inline via a browser-only dynamic import. And honest cryptography: a test certificate produces a Valid signature, not the green Trusted state, which we surface rather than hide.

Accomplishments and what we learned

A real recovery loop that runs live, standards-grade Content Credentials, independently verifiable transparency proofs, and a front end that makes the loop legible while keeping the result decoupled from the 3D scene so it cannot visually fail. We learned to build the loop on real data first and let the visuals be a multiplier, and to read a library's own type definitions before reaching for heavier tools.

What's next

A Conformance-Program CA so the green Trusted state validates against the C2PA production trust list end to end, native Backblaze B2 event delivery on the enterprise Event Notifications tier (the webhook ingest is already wired and downstream-verified), and a live federated network of independent resolvers so recovery spans repositories rather than a single instance. Provenance proves origin, not truth.

Built With

  • b2sdk
  • backblaze-b2
  • c2pa-python
  • contentauth-c2pa-web
  • cose
  • ed25519
  • fastapi
  • fastmcp
  • genblaze
  • gmicloud
  • kotlin
  • modal
  • next.js
  • openai
  • openapi-typescript
  • pdq
  • postgresql
  • pydantic
  • pymerkle
  • python
  • r3f-forcegraph
  • react
  • react-three-fiber
  • render
  • swift
  • swiftui
  • tailwindcss
  • tanstack-query
  • three.js
  • trustmark
  • typescript
  • uv
  • vercel
Share this project:

Updates