What's new

Nostr: Introducing Decentralized Social Communication

T

Tobechi

Guest
You’ve spent years building an identity online. You have a username people recognise, a feed full of things you’ve posted, people who follow you, people you follow back, and enough history on the platform that leaving it would feel like leaving a small part of yourself behind. Now imagine waking up one day and it's just gone. Account suspended, no clear reason given, an appeal form that goes nowhere or takes a very long time to get a response. Everything you built, gone, because a company somewhere decided it should be. That's not even hypothetical, it happens to people constantly, sometimes for actually breaking a rule, sometimes for nothing anyone can point to. Either way, you find out the same thing: it was never really yours to lose, because it was never yours to begin with. Then you try another app, or retry getting on the same app with a different identity. You sign up, choose another username, start again, and now you have to rebuild from scratch. That is how most of the internet works. Your identity is something a platform creates for you and keeps inside its own system. Your handle, profile, posts and follower list all exists because a company decides how they should be represented, where they should live and who gets access to them. You may have built the identity, but the platform owns the machinery that makes it an identity. Nostr starts with a different assumption: what if these platforms never had to create your identity in the first place?

Instead of signing up for an account, you generate a cryptographic keypair. One key is public and becomes the identity other people can find you by. The other is private and lets you prove that you are the person behind it.

There is no company assigning you a username or keeping a master record of who you are. You create the identity yourself, and you can take it to any Nostr client that knows how to work with it. That sounds like a small technical distinction until you follow it through. If your identity doesn't belong to the app, then the app can't be the only place your posts exist. If your posts can exist independently of the app, then the server storing them doesn't necessarily own them either, and if neither your identity nor your posts are tied to one company, you can move between applications without starting your social life from scratch every time. That is the idea behind Nostr.

Honestly, the closest thing to compare it to is email. Gmail and Outlook are two different companies, but they speak the same protocol underneath, so a message from one inbox reaches the other without either company lifting a finger to make that happen. Nostr works the same way. There's no single Nostr app the way there's one X (formerly Twitter), just a bunch of clients speaking the same protocol, and your identity moves between all of them because none of them ever owned it to begin with.

How Nostr got here?​


Nostr wasn't launched by a company looking for a new market in social media, nor was there a foundation behind it with a team of engineers deciding what the platform should become. In November 2020, a pseudonymous developer known as fiatjaf started working on the protocol. A couple of months later, he built the first client called branle, an early attempt at something that looked a little like X. But even then, the idea wasn't to build one social network and convince everyone to move there. Branle was simply one client that could speak Nostr's protocol, with the expectation that other people could build their own. For the next couple of years, Nostr remained relatively small, attracting mostly Bitcoiners and developers who had found their way to it. Then Jack Dorsey (former co-founder of Twitter) took an interest. The story of how he got involved is almost too casual for the amount of money that followed. Someone replied to one of Dorsey's tweets suggesting that he take a look at Nostr, Dorsey asked how he could help fund the project, and less than 24 hours later, 14 BTC, worth around $245,000 at the time, had been sent directly to fiatjaf. A few months later, in May 2023, Dorsey followed that with another $5 million donation through OpenSats. The interesting part isn't simply that Nostr attracted serious funding, it's what that funding didn't turn it into. Fiatjaf remained an independent, pseudonymous maintainer, while much of the money went towards grants for other developers building on the protocol. Dorsey didn't acquire Nostr, there was no company to acquire in the first place. The project didn't suddenly become one person's social network, with everyone else signing up to use it, that is because Nostr was never built around the idea that one company needed to own the thing everyone else was using. There is a protocol. Anyone can implement it. Developers can build clients that use it, people can run relays, and the identity underneath all of those things can remain with the person who created it. Which brings us back to that keypair.

If Nostr isn't giving you an account, what exactly is it giving you?​


Nostr, short for Notes and Other Stuff Transmitted by Relays, uses the same basic public/private key idea that Bitcoin does, although it uses it for a different purpose. Your public key is what the network uses to identify you; your private key is what you use to prove that the identity is yours. You will usually see them represented as an npub and an nsec. The npub is safe to share while the nsec is literally for your eyes only (J Cole fans will understand). If someone gets your nsec, they can sign things as you, which means there is no customer-support department you can call to explain that you were hacked and ask for your account back. There isn't an account to recover because you only have your key as the access to that. Just as uncomplicated as understanding Nostr, creating one is surprisingly simple.

Code:
using NBitcoin; // Version 10.0.10
using NBitcoin.Secp256k1; // Version 4.0.1

// generate a new private key
var key = ECPrivKey.Create(RandomUtils.GetBytes(32));

// derive the public key from it
var pubKey = key.CreateXOnlyPubKey();

Key is the private key, pubKey is derived from it mathematically. You can move from the private key to the public key, but not backwards from the public key to the private key. Your public key doesn't point to an identity stored somewhere else; it is the identity. And because the private key is the thing that proves you control it, a Nostr client doesn't need a central server to vouch for you before you can start using the network. You can generate the keypair yourself, hand the public key to anyone you want to find you, and keep the private key to yourself. From there, the interesting question isn't really who you are anymore. It's what you can do with that identity.

The Event​


Every single thing that happens on Nostr, no matter what it is, comes down to the same basic object: an event. A post, a profile update, a reaction, a chat message, all of it. Doesn't matter what app you're using or what it looks like on screen, underneath it's always the same shape of thing.

A simple Nostr text event looks like this:

Code:
{
  "id": "5c83da77af1dec6d7289834998ad7aafbd9e2191396d904ea59da2c9c69d29a",
  "pubkey": "6e468422dfb74a5738702a8823b9b28168abab8655faacb6c8a692dee65fd282",
  "created_at": 1706195560,
  "kind": 1,
  "tags": [],
  "content": "good morning",
  "sig": "908a15e46fb4d8675bab026fc230a0e3542bfade63da02d542fb78b2a8513fcd0092619a2c8c1221e581946e0e6f7fc7dea9d6d7dd0d15e8b96a2a9ffb2e6ef"
}

No special file format, no binary encoding, just plain JSON anyone can read. Let's go through it piece by piece, because each field represents something.

pubkeyis who posted it. The actual public key from that keypair we just talked about.

created_atis just a Unix timestamp, when it was posted. It is signed along with the rest of the event, and because of these, nobody can rewrite history and claim something was posted earlier or later than it actually was.

kindtells you what type of thing this is. 1 here means it's a plain text note, the basic "post something" kind. There’s a whole bunch of other kinds too. 0 is a profile, your name, bio, picture, basically your Nostr ID card. 3 is your follow list, who you're following. 6 is a repost. 7 is a reaction, a like or an emoji response to something. There's a pair worth pointing out too, 9734 and 9735, which are zap request and zap receipt, the actual mechanics behind sending someone bitcoin over Lightning through Nostr, we'll get into that properly later on, and 30023 is long-form content, actual articles and blog posts instead of short notes, complete with a title, a summary, a published date. You can see all nostr event kinds here and what each of them represents.

Honestly this is the part that makes Nostr way more than just a Twitter clone, because anyone can go define a brand new kind for something completely different. A chat app, a marketplace listing, a discovery mechanism, a calendar event, whatever you're building, you just pick a kind number nobody else is using for that purpose, write up how it should work, and ship it. Nobody has to ask permission or fork anything. If other developers like the idea, they start using the same kind number in their own clients, and now it's just part of Nostr. That's genuinely how most of the "features" you'll hear about got added, someone needed a new kind of thing, so they made one.

tagsis empty in this example, but most events actually use it a lot. It's how you attach structured, filterable bits of information to an event without stuffing everything into the content field as free text.

Say you're replying to someone's post. The reply text itself goes in content, same as always, but which post you're replying to goes in a tag, something like ["e", "3f770d65d3a7..."], where that long string is the id of the event you're replying to. Mention someone by name in your post, and there's usually a ["p", "6e468422dfb7..."] tag alongside it, that public key hex being the person you mentioned.

Why bother splitting it out like this instead of just writing "replying to @someone" in the text? Because a relay can actually search and filter on tags. If everything lived only in content, a relay would have to parse free text to figure out what a post is connected to, which is messy and unreliable. With tags, a client can ask a relay "give me every event that has an e-tag pointing at this specific post," and get back a clean, exact answer. That's basically how entire reply threads get reconstructed, how mentions show up in your notifications, how anything that needs structure works, without Nostr needing a rigid schema for every single kind of event.

Different kinds lean on tags differently too. Long-form articles use a d tag to give the article a stable identifier separate from its content, so you can edit and republish it without it becoming a brand new, unrelated post. We'll come back to this properly, because tags end up being the part that makes an event genuinely queryable, not just readable.

contentis just the actual message. In this case, "good morning"

idis basically the fingerprint of everything in an event. You don't define it, it gets computed through a SHA-256 hash of the event object. Changing any item in the event or one letter of the content, and the id changes completely.

sigis the signature, made with your private key, over the id. Anyone holding the event can check it themselves, recompute the id from the content, then verify the signature matches the pubkey, no relay or server has to vouch for anything. The event just proves itself.

That's really the whole trick. The id ties directly to the content, so nothing can be quietly edited after the fact, and the signature ties the whole thing to a specific person, without anyone needing to trust a company in the middle.

Now, an event on its own doesn't go anywhere by itself. You may have proved that you wrote it, but nobody else can see it yet. That's where relays come in.

Relays​


A relay is a server that accepts Nostr events from clients, stores them and sends them back when any clients ask for them. It sounds like the backend of a normal social network, and at this level, it is doing a familiar job: taking data from a point and making them available to all points requesting for it.

The difference is what the relay isn't responsible for. It doesn't create your identity nor need to approve your account. It doesn't have to establish that an event really came from you because the event already carries a signature that can be checked independently. There isn't one official Nostr relay that every user has to connect to. Your client can publish the same signed event to several relays. Someone following you can have their client check several relays for events from your public key. If one relay has the event, it can be retrieved there; if another doesn't, that doesn't change anything about the event itself.

Relays speak over plain websocket connections, and the protocol between a client and a relay is small. A client sends ["EVENT", <the signed event>] to publish something, and the relay responds with ["OK", <event id>, true or false, <a message>] telling you whether it accepted it. To ask for events back, a client sends a ["REQ", <subscription id>, <filters>] message, where filters look like {"kinds":[1], "authors":["6e468422..."]}, basically "give me kind 1 notes from this specific pubkey." The relay then streams back every matching event it has stored, followed by an ["EOSE"] message meaning ‘end of stored events’, after which it keeps that subscription open and pushes new matching events live as they come in.

Now here's the part that actually matters, and it's the whole reason relays are structured this way instead of just having one central server: there is no single relay, and nobody is required to use any particular one. You can publish the exact same event to five different relays at once if you want. Someone following you doesn't need to know which relays you use, their client just checks the ones it's configured to check, and if it finds your event on any of them, that's enough, because the event proves itself through its signature regardless of which relay it came from.

This is what actually makes Nostr hard to shut down or censor. There is no central database that every relay has to synchronize with and no blockchain keeping the network in consensus. They are independent places where signed events can be stored and retrieved. That independence becomes much more interesting when a relay decides it doesn't want you anymore. Imagine you've been posting through one relay for months and one day it starts rejecting your events. On a conventional social platform, losing the platform can mean losing the account that represents you, along with the audience and history attached to it. On Nostr, the relay only had copies of your events, your identity is still your keypair. You can connect to another relay and publish there, and you can publish the same signed event to several relays in the first place so that no single server is the only place holding a copy.

The event doesn't need to be recreated and your identity doesn't need to be transferred because neither one belonged to the relay. That is the practical consequence of separating identity from infrastructure: a relay can stop carrying your data, but it cannot take your identity with it. And because anyone can run a relay, there doesn't have to be one place where the network lives. Some relays are public, some are built around particular communities or applications, and some people run their own. The software can be open, paid, restricted or configured for a particular use case. The important thing is that the relay is infrastructure, not the owner of the identity passing through it.

Now let’s actually build one​


Everything so far has been description, an event is this shape, a relay does this job. Now we build one, sign it for real, send it to an actual relay sitting somewhere on the internet, and pull it back to prove it landed.

Using C# with NBitcoin and NNostr, generating the identity looks like this:

Code:
using NBitcoin; // Version 10.0.10
using NBitcoin.Secp256k1; // Version 4.0.1
using NNostr.Client; // Version 0.0.55

// generate a new private key
var key = ECPrivKey.Create(RandomUtils.GetBytes(32));

// derive the public key from it
var pubKey = key.CreateXOnlyPubKey();
var pubKeyHex = pubKey.ToHex();
Console.WriteLine($"Public key (hex): {pubKeyHex}");

Generating a public key for Nostr


This is exactly what we walked through in the identity section, except now it's being generated inside an actual application. The public key is what can be shared with the rest of the network. Next, the client can use that key to create a real event:


Code:
var note = new NostrEvent
{
    Kind = 1,
    Content = "good morning from a C# console app"
};
await note.ComputeIdAndSignAsync(key);
Console.WriteLine($"Event id: {note.Id}");
Console.WriteLine($"Public key: {note.PublicKey}");
Console.WriteLine($"Signature: {note.Signature}");

Building a signed Nostr event


Notice you don't set PublicKey or CreatedAt yourself. ComputeIdAndSignAsync handles those pieces for you and generates the signature using the private key you supplied. CreatedAt gets stamped as right now if you leave it blank. If the PublicKey public key has already been set to something that doesn’t match the key, the library throws rather than quietly producing an event that claims to belong to somebody else. Once that call finishes, the event is valid and signed, but it is still only sitting in memory on your machine. So we send it somewhere:


Once that call returns, note has everything a real Nostr event needs: Id, computed exactly the way we walked through earlier, hash of the canonical serialized form. Signature, a Schnorr signature over that id, made with key. And PublicKey, so anyone receiving this event later knows who to check that signature against.

At this point, note is a complete, valid, signed Nostr event, sitting in memory on your machine. It doesn't exist anywhere else yet. Nobody else can see it. That's the next step, actually getting it onto a relay.

Publishing to a Real Relay​


Code:
var client = new NostrClient(new Uri("wss://relay.damus.io"));
await client.ConnectAndWaitUntilConnected();

client.OkReceived += (sender, args) =>
{
    var (eventId, success, message) = args;
    Console.WriteLine(success
        ? $"Relay accepted the event: {eventId}"
        : $"Relay rejected it: {message}");
};

await client.PublishEvent(note);

Publishing events to a relay


The relay here is relay.damus.io but there is nothing inherently special about it. It is one relay among many. You could point the same client at another relay (e.g wss://nos.lol, wss://relay.snort.social, etc.) that speaks the protocol and publish the same signed event there.

If you want to see what's actually out there, nostr.watch tracks thousands of public relays in real time, uptime, latency, which parts of the protocol each one supports. It's a good way to get a feel for just how many of these are running at any given moment, and how disposable any single one of them really is to the network as a whole.

ConnectAndWaitUntilConnected opens the actual WebSocket connection to whichever relay you point it at, and blocks until it's live. OkReceived is how that relay tells you what happened to your event, the ["OK", ...] message from the relays section. Wire it up before you publish. Once PublishEvent(note) runs, the event goes out as ["EVENT", note], and you should see it get accepted.

The relay accepts it, stores a copy and makes it available to clients that ask for it. And if you want that same event to be available from several places, you can publish the exact same event to several relays. The event remains identical because your signature remains identical; you're simply putting more copies of it in more places.

That's the event sitting on one relay now, not floating around everywhere by default. If you wanted it more widely available, you'd publish the same signed event to several relays at once, which is genuinely just calling PublishEvent again against a different NostrClient pointed at a different URL, the event remains identical because your signature remains identical; you're simply putting more copies of it in more places. Now we ask the relay for it back:

Querying It Back​


Code:
client.EventsReceived += (sender, args) =>
{
    var (subscriptionId, events) = args;
    foreach (var e in events)
        Console.WriteLine($"Got it back: {e.Content}");
};

var filter = new NostrSubscriptionFilter
{
    Kinds = new[] { 1 },
    Authors = new[] { pubKeyHex },
    Limit = 5
};

await client.CreateSubscription("my-sub", new[] { filter });

Query the event back


That filter is asking for exactly one thing: the last 5 kind: 1 events from this specific public key. CreateSubscription sends it to the relay as ["REQ", "my-sub", {"kinds":[1],"authors":["..."],"limit":5}], and waits for matching events.

If you run this after publishing the event, the console should print out: "Got it back: good morning from a C# console app"

At this point, we've gone all the way through the loop. A keypair gave us an identity. The private key signed an event. A relay stored it. A client asked for it. The relay returned it. And the event itself carried enough information for anyone receiving it to verify where it came from.

Zaps​


Remember kind 9734 and 9735 from earlier, the ones I said we'd get back to? This is that.

A zap is just Nostr's word for sending someone bitcoin over Lightning, directly, from inside whatever app you're using. See a post you like, hit the lightning bolt, send a few sats straight to whoever wrote it. No ads, no algorithm deciding what gets boosted, just people paying people directly for something they actually valued.

The mechanics are basically the same pattern as everything else we've covered, just two more kinds doing specific jobs. A 9734 is the zap request, a signed event a client creates the moment you hit that lightning bolt, saying who's sending, how much, and to who. That gets sent off to get turned into an actual Lightning invoice behind the scenes. Once it's paid, a 9735 gets published, the zap receipt, proof the payment actually happened, and that's what shows up back on the original post as a little zap count.

What makes this genuinely different from likes or hearts on other platforms is that a zap actually costs something and actually moves value. A like is free and means basically nothing, anyone can tap it without thinking. A zap means someone deliberately spent real sats on your post, which makes it a way better signal of what people actually cared about, not just what they scrolled past and tapped.

Any Lightning wallet can send a zap, that part doesn't need anything special. But to receive one, you need Nostr integration on your end, since the whole request-and-receipt flow only works because it's tied to your public key the same way everything else on Nostr is. It's a small addition on top of the base protocol, but it's honestly one of the things that got a lot of Bitcoiners paying attention to Nostr in the first place, proof that an open, signed-event protocol could carry real money moving between real people, not just posts.

Where This Leaves You​


Four steps, and every single thing built on Nostr follows some version of those four steps under the hood no matter how elaborate it looks from the outside (e.g a marketplace, a chat app, a discovery protocol for finding Bitcoin liquidity e.t.c). What changes from app to app isn't the mechanism, it's what people decide to put inside content, what kind they pick, and what they build on top of the events flowing through.

And people have picked a lot of different things. There's Highlighter for blogging and reading, Nests for live audio chats, Zap.stream for livestreaming, Wavlake for music with actual value-for-value payments baked in, meaning artists get paid directly by listeners over Lightning, no label or platform taking a cut in the middle. You can see a whole list of apps that has been built untop of Nostr here, and none of these needed permission to exist. Someone just picked a kind, decided what it should mean, and built a client for it.

That range is really the point. Nostr isn't a Twitter alternative with some extra stuff bolted on, it's a general way to publish and find signed data, and social posting just happened to be the easiest part to explain first. What you actually build with a keypair, a signed event, and a relay is up to you.
 

Thread statistics

Created
Tobechi,
Replies
0
Views
0
Back
Top