NIP-nip-19 Final
NIP-19: Bech32-Encoded Entities
Human-readable encoding for Nostr identifiers. npub, nsec, note, nprofile, nevent, and naddr formats.
| Type | Nostr Implementation Possibility |
| Number | nip-19 |
| Status | Final |
| Original | https://github.com/nostr-protocol/nips/blob/master/19.md |
NIP-19: Bech32-Encoded Entities
Status: Final
NIP-19 defines human-readable encodings for Nostr identifiers using bech32. This makes keys and references easier to share and reduces errors.
Basic Entities
Simple bech32 encoding of 32-byte values:
| Prefix | Contains | Example |
|---|---|---|
npub | Public key | npub1qqqsyq... |
nsec | Private key | nsec1qqqsyq... |
note | Event ID | note1qqqsyq... |
Encoding
import bech32
def encode_npub(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 encode_nsec(privkey_hex: str) -> str:
"""Encode hex private key as nsec."""
data = bytes.fromhex(privkey_hex)
converted = bech32.convertbits(data, 8, 5)
return bech32.bech32_encode("nsec", converted)
def encode_note(event_id_hex: str) -> str:
"""Encode event ID as note."""
data = bytes.fromhex(event_id_hex)
converted = bech32.convertbits(data, 8, 5)
return bech32.bech32_encode("note", converted)
Decoding
def decode_bech32(bech32_str: str) -> tuple[str, bytes]:
"""Decode any NIP-19 bech32 string."""
hrp, data = bech32.bech32_decode(bech32_str)
converted = bech32.convertbits(data, 5, 8, False)
return hrp, bytes(converted)
# Usage
prefix, data = decode_bech32("npub1...")
print(f"Type: {prefix}, Hex: {data.hex()}")
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 string
Shareable Entities (TLV)
Extended entities use Type-Length-Value encoding to include additional metadata:
| Prefix | Contains | Purpose |
|---|---|---|
nprofile | Pubkey + relays | Share profile with relay hints |
nevent | Event ID + relays + author | Share event with context |
naddr | Kind + pubkey + d-tag + relays | Reference replaceable event |
TLV Types
| Type | Value | Contains |
|---|---|---|
special | 0 | Main data (pubkey, event ID, etc.) |
relay | 1 | Relay URL |
author | 2 | Author pubkey (for nevent) |
kind | 3 | Event kind (for nevent, naddr) |
nprofile — Shareable Profile
def encode_nprofile(pubkey_hex: str, relays: list[str]) -> str:
"""Encode pubkey with relay hints."""
data = bytearray()
# Type 0: pubkey (special)
data.append(0) # type
data.append(32) # length
data.extend(bytes.fromhex(pubkey_hex))
# Type 1: relays
for relay in relays:
relay_bytes = relay.encode('utf-8')
data.append(1) # type
data.append(len(relay_bytes))
data.extend(relay_bytes)
converted = bech32.convertbits(data, 8, 5)
return bech32.bech32_encode("nprofile", converted)
import { nip19 } from 'nostr-tools';
const nprofile = nip19.nprofileEncode({
pubkey: '3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d',
relays: ['wss://relay.damus.io', 'wss://nos.lol']
});
// Decode
const { type, data } = nip19.decode(nprofile);
console.log(data.pubkey); // hex pubkey
console.log(data.relays); // ['wss://...', ...]
nevent — Shareable Event
def encode_nevent(event_id_hex: str, relays: list[str],
author_hex: str = None, kind: int = None) -> str:
"""Encode event with context."""
data = bytearray()
# Type 0: event ID
data.append(0)
data.append(32)
data.extend(bytes.fromhex(event_id_hex))
# Type 1: relays
for relay in relays:
relay_bytes = relay.encode('utf-8')
data.append(1)
data.append(len(relay_bytes))
data.extend(relay_bytes)
# Type 2: author (optional)
if author_hex:
data.append(2)
data.append(32)
data.extend(bytes.fromhex(author_hex))
# Type 3: kind (optional)
if kind is not None:
data.append(3)
data.append(4)
data.extend(kind.to_bytes(4, 'big'))
converted = bech32.convertbits(data, 8, 5)
return bech32.bech32_encode("nevent", converted)
const nevent = nip19.neventEncode({
id: 'event-id-hex',
relays: ['wss://relay.damus.io'],
author: 'author-pubkey-hex',
kind: 1
});
naddr — Replaceable Event Address
For parameterized replaceable events (kinds 30000-39999):
def encode_naddr(kind: int, pubkey_hex: str, d_tag: str,
relays: list[str] = None) -> str:
"""Encode replaceable event address."""
data = bytearray()
# Type 0: d-tag identifier
d_bytes = d_tag.encode('utf-8')
data.append(0)
data.append(len(d_bytes))
data.extend(d_bytes)
# Type 1: relays
for relay in (relays or []):
relay_bytes = relay.encode('utf-8')
data.append(1)
data.append(len(relay_bytes))
data.extend(relay_bytes)
# Type 2: author
data.append(2)
data.append(32)
data.extend(bytes.fromhex(pubkey_hex))
# Type 3: kind
data.append(3)
data.append(4)
data.extend(kind.to_bytes(4, 'big'))
converted = bech32.convertbits(data, 8, 5)
return bech32.bech32_encode("naddr", converted)
const naddr = nip19.naddrEncode({
kind: 30023,
pubkey: 'author-pubkey-hex',
identifier: 'my-article-slug',
relays: ['wss://relay.damus.io']
});
Decoding Any NIP-19 Entity
import { nip19 } from 'nostr-tools';
function parseNostrEntity(input) {
// Remove nostr: prefix if present
const cleaned = input.replace(/^nostr:/, '');
try {
const decoded = nip19.decode(cleaned);
switch (decoded.type) {
case 'npub':
return { type: 'pubkey', pubkey: decoded.data };
case 'nsec':
return { type: 'privkey', privkey: decoded.data };
case 'note':
return { type: 'event_id', id: decoded.data };
case 'nprofile':
return {
type: 'profile',
pubkey: decoded.data.pubkey,
relays: decoded.data.relays
};
case 'nevent':
return {
type: 'event',
id: decoded.data.id,
relays: decoded.data.relays,
author: decoded.data.author,
kind: decoded.data.kind
};
case 'naddr':
return {
type: 'address',
kind: decoded.data.kind,
pubkey: decoded.data.pubkey,
identifier: decoded.data.identifier,
relays: decoded.data.relays
};
default:
return null;
}
} catch {
return null;
}
}
URI Scheme (NIP-21)
The nostr: prefix creates clickable links:
nostr:npub1...
nostr:nprofile1...
nostr:nevent1...
nostr:naddr1...
Clients recognize these and handle appropriately.
Best Practices
Sharing Profiles
Always include relay hints:
// Good: Includes where to find the user
const shareable = nip19.nprofileEncode({
pubkey: pk,
relays: ['wss://relay.damus.io', 'wss://nos.lol']
});
// Less useful: No relay hints
const basic = nip19.npubEncode(pk);
Sharing Events
Include author for better routing:
const shareable = nip19.neventEncode({
id: eventId,
relays: ['wss://relay.damus.io'],
author: authorPk // Helps clients find it
});
Handling Unknown Prefixes
function isValidNostrEntity(input) {
const validPrefixes = ['npub', 'nsec', 'note', 'nprofile', 'nevent', 'naddr'];
const cleaned = input.replace(/^nostr:/, '');
for (const prefix of validPrefixes) {
if (cleaned.startsWith(prefix + '1')) {
return true;
}
}
return false;
}
Security Considerations
Never Share nsec
# DANGER: This exposes your private key!
nsec = encode_nsec(private_key_hex)
print(f"My key: {nsec}") # DON'T DO THIS
# Safe to share
npub = encode_npub(public_key_hex)
print(f"Find me at: {npub}") # OK
Validate Before Use
def safe_decode(bech32_str):
try:
hrp, data = bech32.bech32_decode(bech32_str)
if hrp is None:
raise ValueError("Invalid bech32")
valid_hrps = ['npub', 'nsec', 'note', 'nprofile', 'nevent', 'naddr']
if hrp not in valid_hrps:
raise ValueError(f"Unknown prefix: {hrp}")
return hrp, bech32.convertbits(data, 5, 8, False)
except Exception as e:
raise ValueError(f"Decode error: {e}")
Machine-Readable Summary
{
"nip": 19,
"title": "Bech32-Encoded Entities",
"status": "final",
"defines": [
"npub-encoding",
"nsec-encoding",
"note-encoding",
"nprofile-encoding",
"nevent-encoding",
"naddr-encoding"
],
"prefixes": {
"npub": "public-key",
"nsec": "private-key",
"note": "event-id",
"nprofile": "profile-with-relays",
"nevent": "event-with-context",
"naddr": "replaceable-address"
},
"tlv_types": {
"0": "special",
"1": "relay",
"2": "author",
"3": "kind"
},
"related": [
"/learn/nostr/identifiers",
"/learn/nostr/keys",
"/learn/nostr/specs/nip-01"
]
}