# The provenance-gap cure

A Content Credential can be perfectly valid: the bytes untouched, the signature good. And your verifier will still show *"unknown source."* Because whether to *trust the signer* has no universal public answer: the signer's cert isn't on a gate-kept Trust List, and there is no free, automated path onto one.

Whisper closes that gap by making the address *be* the signer: a routable IPv6 `/128` derived from the key the signer already signs with, DNSSEC-anchored and DANE-pinned under a domain you control, that any verifier resolves with no list to join and you can revoke worldwide at DNS-TTL. The `device_id` is the C2PA signer cert serial already in every manifest. This is **additive** to C2PA (trust anchors are pluggable inputs to the validator), never a fork. This page walks the gap at spec level, the reframe, the exact live calls (keyless to verify, one keyed call to anchor), the who-verified analytics, and, candidly, where it stops.

## The gap, at spec level

Start with what C2PA does well, because the fix rides on top of it. A Content Credential is a set of *assertions* (actions, hashes, metadata), a *claim* that hash-references those assertions, and a *claim signature*. The signature is a `COSE_Sign1`, and the signer's X.509 chain travels *in-band* in the COSE header (`x5chain`, RFC 9360): every intermediate up to the root is embedded, so a verifier builds the chain with **no network call**. The hard binding (`c2pa.hash.data`) hashes the asset bytes, so the whole manifest is tamper-evident. That part works, and Whisper doesn't touch it.

The unresolved question is not *"were the bits changed?"* It is *"should I trust the signer?"* A validator trusts a signer only if its certificate is on an explicit **allowed list**, or chains to a root on a **trust-anchors list**. And here is the honest hook: **C2PA does not mandate any particular PKI**. Trust lists and trust anchors are *pluggable configuration inputs* to the validator, which is precisely why a DNSSEC/DANE anchor is a *legitimate alternative trust source*, not a fork of the standard.

In practice, one list dominates. The official **C2PA Trust List** and **Conformance Program** (mid-2025) is a coalition-managed set of X.509 anchors for CAs issuing to conforming signers, gated by a Product Security Architecture submission and assurance levels. Content signed by an **off-list CA** displays *"unknown source"*, technically indistinguishable, to the reader, from a manifest signed by a recognized org. There is no free, automated, ACME-style path (no *"Let's Encrypt for C2PA"*) for an independent creator, a small newsroom, or an AI agent to get a recognized cert; commercial C2PA certs run on the order of `~$289/yr`, and the recognized-CA set is controlled by a small coalition. The spec also permits self-signed and off-list certs, so an *"anyone can sign anything"* gray zone exists with no open way to make an off-list signer publicly verifiable.

> The root cause has a shape: **trust-list gatekeeping**. The signer is real, the manifest is valid, but the verifier cannot *independently* resolve who the signer is without the gate-kept list. C2PA's own experimental *"Web Domain Trust Anchor"* tries to answer this with a self-signed cert in an HTTPS `/.well-known/c2pa.json` file, but it isn't DNSSEC-signed, and its authors flag **domain takeover** and **privacy** (the validator fetches from the origin) as open problems. The right mechanism is the one that proposal didn't use.

*Diagram: the trust decision.* Same valid manifest, same validator, two paths:

```
                                   ┌─ TODAY (off-list CA) ─────────────────────────────┐
                                   │  C2PA validator ─▶ C2PA Trust List ─▶ "unknown     │
  C2PA manifest ────┬──────────────┤  (trust = the list)  (gate-kept)       source"    │
  COSE_Sign1        │              └───────────────────────────────────────────────────┘
  x5chain (valid)   │              ┌─ WITH WHISPER (self-verifying) ───────────────────┐
                    └──────────────┤  C2PA validator ─▶ signer's DNSSEC zone ─▶ trusted │
                                   │  (+ DANE anchor)     (TLSA 3 1 1)         signer   │
                                   └──────────────────── no list to join ──────────────┘
```

Give the validator a DANE trust anchor and it resolves the signer's `TLSA` under the signer's own DNSSEC zone: trusted, with no Trust-List slot. Trust anchors are pluggable, so this is additive, not a fork.

## The reframe: the address is the signer

You cannot tune your way out of a *trust* question with detection. A valid signature from an off-list CA is genuinely valid; the verifier's problem is that it has no independent way to resolve *who* that signer is. The strictly-stronger move is to change what the verifier can resolve on its own.

Whisper has one primitive: **the address is the identity**. A routable IPv6 `/128` out of `2a04:2a01::/32` (announced by **AS219419**), deterministically derived from a key, DNSSEC-signed to the IANA root, [DANE-EE](/docs/dane) pinned (`3 1 1`), and RDAP-registered: re-derivable and verifiable by anyone with `dig`.

Point it at the signer. Whisper derives the signer's `/128` from the **public key it already signs with** (the `SubjectPublicKeyInfo` of the C2PA claim-signing end-entity cert, or a CAWG org cert, or an in-camera device signer), with the **C2PA signer cert serial** as the domain separator. The private key never leaves the signer's HSM or secure element; only the public SPKI is an input. The result is DNSSEC-anchored, DANE-EE pinned, RDAP-registered: a signer any validator configured with a DANE anchor resolves *directly*, with no gate-kept list, no coalition, and no annual CA fee.

> **The serial is the public index: the `/128` is its cryptographic counterpart.** The signer cert serial sits right there in every manifest; it is a public identifier, not a secret. But the `/128` derives from the in-HSM key *salted* by the serial, so the serial alone yields nothing: you cannot go serial → `/128` without the key, there is no enumerable directory, and RDAP / reverse-DNS return the registry object, never anything private. Because the derivation is **tenant-bound**, the same signer key under two organisations yields two unrelated `/128`s, so no outsider can link a signer across tenants.

*Diagram: the identity-cure pipeline.*

```
  Signer key (SPKI)            ──public key + serial──▶   /128                   ──DNSSEC + DANE-EE 3 1 1──▶   A signer any verifier trusts
  C2PA claim-signer serial                               2a04:2a01:c0d::51e           RDAP-registered            whisper verify --trustless
  private key sealed in HSM                              routable · tenant-bound                                 op:'revoke' → gone at DNS-TTL
  (only public SPKI is an input)
```

The signer serial is already in the manifest: a good public name with no revocation and no open trust path. Whisper binds it to a routable, publicly verifiable `/128` and gives it the off-switch C2PA's optional, cache-heavy OCSP is too slow for.

> **Shipped & live.** Deriving a signer `/128` from the signer's **public key** plus its **C2PA signer cert serial passed as `device_id`** is in production today. Anchor one with the control-plane call below and verify it from the DNSSEC root with tools already on your machine. A first-class typed `--serial` / `--c2pa` flag is on the roadmap; pass the serial as `device_id` today.

## What changes

Nothing here is a new detection heuristic. Each row is a failure mode of the current trust model that stops being *possible*, not one you catch after the fact.

| The gap today | Why it closes under a DANE-anchored signer |
|---|---|
| **Off-list CA → your Content Credential shows "unknown source"** | The signer's `TLSA` under its own DNSSEC zone *is* a trust anchor the validator resolves directly. `dig` the signer's name; the `AAAA` and TLSA agree, or the forgery is a DNSSEC/DANE inconsistency any verifier catches with stock tools. |
| **No free/automated path onto the recognized-CA list** (`~$289/yr` per cert; a coalition gate) | The anchor is DNS you already run: no CA fee, no committee. A stringer, an independent newsroom, or an AI agent becomes publicly verifiable with one `TLSA` record. |
| **A compromised signing key mints spec-valid manifests** until OCSP/CRL propagates (revocation is optional and cache-heavy) | One `revoke` tears down the `/128`, its PTR, and its DANE pin worldwide at DNS-TTL. *Honestly:* this shrinks the exposure window from "until OCSP, if ever" to minutes. It bounds the damage; it does not retroactively un-sign what the stolen key already emitted. |
| **No cross-org trust without both signers on the same list** | A shared DNS root is a shared anchor. A newsroom verifies a *different* org's signer with no private list in common: both already trust the IANA root. |

## Anchor a signer identity

Anchoring is one control-plane call to `POST https://graph.whisper.security/api/query` with your `X-API-Key` header. You pass the signer's **public** key material (the base64 `SubjectPublicKeyInfo` of its C2PA claim-signing / CAWG / device key) and the C2PA signer cert serial as `device_id`; you get back the deterministic `/128` with DANE-EE live, and a WireGuard config if you also want the signer's egress to source from that address.

### The call

```
CALL whisper.agents({op:'connect', args:{
  tier:'wireguard',
  identity_public_key:'<base64 SPKI of the signer key>',   # public half of the C2PA claim-signing key
  device_id:'6A2F9C143B8E00D1'                             # the C2PA signer cert serial
}}) YIELD op, ok, status, result, error
  RETURN op, ok, status, result, error
```

**Over stock tools**: `jq` builds the JSON body so the Cypher's own quotes never fight the shell:

```
# the public key only: the private key never leaves the signer's HSM / secure element
Q="CALL whisper.agents({op:'connect', args:{tier:'wireguard', \
   identity_public_key:'MFkwEwYHKoZIzj0…SPKI', device_id:'6A2F9C143B8E00D1'}}) \
   YIELD op, ok, status, result, error RETURN op, ok, status, result, error"

curl -s https://graph.whisper.security/api/query \
  -H "X-API-Key: whisper_live_xxx" \
  -H "content-type: application/json" \
  --data "$(jq -nc --arg q "$Q" '{query:$q}')"
```

### The response

```
# result carries the deterministic identity, DANE-EE live at anchor time
address     2a04:2a01:c0d::51e
fqdn        signer-6a2f9c143b8e00d1.<tenant>.agents.whisper.online
ptr         signer-6a2f9c143b8e00d1.<tenant>.agents.whisper.online
state       active                     # DNSSEC + DANE-EE (3 1 1) published atomically
tlsa        3 1 1 <sha-256 of the signer leaf key>
```

The signer now has a name any verifier can resolve. There are two ways to point verifiers at it, and you can use both: the **hosted** name above, verifiable trustless from the IANA root; or **bring your own domain**: prove a domain you control and issue the signer under it, so a reader verifies *"signed by `press.example-news.org`"* in their own words. Either way it is a DANE trust anchor a C2PA validator can be configured to consult. The derivation and the BYOD flow are covered in depth in [Signer & C2PA identity](/docs/signer-identity).

### Idempotency and errors

The call is deterministic and honest about conflicts: conservative in what it emits, liberal in what it accepts.

| You send | You get |
|---|---|
| The **same** signer key + serial again | The **same** `/128`: idempotent, safe to retry, safe to run on every publish. No duplicate identities. |
| The same signer key with a **different serial** on your tenant | `409 Conflict`: a signer key binds to exactly one serial, and its identity is fixed at first registration. The `/128` never silently re-pins: revoke it and re-anchor from the matching key, or omit the serial to reuse the existing identity. |
| A **non-string** `device_id` (a number, an array, null) | `400` with an actionable `detail`, never an opaque `500`. Send the serial as a string. |

> **On the CLI:** `whisper create --register` mints a generic signer identity, and `whisper verify` / `whisper policy` / `whisper logs` / `whisper kill --revoke` drive the rest. The typed `--serial` / `--c2pa` flag is not shipped yet. Anchor signers through the control-plane call above, which is live today, and drive them with the CLI verbs once allocated.

### Revoke, worldwide

A compromised signing key, a rotated cert, a decommissioned device: one call tears down the `/128`, its PTR, and its DANE pin everywhere at DNS-TTL speed. It is provable with the same stock tools that proved the identity existed, no Whisper software required.

```
# the control-plane op…
CALL whisper.agents({op:'revoke', args:{agent:'2a04:2a01:c0d::51e'}})
# …or the CLI
whisper kill --revoke 2a04:2a01:c0d::51e

# now prove it, worldwide, at DNS-TTL speed:
dig -x 2a04:2a01:c0d::51e +short                       # -> nothing
curl -s https://whisper.online/verify-identity/2a04:2a01:c0d::51e
# -> {"is_whisper_agent": false, ...}
```

## Verify it: keyless, no account

The signer identity is public by design, so anyone (a fact-checker, a platform's trust & safety desk, an auditor, a reader) can confirm a signer without your key and without taking Whisper's word for it. This is the keyless half of the two-tier surface: verify with no key, anchor and govern with your key.

```
# no key, no account: re-derive and verify the signer's identity, trustless to the IANA root
whisper verify --trustless 2a04:2a01:c0d::51e

# or with only curl: the keyless full-chain verdict
curl -s https://whisper.online/verify-identity/2a04:2a01:c0d::51e
# { "is_whisper_agent": true, "dane_ok": true, "jws_ok": true, "evidence": { ... } }

# the address IS the signer: forward-confirmed reverse DNS names it
dig -x 2a04:2a01:c0d::51e +short
# signer-6a2f9c143b8e00d1.<tenant>.agents.whisper.online.

# the registry object for the /128: RDAP, typed JSON
curl -s https://whisper.online/ip/2a04:2a01:c0d::51e | jq '.handle, .parentHandle'
# "2A04:2A01:C0D::51E/128"
# "2A04:2A01::/32"
```

The `--trustless` flag is the point: nothing there calls back to Whisper's own API as an authority. The CLI re-derives the DNSSEC chain to the IANA root, on your machine, with your resolver. A regulator, or a rival newsroom, can verify a signer *outside* your tenancy. Full mechanics: [Verify an agent](/docs/verify) and [DANE & TLSA](/docs/dane).

## Who verified your content

C2PA verification is designed to need *no* network call: the signer's chain travels in-band via `x5chain`, so the only network events are optional OCSP/CRL and the RFC 3161 time-stamp. The consequence is that a signer is normally **blind** to who verified their content: it is a write-only quadrant. Every other provenance layer, including watermarking, shares that blindness.

Anchor the signer in DNS/DANE and that changes. Every verifier that resolves and validates the DANE anchor leaves DNS, TLSA, and RDAP lookups against Whisper's authoritative servers, and `op:lookups` returns them: who checked this signer, where, and how often.

```
CALL whisper.agents({op:'lookups', args:{agent:'2a04:2a01:c0d::51e', window:'24h'}})
# → 214 DANE/TLSA validations · 63 reverse-DNS · 9 RDAP
#   a verification-analytics stream, and an impersonation tripwire
```

Two honest notes and one payoff. It observes lookups against the *public anchor*, not offline validations that never touch the network: it is a strong signal, not a census. And it is an **early warning**: a spike of resolutions on a signer you did not publish under, or a lifted [identity assertion](/docs/did-web) being checked in the wild, shows up *before* the fake spreads, not in a post-mortem. This is the loop C2PA and SynthID structurally cannot close.

> **Nothing issued in the dark.** Every signer mint and every revoke lands in a public, append-only Merkle log (RFC 6962 `tlog-tiles` with a C2SP signed-note checkpoint), each root anchored to Bitcoin via OpenTimestamps, forming an auditable, non-repudiable issuance-and-revocation trail. *Honest status:* it is tamper-evident, Ed25519-signed, and Bitcoin-anchored, but **not yet independently witnessed** (co-signing across our own servers is availability, not independence); it speaks C2SP `tlog-witness`, so any external witness can co-sign. Read it with `curl -s https://whisper.online/ip/2a04:2a01:c0d::51e/transparency` and `curl -s https://whisper.online/checkpoint`. Details: [Transparency log](/docs/transparency).

And when the signer is an **AI agent marking its own outputs**, the same `/128` is a control plane, not just a name: `op:policy` sets default-deny egress, `op:firewall` allows/denies by host, CIDR, or port, `op:budget` caps it, and `op:revoke` kills it worldwide in one call. Give a generative tool a signer identity *and* govern exactly what it may reach. See [Sign agent outputs](/docs/sign-outputs) and [Egress governance](/docs/egress-governance).

## Honest scope: what this is not

Over-claiming is the credibility trap in provenance. So, candidly:

- **Additive: a legitimate pluggable trust source, not official membership.** Because a C2PA validator takes trust anchors as pluggable configuration, a DANE anchor is a *legitimate alternative trust source*. It is **not** official C2PA Trust-List membership, and DNSSEC/DANE is **not (today) a formally recognized C2PA conformance trust anchor**. Conformance currently centers on X.509 plus the curated Trust List. The formal seam is **CAWG**: surface the signer as a CAWG identity assertion (its `did:web` issuer and `cawg.web_site` URI are already DNS-native), and/or take DANE to the standard as a proposal. Position it as a complementary identity ecosystem, never "already C2PA-approved."
- **Provenance is not truth.** A valid signature proves *who* signed and that the bytes weren't changed, not that the content is true. A genuine signer can sign a staged scene or a photograph of a screen. What Whisper adds is *accountability*: the signer is publicly named and revocable, so the liar is on the record. It is not a veracity oracle.
- **It is not a deepfake detector.** Absence of a credential is not proof of fakery, and most content is unsigned. Whisper raises the value of *signed-authentic*; it does not flag *unsigned-synthetic*. Pair it with detection and watermarking: they are complementary layers.
- **A screenshot or re-encode strips the manifest.** C2PA manifests are embedded metadata; a screenshot or a re-compressing upload deletes them, and the standard concedes it. Signing does not prevent that. The mitigation is durable soft-binding / watermarking, which is *out of scope here* (Whisper anchors identity, not pixels). A public anchor plus `op:lookups` helps you *detect* stripping; it cannot prevent it.
- **Revocation bounds the window; it does not erase the past.** DNS-TTL revocation shrinks a stolen key's exposure from "until OCSP, if ever" to minutes, but between theft and propagation, spec-valid fraud can exist, and revocation cannot un-sign what already shipped.
- **It only helps where verifiers check.** Like all provenance, the value lands where the verifier actually consults the anchor, an ecosystem-adoption dependency C2PA shares.

## Where it fits: C2PA, CAWG, standards, SIEM

Whisper is additive. It rides *on top of* the manifest, the watermark, the in-camera capture, and the SIEM you already run. It replaces none of them. Whisper does not create the C2PA manifest or embed a watermark; it anchors the signer those manifests already reference, adds who-verified analytics, and gives an AI agent a signer identity it otherwise cannot get.

**Standards.** EU AI Act Art.50(2) requires generative-AI outputs be marked *"in a machine-readable format,"* effective, interoperable, and robust "as far as technically feasible"; Recital 133 enumerates *"cryptographic methods for proving provenance and authenticity of content"* and asks that detection be *"accessible to the public."* A public DNSSEC/DANE anchor plus open verification analytics is exactly that public, interoperable bar. The honest limit: the *AI-generated* assertion itself rides in the C2PA manifest (Whisper anchors the signer; it does not assert "this is AI"). **ISO/IEC 22144** (the C2PA architecture, at draft) pulls this into procurement language. The clause-by-clause mapping lives in [EU AI Act · C2PA · ISO 22144](/docs/content-compliance).

> **Shipped vs roadmap.** The **Splunk**, **Microsoft Sentinel** and **OpenCTI** connectors ship today; findings arrive as signed, replayable JSON mapped to CEF and ECS fields. **Roadmap**, labelled as such and not yet available: **STIX 2.1 over TAXII** export, and the first-class typed `--serial` / `--c2pa` API and CLI argument.

**Integrations (proposed, not vendor-endorsed).** Whisper anchors the *signer and domain* boundary, never the manifest bytes, the watermark, or the capture pipeline:

- **C2PA claim-signer.** Derive the `/128` from the claim-signing end-entity cert's public key; the signer serial is the socket. The publicly verifiable, DNSSEC-anchored layer *on top* of the `COSE_Sign1` signature C2PA already carries. Complements the manifest; never replaces it.
- **CAWG identity assertion.** A Whisper DNSSEC domain is a first-class `did:web` issuer; DANE-bind the `cawg.web_site` URI and the `cawg.x509.cose` org cert to your domain to turn CAWG's "well-formed but unrooted" into "trusted" with no S/MIME CA. This is the *formal identity seam* C2PA points at.
- **Sign-outputs for AI agents.** An agent signs its C2PA claim (`c2pa.actions` / `c2pa.created`, IPTC `digitalSourceType: trainedAlgorithmicMedia`) with a Whisper-anchored, revocable signer identity → trusted-signer status without a Trust-List slot. The one thing agent stacks otherwise cannot get. See [Sign agent outputs](/docs/sign-outputs).
- **Camera & device makers.** Derive a *per-unit* signer identity from each device's existing hardware key, DNSSEC/DANE-anchored and individually revocable, so a single CA incident does not force revoking a whole model line, and independent verification survives it. Complements the in-camera signer; never reaches into the secure element.

And it is built to **fail open**: a Whisper outage never blocks a verify. Checks degrade to the anchors already carried in the manifest, and connectivity is preserved.

## Next

- [Signer & C2PA identity](/docs/signer-identity): how the signer `/128` is derived from the key + serial, and the bring-your-own-domain path, in depth
- [Control plane](/docs/control-plane): the full `whisper.agents` op set the anchoring call belongs to
- [EU AI Act · C2PA · ISO 22144](/docs/content-compliance): the clause-by-clause evidence mapping
