Whisper · Docs
Content

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:

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.

i

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:

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:

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.

i

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