[This fragment is available in an audio version.]

I’ve been in the conversation around Twitter’s @bluesky project, and last December I posted @bluesky Identity, a proposal for mapping between social-media identities based on public keys and signatures. Recently @bluesky announced the Satellite contest, whose goal is to take identities on three or more online properties and “Link them in a way that anyone can verify you are the author/owner of all.” Which is more or less what @bluesky Identity is all about. So I pulled together a working demo called “Blueskid” (GitHub). This is a quick walk-through of Blueskid.

Blueskid? · Well, I needed a project name and “@bluesky identity” has six syllables. “Blueskid” is euphonious and only has two, and blueskid.net was available. And I get this mental image of a kid playing blues.

Contest? · Blueskid is not an entry in the Satellite contest. First of all, I’m sort of a @bluesky insider and the idea is to bring in ideas from the community. Second, Satellite is looking for something with a focus on decentralization and radical innovation. Blueskid uses public-key and ledger technologies that, in the software-technology context, are as old as dirt.

I offer Blueskid as a low bar that the Satellite offerings really ought to raise.

Let’s watch it at work.

The assertions · Here’s a recent twitter post:

twitter.com@timbray claims the Bluesky Identity 55555


This tweet contains an “assertion” representing a claim by the Provider Identity (PID) twitter.com@timbray to the Bluesky Identity (BID) 0000000000055555. Let’s unpack that.

  1. We know that the claim is from twitter.com@timbray because it’s posted to the @timbray Twitter account.

  2. Since we’re going to be posting assertions to social media, they need syntax to delimit them from any other text that might be in the post, and to separate the fields. Since I’m a fun-loving guy, the beginning and end of an assertion are marked by "🥁" (U+1F941 DRUM) and the fields are separated by "🎸" (U+1F3B8 GUITAR). I’m not going to claim this is optimal but it worked OK in the demo.

  3. This assertion has two fields. The first is “C”, saying that this is a Claim assertion. The second gives the BID that’s being claimed.

  4. In Blueskid, Bluesky Identities are represented by unsigned 64-bit integers. There’s a lot to be said about how they might be structured and minted, but for the purposes the demo we just need something that can be represented in a string, in this case upper-case hex characters.

Another Twitter post:

twitter.com@timbray grants BID 55555 to tumblr.com@t-runic


It was followed shortly by a Tumblr post:

tumblr.com@t-runic accepts BID 55555 from twitter.com@timbray


These two assertions are designed to work together. Each has six fields:

  1. In the first, “G” says this assertion Grants a BID, “A” that it Accepts one.

  2. The second field gives the BID being granted.

  3. The third is a nonce (in base64). This is currently 64 bits, which is kind of short by nonce standards, and I need to find someone with real cryptographic/security skills for advice. I’m having trouble thinking through attack models. At the moment I think 64 bits is plenty. But it’d be unsurprising if I were wrong.

  4. The fourth is an ed25519 public key, once again in base64. The encoding uses the horrible old ASN.1/PEM/PKIX machinery, which would be silly if the whole world used Go, but many other popular libraries in popular languages assume this is the one and only way to interchange public keys. Thus it’s the right thing to do in an Internet Protocol.

  5. The fifth is the signature (base64 again) produced by applying the corresponding private key to the nonce.

  6. The fifth is the counterparty, in the Grant assertion the receiving PID and in the Accept assertion the granting PID.

There is a another pair of posts to grant that same 55555 Bluesky Identity from twitter.com@timbray to mastodon.cloud@timbray, here and here. Also, note that:

  1. The nonces are different and so are the signatures.

  2. The keys are identical.

  3. The BIDs are identical.

  4. The Grant post is known to be published by twitter.com@timbray and names tumblr.com@t-runic, while the Accept post is known to be published by tumblr.com@t-runic and names twitter.com@timbray.

You might ask where the private key corresponding to the public key is stored. The answer is “nowhere”; it existed in the Blueskid server just long enough to produce the two signatures, then it was overwritten. It doesn’t exist any more.

It is my belief that these social-media posts, taken together, establish that at some point the owner of twitter.com@timbray and of tumblr.com@t-runic had access to the same private key, and published commitments respectively to grant and accept the “55555” BID. (The same exercise was performed for mastodon.cloud@timbray.)

Blueskid also knows about an “Unclaim” assertion, not illustrated here, whose effect is what you’d expect.

Q.E.D. · My claim is that these assertions in social-media posts constitute a verifiable proof that the same entity controlled both PIDs and expressed an intent to share a BID.

But, even if you agree with me, the social-media posts by themselves aren’t very useful. If you wanted to know what BIDs exist and which PIDs they’re shared between, you’d need to read all the posts from everyone in the universe and look for Blueskid assertions. So…

The Ledger · As the @bluesky Identity post outlines, you need a Ledger to make this work. For each of the BID Claim, Grant, and Unclaim assertions, there needs to be a Ledger entry noting what has been done and pointing to the social-media posts that prove it. The Ledger needs to be publicly readable and reliably immutable. Clearly, by processing the Ledger, you can build a little database of what BIDs exist and which PIDs are mapped to them.

The Ledger could be constructed with blockchain technology. That’s not how I’d build it if you asked me to, but it’d work OK. The write rate is probably low enough to survive blockchain’s pathetic update performance.

There’s an important issue the Ledger needs to address, based on the fact that social-media posts are not immutable; even Tweets can be deleted. Simply because I publish an assertion pair like the one illustrated above doesn’t mean that everyone can be confident that they can go and verify it years hence.

Therefore, the Ledger implementation needs to make a believable claim that it won’t append anything to the ledger that it hasn’t verified by fetching the social-media posts and validating all the constraints listed above. I’m not sure what the best way to achieve this is, but I have one idea: There could be multiple implementations, each reading new assertions as they are added to the ledger, repeating the verification, and rejecting assertions that can’t be validated. Hey, this is starting to sound like a blockchain.

What Blueskid does · First of all, it helps generate assertions. For example, you can ask it to make that Twitter/Tumblr BID grant assertion pair for you. Send this to the /grant-assertions endpoint:

  "BID": "55555",
  "Granter": "twitter.com@timbray",
  "Accepter": "tumblr.com@t-runic"

Then it’ll come back with:

 "GrantAssertion": "🥁G🎸55555🎸0E8hIvntXJc=🎸MCowBQYDK2VwAyEAzHaDqVdyhle4wVY/leNyZrtBKJKMVqVWZFfVJ3S8v60=🎸U1vPM6cQ+c5rdTKwa/2l/wjr2Z0Zu33t/qE59+94Ni/0TjEjDqcAZ/LfaFcJ6i+v+uLNhiN5LeiekFYByPWVAQ==🎸tumblr.com@t-runic🥁",
 "AcceptAssertion": "🥁A🎸55555🎸V5+dt5Me0kw=🎸MCowBQYDK2VwAyEAzHaDqVdyhle4wVY/leNyZrtBKJKMVqVWZFfVJ3S8v60=🎸1fLK2wHtRA24c/wu9uiiB42WOFur3TI9VozsYKImY0Vq3HgwDJU6xCX8GiW8rM+KIjOUTem6sQt5vTybK+dbCw==🎸twitter.com@timbray🥁"

Then, once the assertions are posted to social media, you can update the ledger. Here’s an example of a post that records the Twitter/Tumblr assertion pair, which you’d post to the /grant-bid endpoint:

  "GrantPost": "https://twitter.com/timbray/status/1439270526598332424",
  "AcceptPost": "https://t-runic.tumblr.com/post/662682878549852160/mpe"

After you’d posted that, sending a GET to the /ledger endpoint would yield this:

 "Records": [
   "RecType": 0,
   "BID": "0000000000055555",
   "PIDs": [
   "PostURLs": [
   "Key": ""
   "RecType": 1,
   "BID": "0000000000055555",
   "PIDs": [
   "PostURLs": [
   "Key": "MCowBQYDK2VwAyEA7bk+ldmZEGCSAdR1RQek1nQ4Lp058QpcaNGnDlfsS/A="
   "RecType": 1,
   "BID": "0000000000055555",
   "PIDs": [
   "PostURLs": [
   "Key": "MCowBQYDK2VwAyEA+BBQLd4ks4vdJZzX1F4j51gtyfJpLBFpeqkT7t5GJ/0="

I’m not going to spelunk through the JSON, but it says that the BID was claimed then granted twice, and links to the social-media posts which contain the assertions that prove it.

The code tries to be careful. It blocks BIDs from being claimed more than once and, when it processes assertion pairs, takes care that all the conditions listed above apply: The BIDs and keys are the same, the nonces and signatures are different, the signatures validate, and so on. Also it enforces the @bluesky Identity constraint that no public key can be used in more than one BID-grant transaction.

It also provides endpoints that let you query all the BIDs associated with a PID (/pids-for-bid), the reverse query (/bids-for-pid), and given any PID, list the group of PIDs of which it’s a member and which are mapped together via at least one BID. Here’s a little terminal session:

Interacting with Blueskid on the command line

What Blueskid doesn’t do · It doesn’t actually post the assertions to the social-media sites; I did that by hand. This will require a lot of API wrangling and the APIs are frankly not that lovable. It does actually use the Twitter V2 API to retrieve tweets. But Tumblr and Mastodon are just HTTP GETs followed by code that roots through their horrible HTML to find the assertion.

Blueskid’s ledger is a fake. It’s in memory, not persisted at all, and it doesn’t do signature chaining to ensure that it’s immutable. Databases and Merkle trees are hard, but implementing them to do this kind of thing is a fully solved problem.

Acknowledgments · The idea of establishing key ownership by publishing signed assertions in social-media posts is originally due to Keybase.IO, quite some number of years ago.

This work has benefited from several interventions by Paul Hoffman.


Comment feed for ongoing:Comments feed

From: Kevin Marks (Sep 23 2021, at 00:28)

Why have you invented yet another syntax for user at a domain?

The acct: format is annoying enough, but yours is wilfully bad. Put the / back in, or omit it and you have a valid url twitter.com/@timbray or twitter.com/timbray work.

Mastodon already supports rel=me linking, and twitter used to before the last redesign. If you're in touch with them on bluesky, encourage them to turn that back on, and it can be done without posting gibberish to your feeds.


author · Dad
colophon · rights

September 19, 2021
· Technology (90 fragments)
· · Identity (44 more)

By .

The opinions expressed here
are my own, and no other party
necessarily agrees with them.

A full disclosure of my
professional interests is
on the author page.

I’m on Mastodon!