Whisper · Docs
Recipes

Pin a wallet to an identity

Bind a crypto wallet to a verifiable agent address — attributable, auditable autonomous transactions.

An autonomous agent that can sign a transaction is also an autonomous agent that can drain a wallet with nobody's name on the damage. Give a thousand agents hot wallets today and what you actually have is a thousand bare keypairs: an address that moved funds, and no way for anyone downstream — a counterparty, an auditor, a chain-analysis firm, you at 3 a.m. reading logs — to connect that address back to which agent, whose fleet, under what policy, still holding what other keys. On-chain forensics dead-ends at "some EOA sent this," which is exactly the anonymity a hostile actor wants and exactly the accountability gap a legitimate operator can't afford. Whisper already gives every agent a real, verifiable identity — a routable /128, a DNSSEC-signed name, a DANE-pinned TLS key, a public transparency-log entry. This page shows the pattern for extending that same identity to cover a wallet: publish a signed statement, in the agent's own name, that says "this address is mine" — so a counterparty verifies the agent and the wallet from the same public DNS lookup, and every on-chain action the wallet takes inherits an address that was never anonymous in the first place.

Status check, honestly stated. Everything below composes from primitives Whisper ships today — did:web, DANE, the transparency log, and the keyless whisper verify --trustless identity check — plus one DIY step (signing the credential with a stock JOSE library) that needs no Whisper-specific feature. Two honest caveats: there is no single whisper pin-wallet command yet, and whisper verify today takes an address or FQDN, not a --credential file — so the credential's own signature is checked with the stock JOSE step shown below. What follows is the pattern you assemble from shipped parts, written so it drops straight into the trustless verifier the moment the CLI wraps that last step.

The mechanism: a Verifiable Credential, not a mapping table

Don't build a side database that maps agent IDs to wallet addresses — that database is itself a new thing to trust, and it isn't checkable by anyone who doesn't have an account with you. Instead, issue a Verifiable Credential the same way you'd issue any other agent capability claim (see did:web & verifiable credentials): a compact JWS whose issuer and subject are the agent's own did:web, signed with the same P-256 key family that backs its DANE TLSA pin.

header:  {"alg":"ES256","kid":"did:web:acef2002a323d40d4.demo.agents.whisper.online#key-1","typ":"JWT"}
payload: {"iss":"did:web:acef2002a323d40d4.demo.agents.whisper.online",
          "sub":"did:web:acef2002a323d40d4.demo.agents.whisper.online",
          "vc":{"@context":["https://www.w3.org/ns/credentials/v2"],
                "type":["VerifiableCredential","AgentWallet"],
                "credentialSubject":{
                  "chain":"eip155:1",
                  "address":"0x9F2b...c41A",
                  "purpose":"treasury"
                }},
          "nbf":1751328000}
signature: ES256 over header.payload, by key-1

Two design choices make this hold up under scrutiny, not just look plausible:

  1. Self-issued, but not self-authenticating in isolation. The credential alone only proves "whoever holds key-1 says this wallet is theirs" — the same trust model DKIM uses for a mail domain. What makes it worth anything is that key-1 is not a free-floating keypair: it's the same key DANE pins under _443._tcp.<fqdn>, the same key the per-agent CA leaf was minted for, and the whole bundle sits behind a real allocated /128 in 2a04:2a01::/32 (AS219419) with reverse DNS that resolves back to the exact same name. Forging the wallet claim means forging the identity underneath it — DNSSEC, DANE, and reverse DNS all have to lie in agreement, which is the same bar every other Whisper identity proof sets.
  2. Timestamped independently of the issuer's word. Every issuance Whisper's identity plane produces — and, by extension, anything signed with that identity's key — can be checked against the transparency log: an append-only, Ed25519-signed Merkle log in the style of Certificate Transparency (RFC 6962), periodically anchored to Bitcoin via OpenTimestamps. A counterparty doesn't have to take your word for when the binding was made; they can prove the binding existed before a specific transaction did.

Publish the resulting JWS as a plain resource next to the agent's existing did.json, the same well-known-path convention did:web already uses:

https://acef2002a323d40d4.demo.agents.whisper.online/.well-known/did.json
https://acef2002a323d40d4.demo.agents.whisper.online/.well-known/wallets/eip155-1.jwt

Nothing about that second path is Whisper-specific plumbing — it's a static file served over the same TLS session, DNSSEC chain, and DANE pin as everything else at that name.

Verifying a pinned wallet — before you trust a transfer

A counterparty who receives (or is about to send to) 0x9F2b...c41A and wants to know "is this actually a Whisper agent's treasury wallet, or just an address someone typed into a memo" runs four checks, none of which need an account with anyone:

  1. Resolve the claimed agent name and confirm the identity is real (PTR ⇄ AAAA bijection, DNSSEC AD=YES) — see Identity.
  2. Fetch did.json, pull verificationMethod[0].publicKeyJwk.
  3. Fetch the wallet credential, verify the ES256 signature against that key.
  4. Confirm the same key is the one DANE pins at _443._tcp.<fqdn> — if the wallet credential were signed by a different key than the one the TLS/DANE chain trusts, that's the tell of a forged binding, not a real one.

Optionally, a fifth check for anything that matters: pull the credential's transparency-log inclusion proof and confirm it predates the on-chain transaction under scrutiny — a binding minted after a suspicious transfer is a backdated cover story, not an audit trail.

Dual example: publish and verify

With stock tools

No Whisper software anywhere in this — DNS, TLS, HTTPS, and a JOSE library:

# 1 — the identity underneath the wallet is real
dig -x 2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4 +short
# acef2002a323d40d4.demo.agents.whisper.online.

# 2 — the key that will sign the wallet credential is the same key DANE pins
dig +short TLSA _443._tcp.acef2002a323d40d4.demo.agents.whisper.online
# 3 1 1 b653a4ef...fcb82d1d

# 3 — fetch the did:web document and the wallet credential
curl -s https://acef2002a323d40d4.demo.agents.whisper.online/.well-known/did.json | jq .
curl -s https://acef2002a323d40d4.demo.agents.whisper.online/.well-known/wallets/eip155-1.jwt

# 4 — verify the signature with any JOSE library, no Whisper API involved
python3 - <<'PY'
import json, urllib.request
from jose import jws

fqdn = "acef2002a323d40d4.demo.agents.whisper.online"
doc = json.load(urllib.request.urlopen(f"https://{fqdn}/.well-known/did.json"))
jwk = doc["verificationMethod"][0]["publicKeyJwk"]
credential = urllib.request.urlopen(f"https://{fqdn}/.well-known/wallets/eip155-1.jwt").read()

payload = json.loads(jws.verify(credential, jwk, algorithms=["ES256"]))
print(payload["vc"]["credentialSubject"])   # {"chain": "eip155:1", "address": "0x9F2b...c41A", ...}
PY

# 5 — cross-check the claimed wallet's on-chain history against the binding's timestamp
curl -s "https://api.etherscan.io/api?module=account&action=txlist&address=0x9F2b...c41A" | jq '.result[0].timeStamp'

With Whisper

The identity underneath the wallet — the part any forgery has to defeat first — is verified by one shipped, keyless command. whisper verify --trustless re-derives PTR/AAAA/TLSA/did:web from the IANA DNSSEC root in-process, trusting no Whisper API, and hands you the exact key the wallet credential must be signed by:

whisper verify --trustless acef2002a323d40d4.demo.agents.whisper.online
# ✓ PTR ⇄ AAAA bijection          ✓ DANE-EE (3 1 1) pin matches the served leaf
# ✓ DNSSEC chain to the IANA root ✓ transparency-log inclusion (RFC 6962)
# VERDICT: cryptographically proven Whisper agent — Whisper's own API not trusted

That establishes key-1. Checking the wallet credential's ES256 signature against it is the same one-line JOSE step from the stock example above — whisper verify does not yet fold an arbitrary --credential file into its chain walk (it takes an address or FQDN, nothing more). If you'd like whisper to verify a pinned-wallet credential in a single command, file a request against the CLI at github.com/whisper-sec.

For a tenant checking its own fleet's bindings server-side, the control-plane verb that already returns an agent's identity bundle is the natural place this would surface:

CALL whisper.agents({op:'identity', args:{agent:'my-agent'}})

Minting the credential — the signing half — is today a DIY step with the same stock JOSE tooling shown above, run wherever your agent process already holds the key material whisper create provisioned for it. See the integrations page and github.com/whisper-sec for recipe code as this pattern gets packaged; file a request against the CLI if you want whisper to wrap the signing step directly.

Why this beats a wallet with no name on it

A wallet address with no identity behind it is unaccountable by construction — it can move funds, but nobody outside the operator can attach a fact to it stronger than "an address did this." A wallet pinned to a Whisper identity inherits everything that identity already proves: a real allocated /128 under AS219419, a DNSSEC-signed name, a DANE-pinned key, and a transparency-logged issuance history. A counterparty doesn't have to trust your bookkeeping — they resolve the name, verify the signature, and check the timestamp, the same three stock-tool steps whether they've ever heard of Whisper or not. That's the difference between "trust our dashboard" and "verify it yourself" — and for anything moving value autonomously, it's the only difference that will hold up when something goes wrong.

Next

Identity: the address is the credential — the full seven-proof chain a wallet binding rides on · Per-agent CA — why the key signing the wallet credential can't be borrowed from another agent's identity.