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