What is did:key?

The did:key method is the simplest of the W3C DID methods: the identifier IS the public key. There is no DNS lookup, no web server, no blockchain, and no resolver network. Anyone with the DID string can derive the full DID Document deterministically by decoding the identifier itself.

The shape is always: did:key:z<multibase-multicodec-key>. The z prefix is a multibase tag that means base58btc. What follows is a multicodec-prefixed public key — a varint that says “this is an Ed25519 key” (or P-256, etc.) followed by the raw key bytes.

Plain-language summary

  • Self-resolving: A verifier can build the DID Document from the identifier alone — no network call required.
  • No infrastructure: No DNS, no certs, no servers, no chain. The math is the trust anchor.
  • Deterministic: The same key always produces the same DID and the same DID Document. Two parties decoding the string get bit-identical results.
  • Single-key only: A did:key has exactly one verification method. No service endpoints, no rotation history, no metadata beyond the key.

did:key vs did:web — honest comparison

Propertydid:keydid:web
ResolutionDecode the identifier locally. No network.HTTPS GET of /.well-known/did.json. DNS + TLS required.
Key rotationNot supported. A new key is a new DID.Supported. Update the hosted did.json; the DID stays the same.
DecentralizationFully decentralized — no infrastructure dependency at all.Trust collapses to whoever controls the domain and the cert.
DiscoverabilityNone. The DID reveals nothing but the key bytes.You can host descriptive metadata, service endpoints, and contact info alongside.
Service endpointsCannot carry them.Arbitrary services in the DID Document.
Censorship resistanceHigh — nothing to seize.Low — registrar can suspend the domain.
Ecosystem footprintTiny — widely supported by SSI libraries as the default test method.Growing — eIDAS, OpenBadges 3.0, DCC pilots all favor did:web for institutional issuers.

When to use did:key

  • Ephemeral identities: A one-shot signer for a single document or session.
  • Single-purpose verification: The subject DID inside a credential when the holder will not need to rotate.
  • Demos and tests: The default DID method in nearly every SSI conformance suite. Zero setup.
  • Key-only contexts: Anywhere you want a portable cryptographic identity that stays self-contained — no infrastructure to fail, no third parties to trust.
  • Holder DIDs in selective-disclosure flows: Many wallets generate a fresh did:key per credential presentation to break correlation.

When NOT to use did:key

  • Long-lived issuer identities: An issuer whose key is compromised cannot rotate without breaking every credential ever signed. Use did:web (or a ledger-backed method) for issuer identities.
  • Identities with metadata: If you need service endpoints, contact info, alternative key types in the same document, or human-readable descriptors, did:key cannot carry any of it.
  • Brand-bound identities: A credential signed by did:web:university.example.edu tells a verifier who issued it. did:key:z6Mk... tells them only that some keypair signed it.
  • Multi-party governance: No way to express “require N of M signatures” or shared control. The key is the controller, period.

Encoding: multibase + multicodec, in plain bytes

The string after did:key: is built like this:

  1. Start with the raw public key bytes (32 bytes for Ed25519, 33 bytes compressed for P-256).
  2. Prepend a multicodec varint identifying the key type. Ed25519 = 0xed 0x01. Compressed P-256 = 0x80 0x24 (varint encoding of codec 0x1200).
  3. Encode the resulting byte string as base58btc.
  4. Prepend z — the multibase prefix for base58btc.
  5. Prepend did:key:.

That is it. To resolve, the verifier reverses the steps: strip did:key:, strip z, base58btc-decode, read the varint to learn the key type, then read the remaining bytes as the public key. No network, ever.

The multicodec table is the registry that maps varint prefixes to key types. Ed25519 public key = 0xed; compressed P-256 public key = 0x1200. These are encoded as unsigned LEB128 varints (each byte uses 7 bits of payload + 1 continuation bit). 0xed fits in one byte (varint 0xed 0x01). 0x1200 needs two payload bytes (varint 0x80 0x24).

What this demo does and does not do

Demo doesDemo does NOT do
  • Generate Ed25519 / P-256 keypairs in the browser
  • Encode the public key as a real did:key identifier (multibase + multicodec)
  • Build the deterministic DID Document from the key alone
  • Issue a W3C VC 2.0 credential signed with compact JWS
  • Verify a signed credential by decoding the issuer DID itself — no network call
  • Resolve any pasted did:key string to its DID Document and raw key bytes
  • Support key rotation (did:key cannot — this is a method-level limitation, not a demo limitation)
  • Check revocation (StatusList2021 / Bitstring Status List — that is a separate sibling tool)
  • Implement Data Integrity Proofs (URDNA2015 canonicalization) — uses simplified JWS
  • Support did:key key types this demo skips (X25519, secp256k1, BLS12-381)
  • Persist keys (lost on refresh — intentional)

Reference

Generate a did:key

Pick a key type and generate a real keypair via crypto.subtle.generateKey(). The public half is encoded as a did:key identifier; the deterministic DID Document is rendered alongside. The private key stays in browser memory only.

Resolved DID

DID

Encoding breakdown

DID Document (deterministic)

No hosting needed — any verifier with the DID can rebuild this exact JSON.

Ed25519 in crypto.subtle is supported in Chrome 113+, Firefox 130+, Safari 17+. If unavailable, the demo will surface an error and you can switch to P-256 (works everywhere). RSA in did:key is not part of the canonical method spec — the demo encodes the SPKI bytes via multibase only and labels the result clearly.

Issue a Verifiable Credential 2.0

Build a W3C VC 2.0 credential and sign it with the keypair generated on the Generate did:key tab. Signing uses compact JWS (header.payload.signature). The header's kid is the issuer's verification method, which for did:key is always <did>#<multibase-key>.

Claims

Key/value pairs that go inside credentialSubject.

Unsigned VC

Signed VC (with proof block)

This demo signs with a single JWS over canonicalized JSON (sorted keys). The Data Integrity proof spec defines URDNA2015 canonicalization plus a specific proof envelope; production verifiers expect that exact format. Treat this as a teaching scaffold, not a conformant issuer.

Verify a Signed Credential

Paste a signed VC. The verifier extracts the issuer DID, decodes the embedded public key from the did:key identifier itself (no network resolution required — that is the entire point of did:key), and checks the JWS signature.

Inspection

What this verifier does

  • Parses the credential JSON, pulls the issuer field
  • Decodes the issuer's did:key identifier locally to recover the public key — zero network calls
  • Imports the recovered key into Web Crypto with the correct algorithm
  • Reconstructs the signing payload (canonicalized credential without the proof block)
  • Calls crypto.subtle.verify()
  • Checks validFrom ≤ now ≤ validUntil

What it does NOT do

  • Check StatusList2021 / Bitstring Status List for revocation — did:key has no revocation primitive; that is a separate VC-level concern handled by a sibling tool
  • Walk a key-rotation history (did:key cannot rotate by design)
  • Validate every @context URI resolves
  • Run JSON-LD canonicalization (URDNA2015) the way Data Integrity Proofs require

Resolve any did:key (no network)

Paste a did:key identifier. The resolver decodes the multibase prefix, base58btc-decodes the body, reads the multicodec varint to identify the key type, and reconstructs the deterministic DID Document — entirely offline.

Decoded structure

Reconstructed DID Document

Try resolving a did:key generated on this page, then refresh the page (which clears the in-memory keypair) and resolve the same string again — you will get the identical DID Document. That is what “self-resolving” means in practice: the key bytes ARE the document.