Whisper · Docs
Content · Recipes

Sign · verify · who-verified

Six copy-paste recipes put a publicly verifiable, revocable identity under a C2PA signer: bind it to the claim-signer cert serial it already carries, prove it keyless, DANE-anchor it so any verifier trusts it with no Trust-List slot, see who verified your content, revoke one burned signer at DNS-TTL, and sign an AI agent's output under an identity you can trust. Each one runs today against shipped primitives.

They share one primitive: a routable IPv6 /128 that a signer derives from a key it already holds, DNSSEC-signed and DANE-pinned so anyone can verify it and nobody can forge it. Everything below is built from shipped parts: the device /128, keyless verify, the reverse-observability op:lookups, per-signer egress governance, the transparency log, and one-call revoke. Whisper does not create the C2PA manifest, sign the claim, or embed a watermark. Your Content Credentials tool keeps doing that. Whisper anchors the signer that manifest already references. Where a step is a pattern you assemble, or a connector still on the roadmap, it says so in plain words.

What's shipped, stated honestly. The signer /128 derivation from the signer's public key + its cert serial, keyless verify / RDAP, the reverse-observability op:lookups, per-signer egress governance (policy · firewall · budget · revoke), and the Merkle transparency log are all live. Three caveats these recipes are written around: (1) there is no first-class --c2pa-serial CLI flag yet, so a signer is provisioned via the control-plane API (which is live) with the cert serial passed as the generic device_id; (2) a DNSSEC/DANE anchor is not (yet) a formally recognized C2PA conformance trust anchor: today conformance centers on the curated C2PA Trust List, so we surface the anchor via a CAWG identity assertion and as a proposal to the standard, a complementary identity ecosystem, never a fork; (3) a turnkey C2PA signer plugin and a hosted CAWG did:web issuer are roadmap. Whisper anchors the signer identity at the DNS/DANE boundary. It is not the manifest, not the claim signature, not the watermark, and it does not decide whether content is true. Each recipe names exactly where it stops.

The shared primitive: a signer identity anchored in open DNS, not a gate-kept list

Every conformant Content Credential already carries an X.509 claim signer: the claim signature is a COSE_Sign1 whose certificate chain travels in-band in the x5chain header, with the EKU c2pa-kp-claimSigning (OID 1.3.6.1.4.1.62558.2.1). That end-entity cert has a serial. Whisper takes only the signer's public SubjectPublicKeyInfo (SPKI) and, together with the cert serial as a domain separator, deterministically derives a /128 under 2a04:2a01::/32 (AS219419). The private signing key never leaves the signer; the server only ever computes a public address.

C2PA decides whether to trust a signer by checking its cert against an allowed list or a set of trust anchors. Crucially, the spec makes those pluggable configuration inputs to the validator, not a single mandated PKI. That is the honest hook: a DNSSEC-signed DANE anchor is a legitimate alternative trust source, not a fork of C2PA. Today an off-list CA renders your Content Credential as "unknown source", there is no free automated path (no "Let's Encrypt for provenance"), commercial claim-signing certs run ~$289/yr, and the recognized-CA set is governed by a small coalition. The derivation below keeps the signature format exactly as-is and adds the four properties the gate-kept list withholds:

C2PA claim-signer COSE_Sign1 · x5chain EE cert serial = device_id private key stays with signer public SPKI + serial /128 2a04:2a01:c0d3::5e routable · tenant-bound DNSSEC + DANE-EE 3 1 1 A signer anyone verifies no Trust-List slot, no CA fee whisper verify --trustless our API not in the trust path op:revoke → signer gone worldwide at DNS-TTL
The C2PA claim-signer's key is already the root of trust; Whisper binds its public half + the cert serial to a routable, publicly verifiable /128 and gives it the off-switch the Trust List's OCSP/CRL never delivered in real time. The cert serial alone yields nothing: you cannot go serial → /128 without the key.

Every check in the recipes below that doesn't provision is keyless: no account, just DNS and TLS any verifier already has.

Recipe 1: Bind a C2PA signer to its cert serial

Give a signer the public identity it should have had, derived from the key it already signs with and named by the claim-signer cert serial already embedded in every manifest it produces. One call; the reply is the address, and a stock dig confirms the name and the DANE pin.

Boundary. This complements your C2PA signing stack and the CAWG identity assertion. It does not create the manifest, produce the claim signature, or embed a watermark. It takes the public half of the same key your claim signer already uses and makes it addressable, publicly verifiable, and revocable. The op:connect WireGuard peer config it returns is optional for a pure signing key: a signing service can use it to source-bind its egress (Recipe 6), but a document signer only needs the anchored /128 + DANE name.

Pass the claim-signer cert serial as the generic device_id. Re-running with the same signer key + serial returns the same /128: it is idempotent, so it is safe to run from a provisioning loop:

# Bind a C2PA claim-signer to the EE cert serial it already carries. device_id = the serial.
# Re-running with the same signer key + serial returns the SAME /128 (idempotent).
curl -s https://graph.whisper.security/api/query \
  -H "X-API-Key: whisper_live_xxx" \
  -H "content-type: application/json" \
  -d '{"query":"CALL whisper.agents({op:'"'"'connect'"'"', args:{tier:'"'"'wireguard'"'"', identity_public_key:'"'"'<base64 SPKI of the signer key>'"'"', device_id:'"'"'03ac74661e9a4f0c'"'"'}}) YIELD op, ok, status, result, error RETURN op, ok, status, result, error"}' | jq .
{ "op":"connect", "ok":true, "status":200,
  "result":{ "address":"2a04:2a01:c0d3::5e",
             "name":"signer-03ac7466.c2pa.example-press.whisper.online",
             "dane":"3 1 1", "dnssec":"signed",
             "wireguard":"<peer config, optional for a pure signer>" } }

Confirm the /128, its name, and the DANE pin from the other side, keyless: the reverse DNS is forward-confirmed (PTR ⇄ AAAA), so nothing but dig is required:

dig +short -x 2a04:2a01:c0d3::5e
signer-03ac7466.c2pa.example-press.whisper.online.

# …the forward AAAA resolves straight back: the round trip that makes it forge-proof:
dig +short AAAA signer-03ac7466.c2pa.example-press.whisper.online
2a04:2a01:c0d3::5e

# …and the DANE-EE pin over the signer cert's public key is right there in DNS:
dig +short TLSA _443._tcp.signer-03ac7466.c2pa.example-press.whisper.online
3 1 1 b653a4ef…fcb82d1d

The mint is not issued in the dark. Every /128 minted and every one revoked lands in a public, append-only Merkle transparency log, so you and an auditor can replay the full issuance history of a signer: the non-repudiable trail an EU AI Act Art.50(4) disclosure duty wants:

# The signer identity's ordered lifecycle (mint, any rotations, revoke), keyless:
curl -s https://whisper.online/ip/2a04:2a01:c0d3::5e/transparency
i

A supplied serial is checked against a reused identity: the same signer key with a different device_id on your tenant is a 409, and a non-string device_id is a clear 400. That's liberal in what it accepts, strict and unambiguous in what it commits. Honest status of the ledger: it is tamper-evident, Ed25519-signed, and Bitcoin-anchored via OpenTimestamps today; independent third-party witnessing is the next step. It already speaks the C2SP tlog-witness protocol, so any external witness can co-sign. A first-class typed --c2pa-serial argument is on the roadmap; today the serial rides the generic device_id, which is shipped.

Recipe 2: Prove a signer's identity, keyless

Once anchored, any verifier (a newsroom checking a wire photo, a platform verifying at ingestion, a fact-checker triaging a viral clip, a browser extension) proves the signer is real without an account, without a curated CA allow-list, and without trusting Whisper as an authority. This is the move that resolves the signer rather than merely reading the manifest. The proof re-derives against the IANA DNSSEC root.

Boundary. This authenticates the signer's network identity: that the key behind a claim signature is anchored, publicly, to a resolvable name. It sits alongside the C2PA validation your tool already does (hash bindings, the COSE signature, the x5chain); it does not replace it. What it adds is a proof that is verifiable off-ecosystem: something the curated Trust List cannot give a verifier who isn't already configured with the right CA anchors.

The trustless check walks the full chain: DNSSEC to the IANA root, the DANE-EE pin (the same key that signs the claim), and the transparency-log entry. It prints exactly what it trusted:

# Prove any signer identity, trustless: re-derived against the IANA DNSSEC root,
# with no Whisper API trusted as an authority anywhere in the chain.
whisper verify --trustless 2a04:2a01:c0d3::5e
dnssec   pass   DNSSEC-root   AAAA, PTR and TLSA(3 1 1) all DNSSEC-validated to the IANA root
dane     pass   DNSSEC-root   served leaf SPKI-SHA256 == TLSA pin (== the C2PA signer cert key)
ledger   pass   DNSSEC-root   transparency-log entry present, signature verifies
CRYPTOGRAPHICALLY PROVEN, trust anchor: DNSSEC root (IANA) + DANE-EE, Whisper API NOT trusted

The same fact is reachable with tools already in every desk's toolbox: one keyless HTTPS call, or plain dig. A self-signed or off-list signer with no public anchor (what CAWG calls well-formed but unrooted) has no /128 to resolve and says so plainly:

# Is this address a real, DANE-anchored signer identity? One keyless call, no install:
curl -s https://whisper.online/verify-identity/2a04:2a01:c0d3::5e
{"is_whisper_agent":true,"dane_ok":true,"jws_ok":true,
 "evidence":{"address":"2a04:2a01:c0d3::5e",
   "ptr":"signer-03ac7466.c2pa.example-press.whisper.online.",
   "forward_aaaa":"2a04:2a01:c0d3::5e"}}

# An off-list, self-signed signer with no public anchor: no registered /128, no DANE pin:
curl -s https://whisper.online/verify-identity/2a04:2a01::1
{"is_whisper_agent":false,"detail":"no Whisper agent identity anchors this address"}

A lifted or stolen identity assertion buys an impostor nothing here: the identity is a network fact bound to a key, not a bearer credential that can be copied out of one asset and pasted onto another (C2PA's own security model flags exactly that identity-assertion-spoofing mode).

Recipe 3: DANE-anchor your signer, no Trust-List slot

This is the wedge. Content signed by an off-list CA displays as "unknown source"; there is no free, automated path for an independent creator, a stringer, a small newsroom, or an agent to become a recognized signer, and the recognized-CA set is governed by a small coalition. Publish your claim-signer's key as a DNSSEC-signed TLSA record on your own domain and any verifier configured to consult it trusts the signer with no list to join, no gatekeeper, and no annual CA toll.

i

Honest scope: read this first. C2PA trust anchors and allow-lists are pluggable configuration inputs to the validator, so a DANE-anchored signer is a legitimate alternative trust source. But a DNSSEC/DANE anchor is not (yet) a formally recognized C2PA conformance trust anchor. Today conformance centers on X.509 + the curated C2PA Trust List. Position this as a complementary identity ecosystem, surfaced through a CAWG identity assertion (cawg.web_site + a did:web issuer) and offered as a proposal to the standard, never as "already C2PA-approved." It directly answers C2PA's own experimental Web Domain Trust Anchor (a self-signed cert fetched from an HTTPS /.well-known/c2pa.json, which its authors flag as vulnerable to domain takeover) with the mechanism that proposal didn't use: DNSSEC.

a verifier reading a Content Credential asks: trust this signer? Content Credential COSE_Sign1 signer off-list CA central C2PA Trust List curated CAs · pay-to-play ~$289/yr · small coalition ✗ “unknown source” off-list signer not recognized is it on the list? the signer's own domain DNSSEC-signed TLSA (3 1 1) == the claim-signer cert key ✓ trusted signer self-verifying · no list to join resolve the signer domain-owner controlled · revocable at DNS-TTL · surfaced via a CAWG identity assertion (did:web + cawg.web_site)
Two ways a verifier answers "trust this signer?": the gate-kept Trust List that renders an off-list signer as unknown source, or a DNSSEC-signed TLSA record the domain owner controls. A complementary anchor, not (yet) a formal C2PA conformance anchor.

Anchor under a domain you actually own (BYOD) so a verifier reads "signed by newsroom.example," not an opaque hosted label. Proving the domain is one shipped control-plane call (op:domain); thereafter your signer names live under your zone:

# Prove a domain you own and issue signer identities under it (op:domain, shipped).
curl -s https://graph.whisper.security/api/query -H "X-API-Key: whisper_live_xxx" \
  -H "content-type: application/json" \
  -d '{"query":"CALL whisper.agents({op:'"'"'domain'"'"', args:{domain:'"'"'newsroom.example'"'"'}})"}'
# → returns the DS + delegation to publish once; then your signer's TLSA lives at
#   _443._tcp.c2pa.newsroom.example, DNSSEC-signed, under YOUR control.

What a DANE-aware validator does is exactly what you can do by hand: extract the claim-signer EE cert from the Content Credential, hash its public key, and compare it to the DNSSEC-signed TLSA pin on the signer's domain. If they match, the signer is trusted, with no list consulted and no CA phoned home:

# Export the claim-signer EE cert from the manifest (your C2PA tool does this), then:
openssl x509 -in claim-signer.pem -pubkey -noout \
  | openssl pkey -pubin -outform der \
  | openssl dgst -sha256           # the SPKI-SHA256 of the signer's key
SHA2-256(...)= b653a4ef…fcb82d1d

# …and it equals the DNSSEC-signed TLSA 3 1 1 pin on the signer's own domain:
dig +short TLSA _443._tcp.c2pa.newsroom.example
3 1 1 b653a4ef…fcb82d1d          # match → trusted signer, no Trust-List slot

Two organizations that both trust the DNS root now verify each other's signers with no shared private list: the cross-org trust the curated model can't give without both parties joining it.

Recipe 4: See who verified your content

Here is the quadrant no incumbent occupies. C2PA verification needs no network call: the signer cert travels in the manifest's x5chain, so a verifier builds the chain offline. That makes provenance write-only from the signer's side: once content is out, you have zero visibility into who checked it, where, or how often. The moment your signer identity is DNS/DANE-anchored, that changes: a verifier resolving and validating your anchor generates DNS/TLSA/RDAP lookups against Whisper's own authoritative servers, and op:lookups turns those into "who verified my content" analytics plus an impersonation early-warning.

Boundary. op:lookups reports resolution and RDAP accesses against your signer identity's authoritative records: the PTR / AAAA / TLSA queries and /ip reads a DANE-aware verifier makes. It measures the volume and source of those lookups against your one anchor, never which asset a verifier was checking, so any per-asset correlation is yours to make against your own publish log, not something Whisper performs. It is a signal about who is checking your anchor; it does not see a verification performed purely from the in-manifest chain, and it is not a claim to observe traffic inside anyone else's network. It is the feedback loop C2PA and watermarking structurally lack, not a surveillance tap.

provenance verify is write-only until the signer is DNS-anchored Verifiers newsroom · platform fact-checker · browser checking your content in-manifest chain certs in COSE x5chain 0 network calls → signer blind DANE-anchored signer verify resolves TLSA · RDAP lands on Whisper authoritative op:lookups who verified · where · how often ⚠ impersonation tripwire spike beyond your publish cadence
The only verification a signer can see is one that consults a DANE-anchored identity. op:lookups turns that into "who verified your content." A spike in verification volume beyond your own publishing cadence, cross-referenced against your own publish log, is an early warning that your identity is being verified for content you never signed.

Ask who has been verifying a single signer identity (keyless, per-address) or the whole picture with your key:

# Who resolved / RDAP-queried this signer's DANE anchor? Keyless, per-address:
curl -s https://whisper.online/ip/2a04:2a01:c0d3::5e/lookups
{"address":"2a04:2a01:c0d3::5e","window":"24h",
 "lookups":[{"kind":"TLSA","count":184},{"kind":"PTR","count":57},
            {"kind":"rdap","count":22}]}

# With your key (op:lookups): where your byline is being verified, and any spike:
curl -s https://graph.whisper.security/api/query -H "X-API-Key: whisper_live_xxx" \
  -H "content-type: application/json" \
  -d '{"query":"CALL whisper.agents({op:'"'"'lookups'"'"', args:{agent:'"'"'2a04:2a01:c0d3::5e'"'"', window:'"'"'24h'"'"'}})"}' | jq .
  184 TLSA resolutions: your byline verified from 6 regions in 24h
  ⚠ spike: verify volume 3× your trailing baseline → cross-check your own publish log for identity lift

Feed a suspicious source straight into the attribution graph (below) and anonymous verification traffic becomes an attributed operator. That's the moment someone starts trading on your name.

Recipe 5: Revoke one compromised signer at DNS-TTL

When a signing key is stolen or a signer is compromised, the C2PA answer is CRL/OCSP plus removal from the Trust List: committee-paced, cache-heavy, and (per the spec) revocation checking is optional, so a compromised signer can stay "trusted" long after. In 2025 a camera vendor had to suspend its authenticity service and revoke its entire set of C2PA device certificates after a security flaw (a fleet-wide reset). With a per-unit Whisper identity you revoke the one burned key worldwide at DNS-TTL, and every other signer keeps working:

Boundary. This complements C2PA's own OCSP/CRL and Trust-List removal. It does not replace them, and it cannot retroactively un-sign what a stolen key already minted. A manifest carrying an RFC 3161 TSA countersignature and signed before the revocation stays syntactically valid. What DNS-TTL revocation changes is the exposure window (from "until OCSP, if ever" down to minutes), so it bounds the damage, it does not erase the past.

One verb, or one control-plane call. After it lands, every keyless check from Recipe 2 flips to is_whisper_agent:false for that address within the DNS TTL: no CRL to distribute, no Trust-List committee, no fleet-wide firmware reset:

# A signer key is burned. Revoke THIS unit's identity worldwide at DNS-TTL,
# not a fleet-wide cert reset. Every keyless check flips to false within the TTL.
whisper kill --revoke 2a04:2a01:c0d3::5e

# The same over the control plane (op:revoke): tears down /128 + PTR + DANE pin:
curl -s https://graph.whisper.security/api/query -H "X-API-Key: whisper_live_xxx" \
  -H "content-type: application/json" \
  -d '{"query":"CALL whisper.agents({op:'"'"'revoke'"'"', args:{agent:'"'"'2a04:2a01:c0d3::5e'"'"'}})"}'

The revoke is itself written to the transparency log, so the burn is auditable: an investigator can prove exactly when the signer stopped being valid, and any manifest signed after that timestamp fails to verify against a live identity worldwide. Revocation, counterfeit-signer detection, and containment are, deliberately, the same mechanism.

Recipe 6: Sign an AI agent's output under a Whisper-anchored identity

Under EU AI Act Art.50(2), a generative-AI provider must mark synthetic output as machine-readable and detectable-as-AI; C2PA's IPTC digitalSourceType (trainedAlgorithmicMedia) and c2pa.actions (c2pa.created) carry exactly that assertion. The standardized part is what the manifest says; the open question is who signed it: signer trust for AI agents is explicitly unresolved, and no agent gets a Trust-List slot. An agent that already holds a Whisper /128 and a per-agent CA leaf (deterministic, DANE-EE 3 1 1) has precisely what it lacks: a publicly verifiable, revocable signer identity, with no list to join and no ~$289/yr CA fee.

i

Shipped vs. roadmap, plainly. Shipped: the agent's /128, its per-agent CA leaf (DANE-EE, revocable), keyless verify, and op:lookups. Roadmap: a turnkey Whisper C2PA signer plugin and a hosted CAWG did:web issuer. Today you assemble the pattern: point your C2PA tool's external-signer hook at the per-agent leaf, and (optionally) run your own identity-claims aggregator as a did:web under your Whisper-anchored, DNSSEC-signed domain. And honestly on Art.50: a signature proves who + integrity, not that content is AI: the AI-generated declaration rides in the C2PA manifest's digitalSourceType; Whisper anchors the signer, it does not make you compliant. Recital 133 lists "cryptographic methods for proving provenance" as an enumerated technique and asks that verification be "accessible to the public," which an open DNS/DANE anchor advances.

Wire the agent's leaf as the claim signer and mark the output as AI-generated. The private leaf key stays on the agent; only the signature and the manifest go on the wire:

# The agent already holds a Whisper /128 + a per-agent CA leaf (DANE-EE 3 1 1):
# a publicly verifiable, revocable signer identity, no Trust-List slot required.
# Mark the output AI-generated in the manifest, and sign with the per-agent leaf:
#   c2pa.actions:      c2pa.created
#   digitalSourceType: trainedAlgorithmicMedia   (IPTC, declares it AI)
c2patool asset.png --manifest ai-manifest.json \
  --signer-path ./whisper-agent-leaf-signer.sh --reserve-size 20000 --output signed.png
# --signer-path = your external-signer hook → the agent's per-agent CA leaf;
# --reserve-size reserves bytes for its signature (c2patool requires it for an external signer).
# The result: a Content Credential whose signer is DNSSEC/DANE-verifiable and revocable.

Verifiers then resolve the agent-signer exactly like Recipe 2 (no account, no list) and, because it is a real network identity, the same egress governance that fences any agent fences this one: default-deny the signing service's egress to only your publish endpoint and the C2PA TSA, cap it, and keep the one-call kill-switch ready:

# Any verifier proves the agent-signer, keyless: no Trust-List slot involved:
whisper verify --trustless signer-agent.c2pa.example-press.whisper.online

# Govern the signing service's egress: default-deny, allow only what it must reach.
whisper policy set --agent 2a04:2a01:c0d3::a1 \
  --default deny --allow publish.example-press.com,tsa.c2pa-timestamp.example

# CAWG path: a Whisper DNSSEC-anchored domain is a first-class did:web issuer:
#   ICA VC issuer:  did:web:c2pa.example-press.whisper.online
#   cawg.web_site.uri points at the domain you proved in Recipe 3.

The result is the one thing agent stacks cannot otherwise get: an AI output whose signer is trusted and revocable, anchored in public DNS rather than a coalition's allow-list. See Sign agent outputs for the full external-signer pattern and did:web & VCs for the CAWG issuer.

Attribution: name whoever is trading on your signer (keyed)

The recipes above need no key. If you hold one, the same public API answers a deeper question: who is behind a source that is lifting your identity assertion, spoofing your brand, or scraping your verify endpoint. Passing an address to whisper.identify over POST https://graph.whisper.security/api/query fingerprints the operator across rotating clouds and residential proxies, because it tracks the infrastructure and the tooling, never the disposable last IP:

curl -s https://graph.whisper.security/api/query \
  -H "X-API-Key: whisper_live_xxx" \
  -H "content-type: application/json" \
  -d '{"query":"CALL whisper.identify(\"34.90.x.x\")"}' | jq .
# operator fingerprinted across AWS / GCP / Azure; residential swarm collapsed by JA4

This is the graph API, not a CLI verb: the call above is the interface. The read-only verbs identify, origins, walk, variants, and history each return a reproducible, replayable JSON evidence chain a verification desk or an ICC-grade dossier can reproduce. More in Graph & cognition.

Provenance is history, not truth: what signing can't do

Over-claiming is the credibility trap in this space, so here is the honest boundary of everything above:

Where each recipe stops and what's roadmap

Honest scoping, grouped by what each recipe touches. Whisper anchors the signer at the DNS/DANE boundary; the manifest, the pixels, and the veracity of the content stay exactly where they are.

RecipeComplementsDoes not touch / replace
Bind a signer · DANE-anchor C2PA Content Credentials, the CAWG identity assertion creating the manifest or the claim signature: that stays with your C2PA tool
Prove keyless · Who-verified contentcredentials.org verify, the in-manifest x5chain embedding a watermark; the pixels; whether the content is true
Revoke a signer C2PA OCSP / CRL + Trust-List removal already-timestamped manifests signed before the revoke
Sign an agent's output C2PA AI assertions (digitalSourceType), CAWG did:web the "is this AI?" claim itself: that rides in the manifest, not the signature

Roadmap, clearly labelled. Streaming this evidence into a SIEM ships today for Splunk, Microsoft Sentinel and OpenCTI (signed JSON → CEF/ECS). A STIX 2.1 over TAXII feed for sharing signer-revocation and impersonation evidence across a provenance coalition is proposed, not yet available. The first-class typed --c2pa-serial argument is roadmap: provision signers via the control-plane API above, passing the cert serial as the generic device_id, which is live. A turnkey C2PA signer plugin and a hosted CAWG did:web issuer are roadmap; the external-signer + did:web pattern in Recipe 6 works today. And DANE as a formal C2PA conformance trust anchor is a proposal to the standard, surfaced via CAWG in the meantime, not "already approved." The transparency log is tamper-evident, Ed25519-signed and Bitcoin-anchored today; independent witnessing is the next step. Nothing on this list is required for the six recipes; they run on shipped primitives alone.

Next