The Nostr Protocol

This is a Nostr 101, but more technical! Nostr operates without a central server, relying on clients (user applications) and relays (servers) to manage data. Clients are intentionally smart, whilst relays are intentionally ‘dumb’. Anyone can build a client and anyone can run a relay. Every user has a public key for identification, and all posts, known as events, are signed to ensure they are authentic.

Event Structure

At the heart of Nostr are events, which are JSON objects with specific fields:

  • ID: A unique hash of the event data.
  • Public Key: Identifies the user who created the event.
  • Timestamp: When the event was created.
  • Kind: Defines the event type, like a text note or user metadata.
  • Tags: Additional context, such as references to other events or users.
  • Content: The main text or data of the event.
  • Signature: Verifies the event’s authenticity using cryptography.

These events are signed with the user’s private key, ensuring that only the owner could have created them, which helps maintain trust in a decentralized system.

Event Kinds

Relays act like databases, storing events and distributing them to clients based on their requests. Events are categorized by “kinds,” which determine how relays handle them. Some examples:

  • Kind 0 is for user metadata, like profile information.
  • Kind 1 is for short text notes, similar to social media posts.
  • Kind 3 is for contacts or ‘follow lists’
  • Kind 6 is for reposts

There are many more kinds defined by the protocol. A full list can be found on the Nostr GitHub repository.

Event Kinds and Relay Behavior

Event kinds categorize events and dictate how relays handle them, providing flexibility for various use cases: Events are further classified by kind ranges, influencing storage and replacement policies:

Kind RangeTypeExample Kind
0-9999Regular: Standardized KindsKind 1 (text note)
10000-19999Replaceable EventsKind 10,000 (mute list)
20000-29999 Ephemeral EventsKind 20200 (temp status update)
30000-39999Parameterized Replaceable EventsKind 30001 (Bookmarks or categorized posts)
40000-49999Application specific or custom useTBC

Event Structure

Nostr events are simple JSON objects with a defined structure. The fields include:

FieldDescription
id32-bytes lowercase hex-encoded SHA256 hash of the serialized event data
pubkey32-bytes lowercase hex-encoded public key of the event creator
created_atUnix timestamp in seconds
kind Integer between 0 and 65535, specifying event type
tagsArray of arrays, each with strings, e.g., ["e", "event_id", "relay_url"]
contentArbitrary string, the main content of the event
sig64-bytes lowercase hex of the signature, matching the id

The event is serialized using UTF-8 encoding. The id is the SHA256 hash of this serialized string, and the sig is a Schnorr signature on the ‘secp256k1’ curve, verifying the event’s authenticity. This approach, detailed in here, ensures that only the holder of the corresponding private key could have created the event.

Event JSON Example

{
  "content": "Whats the most exciting thing happening in and around Nostr right now?\n\n#AskNostr",
  "created_at": 1740858071,
  "id": "a4eaed043dc40569dabfcd99f53aad8dd6e9450ed64eb61be005db0ec645b042",
  "kind": 1,
  "pubkey": "a60e79e0edad5100d7543b669e513dbc1c2170e8e9b74fdb8e971afd1e0e6813",
  "sig": "8f9a27944518e93469eddbbce1659fe53ac903d78091a491dc083ce803a6a3cd00c5fcbec2954ea09a6c2617387f05d01eb063b6e96f57e1297aa101dc44e2a5",
  "tags": [["t", "AskNostr"]]
}

Coding

Event Tags

Tags enhance event functionality and are what make Nostr more than just a stream of notes. Clients interpret tags to render replies, mentions, or hashtags in the UI. Relays index tags for filtering and querying events (e.g., fetch all events with t:AskNostr).

  • "e": Refers to another event, e.g., ["e", "event_id", "relay_url"], for threading or replies.
  • "p": Refers to a user, e.g., ["p", "pubkey"], for mentions or follows.
  • "a": Refers to an addressable event, e.g., ["a", "kind:pubkey:d_tag", "relay_url"], for specific updates.

Communication Protocol

WebSockets enable real-time communication between clients and relays using JSON-formatted messages: clients publish events like notes or metadata with ["EVENT", <event>], subscribe to specific event streams (e.g., posts with a hashtag) using ["REQ", <subscription-id>, <filters>], and receive matching events from relays as ["EVENT", <subscription-id>, <event>], with relays signaling the end of a data stream via ["EOSE", <subscription-id>]; clients can also terminate subscriptions with ["CLOSE", <subscription-id>]. all encoded in UTF-8 text for seamless, bidirectional updates.

Disclaimer

I am by no means a developer, let alone one that specializes in the Nostr protocol. Much of this page is a distillation of my rudimentary understanding, combined with research from other more technical writings about the protocol. If you spot any mistakes or gaping holes, please get in touch.


If you found this post useful, please share it with your peers and consider following and zapping me on Nostr. If you write to me and let me know that you found me via this post, I’ll be sure to Zap you back! ⚡️

Follow Me ✅
Newer post

Nostr Utilities

The Nostr Protocol