Nostr and ATProto
This post could’ve been titled “Nostr vs ATProto”, but that really isn’t what I wanted to do here. While I will be comparing and contrasting them a lot, and that’s kind of even the point of writing this, I didn’t want to really pit the two against each other at all, and especially not with the title. I also want to try avoiding commenting on the differences between the communities that have formed on the protocols and their apps, although I definitely will be looking at the philosophical differences between the two a lot - also kind of the point of writing this. This also isn’t a super deep technical post, though it assumes familiarity with technical concepts. I also might come back to edit parts of it and add more later.
You can read and leave comments on this post here on Bluesky, or here on Nostr, or even here on Mastodon.
So I wrote a paragraph mostly about what this post isn’t about, with a little bit about what I will talk about in it, but I haven’t really explained what this post is, or why I’m writing it. Honestly, I’m not completely sure of the first one yet either; I’m figuring that out as I write it. The paragraph at the top are really serving as guidelines for myself as I write this.
However, I can explain how this post came to be. It started with a showerthought (I was literally in the shower) about how similar ATProto and Nostr really are. This thought came to me after ruminating on ATProto Relays and Nostr Relays, and thinking about how my favorite feature of Nostr Relays (spoiler: it’s filtering) could be added to ATProto Relays, and why you would want to do that. More broadly, this made me think that the two protocols are similar enough that they are likely to slowly converge over time as they learn from each other.
A direct result of those thoughts (after getting out of the shower, of course) was to search the internet for a good comparison of Nostr and ATProto. A direct result of my failure to find any was this Bluesky skoot (There’s a lot of good replies and thoughts in that thread as well—you probably want to read it before continuing with this post). A direct result of my skooting that was this reply. Before, I’d been tentatively considering writing a purely technical comparison after not finding any, but that reply really set the stage for deciding what I wanted to do in this post.
So, to start, let’s look at…
How we got here
A Caged Bird
or, Twitter
Twitter here could, in theory, be replaced here by just “Centralized Social Media”, but really it was Twitter that got us here. Both ATProto and Nostr exist because of Twitter - the AT Protocol very directly so, Nostr as a response to “censorship” (real or perceived) on Twitter. ATProto is the result of Bluesky’s original mission - to build a decentralized protocol Twitter could adopt. Post-Elon, who knows if that will ever happen, but, well, that is how it started.
Twitter sprang into existence in 2007, as a small, SMS-based service that allowed people to post short status updates - tweets, as they became known. Who knows if it was the first of its kind? Well, it certainly became the most popular. It really was the service that was able to popularize the concept of microblogging. It developed a multitude of subcultures, each with their own unique characteristics, often intersecting with each other in fascinating, unpredictable places and ways. And while Twitter certainly never became as popular as some of its big tech companions, it may have had the greatest cultural impact - it was one of the only places in existence where an average person (you!!) could, say, ratio a presidential candidate or give interesting new details on a story to some famous journalist (I don’t know, I just made those up). Some have said it was the first “global town square”.
Over the years of Twitter’s existence, lots of things happened to Twitter. Moderation issues including Donald Trump, authoritarian governments around the world, all sorts of mini community wars and harassment, etc. Twitter, as beautiful as it was, well… kind of sucked, and people drew many different (not mutually exclusive and often overlapping!!) conclusions about why. Some, like Christopher Bouzy of Spoutible, concluded that the platform’s moderation simply wasn’t enough for what the platform had become, and people needed a smaller, more closed space with stricter moderation policies. Others concluded that a global-scale social network is simply an inherently bad idea and people should stick to smaller, more tight-knit communities. But one of the most popular conclusions was that something as important as Twitter - whether you considered it a “global town square” or a place to make connections with your community or Whatever Else - simply could not and should not be controlled by a single corporation. Indeed, this was the conclusion that Twitter themselves came to! This is the conclusion that both ATProto and Nostr are founded upon - the idea of a move from closed, centralized, corporate-owned social platforms to a world of open, decentralized social protocols.
But ATProto and Nostr don’t exist in a vacuum. They weren’t the only ones to come to this conclusion. They weren’t even the first. And that brings us to…
The Mastodon in the Room
or, ActivityPub and the Fediverse
⚠️ I am not an expert on ActivityPub. Take everything in this section with a grain of salt. If I get something wrong, please correct me. ⚠️
ActivityPub is kind of a big deal in the decentralized social protocols world. It’s not the first, either - it would be extremely hard to really find a first. But it is, at least for now, the largest, and realistically is about to become a lot larger, at least if Meta Threads federates with it.
It’s also got an entirely different philosophy to either Nostr or ATProto - while both of the latter are based on a more individualistic approach to decentralization, ActivityPub opted for a more collectivist approach, one that favors tight-knit communities over a global network (that hasn’t stopped people from trying to build global networks with it, though.)
(Side-note: I should also mention that whether the Fediverse should focus on smaller communities or mass-interconnection has been a debate even within the Fediverse since right about the beginning, which a lot of the differing viewpoints around this topic explained brilliantly by Evan Podromou. Since Small Fedi seems to be the dominant philosophy shaping the current Fediverse, I’ve mostly focused on Small Fedi when talking about ActivityPub here.)
There are many different server implementations of the ActivityPub Spec, each adding their own unique flair to the ecosystem. The most popular of these implementations is Mastodon. ActivityPub is also, like I said above, kind of a big deal in the decentralized social protocols world. Almost everyone working on decentralized protocols after ActivityPub has been forced to acknowledge its existence, draw comparisons to it, and often been bridged to it. In fact, when Jack Dorsey fired off his famous tweet thread announcing Bluesky, he was definitely aware of ActivityPub, given that in a reply to a reply to that thread, he stated “ActivityPub is great.”
Because ActivityPub uses a federation model centered around small community servers, it has a lot of the benefits of centralized social media. For example, it makes it relatively easy to support private content, since it’s a push-based protocol - only those whose inboxes you push content to can view it (there’s also an “Everyone” option that makes your content fetchable, I think). This is also why the Fediverse has things like Follow Requests, server-to-server DMs (though your instance admin can view them - ActivityPub kind of assumes you trust them), and real blocks that mostly work.
However, many of the more collectivist choices made in ActivityPub were concluded to not be conductive to a “decentralized Twitter”, and both ATProto and Nostr exist in large part because of this. In fact, both ATProto and Nostr strayed from ActivityPub for the same reasons - identity is extremely tied to your initial server. There are good reasons for this, given that ActivityPub is largely used by smaller communities who federate with each other, but it does have an important consequence:
Your data is not really portable. You can move accounts to another server, and if your old server is well-behaved it can add a redirect to your new account, which will help automatically transfer your old social connections over to your new account, but this doesn’t include any of your data except your follows and followers, and falls apart if your old server goes offline, is adversarial to you or your current server, or in basically any situation where you can’t get that redirect.
There are many other philosophical differences between the ActivityPub camp and the Nostr and ATProto camp, but this one is the most important one, at least in my opinion - both ATProto and Nostr have sections explaining “Why not just go with ActivityPub?” that state this as their primary reason. Both ATProto and Nostr have real account portability by design.
Both of these protocols don’t have much in common with ActivityPub, so I won’t talk about ActivityPub too much here. But there is one older protocol that both of them extensively draw inspiration from…
Secure Scuttlebutt
This is where things start to get pretty interesting. In 2014, a New Zealand programmer named Dominic Tarr was living on a sailboat. As you might assume, such a life includes little internet, and when it comes, in sporadic bursts. Centralized social media, like Twitter, wants you to be connected at all times, scrolling your feed and looking at ads. Tarr didn’t want that. The result? He designed a protocol designed for offline-first, intentional, slow communication, free from Big Tech. Its name? Secure Scuttlebutt.
Scuttlebutt uses an append-only log of cryptographically signed messages. Your identity is an Ed25519 keypair and is pretty much tied to a single device. One consequence of this is that, as the Scuttlebutt developer docs themselves acknowledge, “If a user loses their secret key or has it stolen, they will need to generate a new identity, and tell people to use their new one instead.”
Because it’s an append-only log, every message must contain a reference to the previous message - a bit like a blockchain. That also means that deletes are straight-up impossible. This is also not necessarily a bad thing, just a trade-off.
Scuttlebutt started as a purely peer-to-peer protocol, using a gossip model - in fact, that’s where its name comes from; in sailor-slang, scuttlebutt means “water-cooler gossip”. The first popular Scuttlebutt client was an app called Patchwork, authored by Paul Frazee (keep this guy in mind, he’s gonna be important later), and initially the protocol and client often evolved together, adapting to each other’s needs.
By default, when you add to your append-only log, that addition only exists on your device; but the next time you connect to a peer running a Scuttlebutt client, your two clients will sync with each others’ logs, and then verify them against each others’ public keys. And to verify the newest part of a Scuttlebutt log, you need the whole log - this ensures that if someone gets part of your content, they get all of it.
But you don’t just sync each others’ content - your clients sync all the logs they have locally. That’s why it’s called the gossip model - once you put out a post, as long as you’re connected to a few peers every once in a while, your post will spread as fast as gossip to the friends of your friends. It usually takes time for that information to spread to everywhere, which keeps the pace of Scuttlebutt life somewhat slow and relaxed, with the most active communities being, again, small and tight-knit. Scuttlebutt is definitely not a global social network. The gossip model was driven by the social graph, allowing users to sync with others based on who they follow and who their connections follow. This mechanism relied on cloud bot users, known as “pubs,” acting as connectors and community hubs.
Scuttlebutt syncing took time due to the necessity of syncing all activity. Pubs played a crucial role in facilitating connectivity within the network, ensuring that users could discover others either by sharing a pub or by following users who were connected to them.
Scuttlebutt’s evolution was influenced by the desire for decentralized communication, distinct from the centralized nature of platforms like Twitter. It offered an alternative for those seeking intentional, offline-first communication free from the constraints of Big Tech. While initially designed for smaller, tight-knit communities, the ideas and learnings from Scuttlebutt inspired later attempts to build decentralized networks suitable for global networking.
So, now the stage is mostly set. Twitter was the first “global town square”, a social network connecting people and ideas worldwide - but not without a myriad of problems, which many concluded were due to its centralized nature. ActivityPub and Scuttlebutt (and others) experimented with decentralizing the social world, mostly with a focus on smaller communities, though as they evolved people tried to make them more suitable for global networking. Neither of them would prove viable for global social networks, but the learnings from them would help develop the next generation of social protocols.
Freeing the Bird
or, where ATProto and Nostr came from
All of this is important background for understanding the motivation behind these two protocols. Twitter started it all by showing us what microblogging at scale - a “global town square” - looks like. It showed us how many problems there are with it, and to some, that the only way to fix them is to remove corporate control. ActivityPub and Scuttlebutt showed us two very different ways of doing so, each with their own major benefits and major drawbacks. But there’s still a long way to go from these experiments, which were largely paving the way in the late 2010s, to where we are now, almost halfway into the third decade of the 21st century. To fill in these gaps, we can start towards the end of the second decade of the 21st century.
It wasn’t just people outside Twitter who were aware of the multitude of issues with Twitter - of course Twitter noticed them too. Twitter had started as a much more open company than it was at this point in December of 2019 - over the years, they’d taken, for a variety of reasons, a more centralized path, facing investor pressure for returns, and other such things. Twitter knew that, in the words of founder then-CEO Jack Dorsey, “centralized enforcement of global policy to address abuse and misleading information is unlikely to scale over the long-term without placing far too much burden on people.” Jack and the rest of Twitter drew the same conclusion as ActivityPub and Scuttlebutt had before - corporate control of social media was simply bad for everyone. Twitter was a company full of people who realized the service was just in a shitty position no matter how you looked at it, and who were doing everything in their power to keep things healthy despite it all - and they saw a way out: to build on, or build, an open protocol for a global social network. And for all the reasons we talked about before, about ActivityPub and Scuttlebutt, neither of those protocols were up to the task.
So the Bluesky initiative began. The early history of the project is much better documented elsewhere, but one of the most interesting things to come out of it at this early stage was an ecosystem review of existing decentralized protocols. It was authored by a Zcash developer named Jay Graber, who would go on to become CEO of Bluesky. It included contributions from several notable people in the decentralization space, including Christine Lemmer-Webber, co-author of the ActivityPub spec, Paul Frazee of Patchwork (and at the time now working on Beaker Browser and Dat), Whyrusleeping from IPFS, and Rabble of early Twitter (at the time working on planetary.social, a Scuttlebutt client). It lays out the state of numerous decentralized protocols, including ActivityPub and Scuttlebutt, and explains how user discovery, moderation, etc works in each of them.
At the end of all this ecosystem review, Bluesky concluded that none of these existing protocols was really suitable for their goal - a decentralized protocol Twitter, a global social network, could run on. So they decided to create their own - ATProto - and incorporated into a Public Benefit LLC to help achieve this goal. And when their initial team was hired, it included none other than Paul Frazee of Patchwork, in addition to Aaron Goldman, a former security engineer at Twitter, and Daniel Holmgren, an engineer with experience building on IPFS.
Now, while all of this was happening, a Bitcoin enthusiast under the pseudonym Fiatjaf was working on his own little thing. His idea was a non-peer-to-peer reimagining of Scuttlebutt and what it would take to make a similar protocol usable on a global scale. And on November 7th, 2020, the first basic working code for his idea of “Relays” quietly slipped onto the scene. Nostr’s initial description even cites Scuttlebutt as an inspiration - the main design differences between the two (at a high level) are that Nostr moves from a p2p network, with pubs as an afterthought, to a purely client-relay model, and that Nostr events are all separate units that do not form a chain.
His motivation for creating this protocol was, somewhat similarly to Bluesky, problems with Twitter. Bluesky was motivated by the idea that content moderation at scale is impossible to do well, and centralizing it in the hands of a single company was a bad idea. Nostr, meanwhile, views moderation itself as an enemy - as censorship that the protocol should be resistant to. While in reality, even Nostr has ultimately ended up exploring different forms of communal moderation, the primary motivation behind Nostr’s design choices is an idea of extremely high censorship resistance. This implies that the design, rather than optimizing for consistency, should optimize for availability - if someone wants to see your content, they should be guaranteed to be able to get it from somewhere. The protocol design is pretty conducive to this.
Both of these efforts were toiling away in the darkness, waiting for their moment in order to replace centralized social media with a decentralized future. Then in late 2022, something remarkable happened. Centralized social media fell prey to one of its prime weaknesses, right where everyone could see, thanks to one very famous billionaire. Elon Musk payed 44 billion dollars for Twitter, released the so-called “Twitter Files”, and Jack Dorsey, who had earlier kicked off the Bluesky initiative with 13 million dollars, put out a little manifesto in response, titled a native internet protocol for social media. Within a few hours, someone responded pointing him to the Nostr protocol, and he grew very interested, soon giving fiatjaf 14 Bitcoin to help fund Nostr development. A few months later, Bluesky launched their reference app for the AT Protocol. About a year later, Jack Dorsey left the Bluesky board, having chosen to focus on Nostr instead, as it aligned with his “free-speech-Bitcoin-vibes” ethos better. This was despite the fact that ATProto basically does everything he wants in a decentralized social protocol, but he prefers the more Bitcoin-y community of Nostr.
Okay, so that’s how we got here. Now we’ve arrived, back in the present. Let’s look at…
Where we are
Both Nostr and ATProto follow a similar pattern: adapting peer-to-peer data models to work in a client-server model (that isn’t quite federation). The peer-to-peer world had to deal with a unique problem: because there were no servers, there was no canonical source for data where you could go to verify its integrity. Thanks to the wonders of modern cryptography, efforts like Scuttlebutt, IPFS, and Dat all were able to use self-certifying data structures that could be verified independently of any third-party authority. A good example of this is a Merkle Tree, which is a data structure that ATProto also uses (be sure to watch that video, it’s very good and explains well why peer-to-peer networks need this).
As it turned out, these data structures and their benefits would help solve many of the problems the federated world faces. Specifically, the federated world, while no longer reliant on a single central server, often ends up simply shifting this reliance to smaller centralized servers that are the only canonical source for user data. When done correctly, applying peer-to-peer data models to the server would reduce this reliance and make data more independent of servers, while also allowing the big-world networking that only servers can achieve.
This sounds like a perfect solution, but it’s worth mentioning that it does have some important tradeoffs compared to a pure federation approach like ActivityPub’s. For example, while deletes are still possible on both protocols (though rather difficult on Nostr, which you might be able to piece together why), if someone has your data saved from before your deletion, it is much easier to prove that you said it and hold it up as yours than it is on a protocol that doesn’t have you cryptographically sign everything. And since both protocols heavily optimize for public content, things like Direct Messaging become much more difficult - in fact, on Nostr, DMs are public like everything else (their content is encrypted so no one else can read them). In general, trying to keep data private becomes extremely difficult; these protocols have delivery models which both center around the same self-certifying data being replicated in many places so anyone who wants it can get at it. With this, things like blocking other users become basically impossible, since there’s no canonical source to restrict content from.
Now let’s look at a few different protocol building blocks and how each protocol handles them.
Identity
Identity in networks is a difficult problem. Ideally, you want identifiers to be human-meaningful - for example, a Twitter handle. If I see the Twitter handle @jack, I can be fairly sure that that’s Jack Dorsey. You also want them to be secure - only @jack should be able to create a post that says it’s from @jack, and I shouldn’t easily be able to take over the account @jack without gaining access to some kind of key. And you probably also want them to be decentralized, so that @jack isn’t beholden to anyone else to hold his identity, and can move around.
Unfortunately, it’s not easy to have all three of these nice properties - Secure, Human-Meaningful, and Decentralized - at once. Almost every system which tries to have all three has to end up compromising on one of them. This trilemna is known as Zooko’s Triangle. As examples:
Twitter usernames are secure - I can’t just put out a tweet that looks like it’s from @jack - and human-meaningful - a guy with the handle @jack is probably named Jack. But they’re obviously not decentralized - they are all reliant on Twitter’s servers, and it’s Twitter who decides that @jack points to Jack Dorsey’s account. If they, say, wanted to rebrand to X, and someone was using the @x handle, Twitter could easily take it from them and make their own handle @X.
Scuttlebutt, meanwhile, has identity that’s decentralized - it’s just your private key, essentially a random number - and your public key, the part other people can see. It’s also secure - I need to actually have your private key to pretend to be you. But a public key, which is also just a number (derived from your private key), is not very human meaningful.
If you’re familiar with ActivityPub, you might argue that ActivityPub usernames are all three. This isn’t really true - ActivityPub usernames behave like Twitter usernames, except instead of just one big central Twitter server deciding what username points to what, this is handled in smaller centralized servers which federate with each other.
Nostr and ATProto also experience this problem, and they both share a few views around identity, listed out here so each one corresponds to a side of Zooko’s Triangle:
- Your identity should not be permanently tied to a single server - Decentralization
- Your data should be cryptographically verifiable as coming from your identity - Security
- There are two “layers” of identity - a permanent computer-oriented one and a changeable human-friendly one - Human-Meaningful.
Even with these similarities, how that really plays out in both protocols looks extremely different. The idea that your data is cryptographically verifiable as yours implies a keypair somewhere. In Nostr, that’s exactly it - your identity is just a secp256k1 keypair. Nothing more, nothing less.
That sounds very much like the permanent computer-oriented layer of identity. So the human-friendly identity is handled by a Nostr event of the profile type - this contains stuff like your bio, display name, and avatar. There’s also NIP-05, which allows using the .well-known/nostr.json path on a domain to get email-style usernames, like jack@cash.app
- and this includes a special case, _@domain
, that gets treated by clients as just @domain
. When you @mention
someone in a Nostr note, it’s just @<their public key>
, which clients then simply display as their display names. Notably, having either a display name or even a real NIP-05 username is completely optional under Nostr, and your public key really is your identity.
This looks like mostly a success, at least in terms of taking those views and treating them as criteria. Nostr actually takes the first point - identity should not be permanently tied to a single server - and goes slightly further: in Nostr’s model, where your identity really is just your keypair, no servers are involved in identity at all. Why would you want that? A major benefit of this approach is that if any of the servers involved in the system goes down or is no longer friendly with you, your identity doesn’t even need to be “recovered” - it’s just there, the same as before. This works well with the Nostr Relay model, which we’ll discuss in the next section.
The drawbacks of this approach are the same as Scuttlebutt. Thanks to the relay model, your identity is no longer tied to a single client on a single device - you can easily move around, between relays, between clients, between devices. This, by itself, for most people, is a good thing, but it comes with an entirely different kind of problem:
Managing a cryptographic keypair is simply not very user-friendly. You simply can’t expect most people to write it down and keep it in a safe place or even take the time to understand what it means. People expect username-password systems, and sure, newer technology like passkeys is actually more secure and potentially easier - but that comes with actual benefits over username-password for most people! Managing a keypair is not only unfriendly, it’s incredibly risky. Since the entirety of your identity is your keypair, and to sign in to Nostr clients is to give them your private key - well, you can probably see where this is going. And again, since your identity is just your keypair, just like with Scuttlebutt, if an attacker gets a hold of your private key, that identity is gone. No longer yours. There’s no-one you can go to for help, no-one who can recover that account, no password reset link.
That sounds very negative, but it is worth noting that at least for web Nostr clients, there is a (relatively) good solution to the sign-in problem - NIP-07. In the NIP-07 world, you don’t give every client your private key - you give it once to a browser extension, and then every time a web client wants to do something on your behalf, instead of directly using your private key to sign messages etc, it delegates that to your trusted extension. This is a lot better than giving your private key out to every client that has some cool new feature you want to try. Of course, this doesn’t help with recoverability - if you lose your private key, whether to your memory or to an attacker, it’s still gone. There are attempts to solve this, too, which I’ll talk about in “Where we’re going” because it has interesting future implications.
ATProto looks at things a little differently. Because of the aforementioned difficulties involved with users managing their own private keys, Bluesky chose to have your signing keypair live on a server - your Personal Data Server, or PDS. Your PDS is responsible for serving your Data Repository to other services on the network, and serves as more-or-less the canonical source for your content. However, your Repository is fully self-certifiable (that means someone can check whether or not you created the content in a copy of your Repo without needing a third party to verify), and so is not permanently tied to your PDS. This is because your PDS is not the canonical source for your identity - but your identity is also not something as small as a keypair here, and does not live entirely client side.
Instead, ATProto uses their own homegrown DID (Decentralized IDentifiers, W3C spec with the aim of helping, well, decentralize identity) method called did:plc, for PLaCeholder. Why is it named “placeholder”? Well, because as of now, it’s centralized. That’s right, the supposedly “Decentralized” Identifier is centralized - and Bluesky actively doesn’t want it to be that way. did:plc was initially intended to be a placeholder until a decentralized method was able to meet their requirements - “a strongly consistent, highly available, recoverable, and cryptographically secure method with fast and cheap propagation of updates”. did:plc has all of these at one major cost - it’s centralized. However, the data in a did:plc is self-certifying (you don’t need to trust/rely on plc.directory to verify the information), so it’s conceivable for it to become more decentralized in the future. (You can also use a did:web, which removes this centralization but forces you to manage everything yourself and relies permanently on your control of a web host on a domain, thus removing most of PLC’s benefits. This is pretty niche, so I won’t talk about it in detail here.)
A did:plc: contains two public keys - your rotation key and your signing key. This signing key is the aforementioned key that the PDS uses to sign your data. The rotation key is important because it manages your did:plc: and thus is needed to sign updates to your DID document, such as when migrating PDSes. The canonical source for your current PDS, valid signing key, handle, and rotation keys (which can also be rotated) are all your DID document. In this way, a DID serves as a “Theseus Identity”, an idea Aaron Goldman laid out well in this YouTube video.
The canonical source of your identity is your DID doc, and all the information in it, i.e. your handle and current PDS must be a two-way connection - your handle is a domain with a dns txt record or ./well-known/atproto-did that must point to your DID, providing two way verification, and whatever PDS your DID document points to must actually have your account on it. Meanwhile, the PDS handles data, and implements a standard, user-friendly login system, and signs your updates with your key on the server side.
Here, there was a trade-off between principles of security, recoverability, and user-friendliness, and a principle of max-decentralization - low-friction identity, with no centralizing points of control at all, extreme takeover resistance. Notice that
Where ATProto chooses user-friendliness, Nostr chooses max-decentralization. This is a trend that repeats in many other parts of each protocol’s design, as we’ll see.
Data
In the traditional federated world of protocols like ActivityPub, there had never been much of an emphasis on data, and the formats and structures it’s stored in. The federated world thought much more about how servers should communicate messages rather than how they should store data - this difference is laid out well by Bryan Newbold, who incidentally now works on protocol design at Bluesky. This emphasis on communication standards rather than data standards is a big part of why there’s no standard “fediverse repo” that you can transfer between servers, and other such problems in the federated world.
The peer-to-peer world, as we looked at earlier, couldn’t afford to define pure transport protocols - they had to design standardized data structures that were self-certifying and self-contained. An example of such a data structure is a blockchain, and indeed, the peer-to-peer community and the blockchain community learned much more from each other than either of them and federation did from each other.
This was the status quo until ATProto and Nostr came along and broke the mold by bringing these self-certifying data structures into the client-server world. They both use asymmetric cryptography to make this data self-certifying, but the similarities basically end there.
In the Nostr model, servers are dumb. They have basically one job - transmit data. There’s only one kind of server in Nostr - a Relay, and a Relay does only three things:
- Receive data to store
- Return that data when asked for it
- Provide a continuous stream of the data being placed on that Relay
Notably, Relays store data. Data is placed on Relays. All this data is created on the client-side. Relays don’t manage identity or any of that. Your keys live with your client, and it’s your client who signs your events
(a piece of data in Nostr terminology.) When you fetch data from a Relay, it comes back with signatures and all - which, guess what, your client verifies. Your client almost operates under the assumption that Relays will try to do weird stuff, people will submit fake events, etc - and so Nostr removed the requirement of trust, by making clients verify everything themselves. A trade-off!
Nostr, by optimizing for censorship resistance, needs to remove as much rigidity from its design as possible. Data needs to be cheap to create and transmit and store. So Nostr events all exist as individual units following a fixed JSON format with a strict signing convention. Unlike Scuttlebutt, these events don’t need to form a chain - they are purely self-contained. Like your identity, there’s no canonical source for them either - by design, you’re supposed to be able to get them from pretty much any relay that has them. When you create the event, your client signs it and then just publishes it to as many relays as possible, from where it will circulate into other Relays, consuming clients will republish them, etc. Because they are signed against your public and are fully self-contained, it’s trivial to verify them too, removing the necessity of trust in the Relay you get the event from.
ATProto data is also very portable, but it is slightly more rigid than Nostr data is. Instead of using these one-off events which are fully self-certifying, ATProto stores your data as records in what it calls a repo. These records live under a collection like app.bsky.feed.post
and are given an rkey
(record key). Together, this forms a URI for any given record that looks like at://did/collection/rkey
. Importantly, records are mutable, unlike nostr events, and the contents an at:// uri points to may change. However, all the commits to your repo, which contain changes like record creation, editing, and deletion, are content-addressed using a CID, and these are immutable, and are all signed using your repo’s signing key (the one from your DID doc, remember?) Your commits can also optionally form a chain if you want, but when they don’t, deletes are easier. (If all of that flew over your head, don’t worry. All you need to know is that ATProto allows deletes and edits, while Nostr can’t.) Because your data all lives in this repo, unlike Nostr, ATProto actually has a canonical source for your data.
There’s also a single place where your repo lives, instead of being scattered as a bunch of events across Relays like in Nostr. Your repo lives in your Personal Data Server - as the name implies, a PDS is designed to store your personal data. While Nostr Relays are dumb pipes, PDSes are more like a user agent, which really performs almost all actions on the user’s behalf. It’s responsible for signing and storing commits to your repo and wrapping them in a nice API that’s easy for clients to use.
Actually, we should probably take a minute just to talk about deletes and edits. When I said Nostr can’t allow deletes and edits, that wasn’t completely true: Nostr does have a way to request deletes from Relays, which most but not all Relays support, but the real trouble is figuring out what a delete even means (and edits are straight-up impossible since Nostr event IDs are fully content-addressed). Nostr’s model is fundamentally based on an idea of events flowing from the creator into Relays, which then flow into other people’s clients, which cache them and republish them to other Relays, and so on. An event doesn’t have a location to be deleted from - it could be (and in Nostr’s model, should be!) anywhere and everywhere.
In ATProto, your repo actually has a place where it lived - your PDS, as specified in your DID doc. And at:// uris are mutable, so a commit can actually change the content it points to. Deletes remove content from your repo - although anybody who has a copy of your content pre-delete will still have it and can very easily cryptographically prove that it’s your content.
Trust
Nostr and ATProto have relatively similar approaches to trust, though with some important differences. Nostr trusts nobody, and is built accordingly, with clients verifying everything themselves. ATProto assumes you trust somebody, but lets you choose whom you trust, and provides the mechanisms needed to verify that trust is placed correctly (although this could be improved).
Nostr, as mentioned earlier, was designed to basically eliminate the necessity of trust in the first place. Because everything is verified client-side, and essentially functions as a bunch of self-authenticated units of data traveling between relays and clients, there really is no one to trust. Relays can choose not to carry content, but other relays might have them instead. However, the fact that all data moves as individual units means that it would be harder to spot if only certain events are available.
Since every user is assumed to be pointing their client at more than one relay, it doesn’t really matter if one relay chooses not to carry someone’s content; there’s a high likelihood another one is. If many relays agree to hide something from the network, then it won’t show up, but that’s pretty unlikely to happen. As for trusting the authenticity of the content delivered by the relay, because it’s cryptographically verifiable as coming from the attached pubkey, any shenanigans will be spotted quickly. And verifying a pubkey’s identity is done by attaching it to a trusted NIP-05, i.e. @jack@cash.app or @jb55.com.
ATProto isn’t that different, all things considered, but there’s multiple other hops between the source of data and the client you view it in. Each ATProto PDS puts out a cryptographically verifiable stream of commits being pushed to repos on the PDS, carrying every bit of data to the subscribers, called the firehose. Because there are a lot of PDSes, an optimization also called a Relay was introduced, which basically aggregates PDS firehoses into its own giant firehose. In a way, this Relay could be considered its own centralization point where bad untrustworthy things could happen, but once more than one Relay exists this should be less of a problem. At the Relay and PDS, everything is cryptographically verifiable, and as a bonus because of ATProto’s repo structure, you can tell if you’re not getting the whole picture.
After the Relay, things get a bit murkier, because as an optimization ATProto applications use something called the AppView. The AppView reads in the firehose from the Relay constantly and pieces it together into fully hydrated and speedy APIs which make clients’ lives much easier. The thing about the AppView is that it’s basically centralized, and though it’s not super difficult to spot inconsistencies between what the AppView gives you and the true state of the network, the AppView doesn’t even provide the cryptographic signatures that were passed into it, making its trustworthiness a bit murky at some unknown time in the future, at which point other contenders will hopefully exist to replace it, based on analysis of which one is more trustworthy by comparing the data each AppView gives you with what actually exists on the Relay and PDSes.
Privacy
Everything is completely public on both protocols and in fact being actively broadcasted to loads of consumers, not just sitting around waiting to be stepped on and found. Nothing you do is really hideable from anyone.
However, at least on ATProto, there have been attempts to add some semblance of privacy to the network. For example, there are AppView-enforced blocks, but they can be bypassed very easily. There is also a setting which asks the client to not show your posts to logged-out users, but this is superficial at best, since only some clients really follow it anyways, and the “official” popular client does so it does kind of work. But overall these measures both run a risk of making people feel like their posts and other activity are hidden and safe, lulling them into acting with less precaution than they should, especially since there is a lack of user awareness around the all-public nature of data on the network.
No such attempts have been made on Nostr. This is on the one hand unfortunate, but on the other hand possibly better since it is more honest about the true nature of how public everything is on the network.
Development
Due to the Bluesky devs’ past experiences with developing on peer-to-peer and federated protocols, many of them felt burnt by a Scuttlebutt-and-Nostr-style approach to development, where specifications were loose and implementations varied wildly. Because of these past experiences, Bluesky chose to go with a slightly more slow, intentional, and centralized development model. The protocol is mostly developed within Bluesky the company, though often adapts to the needs and feedback from the wider ATProto developer community, and community members often contribute to both the protocol and the clients. The rollout of core features like federation and stackable moderation has also been much more slow on ATProto than similar features in Nostr implementations, because in general Bluesky prefers to take their time and “get it right” and standardized before letting things out into the wild. Also, despite the existence of third-party clients, the “official” Bluesky app and service is still the most popular one by a huge margin, due to its being the default (and basically only) inroad into the protocol and ecosystem. There are other up-and-coming AT Protocol projects that aren’t just Twitter clones, like WhiteWind for blogging, but overall the ecosystem remains sparse compared to Nostr.
Nostr, meanwhile, takes the same approach as these previous projects - the protocol itself just exists, very small, letting anyone expand on it. When an extension wants to become standardized, it’s reviewed by a small team including fiatjaf and a few others, and becomes part of the NIPs repository (Nostr Implementation Possibilities). This is basically classic BDFL open-source. However, clients and relays are free to try their own wild things without being “official” NIPs, and any NIP proposal must be adopted by a few clients and relays before it can be considered for “official” status. So it’s a much wilder, freer ecosystem so far.
Applications
One of the places where ATProto and Nostr differ greatly is their model for building applications.
ATProto takes the AppView approach. An AppView is basically a service that reads in the firehose of all the public data on the network, and indexes it into hydrated “views” as an API which clients then use. AppViews are pretty resource-intensive to run and functionally centralized in nature. If you want to make a new ATProto app, you first design your schemas for content in a DSL called Lexicon. Then you make a client that can start publishing your record type, and retrieving and displaying it. For the retrieval and displaying, you create an AppView which monitors the firehose for your record types and indexes them into hydrated views, which your client can then fetch from and display nicely and neatly. This is, for example, how the Bluesky app can show a list of users who liked a post; because instead of the client having to crawl the entire network itself and figure out which likes are for the post you just viewed and then get the DID and fetch each of that user’s profiles and whether or not you’re following them by checking your own repo, and whether or not they’re following you by looking all over their follow lists, the client just makes one HTTP request and makes the result human-readable. Nice and fast. Of course, the relief that comes to the client means a lot of responsibility is thrusted onto the AppView, which becomes very resource-intensive to run.
The first steps to the Nostr model look similar at first, but rapidly diverge. With Nostr, you also start with defining event kinds, and then creating a client which can publish them, and then adding fetching and displaying. The key difference is in how events are fetched. With ATProto, you write an AppView to do the heavy lifting; with Nostr, the heavy lifting is shared between the Relay and the Client. When defining your event kinds, you make sure to also define how to use the “tags” field for that event kind, which is an array of key-value pairs with single letter keys which are indexed by the relays the events are sent to. Basically, if you want to do any kind of linking between events, or inserting any kind of indexable data, that’s where you want to do it.
Then for the fetching of the data, we use Nostr’s filtering system. With Nostr, there are two kinds of communication between the client and the relays; publishing events, which pushes the signed client-created event into the relay’s data store, and subscription. Subscription is the interesting part we’re looking at here.
Nostr clients can request a subscription to a stream of events from the relays they’re connected to, and this stream subscription can have filters attached. A filter is fully specified using the following attributes, all optional:
{
"ids": <a list of event ids>,
"authors": <a list of lowercase pubkeys, the pubkey of an event must be one of these>,
"kinds": <a list of a kind numbers>,
"#<single-letter (a-zA-Z)>": <a list of tag values, for #e — a list of event ids, for #p — a list of pubkeys, etc.>,
"since": <an integer unix timestamp in seconds, events must be newer than this to pass>,
"until": <an integer unix timestamp in seconds, events must be older than this to pass>,
"limit": <maximum number of events relays SHOULD return in the initial query>
}
By adding multiple filters, you can get all the events matching any of the filters. By adding multiple attributes to a single filter, you add multiple conditions that all have to be fulfilled for events to make it through that filter. Filters are expressly the mechanism for fetching content, since subscriptions are supposed to start by backfilling everything that meets the criteria, and then pushing any new events that meet the filters’ requirements to the client.
By studying the filter specification, it’s clear that basically every behavior of ATProto AppViews can be recreated through filters on the client-side, knowing how tags allow extensibility as well. There’s an obvious cost though: clients must be very complex and do a lot of work themselves, and for big events duplicating a lot of effort that could be handled by something akin to an AppView. The benefit of this is that it is very generic and means that any relay can generally be used for any functionality since everything you need is baked into the core protocol, and the speed of development is basically only constrained by the client, and not an AppView. And by not spending any resources on building a giant indexer yourself, you basically shift the cost onto the Relays instead. It’s another example of the more “bazaar” philosophy of Nostr compared to a more “cathedral” approach from ATProto.
So, all in all, this gives a pretty good picture of where the two protocols are now. But exciting things are on the horizon for both. We’re heading into uncharted territory…
Where we’re going
When Jack Dorsey wrote a native internet protocol for social media, he wrote that “As far as the free and open social media protocol goes, there are many competing projects: @bluesky is one with the AT Protocol, nostr another, Mastodon yet another, Matrix yet another…and there will be many more. One will have a chance at becoming a standard like HTTP or SMTP.”
That’s one way of thinking about it, as a competition for the final spot of “the standard for social”. But as you’ve probably noticed from reading this post up to here, I don’t really agree with this viewpoint. ATProto, Nostr, ActivityPub, Scuttlebutt, Matrix, IPFS, Dat, Holepunch, and others all share similar goals, yet have vastly different perspectives about how to accomplish them. Maybe these different perspectives will all lose! Maybe, as Jack says, one of them will win, becoming a standard that everyone adopts. Or maybe they will all learn from each other and slowly begin to converge. And it’s not hard to make the case that that last possibility will happen for at least two of these protocols - of course, Nostr and ATProto. In fact, that’s already happening.
Convergence
Because a lot of core ideas in the protocols were already very similar, they can quite easily borrow ideas from each other in order to improve themselves. By making nearly opposite compromises, they now face roughly opposite problems as well - but often, the other protocol already has a solution waiting for them. So first let’s look at some of the ways Nostr is becoming more like ATProto.
First, the idea of keys in a server, instead of purely client-side. As mentioned earlier, one of the dangers of Nostr keys is that by giving them to lots of random clients you try, they might accidentally end up in the hands of bad actors. One of the solutions to this was NIP-07 browser extensions; another one is the idea of an NSecBunker, for Nostr Secret Key Bunker. The idea is that this is a server, similar to a PDS, which holds your Nostr private key, and when your client wants to sign an event, it makes a request to your NSecBunker to sign that event using your private key, which stays safe in your Bunker. These requests usually are authenticated using measures like OAuth. It allows Nostr to bring back at least one part of the user experience people are familiar with.
Another idea that Nostr is ending up trying is something similar to AppViews. This is particularly divisive within the community, with many feeling that only the relay-based filtering mechanisms should be used to build clients. But because this is often inefficient, clients like Primal have begun doing their own pre-indexing of many users and posts in order to improve their UX. Unfortunately, Primal’s is proprietary, and only Primal can interact with it, due to the lack of any built-in support for AppView-style services in the Nostr protocol, vs. ATProto’s numerous mechanisms to provide explicit support for this use case.
Meanwhile, some Nostr ideas are naturally going to the ATProto world as well. The idea of keys directly owned by the users has long been floated, and at this point developers can get control of their did:plc and its rotationKeys (fun fact: I set one of my plc rotationKeys to my Nostr pubkey). Unfortunately no nice UI exists for this yet. And as for signing keys, with commits that could be pushed to a PDS instead of made there, that would rely on a PDS supporting this use case. No PDS implementation currently supports this, but there is one in development which hopes to at some point ;)
Another idea which I hope to see adopted in the ATProto world is something similar to Nostr’s filters model. While the AppView model is nice for production apps, something like Nostr filters could help a lot early in development to just play with an idea and try it out. And it could help those with concerns about the trustworthiness of AppViews quickly verify it against certain queries. You can do a shocking amount with backlinks alone.
Of course, the slow convergence of both protocols isn’t the only way the divide between them is being bridged…
Bridging
Recently, Bridgy Fed started bridging the Fediverse and the ATmospherewith each other. For a while, services like Mostrhave been bridging the Fediverse and Nostr with each other. Now, if you visit the Mostr homepage and scroll down, you can probably see where this is going…
Soon after Bridgy Fed started bridging the Fediverse and the ATmosphere, Nostr users experimented with this to bridge between Nostr and Bluesky. Very much an indirect hack, but also a glimpse at the future.
One of the most important promises of decentralized social media was that no matter what service you signed up on and post on, you would be able to see content from and interact with anyone, no matter which service they used either. Now, all this would work, if every service signed on to the same decentralized social protocol. However, instead, we have many, and none of them show much of a sign of becoming the singular standard for social media. Instead of Jack’s vision of one winner, bridges offer a vision of a world where every protocol can win, and it truly won’t matter which protocol your service uses, either.
While the bridging I talked about above was very indirect, Bridgy Fed itself may soon have native Nostr support. Soon all three major decentralized protocols may be able to talk to each other, and easily too.
So. Let’s recap what we’ve been through in this post so far. In the beginning, there was Twitter. Twitter’s problems caused them to look to decentralization as a way to make social media more fair. This caused many new decentralized protocols to emerge, taking inspiration from older ones. Of these new protocols, two of them, Nostr and ATProto, evolved in similar directions, yet unaware of each other made many opposite compromises. And now they are evolving back towards each other, converging in potentially very interesting ways, with bridging offering to make social media not just platform- but protocol-agnostic.
The future is looking good for decentralized social media.
You can join the conversation on Bluesky here.
Comments from Bluesky:
Or on Nostr: