A Nostr keypair is already a decentralized identifier. The did:nostr method [[DID-NOSTR]] gives it a W3C DID [[DID-CORE]] form: the identifier is the public key, the DID document can be generated from it offline, and everything else — profile, relays, social graph — is optional enrichment derived from signed Nostr events. This primer introduces the method to readers from the DID and Verifiable Credentials ecosystem who do not know Nostr, and to readers from the Nostr ecosystem who do not know DIDs.

This document is informative (non-normative). It complements the normative Nostr DID Method Specification [[DID-NOSTR]]; where the two differ, the specification governs.

This document is a work in progress and may be updated, replaced, or obsoleted by other documents at any time. Feedback and contributions are encouraged through the GitHub repository.

Introduction

This primer is written for two audiences: people from the DID / Verifiable Credentials world who don't know Nostr, and people from the Nostr world who don't know DIDs. It answers the questions implementers actually ask — several come straight from the CCG mailing-list thread on the v0.1.0 announcement — and walks through the identifier, the DID document, resolution, and updates.

The design in one sentence: the baseline is a pure function of the public key (no network, no registry, no domain), and every networked feature layers on top as optional, verifiable enrichment.

Nostr, for DID implementers

Nostr is a pub/sub protocol over WebSockets. Users hold a secp256k1 keypair, sign small JSON events (a post, a profile, a contact list), and publish them to relays — simple, interchangeable servers that store and forward events. Clients can read a user's events back from any relay that has a copy; no relay is authoritative, and relays can be added or dropped freely. Every event carries the author's public key and a BIP-340 [[BIP-340]] Schnorr signature, so authenticity is checked by the reader, not trusted from the server.

Events have a kind number. Three kinds matter for this method:

KindContentsFeeds into the DID document as
0profile metadata (name, about, picture)profile
3contact listfollows
10002preferred relaysservice (type Relay)

NIP-01 [[NIP-01]] is the short technical overview of the protocol; for a higher-level introduction there is a good video overview by Jack Dorsey and Rabble.

DIDs, for Nostr developers

A Decentralized Identifier [[DID-CORE]] (DID) is a W3C-standard identifier that resolves to a DID document — a small JSON-LD document describing the identity: its public keys (verification methods), what they may be used for (authentication, assertionMethod), and optional service endpoints. DIDs are the identifier layer used by Verifiable Credentials, Solid / Linked Web Storage, and a growing ecosystem of wallets and resolvers.

did:nostr is the bridge: your existing Nostr key, expressed in a form that W3C tooling understands. You don't register anything anywhere — if you have a Nostr key, you already have a did:nostr.

The identifier

did:nostr:<64-character lowercase hex public key>

For example:

did:nostr:124c0fa99407182ece5a24fad9b7f6674902fc422843d3128d38a0afbee0fdd2

The identifier is always the raw 64-character lowercase hex public key — the same form that appears in Nostr events themselves [[NIP-01]]. Bech32 encodings such as npub are display conveniences in Nostr clients; they never appear in a did:nostr.

Because the key is in the identifier, the binding between identifier and key is permanent — there is no registry to consult and nothing to squat. (The flip side — what happens if the key is compromised — is covered in .)

The DID document

The minimal document is a pure function of the public key. Given only the identifier, any resolver produces:

{
    "@context": ["https://www.w3.org/ns/did/v1", "https://www.w3.org/ns/cid/v1", "https://w3id.org/nostr/context"],
    "id": "did:nostr:124c0fa99407182ece5a24fad9b7f6674902fc422843d3128d38a0afbee0fdd2",
    "type": "DIDNostr",
    "verificationMethod": [
        {
            "id": "did:nostr:124c0fa99407182ece5a24fad9b7f6674902fc422843d3128d38a0afbee0fdd2#key1",
            "type": "Multikey",
            "controller": "did:nostr:124c0fa99407182ece5a24fad9b7f6674902fc422843d3128d38a0afbee0fdd2",
            "publicKeyMultibase": "fe70102124c0fa99407182ece5a24fad9b7f6674902fc422843d3128d38a0afbee0fdd2"
        }
    ],
    "authentication": ["#key1"],
    "assertionMethod": ["#key1"]
}
      

Reading it line by line:

An enriched document adds material projected from the owner's signed events: service entries for relays (kind 10002), a profile (kind 0), a follows list (kind 3), optional alsoKnownAs links to the same identity elsewhere (a WebID, a fediverse handle, another DID), and a modified timestamp — the most recent created_at across the signed parts, so consumers can detect staleness.

Resolution: an offline core with optional enrichment

This is the part that most often surprises DID implementers, so it is worth stating plainly: the baseline resolver needs no network at all.

Layer 0 — offline (the conformance floor). Parse the pubkey out of the identifier and construct the minimal document above. Supporting this is a normative requirement of the specification — every conforming resolver can do it. It already provides full cryptographic functionality — authentication and signature verification work with no relay, no domain, no lookup.

Layer 1 — relay enrichment (optional). Query Nostr relays for the user's kind 0 / 3 / 10002 events, verify their signatures, and project them into the document as profile, follows, and service. Which relays? A local cache, a set of defaults, or relays already discovered for that key — since events are signed, any relay with a copy is as good as any other.

Layer 2 — HTTP / .well-known (optional). Any HTTP host can serve a pre-built document at:

https://<domain>/.well-known/did/nostr/<pubkey>.json

This makes resolution a cacheable HTTP GET — CDN-friendly, no WebSocket needed — with normal HTTP caching (ETag, Cache-Control) on top.

"How do I know which domain to query?"

You don't have to — that is the point of the layering. There is no canonical domain for a did:nostr, because the identifier is self-certifying and the baseline document is generated locally (Layer 0). A .well-known host is a cache, not a root of trust: anyone can mirror documents, and a consumer can always verify the signed source events or fall back to Layers 0–1. Domains enter the picture only when you have one in hand — your own resolver, a mirror you operate or trust, or a hint such as a didResolver field in the user's NIP-05 [[NIP-05]] identifier. If you have no domain, you have lost nothing: resolve offline and enrich from relays.

"Are updates guaranteed to reach resolvers?"

No — and the method is designed so that they don't have to be. Updates happen at the event layer: publishing a newer signed kind 0 / 3 / 10002 event changes what the document projects on the next resolution. Relay propagation is eventually consistent — an event published to one relay reaches others only if someone rebroadcasts it, and a resolver querying a different relay set may see older state for a while. The specification therefore keeps every guarantee on the layer that can honour it:

If your application needs stronger freshness, publish to (and resolve from) an overlapping relay set, or serve your own .well-known document — both are deployment choices, not protocol changes.

What the method does not do (yet)

The identifier is permanently bound to one key, so:

Until then, key management advice is the standard Nostr advice: guard the private key, back it up, and treat it as the identity.

Try it

Further reading