Nostr Beginner 5 min read
Identifiers and Encoding
Nostr identifier formats: NIP-19 bech32 encoding, NIP-05 DNS verification, and shareable references.
identifiers NIP-19 NIP-05 bech32 npub nprofile DNS
Identifiers and Encoding
Nostr uses several identifier formats to make sharing and verification easier. Raw hex keys work but are hard to read—bech32 encoding and DNS verification provide human-friendly alternatives.
NIP-19: Bech32 Entities
Bech32 encoding adds:
- Human-readable prefixes
- Error detection
- Checksums
Basic Entities
| Prefix | Contains | Example |
|---|---|---|
npub | Public key | npub1qqqsyq... |
nsec | Private key | nsec1qqqsyq... |
note | Event ID | note1qqqsyq... |
Extended Entities (TLV-encoded)
| Prefix | Contains | Purpose |
|---|---|---|
nprofile | Pubkey + relay hints | Shareable profile |
nevent | Event ID + relay hints + author | Shareable event |
naddr | Kind + pubkey + d-tag + relays | Replaceable event ref |
Encoding and Decoding
Python
import bech32
def npub_encode(pubkey_hex: str) -> str:
"""Encode hex pubkey as npub."""
data = bytes.fromhex(pubkey_hex)
converted = bech32.convertbits(data, 8, 5)
return bech32.bech32_encode("npub", converted)
def npub_decode(npub: str) -> str:
"""Decode npub to hex pubkey."""
hrp, data = bech32.bech32_decode(npub)
if hrp != "npub":
raise ValueError(f"Expected npub, got {hrp}")
converted = bech32.convertbits(data, 5, 8, False)
return bytes(converted).hex()
# Usage
pubkey_hex = "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"
npub = npub_encode(pubkey_hex)
print(npub) # npub1808...
decoded = npub_decode(npub)
print(decoded == pubkey_hex) # True
JavaScript (nostr-tools)
import { nip19 } from 'nostr-tools';
// Encode
const npub = nip19.npubEncode(pubkeyHex);
const nsec = nip19.nsecEncode(secretKeyBytes);
const noteId = nip19.noteEncode(eventIdHex);
// Decode
const { type, data } = nip19.decode(npub);
console.log(type); // 'npub'
console.log(data); // hex pubkey
// Extended entities
const nprofile = nip19.nprofileEncode({
pubkey: pubkeyHex,
relays: ['wss://relay.damus.io', 'wss://nos.lol']
});
const nevent = nip19.neventEncode({
id: eventIdHex,
relays: ['wss://relay.damus.io'],
author: authorPubkey
});
NIP-05: DNS Identifiers
Map a human-readable identifier to a Nostr pubkey:
alice@example.com → npub1...
How It Works
- User claims
alice@example.com - Server hosts
/.well-known/nostr.json:
{
"names": {
"alice": "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"
},
"relays": {
"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d": [
"wss://relay.example.com"
]
}
}
- Clients fetch and verify
Verification Process
import httpx
async def verify_nip05(identifier: str) -> dict | None:
"""Verify a NIP-05 identifier."""
if "@" not in identifier:
return None
name, domain = identifier.split("@", 1)
# Fetch nostr.json
url = f"https://{domain}/.well-known/nostr.json?name={name}"
try:
async with httpx.AsyncClient() as client:
response = await client.get(url, timeout=10)
response.raise_for_status()
data = response.json()
# Extract pubkey
pubkey = data.get("names", {}).get(name)
if not pubkey:
return None
# Extract relays (optional)
relays = data.get("relays", {}).get(pubkey, [])
return {
"identifier": identifier,
"pubkey": pubkey,
"relays": relays
}
except Exception as e:
return None
# Usage
result = await verify_nip05("alice@example.com")
if result:
print(f"Pubkey: {result['pubkey']}")
JavaScript
import { nip05 } from 'nostr-tools';
const profile = await nip05.queryProfile('alice@example.com');
if (profile) {
console.log('Pubkey:', profile.pubkey);
console.log('Relays:', profile.relays);
}
Setting Up NIP-05
Static File (Simple)
Create /.well-known/nostr.json:
{
"names": {
"alice": "pubkey-hex",
"bob": "another-pubkey-hex",
"_": "default-pubkey-hex"
}
}
The _ entry is used when no name is specified (just @domain.com).
Dynamic Endpoint (Advanced)
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/.well-known/nostr.json")
async def nostr_json(name: str = Query(None)):
# Look up in database
user = await db.get_user_by_name(name)
if not user:
return {"names": {}}
return {
"names": {
name: user.pubkey
},
"relays": {
user.pubkey: user.preferred_relays
}
}
CORS Headers
Enable cross-origin requests:
Access-Control-Allow-Origin: *
Identifier Best Practices
For Sharing Profiles
Use nprofile with relay hints:
const shareable = nip19.nprofileEncode({
pubkey: myPubkey,
relays: ['wss://relay.damus.io', 'wss://nos.lol']
});
// Share: nostr:nprofile1...
For Sharing Events
Use nevent with context:
const shareable = nip19.neventEncode({
id: eventId,
relays: ['wss://relay.damus.io'],
author: authorPubkey,
kind: 1 // Optional
});
// Share: nostr:nevent1...
For Agent Identity
- Generate dedicated agent keypair
- Set up NIP-05:
agent@yourdomain.com - Publish profile (kind 0) with NIP-05 field
{
"kind": 0,
"content": "{\"name\":\"My Agent\",\"about\":\"AI assistant\",\"nip05\":\"agent@yourdomain.com\"}"
}
URI Scheme (NIP-21)
The nostr: URI scheme enables clickable links:
nostr:npub1...
nostr:nprofile1...
nostr:nevent1...
nostr:naddr1...
Clients recognize these and handle appropriately.
Common Patterns
Parse Any Identifier
function parseNostrIdentifier(input) {
// Remove nostr: prefix if present
const cleaned = input.replace(/^nostr:/, '');
// Check for NIP-05
if (cleaned.includes('@')) {
return { type: 'nip05', value: cleaned };
}
// Try bech32 decode
try {
const decoded = nip19.decode(cleaned);
return { type: decoded.type, value: decoded.data };
} catch {
// Try raw hex
if (/^[0-9a-f]{64}$/i.test(cleaned)) {
return { type: 'hex', value: cleaned };
}
}
return null;
}
Machine-Readable Summary
{
"topic": "nostr-identifiers",
"audience": "ai-agents",
"prerequisites": ["nostr-keys"],
"key_concepts": [
"bech32-encoding",
"nip19-entities",
"nip05-verification",
"uri-scheme"
],
"code_examples": ["python", "javascript"],
"identifier_types": {
"npub": "public-key",
"nsec": "private-key",
"note": "event-id",
"nprofile": "pubkey-with-relays",
"nevent": "event-with-context",
"naddr": "replaceable-event-ref"
},
"related": [
"/learn/nostr/keys",
"/learn/nostr/specs/nip-19",
"/learn/nostr/specs/nip-05"
]
}