Inspiration
AI media tooling is excellent at generation and poor at accountability. From 2 August 2026, Article 50(2) of the EU AI Act requires generative outputs to be "marked in a machine-readable format and detectable as artificially generated or manipulated," with breaches of Article 50 carrying fines up to €15,000,000 or 3% of worldwide annual turnover. Yet there is no clean path from "a model produced this file" to "here is a record a regulator would accept."
Genblaze already produces the raw provenance. Its own docs/features/trust-modes.md names the gap
precisely: Mode 1 (integrity) ships today, and it proves self-consistency, not authorship —
"a tamperer can modify the asset, recompute the manifest, re-embed, and produce a manifest that
verifies against itself." Mode 3, the C2PA bridge, is listed as roadmap. Backblaze B2 already sells
the missing half: Object Lock, which makes a record physically unrewritable.
Attestable is the bridge between the two.
What it does
- Generates through a five-step chained Genblaze pipeline: key art → image-to-video → voiceover → music bed → FFmpeg composite, with per-stage fallback models.
- Seals the deliverable with a signed C2PA Content Credential built from the Genblaze manifest — one C2PA action per generation step, each carrying the provider, model, prompt and seed that produced it, plus the manifest's canonical hash.
- Anchors an audit record in Backblaze B2 under Object Lock retention, committing the manifest hash and both asset digests, so an attacker who edits the asset cannot also edit the record that would expose the edit.
- Verifies every asset by reading it back before publishing it, and gives anyone a public verification page — no login — showing what the credential proves and, just as importantly, what it does not.
- Proves it live. A "Run tamper attempt" button corrupts a copy of the asset and tries to
delete its audit record. The credential reports
assertion.bmffHash.mismatch; the delete is refused by retention. Nothing is pre-recorded.
How we use Genblaze
Genblaze is the generation and provenance engine, not a wrapper:
- Chained multi-provider
Pipelinewithinput_fromfan-in — motion consumes the key art while voiceover and music run as independent branches that the compositor recombines — andfallback_modelsper stage. - Provider swapping as configuration. Stages resolve at runtime from whichever credentials are present, so Kling → Runway or ElevenLabs → LMNT is a provider-table edit, not an orchestration rewrite. That is the argument the Pipeline API exists to make, so the app is built to show it.
- The manifest is the product input.
Manifest.from_run(run)andcanonical_hashare what the C2PA claim is built from and commits to. ObjectStorageSinkwithmanifest_lock— Genblaze's own Object Lock hook, so the manifest lands under retention in the same call that uploads it.- Content-addressable keys, streaming step events for the live UI, and
StorageBackendimplemented a second time so the keyless demo path runs production code.
We built the genblaze-c2pa Mode 3 bridge the maintainers documented but have not shipped. It
lives in attestable/c2pa_bridge/, written against the SDK's public data model so it can be lifted
out and used standalone. It maps GENERATE→c2pa.created/c2pa.edited, MIX/TRANSCODE→
c2pa.converted, INGEST→c2pa.placed, provider+model→softwareAgent, and adds two custom
assertions: org.attestable.genblaze.provenance (the pointer and canonical hash back to the
manifest of record) and org.attestable.compliance.eu-ai-act.
Digital source types are chosen per step, not applied uniformly. Trained-model output is marked
trainedAlgorithmicMedia; a chained model step becomes compositeWithTrainedAlgorithmicMedia; the
offline renderer's procedural placeholders are marked plain algorithmicMedia. Labelling a gradient
card as trained-model output would be exactly the false provenance claim the project exists to
prevent.
Two SDK findings worth reporting upstream, both hit while building this:
C2paSignerInfo.ta_urlmust be a NULL pointer when there is no timestamp authority. Passingb""makes every sign fail withSignature: empty string, and the Python constructor rejectsNone.c2pa.createdrequires adigitalSourceType; omitting it yieldsassertion.action.malformedat read time while signing appears to succeed.
How we use Backblaze B2
B2 is the system of record, not a file dump. Five prefixes, five different guarantees:
attestable/assets/{sha[:2]}/{sha[2:4]}/{sha}.{ext} content-addressed, deduplicated
attestable/manifests/{run_id}.json Genblaze manifest — Object Lock
attestable/sealed/{asset_id}.{ext} deliverable with embedded credential
attestable/c2pa/{asset_id}.c2pa detached credential copy
attestable/audit/{tenant}/{yyyy-mm}/{run_id}/seal.json audit seal — Object Lock
attestable/scratch/{run_id}/… intermediates — lifecycle expiry
- Object Lock on
manifests/andaudit/;GOVERNANCEfor a resettable demo,COMPLIANCEfor production, where not even the account root can remove the object before expiry. - Lifecycle rules expire
scratch/and abort stale multipart uploads, applied byattestable doctor. - Event Notifications on
sealed/fire a webhook so downstream work starts without polling. The endpoint verifies B2's HMAC-SHA256 signature and returns 503 until a secret is configured, rather than trusting unsigned callers. - Presigned URLs for the private reviewer view; durable credential-free URLs for anything persisted into a manifest.
- Bandwidth Alliance CDN support for zero-egress public verify pages. Honestly stated: free egress requires a partner CDN; direct API egress is free to 3× stored bytes, then $0.01/GB.
The local fallback store implements StorageBackend including retention semantics — an object
under an ObjectLockConfig refuses later deletes and overwrites, exactly as B2 would. That is why
the tamper demonstration is meaningful without credentials: the same call and the same assertion run
against either backend.
Providers and models
| Stage | Provider | Model | Fallback |
|---|---|---|---|
| Key art | GMI Cloud | seedream-5.0-lite |
flux-1-schnell, then OpenAI gpt-image-1 |
| Motion | GMI Cloud | kling-v3-image-to-video |
seedance-1-0-pro-fast-251015 |
| Voiceover | ElevenLabs | eleven_v3 |
eleven_multilingual_v2 |
| Music bed | Stability AI | stable-audio-2.5 |
— |
| Composite | Genblaze | FFmpegCompositor (ffmpeg) |
— |
| Claim signing | c2pa-rs 0.90.1 via c2pa-python 0.37.1 |
ES256 | — |
| Offline fallback | Attestable LocalSynthProvider |
procedural (marked algorithmicMedia) |
— |
The music bed is generated rather than licensed, so nothing unlicensed ships in an output or in the demo video.
How we built it
Python 3.12, FastAPI + Jinja for the app, SQLite as a rebuildable index over the durable B2 record,
an in-process worker pool with SSE progress, c2pa-python for signing, and cryptography for the
ES256 development chain. 62 tests run with no credentials.
Challenges
Signature: empty string. Every C2PA sign failed identically until we isolated it to an emptyta_urlneeding to be a NULL pointer.- Genblaze's
file://allowlist. Asset transfer accepts local URLs only from the system temp directory — an anti-arbitrary-file-read control. The right fix was to render into temp as the SDK intends, not to widen the allowlist. - The digests genuinely do not match. Embedding a credential rewrites the file, so the digest Genblaze committed cannot equal the delivered file's digest. Rather than paper over it, the audit seal records both and each is checked against the artifact it binds.
- Saying "untrusted" out loud. A development certificate produces valid signatures from an issuer no trust list vouches for. It would have been easy to show a green check. The verify page reports anchored trust and public trust as two separate rows.
What's next
Enrol a C2PA Conformance Programme certificate; add SynthID or a comparable imperceptible watermark
as the third layer the Commission's Code endorses; move from an in-process pool to a shared queue
for multi-node deployment; and open the genblaze-c2pa bridge as a standalone package with a
feature-request issue on the Genblaze repo.
Known limitations
Stated plainly because a provenance tool that overstates itself is self-defeating:
- The signing certificate is a development certificate. Signatures verify cryptographically, but the issuer is on no trust list, so third-party C2PA viewers will correctly report the signer as untrusted. Production requires a Conformance Programme certificate. The verify page says so on every asset.
- No imperceptible watermark. We implement the C2PA and registry layers of the Commission's recommended three; a screenshot or re-encode that strips metadata defeats the credential, which is why the detached copy and the B2 registry exist as fallbacks.
- Attestable helps you comply; it does not certify compliance. That remains the deployer's obligation. The Article 50(2) marking obligation is also deferred to 2 December 2026 for systems already on the market before 2 August 2026, and the Commission's Code of Practice is voluntary guidance rather than a binding technical mandate.
- Single-node. Jobs run in an in-process thread pool with SQLite as the index. The durable record lives in B2; the database is a rebuildable cache.
Try it in 60 seconds
No API keys and no B2 account required — the app runs the same code paths against an offline renderer and a local store that emulates Object Lock.
- Submit the pre-filled brief. Watch the live pipeline, seal and audit events.
- Open the verification page and read the per-step provenance table.
- Click Run tamper attempt — the credential fails, the delete is refused.
- Click Download audit bundle for the single JSON an auditor would receive
Log in or sign up for Devpost to join the conversation.