Skip to main content

Nostr

Nostr (Notes and Other Stuff Transmitted by Relays) is a simple, censorship-resistant protocol for decentralized communication.

Core Concepts​

Events​

Everything in Nostr is an event — a signed JSON object:

{
"id": "abc123...",
"pubkey": "npub1...",
"created_at": 1234567890,
"kind": 1,
"tags": [
["e", "reply-to-event-id"],
["p", "mentioned-pubkey"],
["t", "bitcoin"]
],
"content": "Hello, Nostr!",
"sig": "signature..."
}

Key properties:

FieldDescription
idSHA256 hash of the serialized event
pubkeyAuthor's public key (hex)
created_atUnix timestamp
kindEvent type (determines meaning)
tagsArray of arrays for metadata
contentEvent payload
sigSchnorr signature (secp256k1)

Keys​

Your identity is a keypair:

Private Key (nsec)Public Key (npub)
Keep secret!Share freely
Signs eventsYour identity
Controls accountOthers mention you
Cannot be recoveredDerived from nsec
nsec1xyz789...  →  npub1abc123...

No registration. No username. Just cryptographic keys.

Key Formats​

FormatPrefixUse
npubnpub1Public key (bech32)
nsecnsec1Private key (bech32)
notenote1Event ID
neventnevent1Event with relay hints
nprofilenprofile1Profile with relay hints
naddrnaddr1Addressable event reference

Relays​

Relays are servers that store and forward events:

Key properties:

  • Relays don't verify identity — the signature does
  • Relays can filter, rate-limit, or charge
  • Users choose which relays to use
  • Events are replicated across relays

Multiple Relays​

Users connect to multiple relays for redundancy:

const relays = [
"wss://relay.damus.io",
"wss://relay.nostr.band",
"wss://nos.lol",
"wss://relay.snort.social"
];

If one relay goes down or censors you, others still have your events.

Event Kinds​

Basic Kinds​

KindPurposeReplaceable
0Profile metadataYes
1Text note (short post)No
2Relay list (deprecated)Yes
3Contact list (follows)Yes
4Encrypted DM (legacy)No
5Event deletionNo
6RepostNo
7Reaction (like)No

Extended Kinds​

KindPurpose
1063File metadata
1984Report
9734Zap request
9735Zap receipt
10002Relay list metadata
30000Categorized people list
30008Profile badges
30009Badge definition
30023Long-form content
30311Live event

Replaceable vs Regular​

Event TypeKindsBehavior
Regular1, 4, 7, etc.Permanent and unique, can only be deleted
Replaceable0, 3, 10002, etc.Latest event replaces previous
Addressable30000-39999Identified by kind + pubkey + d-tag, updatable

Tags​

Tags provide metadata and enable threading:

Common Tags​

TagFormatPurpose
e["e", "<event-id>", "<relay>", "<marker>"]Reference event
p["p", "<pubkey>", "<relay>"]Mention user
t["t", "hashtag"]Hashtag
a["a", "<kind>:<pubkey>:<d-tag>"]Reference addressable
d["d", "identifier"]Addressable identifier
r["r", "url"]Reference URL

Threading with e-tags​

{
"kind": 1,
"tags": [
["e", "root-event-id", "", "root"],
["e", "reply-to-id", "", "reply"],
["p", "author-of-root"],
["p", "author-of-reply"]
],
"content": "This is a reply in a thread"
}

NIPs (Nostr Implementation Possibilities)​

NIPs are specifications for Nostr features:

Core NIPs​

NIPDescription
NIP-01Basic protocol
NIP-02Contact list
NIP-05DNS verification
NIP-19bech32 encoding

Identity & Auth​

NIPDescription
NIP-07Browser extension signing
NIP-46Remote signing (bunker)
NIP-98HTTP authentication

Messaging​

NIPDescription
NIP-04Encrypted DMs (legacy)
NIP-17Private DMs (improved)
NIP-44Versioned encryption

Payments​

NIPDescription
NIP-57Zaps (Lightning)
NIP-47Nostr Wallet Connect

Content​

NIPDescription
NIP-23Long-form content
NIP-51Lists
NIP-52Calendar events
NIP-53Live activities
NIP-58Badges
NIP-65Relay list metadata

WebSocket Protocol​

Clients communicate with relays via WebSocket:

Client to Relay​

// Subscribe to events
["REQ", "sub-id", { filters... }]

// Publish an event
["EVENT", { event... }]

// Close subscription
["CLOSE", "sub-id"]

// Authenticate (NIP-42)
["AUTH", { auth-event... }]

Relay to Client​

// Event matching subscription
["EVENT", "sub-id", { event... }]

// End of stored events
["EOSE", "sub-id"]

// Notice/error
["NOTICE", "message"]

// Command result
["OK", "event-id", true/false, "message"]

// Auth challenge
["AUTH", "challenge-string"]

Filters​

Query events with filters:

{
"ids": ["abc123..."],
"authors": ["pubkey1...", "pubkey2..."],
"kinds": [1, 6, 7],
"since": 1234567890,
"until": 1234599999,
"#e": ["event-id"],
"#p": ["pubkey"],
"#t": ["bitcoin", "nostr"],
"limit": 50
}

NIP-05 Verification​

Human-readable identifiers via DNS:

alice@example.com

Resolution:

GET https://example.com/.well-known/nostr.json?name=alice

Response:

{
"names": {
"alice": "pubkey-hex..."
},
"relays": {
"pubkey-hex...": ["wss://relay1...", "wss://relay2..."]
}
}

Zaps (Lightning Payments)​

NIP-46 Remote Signing​

Keep keys secure with remote signing:

Connection string:

bunker://npub...@relay.example?secret=abc123

NIP-98 HTTP Authentication​

Use Nostr identity for HTTP APIs:

GET /api/data HTTP/1.1
Authorization: Nostr <base64-encoded-kind-27235-event>

Enables:

  • Solid pod access via Nosdav
  • API authentication
  • File uploads

Implementations​

Clients​

ClientPlatformFocus
DamusiOSFull-featured
AmethystAndroidFull-featured
PrimalWeb, iOS, AndroidCaching, search
SnortWebClean UI
CoracleWebPrivacy
GossipDesktopOutbox model

Relays​

RelayLanguageFeatures
strfryC++High performance
nostreamTypeScriptEasy setup

Libraries​

LibraryLanguage
nostr-toolsJavaScript
NDKJavaScript
rust-nostrRust
python-nostrPython

Signers​

SignerPlatform
AmberAndroid
nos2xBrowser
AlbyBrowser
nsec.appWeb

Quick Example​

import {
generateSecretKey,
getPublicKey,
finalizeEvent,
SimplePool
} from 'nostr-tools';

// Generate keys
const sk = generateSecretKey();
const pk = getPublicKey(sk);

// Create and sign an event
const event = finalizeEvent({
kind: 1,
created_at: Math.floor(Date.now() / 1000),
tags: [["t", "nostr"]],
content: 'Hello, Nostr!'
}, sk);

// Publish to relays
const pool = new SimplePool();
const relays = ['wss://relay.damus.io', 'wss://nos.lol'];

await Promise.all(
pool.publish(relays, event)
);

// Subscribe to events
const sub = pool.subscribeMany(
relays,
[{ kinds: [1], limit: 10 }],
{
onevent(event) {
console.log('Received:', event.content);
},
oneose() {
console.log('End of stored events');
}
}
);

Learn More​