Signer & C2PA identity
Give a C2PA claim signer a routable IPv6 /128 derived from the signing key it already holds, with its certificate serial (or its CAWG identity) as the domain separator. The signer stops being a name on someone else's Trust List and becomes a fact anyone can resolve: DNSSEC-anchored, DANE-EE pinned, RDAP-registered, revocable worldwide in one call.
This is the spine of the content vertical. A Content Credential is already tamper-evident and already carries a signer, but whether a verifier trusts that signer today rests on a gate-kept list you may never be admitted to. Everything else in this vertical (the provenance-gap cure, the CAWG and newsroom integrations, the EU AI Act evidence) builds on the one idea below: the signer's public identity moves out of a private allow-list and into open, DNSSEC-signed DNS the signer's own domain controls.
Shipped & live. Deriving a /128 from the claim-signer's public key with its certificate serial as device_id is in production today. Provision one with the control-plane call below and verify it from the DNSSEC root with tools already on your machine. Formal C2PA Trust-List recognition is a separate, standards-track path: see Where this fits. This anchor is additive to C2PA, never a fork.
Two tiers, per Postel's Law. With no API key anyone can verify a signer's identity from stock tools (dig, curl, RDAP) because the identity is public by design. With your key you provision and govern: mint the signer /128, publish its DANE pin, pull its who-verified analytics, and revoke it. Verification never needs an account; the control plane does.
The signer, the certificate, and the trust list
Strip a Content Credential to its trust-bearing parts and there are three: a set of assertions (the actions, the hashes, the metadata), a claim that hash-references those assertions, and a claim signature over the claim. That signature is a COSE_Sign1 (RFC 9052), and its signer is an X.509 end-entity certificate. The spec is explicit that only X.509 certificates may be used for signing. The end-entity profile is tight: extended key usage c2pa-kp-claimSigning (OID 1.3.6.1.4.1.62558.2.1), keyUsage = digitalSignature, and basicConstraints cA = false.
The signer's whole certificate chain travels in-band. It rides in the COSE header as x5chain (RFC 9360): every intermediate up to, but not including, the root is embedded, so a verifier builds the chain and checks the signature with no network call. That is elegant and offline, and it is also the whole hinge. Because everything needed to validate the signature is in the file, the only question left is the one the file can't answer for you: should you trust this particular end-entity certificate?
C2PA answers with a trust list. A validator trusts a signer if its certificate sits on an explicit allowed list, or chains to a root on a trust-anchors list. The load-bearing detail, and the honest hook for everything below, is that C2PA does not mandate any particular list or PKI. The trust list and the trust anchors are pluggable configuration inputs to the validator. Point the validator at a different anchor and it trusts a different set of signers, entirely within spec.
The gap this opens. The official C2PA Trust List and Conformance Program (mid-2025) is curated by a small coalition; there is no “Let's Encrypt for C2PA” (commercial signing certs run about $289/yr). An off-list CA renders a technically-perfect manifest “unknown source,” which quietly locks out independent creators, small newsrooms, and AI agents. C2PA's own experimental Web Domain Trust Anchor reaches for the same fix, but fetches a self-signed cert from an HTTPS /.well-known/c2pa.json file, and flags domain-takeover and verifier-side privacy as open problems.
Because the anchors are pluggable, a DNSSEC/DANE anchor under the signer's own domain is a legitimate alternative trust source, additive, not a fork. It answers exactly the Web-Domain-Trust-Anchor idea with the mechanism that proposal didn't use: a DNSSEC-signed DANE record instead of an origin-fetched JSON file, plus RDAP registration and one-call revocation. The manifest stays theirs; the signer becomes publicly, independently verifiable.
How the derivation works
claim-signer public key (SPKI) ──derive · domain-sep = cert serial | CAWG id──▶ /128 ──DNSSEC + DANE-EE 3 1 1──▶ a signer anyone verifies
ES256 / PS256 · secure element 2a04:2a01:c2a5::5e91:…:2ba5 RDAP-registered whisper verify --trustless
(the key behind the COSE_Sign1) routable, tenant-bound op:'revoke' → gone at DNS-TTL
(private signing key stays put)
The signer's /128 is not handed out of a pool and written to a database. It is computed, the same way on every node, from inputs the signer already has. Three things go in:
| Input | What it is | Where it lives |
|---|---|---|
| Signer public key | the SubjectPublicKeyInfo (SPKI) of the claim-signer's end-entity key: the ES256/PS256 key in your signing tool, secure element, or HSM |
the public half is submitted; the private signing key never leaves the signer |
device_id = signer cert serial (or CAWG identity) |
the X.509 serial of the claim-signer certificate already carried in every manifest's x5chain, or, for a named creator/org, the CAWG identity |
submitted with the request; the public index |
| Signing-role separator (optional) | a per-role domain separator so one org can hold many addressable signer identities: a newsroom desk, a per-camera signer, a per-agent signer | optional; omit it for a single signer identity |
Those inputs are combined by a one-way derivation, with a Whisper-held secret mixed in, into a stable, unguessable interface identifier scoped to your tenant:
# inputs -> a stable, forge-proof interface identifier
derive( claim-signer public key, signer cert serial [| CAWG identity] [, role], your tenant ) --> 64 uniform bits
# the /64 prefix is your tenant block; the low 64 bits are the derived id
/128 = < your tenant /64 prefix > : < derived interface id >
Four properties fall straight out of that derivation, and each one is load-bearing:
- Deterministic. The same
(key, serial[, role])yields a byte-identical/128every time, on every server: exactly one candidate, never a random retry. A signer re-registering re-derives its own address; both authoritative nodes mint the identical identity with zero replication between them. - Forge-proof. The address is a function of a key only the signer holds. An attacker with the manifest's serial and even the signer's public key still cannot become that signer: the server-side secret and the DANE pin (below) are the parts they can never produce. Lifting a valid identity assertion off one asset and re-embedding it in another no longer yields a verifiable signer.
- Tenant-bound & unlinkable. Your tenant's own
/64is folded into the derivation. The same signer key + serial under a different tenant produces a different address, so an outsider cannot derive or enumerate a signer's address in a tenant they don't control. The serial alone yields nothing: you cannot go serial →/128without the key, there is no enumerable directory, and RDAP/reverse-DNS return the registry object, never a lookup table of signers. - Liberal in, strict out. The serial is accepted generously (whitespace stripped, hex normalized, the leading
0xor colons tolerated), then held to a canonical form. A malformeddevice_idfails closed with a clear message, never a silent wrong address.
The moment the address is derived it is published as a full identity, atomically: an AAAA, a forward-confirmed PTR, and a DANE-EE TLSA 3 1 1 record that pins the claim-signer's leaf key directly, all DNSSEC-signed to the IANA root and registered in RDAP. That TLSA pin is what turns “the address is derived from the signer's key” into “the address is provable against the exact key that signed the COSE_Sign1.” See Publish the signer as a DANE record below, and DANE & TLSA for the byte-for-byte record.
The private signing key never moves. The signer submits only its public SPKI: the same public half of the key it already uses to produce the COSE_Sign1. The server derives a public address from public inputs plus a server-side secret; it never sees, holds, or derives the signer's private key. The signer proves ownership later by signing against the DANE pin, exactly as it signs a claim today.
Provision a signer identity
Provisioning is one control-plane call: whisper.agents with op:'connect', the claim-signer's public SPKI, and the certificate serial passed as device_id. It returns the deterministic /128 and the transport config. The endpoint is POST https://graph.whisper.security/api/query, authed with an X-API-Key header. No key ever travels in the body.
CALL whisper.agents({op:'connect', args:{
tier: 'wireguard',
identity_public_key: '<base64 SubjectPublicKeyInfo of the claim-signer key>',
device_id: '03:AC:74:66:1E:05:04:1B:8F:4E:5A:9C:2D:6E:71:B0' // the C2PA signer cert serial
// ecu_serial: 'claim-signer' // optional role separator: a distinct /128 per signing role
}}) YIELD op, ok, status, result, error
RETURN op, ok, status, result, error
With stock tools: just curl, no Whisper software. A quoted heredoc keeps the Cypher single-quotes intact so it pastes and runs as-is:
curl -s https://graph.whisper.security/api/query \
-H "X-API-Key: whisper_live_xxx" \
-H "content-type: application/json" \
-d @- <<'JSON' | jq .
{"query":"CALL whisper.agents({op:'connect', args:{tier:'wireguard', identity_public_key:'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...<SPKI>...', device_id:'03:AC:74:66:1E:05:04:1B:8F:4E:5A:9C:2D:6E:71:B0'}}) YIELD op, ok, status, result, error RETURN op, ok, status, result, error"}
JSON
The response is the standard envelope; result carries the derived address and the name. Because the signer holds its own key, no private key is ever returned: only the public identity:
{
"op": "connect", "ok": true, "status": 200,
"result": {
"tier": "wireguard",
"address": "2a04:2a01:c2a5:1e0f:5e91:a3d7:1b0c:2ba5", // the deterministic /128
"fqdn": "5e91a3d71b0c2ba5.<tenant>.agents.whisper.online",
"tlsa": "3 1 1 9f2b7c41a0e6d85b…c7b31d24", // DANE-EE pin of the signer's SPKI
"server_public_key": "…",
"endpoint": "…:51820",
"wireguard_config": "[Interface]\nAddress = 2a04:2a01:c2a5:1e0f:5e91:a3d7:1b0c:2ba5/128\n…"
},
"error": null
}
The call is idempotent and honest in its errors: Postel all the way down. Re-running with the same key and serial returns the same /128 (a re-derivation, not a new allocation); the same key with a different serial on your tenant is a clear 409, never a silent re-pin; a non-string device_id is a 400 that says exactly what was wrong, never an opaque 500. For every other op on this same endpoint, see the Control plane reference; for the transport mechanics and the SOCKS5 / AnyIP alternatives, Connect & egress.
A first-class typed --c2pa-serial / --signer argument is on the roadmap, not shipped. Today you bind the signer via the generic device_id field shown above: pass the certificate serial straight into it; that path is live. When the typed flag lands it will be a thin wrapper over exactly this call. The shipped CLI verbs are whisper verify --trustless, whisper create --register, whisper kill --revoke, whisper policy, and whisper logs. See CLI & one-command.
Publish the signer as a DANE record
The pin is what makes “publicly verifiable” literal. Whisper publishes the signer's leaf key as a DANE-EE TLSA 3 1 1 record under the identity's DNSSEC-signed name: 3 (domain-issued end-entity, no CA in the path), 1 (the SubjectPublicKeyInfo), 1 (its SHA-256). A verifier resolves it and matches the exact key that produced the COSE_Sign1, with no central list and no CA phone-home:
# the DANE-EE pin: the signer's SPKI, DNSSEC-signed, re-derivable by anyone
dig +dnssec TLSA 5e91a3d71b0c2ba5.<tenant>.agents.whisper.online +short
3 1 1 9f2b7c41a0e6d85b3c74fa19e0c25d6b8471af03e9c1d2b5a6f4e809c7b31d24
# the address names the signer, and the name resolves back: forward-confirmed
dig -x 2a04:2a01:c2a5:1e0f:5e91:a3d7:1b0c:2ba5 +short
5e91a3d71b0c2ba5.<tenant>.agents.whisper.online.
That single record replaces the entire “is this signer on the list?” question with “does the signer's own DNSSEC-signed zone vouch for this key?” That's an answer anyone with a resolver can compute. No Trust-List slot to be admitted to, no coalition gatekeeper, no annual CA toll, and per-unit revocation at DNS-TTL (next section).
Re-home it under your own domain. A newsroom or brand doesn't want readers verifying agents.whisper.online; it wants “signed by press.example-news.com.” With op:'domain' you prove a domain you control (delegation + DS) and issue signer identities under it, so the DANE pin and the reverse name live in your DNSSEC zone. The signer's identity then reads back as your own domain, verifiable by anyone who already trusts the DNS root, which is everyone. See DANE & TLSA for the record byte-for-byte and DNSSEC for the chain it hangs from.
CAWG: did:web and cawg.web_site
The claim signer says which tool produced the content; the CAWG identity assertion says which named human or org stands behind it. CAWG Identity Assertion v1.2 (DIF, ratified 2025-12-15) has a credential holder sign a signer_payload that hash-references the manifest's assertions (including the hard binding) via signer_payload.sig_type. Two credential types are defined: cawg.x509.cose (an end-entity X.509 S/MIME-style cert, COSE-signed, org identity via CAs) and cawg.identity_claims_aggregation (a W3C Verifiable Credential from an identity-claims aggregator). It carries the same “who anchors the root?” problem as the claim signer: an assertion is cawg.identity.trusted only if it chains to a recognized root, otherwise merely cawg.identity.well-formed.
Two seams inside CAWG are already DNS-native, and a Whisper DNSSEC domain slots straight into both:
- The ICA issuer is a
did:web. In real deployment the identity-claims-aggregation VC'sissueris a decentralized identifier: adid:web, which resolves through DNS + HTTPS and is therefore domain-anchored. A Whisper DNSSEC-anchored domain identity is a first-classdid:webissuer root: run your own issuer that verifiers trust via DNS, rather than re-centralizing on a third-party aggregator. verifiedIdentities[]carriescawg.web_site. That is a requireduri: a domain the actor controls. DANE-bind thecawg.web_site.uriand thecawg.x509.coseorg certificate to your DNSSEC zone and you turn CAWG's “well-formed but unrooted” into “trusted”, without an S/MIME CA in the loop. Heredevice_idis the CAWG identity rather than the claim-signer serial.
CAWG's own documentation flags that trust lists for identity-assertion signers are an unresolved, urgent problem. That makes CAWG the clean, standards-track seam a DNSSEC/DANE-anchored signer slots into, and, honestly, the formal-recognition path for this whole approach (see Where this fits).
Verify: keyless, no account
The identity half is public on purpose: a platform verifying at ingestion, a fact-checker triaging a viral clip, a reader, an auditor. Any of them can prove a signer's /128 with no Whisper account and without taking Whisper's word for it. Four independent checks, all from tools already on the machine:
# 1. The keyless verdict endpoint (takes an address or an FQDN)
curl -s https://whisper.online/verify-identity/2a04:2a01:c2a5:1e0f:5e91:a3d7:1b0c:2ba5 | jq .
{
"is_whisper_agent": true,
"dane_ok": true,
"jws_ok": true,
"evidence": { "aaaa": "...", "ptr": "5e91a3d71b0c2ba5.<tenant>.agents.whisper.online.",
"tlsa": "3 1 1 9f2b7c41…c7b31d24" }
}
# 2. Forward-confirmed reverse DNS: the address names the signer, the name resolves back
dig -x 2a04:2a01:c2a5:1e0f:5e91:a3d7:1b0c:2ba5 +short
5e91a3d71b0c2ba5.<tenant>.agents.whisper.online.
# 3. The registry record: RDAP, IP-anchored to the /128
curl -s https://whisper.online/ip/2a04:2a01:c2a5:1e0f:5e91:a3d7:1b0c:2ba5 | jq '.handle, .parentHandle'
# 4. The full chain re-derived on YOUR machine, against the IANA root: Whisper NOT in the trust path
whisper verify --trustless 5e91a3d71b0c2ba5.<tenant>.agents.whisper.online
A target that isn't a Whisper identity gets a clean 200 {"is_whisper_agent": false}. A negative verdict is a successful answer, not an error; only genuinely malformed input draws a 400, never a 500. --trustless is the strong form: it validates DNSSEC from the root in-process, on your resolver, so the proof holds even for a party that won't take Whisper's word for anything: a fact-checker resolving a claimed signer in seconds, a platform recognizing a legitimate off-list signer it would otherwise penalize. The full walk lives in Verify an agent.
Revoke: per-unit, worldwide
A compromised signing key, a decommissioned camera, a retired agent: one revoke away from having no network identity anywhere. The call tears down the /128, its PTR, and its DANE pin across both authoritative servers, and the change propagates at DNS-TTL speed:
CALL whisper.agents({op:'revoke', args:{agent:'2a04:2a01:c2a5:1e0f:5e91:a3d7:1b0c:2ba5'}})
# prove it: zero Whisper software, the same stock tools that proved it existed:
dig -x 2a04:2a01:c2a5:1e0f:5e91:a3d7:1b0c:2ba5 +short # -> nothing
curl -s https://whisper.online/verify-identity/2a04:2a01:c2a5:1e0f:5e91:a3d7:1b0c:2ba5
# -> {"is_whisper_agent": false, ...}
Revocation isn't a database flag you have to trust; it's provable the same way the identity was: the reverse lookup goes empty and the keyless verdict flips to false for everyone, everywhere, at once. Contrast C2PA's own revocation story, which the standard makes optional to check and routes through OCSP/CRL: academic testing has found conforming validators accepting revoked certs, so a compromised signer can stay “trusted” long after. The exposure window shrinks from “until OCSP, if ever” to minutes.
And revocation here is per-unit, which is the whole point. In 2025 a camera maker had to suspend its authenticity service and revoke its entire set of C2PA device certificates after a security vulnerability: a per-model blast radius, still unrestored many months later. Per-signer DANE-anchored identities invert that: one op:'revoke' cuts off a single compromised unit at DNS-TTL while the rest of the fleet keeps signing.
Who verified your content
Here is a capability C2PA structurally cannot give you. Because the signer's chain travels in-band, verification needs no network call, which means a signer normally has zero visibility into who verified their content. Provenance, from the signer's side, is write-only.
A DNS/DANE-anchored signer closes that loop. When a verifier resolves the DANE anchor (checks the TLSA, the PTR, the RDAP object) it generates lookups against Whisper's authoritative servers. op:'lookups' (and the keyless GET /ip/<addr>/lookups) returns who resolved or queried this signer's identity: a “who-verified-your-content” stream, and an early-warning tripwire that someone is checking, or probing to spoof, your signer, before anything downstream happens.
# who has been resolving / RDAP-querying this signer identity: reverse observability
curl -s https://graph.whisper.security/api/query \
-H "X-API-Key: whisper_live_xxx" \
-H "content-type: application/json" \
-d @- <<'JSON' | jq .
{"query":"CALL whisper.agents({op:'lookups', args:{agent:'2a04:2a01:c2a5:1e0f:5e91:a3d7:1b0c:2ba5'}}) YIELD op, ok, result RETURN op, ok, result"}
JSON
# or keyless, no account: the same answer over the public RDAP-anchored endpoint
curl -s https://whisper.online/ip/2a04:2a01:c2a5:1e0f:5e91:a3d7:1b0c:2ba5/lookups | jq .
Honest limit: this sees verifiers who resolve the DANE anchor (those who check the signer's DNS identity), not a purely in-manifest signature check that never touches the network. It is still the empty quadrant no incumbent occupies: the signer's own outbound activity is the companion op:'logs'. Both are live.
Nothing signed in the dark
Every identity mint and every revocation lands in a public, append-only RFC 6962 tlog-tiles Merkle log with Ed25519 signed-note checkpoints, each root anchored to Bitcoin via OpenTimestamps. For a regulated provenance program (the EU AI Act Article 50 disclosure duties, an evidentiary chain for a fact-checking desk) that is a non-repudiable, independently-timestamped record of exactly which signer identities were issued and revoked, and when.
Honest status. The log is tamper-evident, Ed25519-signed, and Bitcoin-anchored, but it is not yet independently witnessed. Our two nodes co-signing each other's checkpoints is availability, not independence. The log speaks the C2SP tlog-witness protocol, so an external witness can co-sign; until one does, treat the anchor as tamper-evidence, not third-party attestation. Endpoints: /checkpoint, /checkpoint/key, /ledger. It is GDPR-compatible by design: salted opaque commitments plus selective disclosure, so an op:'erase' makes a leaf's meaning unrecoverable while the proofs stay valid.
Full mechanics in Transparency log.
Signing agents & egress governance
The highest-ceiling socket is an AI agent or tool signing its own outputs. C2PA already standardizes the content of the AI assertion: c2pa.actions with c2pa.created, and the IPTC digitalSourceType (trainedAlgorithmicMedia for fully-AI, compositeWithTrainedAlgorithmicMedia for AI-assisted). But who signed it is the open question, and agent stacks are exactly the parties a curated Trust List excludes. An agent whose signer identity is Whisper-anchored gets trusted-signer status with no Trust-List slot and no CA fee: the agent's /128-anchored identity is its C2PA signer identity. The recipe is Sign agent outputs.
Because the signer is now a governed network identity, the same control plane governs what it may reach and caps what it may spend: the full egress-governance surface, on the same endpoint and key:
op:'policy': graph-first, default-deny egress by name, category, or geography (a signing agent talks to your publishing endpoint and nothing else).op:'firewall': allow/deny byhost,cidr, orport.op:'budget': cap a signer's traffic with a hard kill-switch.op:'revoke': cut a compromised signer off worldwide, in one call.
This is where the anchor meets regulation. A publicly-resolvable signer advances the EU AI Act Article 50(2) bar that a machine-readable mark be “effective, interoperable, robust and reliable” and its verification “accessible to the public”. Recital 133 lists “cryptographic methods for proving provenance and authenticity of content” among the enumerated techniques. Honest limit: the AI-generated claim rides in the C2PA manifest; Whisper anchors the signer, not the AI-ness. The compliance mapping is in EU AI Act · C2PA · ISO 22144.
Where this fits, and where it doesn't
Whisper anchors the signer at the DNS/DANE boundary: publicly verifiable, addressable, revocable. It is additive: it complements the anchors you already run and deliberately stops at the manifest. It does not create the Content Credential, and it does not embed a watermark.
- C2PA / Content Credentials / CAI. The tamper-evident manifest (who, when, which tools, which edits) is theirs, and it stays. Whisper anchors the same signer those manifests already reference, so it complements the manifest; it does not replace it.
- CAWG. The standards-track identity seam: the
did:webissuer andcawg.web_siteabove are already DNS-native. This is the formal-recognition path: DNSSEC/DANE anchoring is a complementary identity ecosystem, surfaced via CAWG and/or a proposal to the standard. - Watermarking (SynthID, Meta Seal). An invisible pixel signal that survives the metadata strip a manifest can't: AI-origin detection, complementary. We anchor identity, not pixels.
- Where Whisper does not go. It is not a deepfake detector: the absence of a credential is not proof of fakery. It does not make signed content true (provenance is origin and history, never veracity); a genuine signer can sign staged or false content, and what we add is accountability: the signer is publicly named. It does not stop a screenshot or re-encode from separating the credential from the asset: that is watermarking's and durable soft-binding's job; a public anchor improves recovery odds and lets analytics detect stripping, but does not prevent it.
The one claim we will not overstate. DNSSEC/DANE anchoring is not (yet) a formally recognized C2PA conformance trust anchor: today conformance centers on X.509 plus the curated C2PA Trust List. We position DANE as a complementary identity ecosystem surfaced through CAWG and a proposal to the standard, never “already C2PA-approved.” No product makes anyone Article-50 compliant; a publicly-verifiable signer evidences and strengthens, it does not guarantee. And no specific vendor is named or implicated as a breach victim anywhere in these docs: the incidents cited are the public, class-level ones.
Next
- Provenance-gap cure: this signer identity, applied to the exact trust-list / off-list-CA problem it was built for.
- C2PA · CAWG · newsroom: dropping the signer
/128into a claim signer, a CAWG identity assertion, and a newsroom signing pipeline. - DANE & TLSA: the
3 1 1record that makes the signer provable against its key, byte for byte. - Sign agent outputs: an AI agent signing its own C2PA claims under a verifiable, revocable identity.