Nostr Protocol — Complete Technical Reference
- Nostr Protocol
Nostr Protocol
Notes Other Stuff Transmitted by Relays
An open, decentralized protocol for censorship-resistant social networking and beyond.
Core Architecture
┌─────────┐ WebSocket ┌─────────┐
│ Client │ ────────────────> │ Relay │
│ (you) │ <──────────────── │(server) │
└─────────┘ EVENT / REQ └─────────┘
│ │
│ secp256k1 keypair │
│ + Schnorr signatures │
└────────────────────────────────
Three primitives only:
- Keypair (secp256k1) = Identity
- Event (signed JSON) = Every piece of data
- Relay (WebSocket server) = Dumb storage and forwarding
No servers you depend on. No accounts. No passwords.
The Event Object
Every object in Nostr is an event. Format:
{
"id": "<sha256 of serialized event data>",
"pubkey": "<32-byte hex public key>",
"created_at": <unix timestamp>,
"kind": <integer 0-65535>,
"tags": [["e", "<event-id>"], ["p", "<pubkey>"], ...],
"content": "<arbitrary string>",
"sig": "<64-byte Schnorr signature>"
}
Event Serialization (for ID generation)
[0, "<pubkey>", <created_at>, <kind>, <tags>, "<content>"]
Critical serialization rules:
- UTF-8 encoding
- No extra whitespace
- Escape:
\n,\",\\,\r,\t,\b,\fin content id=sha256(this serialization)
Signature
- Algorithm: Schnorr signatures (BIP-340) on secp256k1
- Signed over the
idfield - Verifiable by anyone using the
pubkey
Standard Tags
| Tag | Meaning | Indexed? |
|---|---|---|
e |
Event ID reference | Yes |
p |
Pubkey reference | Yes |
a |
Addressable event (kind:pubkey:d) | Yes |
r |
Relay URL | No |
t |
Hashtag | Yes |
relays |
Relays for fetching data | No |
amount |
Lightning amount in msats | No |
lnurl |
Lightning URL | No |
d |
Unique identifier (addressable events) | Yes |
alt |
Human-readable description | No |
Event Kind Ranges
| Range | Category | Behavior |
|---|---|---|
| 0 | Metadata | Replaceable (latest per pubkey) |
| 1 | Text Note | Regular (stored forever) |
| 3 | Follows | Replaceable |
| 4 | Encrypted DM | Regular |
| 5 | Deletion | Regular |
| 6 | Repost | Regular |
| 7 | Reaction | Regular |
| 40-49 | Channels | Regular |
| 1k-10k | Regular | Stored forever |
| 10k-20k | Replaceable | Latest per (pubkey, kind) |
| 20k-30k | Ephemeral | Not stored |
| 30k-40k | Addressable | Latest per (pubkey, kind, d) |
| 50xx | Job Request (DVM) | Regular |
| 60xx | Job Result (DVM) | Regular |
| 9734 | Zap Request | Regular |
| 9735 | Zap Receipt | Regular |
| 10002 | Relay List Metadata | Replaceable |
| 13194 | NWC Info | Replaceable |
| 23194 | NWC Request | Ephemeral |
| 23195 | NWC Response | Ephemeral |
| 30023 | Long-form Article | Addressable |
| 31339 | Agent Metadata | Addressable |
Client-Relay Communication
WebSocket Messages
All messages are JSON arrays.
Client → Relay
EVENT — Publish an event
["EVENT", "<subscription-id>", <event object>]
REQ — Subscribe to events
["REQ", "<subscription-id>", {
"authors": ["<pubkey>", ...],
"kinds": [0, 1, 4],
"#e": ["<event-id>"],
"since": 1700000000,
"until": 1700100000,
"limit": 100
}]
CLOSE — Unsubscribe
["CLOSE", "<subscription-id>"]
Relay → Client
EVENT — Matching event
["EVENT", "<subscription-id>", <event object>]
EOSE — End of stored events
["EOSE", "<subscription-id>"]
NOTICE — Human-readable message
["NOTICE", "This relay is full"]
OK — Event acceptance/rejection
["OK", "<event-id>", true, ""]
["OK", "<event-id>", false, "error: duplicate"]
Filter Parameters
| Param | Type | Description |
|---|---|---|
ids |
hex[] | Event IDs (64 chars each) |
authors |
hex[] | Pubkeys (64 chars each) |
kinds |
int[] | Event kind numbers |
#e |
hex[] | Events referenced (by e tag) |
#p |
hex[] | Pubkeys referenced (by p tag) |
#a |
strings[] | Addressable events |
since |
unix ts | Events created at or after |
until |
unix ts | Events created at or before |
limit |
int | Max events in initial query |
Multiple filters in one REQ = OR logic. Multiple conditions in one filter = AND logic.
Identifier Formats (NIP-19)
| Prefix | Format | Example |
|---|---|---|
npub |
Public key (bech32) | npub1... |
nsec |
Private key (bech32) | nsec1... |
note |
Event ID (bech32) | note1... |
nevent |
Event with relays | nevent1... |
naddr |
Addressable event | naddr1... |
nprofile |
Profile with relays | nprofile1... |
Convert hex to npub:
# Using nostr-cli
echo "<hex-pubkey>" | nostr nip19 encode
NIP-65: Relay List Metadata
Published as kind 10002. Tells clients which relays to use.
{
"kind": 10002,
"content": "",
"tags": [
["r", "wss://relay.damus.io"],
["r", "wss://nos.lol", "read"],
["r", "wss://relay.primal.net", "write"],
["r", "wss://expensive-relay.example.com", "write"]
]
}
- No marker = both read and write
readmarker = only read fromwritemarker = only publish to
Best practice: 2-4 relays of each type. Spread your kind 10002 widely.
Key Properties
Censorship Resistance
- No central authority
- Content persists as long as one relay stores it
- Client-side signing prevents relay forgery
- Multi-relay publishing for redundancy
Extensibility
- New event kinds via NIPs
- Relays don’t need updates for new kinds
- Clients implement whatever NIPs they choose
Privacy
- DMs encrypted (NIP-44, NIP-17)
- Metadata hiding (NIP-59 gift wrap)
- No phone number, no email, no KYC
Further Reading
- NIPs — Full NIP catalog
- Relays — Relay management
- Encryption — Privacy specs
- Zaps — Lightning payments on Nostr
Write a comment