{"id":22026829,"url":"https://github.com/timbray/blueskidgo","last_synced_at":"2026-04-10T10:02:10.176Z","repository":{"id":46971628,"uuid":"360717529","full_name":"timbray/blueskidgo","owner":"timbray","description":"@bluesky Identity tools in Go","archived":false,"fork":false,"pushed_at":"2021-09-21T21:58:42.000Z","size":57,"stargazers_count":6,"open_issues_count":3,"forks_count":2,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-05-07T10:52:26.617Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-2-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/timbray.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-04-23T00:28:08.000Z","updated_at":"2022-05-17T23:20:11.000Z","dependencies_parsed_at":"2022-09-18T04:50:32.417Z","dependency_job_id":null,"html_url":"https://github.com/timbray/blueskidgo","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/timbray/blueskidgo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timbray%2Fblueskidgo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timbray%2Fblueskidgo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timbray%2Fblueskidgo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timbray%2Fblueskidgo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/timbray","download_url":"https://codeload.github.com/timbray/blueskidgo/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timbray%2Fblueskidgo/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31637747,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-10T07:40:12.752Z","status":"ssl_error","status_checked_at":"2026-04-10T07:40:11.664Z","response_time":98,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-30T07:32:16.217Z","updated_at":"2026-04-10T10:02:10.147Z","avatar_url":"https://github.com/timbray.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Blueskidgo\n\n@bluesky Identity tooling in Go\n\nMiscellaneous infrastructure for the **@bluesky Identity** \nscheme as proposed [here](https://www.tbray.org/ongoing/When/202x/2020/12/01/Bluesky-Identity).\n\nCurrently the short-term goal is to sketch in enough of the\nprotocol to illustrate it for the purposes of the \n[Satellite bluesky contest](https://blueskyweb.org/satellite).\n\n### The Server\n\nAt the top level, `blueskid.go` contains a small http server\nthat listens on port 8123 by default, but you can change\nthat with the `--port` option. Let's just call this the \nServer.\n\n### Identities\n\nAccounts on Providers such as Twitter and Reddit are \ncalled *Provider Identities* (PIDs),\nand use syntax such as `twitter.com@timbray` and\n`reddit.com@timbray`.\n\nA *Bluesky Identifier* (BID) is a higher-level construct \nwhich may be used to map together multiple PIDs so they \ncan be considered a single source, for purposes such as \nreputation metrics. In this implementation, a BID is \nrepresented by a 64-bit unsigned integer, or alternately\nby a 16-character hex string (in all-caps) giving its value.\n\n### Assertions \n\nThe Blueskid protocol relies on embedding assertions in\nthe text of social-media posts.  These assertions have a small number \nof string fields.\n\nThe beginning and end of an assertion are marked by \"🥁\" \n(U+1F941 DRUM) and the fields are separated by \"🎸\"\n(U+1F3B8 GUITAR).  It would probably be better to use \ntwo different charactures to mark the start and end\nof the assertion. Regrettably there is no harmonica emoji.\n\nThere is a possibility that a PID might include 🥁 or 🎸, \nso in all assertions that contain a PID, that PID \nappears in the last field, to allow the use of libraries such as \nGo's `strings.SplitN`, which specify the maximum number \nof fields.\n\nThe first field of every assertion is a single character \nidentifying the type of assertion. \"C\" means Claim BID, \n\"G\" means Grant BID, \"A\" means Accept BID, and \"U\" means \nUnclaim BID.\n\n### Claiming a BID\n\nThe Server can generate a BID Claim assertion. To do this,\nsend a `POST` to the `/claim-assertion` endpoint as follows:\n\n```json\n{\n  \"BID\": \"309F0000021\"\n}\n```\nThe BID should be provided in hex.\n\nAssuming nothing goes wrong, you'll get back a JSON\nconstruct that looks something like this:\n\n```json\n{\n  \"ClaimAssertion\": \"🥁C🎸309F0000021🥁\"\n}\n```\n### Sharing BIDs between PIDs\n\nThe Server can generate a pair of assertions by which a PID \non a @bluesky Provider can grant shared ownership of a *Bluesky \nIdentity* (BID) to another account on the same \nor another Provider.  To do this, send a `POST` to \nthe `/grant-assertions` endpoint as follows:\n\n```json\n{\n  \"BID\": \"309F0000021\",\n  \"Granter\": \"twitter.com@tim\",\n  \"Accepter\": \"reddit.com@tim\"\n}\n```\n\nThe BID should be provided in hex.\n\nAssuming nothing goes wrong, you'll get back a JSON\nconstruct that looks something like this:\n\n```json\n{\n  \"GrantAssertion\": \"🥁G🎸309F0000021🎸eF2QINuVp9Q=🎸MCowBQYDK2VwAyEAj9Z3Lf5Rxylw6WParFBmeSnyhb7rK4+n1QsQba1OX2Q=🎸a/N23VuG3n7p0lfUbfPxzdDb0Ur81S3vThG0x1ZoLtf8eUHP+4AD6sOVEkx2nPkmGyMUfTyPzUcTZ/HvGs08CA==🎸reddit.com@tim🥁\",\n  \"AcceptAssertion\": \"🥁A🎸309F0000021🎸CJHlLHY9das=🎸MCowBQYDK2VwAyEAj9Z3Lf5Rxylw6WParFBmeSnyhb7rK4+n1QsQba1OX2Q=🎸rnwypUgFm5YmmFVxsh8mTInvAeAUxET8lUVId9OU9cR9wtfMWyXVDMkQyVnHoCqnUSn18+9HGr2gEF7lXOwYDg==🎸twitter.com@tim🥁\"\n}\n```\n\n### Unclaiming a BID\n\nThe Server can generate a BID Unclaim assertion. To do this,\nsend a `POST` to the `/unclaim-assertion` endpoint as follows:\n\n```json\n{\n  \"BID\": \"309F0000021\"\n}\n```\nThe BID should be provided in hex.\n\nAssuming nothing goes wrong, you'll get back a JSON\nconstruct that looks something like this:\n\n```json\n{\n  \"UnclaimAssertion\": \"🥁U🎸309F000021🥁\"\n}\n```\n\n### Verifying assertions\n\nTo process a grant of a BID from PID to PID, it is necesary\nto validate a pair of assertions - one from the granter, one\nfrom the accepter - very carefully. \n\nThis includes verifying the signatures using the provided\npublic key to prove that the creator of the posts containing\nthe Grant and Accept assertions was at one point in time \nin possession of the private key that was used to generate\nboth. \n\nThere are several other sanity checks in the function\n`checkGrantAssertion` and since I'm not a crypto weenie, I \nprobably missed a few that need to be added.\n\n### Retrieving assertions \n\n@bluesky Identity assumes that assertions claiming and \nsharing BIDs will be posted to social-media Providers, \nfor example Twitter.  Retrieving data from Providers\nis sufficiently idiosyncratic that custom code is required\nfor each.\n\nCode in `twitter.go` uses the V2 Twitter API to retrieve a tweet\ncontaining a blueskid assertion and unpack it. \n\nThese days, you can't just do an HTTP GET on a tweet URL\nand receive the content. So to use this, you need to get \na Twitter Developer Account \napproved, retrieve a bearer token, and arrange for the\n`TWITTER_BEARER_TOKEN` environment variable to have that \nvalue.\n\n`tumblr.go` and\n`mastodon.go` make a best-effort to pull the assertion out\nof the jumble of HTML this kind of site produces.\n\n### Cryptography\n\nThis software uses only ed25119 (EdDSA) keys.\n\n`ed25519.go` provides utilities for converting public keys\nback and forth between string and binary representations.\nThis uses the horrible old ASN.1/PEM/PKIX machinery, which\nwould be silly if the whole world used Go, but many other\npopular libraries in popular languages assume this is the \none and only way to interchange public keys. Thus this is \nthe right\nthing to do in an Internet Protocol.  At least you don't \nhave to think about it.\n\n### The Ledger\n\nThe @bluesky Identity protocol requires the presence of\na Ledger, to which a record of Claim, Unclaim, and Grant\nBID transactions are committed immutably.  \n\nThe Server implements (see `ledger.go`) an ephemeral ledger \nthat is a fake, lives only in memory and is not persisted. \nDatabases are hard and this is just a demo!\n\nHowever, the API offered by the Server for updating and \nscanning the ledger constitutes a proposal for what the\nAPI for a less-fake ledger must look like.\n\nWhen a BID Claim assertion has been posted, send a POST to\nthe `/claim-bid` endpoint as follows:\n\n```json\n{\n  \"Post\": \"url of social-media post containing the BID claim assertion\"\n}\n```\nThere is no response body.\n\nWhen a BID Grant assertion and corresponding BID CLaim \nassertion have both been posted, send a post to the \n`/grant-bid` endpoint as follows:\n\n```json\n{\n  \"GrantPost\":  \"url of social-media post containing the BID-claim assertion\"\n  \"AcceptPost\": \"url of social-media post containing the BID-accept assertion\"\n}\n```\n\nThere is no response body.\n\nWhen a BID Unclaim assertion has been posted, send a POST to\nthe `/unclaim-bid` endpoint as follows:\n\n```jaon\n{\n  \"Post\": \"url of social-media post containing the BID unclaim assertion\"\n}\n```\nThere is no response body.\n\n### Ledger records\n\nEach ledger record has four fields. \n\n\"RecordType\" must be \none of \"Claim\", \"Grant\", or \"Unclaim\". [Actually, in the \ncurrent implementation the values are 0, 1, and 2.]\n\n\"BID\" must be a hex encoding of the 64-bit BID being\ntransacted. \n\n\"PIDs\" is an array with one or two members. In\nClaim and Unclaim records, it has one element giving the\nPID claiming or unclaiming.  In a Grant record it has\ntwo elements giving the granting and accepting PIDs.\n\n\"Posts\" is an array with one or two members. In Claim and\nUnclaim records, it has one element giving the \nURL of the social-media post\ncontaining the assertion.  In a Grant record it the \nfirst element is the URL of the social-media post \ncontaining the Grant assertion, the second the URL of \nthe social-media post containing the Accept assertino.\n\nTo get a JSON dump of the current status of the ledger, \ndo a GET on the `/ledger` endpoint.\n\n### The database\n\nWhen the ledger is updated, the server updates internal\ntables containing the mappings between BIDs and PIDs.  This \nis fairly necessary, because to claim a PID, there needs\nto be a check that it wasn't claimed by someone else.\n\nThere are three endpoints provided to query the database. \n`/bids-for-pid` takes a single query parameter named `pid` \nand yields a JSON list of the BIDs mapped to that PID.\n\nTHe inverse service is provided by `/pids-for-bid`, which\ntakes a single query parameter named `bid`.\n\nFinally, the `pid-group` endpoint, which takes a single\nquery parameter `pid`, yields a list containing this PID \nand all other PIDs that are mapped to it through one BID or\nanother.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftimbray%2Fblueskidgo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftimbray%2Fblueskidgo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftimbray%2Fblueskidgo/lists"}