Nomadic identity(implemented by Hubzilla) seems like the ideal implementation for identity the decentralized social networks.
From their wiki:
The ability to authenticate and easily migrate an identity across
independent hubs and web domains. It provides true ownership of an online identity,
because the identities of the channels controlled by an account on a hub
are not tied to the hub itself. A hub is more like a "host" for channels.
With Hubzilla, you don't have an "account" on a server like you do on typical websites;
you own an identity that you can take with you across the grid by using clones.
@petre2dor Interesting idea.
The ability to authenticate and easily migrate an identity across
independent hubs and web domains.
This is already possible in the ActivityPub model, you can export your data and move to another instance.
It provides true ownership of an online identity,
because the identities of the channels controlled by an account on a hub
are not tied to the hub itself. A hub is more like a "host" for channels.
"True ownership of an online identity" would involve a model of permanent identity ownership. How is that possible with traditional means like TLD registrars that can suspend your domain due to ToS violations, BGP hijacking, impersonation?
Keep in mind this is a federated project, not a decentralized one!
To implement a proper nomadic identity system, a given software would have to generate a globally unique ID for every single profile. Zot (the protocol Hubzilla uses) generates a 256-bit GUID, for which the expected collision time is around a million longer than the existence of the entire universe.
Zot links this GUID to a public key, and nomadic identity is accomplished by cloning data across each server, so that each URI is dereferenced to the GUID. By comparison, ActivityPub is also supposed to be dereferencable (so each URI should point to an object).
The problem comes in implementing the id parameter. Whereas Zot has a GUID that is authoritative over the URI (meaning GUID is used internally and URI is only for lookup), ActivityPub uses the URI as the ID (meaning each AP implementation must implement its own UID system internally).
Let's take an example. Webfinger is commonly used for lookup, and relies on each server having a /.well-known directory used to probe for public keys and such. Identities are usually federated out as pointers in the format [email protected] or @[email protected], similarly to email.
A sample zot response for [email protected] might look like this:
zot response
{
{
"success": true,
"signed_token": "KBJrKTq1qrctNuxF3GwVh3GAGRqmgkirlXANPcJZAeWlvSt_9TMV097slR4AYnYCBEushbVqHEJ9Rb5wHTa0HzMbfRo8cRdl2yAirvvv5d98dtwHddQgX1jB0xEypXtmIYMdPGDLvhI1RNdIBhHkkrRcNreRzoy4xD--HM6m1W0-A8PJJJ9BcNxmGPcBtLzW08wzoP9trJ3M7DQ6Gkk6j7iwVsyApw1ZBaDvabGTdc_SFV-Iegtqw3rjzT_xXWsfzMlKBy-019MYn_KS-gu23YzjvGu5tS_zDfkQb8DMUlPLz5yyxM0yOMlUDtG2qQgIJAU2O0X6T5xDdJ6mtolNyhepg845PvFDEqBQGMIH1nc47CNumeudDi8IWymEALhjG_U8KAK7JVlQTJj2EKUb0au1g6fpiBFab5mmxCMtZEX3Jreyak5GOcFFz-WpxuXJD9TdSoIvaBfBFOoJnXkg2zE4RHXeQzZ2FotmrbBG5dm8B-_6byYGoHBc08ZsWze1K96JIeRnLpBaj6ifUDcVHxZMPcGHHT27dvU2PNbgLiBjlAsxhYqkhN5qOHN8XBcg2KRjcMBaI3V0YMxlzXz5MztmZq3fcB1p-ccIoIyMPMzSj3yMB7J9CEU2LYPSTHMdPkIeDE6GaCkQKviaQQJQde346tK_YjA2k7_SOBmvPYE",
"guid": "sebQ-IC4rmFn9d9iu17m4BXO-kHuNutWo2ySjeV2SIW1LzksUkss12xVo3m3fykYxN5HMcc7gUZVYv26asx-Pg",
"guid_sig": "Llenlbl4zHo6-g4sa63MlQmTP5dRCrsPmXHHFmoCHG63BLq5CUZJRLS1vRrrr_MNxr7zob_Ykt_m5xPKe5H0_i4pDj-UdP8dPZqH2fqhhx00kuYL4YUMJ8gRr5eO17vsZQ3XxTcyKewtgeW0j7ytwMp6-hFVUx_Cq08MrXas429ZrjzaEwgTfxGnbgeQYQ0R5EXpHpEmoERnZx77VaEahftmdjAUx9R4YKAp13pGYadJOX5xnLfqofHQD8DyRHWeMJ4G1OfWPSOlXfRayrV_jhnFlZjMU7vOdQwHoCMoR5TFsRsHuzd-qepbvo3pzvQZRWnTNu6oPucgbf94p13QbalYRpBXKOxdTXJrGdESNhGvhtaZnpT9c1QVqC46jdfP0LOX2xrVdbvvG2JMWFv7XJUVjLSk_yjzY6or2VD4V6ztYcjpCi9d_WoNHruoxro_br1YO3KatySxJs-LQ7SOkQI60FpysfbphNyvYMkotwUFI59G08IGKTMu3-GPnV1wp7NOQD1yzJbGGEGSEEysmEP0SO9vnN45kp3MiqbffBGc1r4_YM4e7DPmqOGM94qksOcLOJk1HNESw2dQYWxWQTBXPfOJT6jW9_crGLMEOsZ3Jcss0XS9KzBUA2p_9osvvhUKuKXbNztqH0oZIWlg37FEVsDs_hUwUJpv2Ar09k4",
"key": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA7QCwvuEIwCHjhjbpz3Oc\ntyei/Pz9nDksNbsc44Cm8jxYGMXsTPFXDZYCcCB5rcAhPPdZSlzaPkv4vPVcMIrw\n5cdX0tvbwa3rNTng6uFE7qkt15D3YCTkwF0Y9FVZiZ2Ko+G23QeBt9wqb9dlDN1d\nuPmu9BLYXIT/JXoBwf0vjIPFM9WBi5W/EHGaiuqw7lt0qI7zDGw77yO5yehKE4cu\n7dt3SakrXphL70LGiZh2XGoLg9Gmpz98t+gvPAUEotAJxIUqnoiTA8jlxoiQjeRK\nHlJkwMOGmRNPS33awPos0kcSxAywuBbh2X3aSqUMjcbE4cGJ++/13zoa6RUZRObC\nZnaLYJxqYBh13/N8SfH7d005hecDxWnoYXeYuuMeT3a2hV0J84ztkJX5OoxIwk7S\nWmvBq4+m66usn6LNL+p5IAcs93KbvOxxrjtQrzohBXc6+elfLVSQ1Rr9g5xbgpub\npSc+hvzbB6p0tleDRzwAy9X16NI4DYiTj4nkmVjigNo9v2VPnAle5zSam86eiYLO\nt2u9YRqysMLPKevNdj3CIvst+BaGGQONlQalRdIcq8Lin+BhuX+1TBgqyav4XD9K\nd+JHMb1aBk/rFLI9/f2S3BJ1XqpbjXz7AbYlaCwKiJ836+HS8PmLKxwVOnpLMbfH\nPYM8k83Lip4bEKIyAuf02qkCAwEAAQ==\n-----END PUBLIC KEY-----\n",
"name": "Mike Macgirvin",
"name_updated": "2012-12-06 04:59:13",
"address": "[email protected]",
"photo_mimetype": "image/jpeg",
"photo": "https://zothub.com/photo/profile/l/1",
"photo_updated": "2012-12-06 05:06:11",
"url": "https://zothub.com/channel/mike",
"connections_url": "https://zothub.com/poco/mike",
"target": "",
"target_sig": "",
"searchable": false,
"permissions": {
"view_stream": true,
"view_profile": true,
"view_photos": true,
"view_contacts": true,
"view_storage": true,
"view_pages": true,
"send_stream": false,
"post_wall": false,
"post_comments": false,
"post_mail": false,
"post_photos": false,
"tag_deliver": false,
"chat": false,
"write_storage": false,
"write_pages": false,
"delegate": false
},
"profile": {
"description": "Freedom Fighter",
"birthday": "0000-05-14",
"next_birthday": "2013-05-14 00:00:00",
"gender": "Male",
"marital": "It's complicated",
"sexual": "Females",
"locale": "",
"region": "",
"postcode": "",
"country": "Australia"
},
"locations": [
{
"host": "zothub.com",
"address": "[email protected]",
"primary": true,
"url": "https://zothub.com",
"url_sig": "eqkB_9Z8nduBYyyhaSQPPDN1AhSm5I4R0yfcFxPeFpuu17SYk7jKD7QzvmsyahM5Kq7vDW6VE8nx8kdFYpcNaurqw0_IKI2SWg15pGrhkZfrCnM-g6A6qbCv_gKCYqXvwpSMO8SMIO2mjQItbBrramSbWClUd2yO0ZAceq3Z_zhirCK1gNm6mGRJaDOCuuTQNb6D66TF80G8kGLklv0o8gBfxQTE12Gd0ThpUb5g6_1L3eDHcsArW_RWM2XnNPi_atGNyl9bS_eLI2TYq0fuxkEdcjjYx9Ka0-Ws-lXMGpTnynQNCaSFqy-Fe1aYF7X1JJVJIO01LX6cCs-kfSoz29ywnntj1I8ueYldLB6bUtu4t7eeo__4t2CUWd2PCZkY3PKcoOrrnm3TJP5_yVFV_VpjkcBCRj3skjoCwISPcGYrXDufJxfp6bayGKwgaCO6QoLPtqqjPGLFm-fbn8sVv3fYUDGilaR3sFNxdo9mQ3utxM291XE2Pd0jGgeUtpxZSRzBuhYeOybu9DPusID320QbgNcbEbEImO8DuGIxuVRartzEXQF4WSYRdraZzbOqCzmU0O55P836JAfrWjgxTQkXlYCic-DBk-iE75JeT72smCtZ4AOtoFWCjZAABCw42J7JELY9APixZXWriKtjy6JI0G9d3fs6r7SrXr1JMy0",
"callback": "https://zothub.com/post",
"sitekey": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1IWXwd/BZuevq8jzNFoR\n3VkenduQH2RpR3Wy9n4+ZDpbrUKGJddUGm/zUeWEdKMVkgyllVA/xHdB7jdyKs1X\nuIet9mIdnzvhdLO/JFD5hgbNG2wpSBIUY6aSNeCFTzszqXmuSXMW5U0Ef5pCbzEA\nnhoCoGL1KAgPqyxnGKUlj7q2aDwC9IRNtAqNyFQL67oT91vOQxuMThjlDhbR/29Q\ncYR4i1RzyahgEPCnHCPkT2GbRrkAPjNZAdlnk9UesgP16o8QB3tE2j50TVrbVc/d\nYRbzC56QMPP9UgUsapNeSJBHji75Ip/E5Eg/kfJC/HEQgyCqjCGfb7XeUaeQ7lLO\nqc7CGuMP+Jqr/cE54/aSHg8boTwxkMp11Ykb+ng17fl57MHTM2RJ99qZ1KBkXezR\nuH1lyvjzeJPxEFr9rkUqc4GH74/AgfbgaFvQc8TS7ovEa5I/7Pg04m7vLSEYc6UF\nYJYxXKrzmZT2TDoKeJzaBBx5MFLhW19l68h9dQ8hJXIpTP0hJrpI+Sr6VUAEfFQC\ndIDRiFcgjz6j7T/x8anqh63/hpsyf2PMYph1+4/fxtSCWJdvf+9jCRM8F1IDfluX\n87gm+88KBNaklYpchmGIohbjivJyru41CsSLe0uinQFvA741W00w6JrcrOAX+hkS\nRQuK1dDVwGKoIY85KtTUiMcCAwEAAQ==\n-----END PUBLIC KEY-----\n"
}
],
"site": {
"url": "https://zothub.com",
"directory_mode": "primary",
"directory_url": "https://zothub.com/dirsearch"
}
}
Whereas a sample ActivityPub response for [email protected] might look like this:
ActivityPub response
{"@context": "https://www.w3.org/ns/activitystreams",
"type": "Person",
"id": "https://social.example/alyssa/",
"name": "Alyssa P. Hacker",
"preferredUsername": "alyssa",
"summary": "Lisp enthusiast hailing from MIT",
"inbox": "https://social.example/alyssa/inbox/",
"outbox": "https://social.example/alyssa/outbox/",
"followers": "https://social.example/alyssa/followers/",
"following": "https://social.example/alyssa/following/",
"liked": "https://social.example/alyssa/liked/"}
The relevant bits are:
"guid": "sebQ-IC4rmFn9d9iu17m4BXO-kHuNutWo2ySjeV2SIW1LzksUkss12xVo3m3fykYxN5HMcc7gUZVYv26asx-Pg""id": "https://social.example/alyssa/".If the DNS goes down or changes, then every single AP implementation needs to do its own deduplication, in its own database. On the other hand, zot doesn't need to dedupe anything because the GUID is globally unique.
Note that this doesn't prevent exporting a profile's posts and importing them somewhere else; however, it does mean that this will break profiles for any software that is unaware of the concept of accounts moving, e.g. Mastodon. At the moment, Mastodon converts all ActivityPub Objects into its own database format. Webfinger is used not only for lookup, but also to create a username/domain pair that identifies every single user the server is aware of. So what might federate out as mastodon.art/users/55816 is irrelevant, because Mastodon then stores that response as @[email protected] and refers to it internally as such. When mastodon.art's admin changed the username from krita to Krita, a lot of Mastodon servers stored "user 55816" as a separate profile, leading to duplicate krita/Krita profiles on those servers.
The main takeaway here is that the ActivityPub spec doesn't cover what to do with an object after lookup; that's left entirely to the software. The only constraint is that ActivityPub ID should be dereferencable and globally unique; as a consequence, most implementations (i.e. Mastodon/Pleroma) simply tie this to the DNS, because each domain has its own unique namespace. Moving from one server to another would therefore mean re-federating out your profile as a different URI, with the burden of deduplication falling on the server software that you federate with. If the remote server doesn't dedupe the Object it receives after URI lookup, it will simply store it as a separate Object.
Extra context:
Takeaways and tldr:
id is a URI for lookupid is just a URI, each AP implementation must implement its own ID system internally, at software-level rather than spec-levelTo prevent future headaches, PixelFed should at least offer some alternative way to uniquely identify an account that isn't tied to DNS, so that moved profiles can easily be deduplicated.
First, a primer: we can note that ActivityPub has separate id and url fields. At a fundamental level, this means that we can use id for machine-readable URIs and url for human-readable URLs, even if both need to be https: as currently specified by ActivityPub.
It is recommended practice to choose long-lasting URIs, so that they remain accessible instead of 404ing whenever something changes. Per W3C, the following should not be included in your URIs:
Note that it is acceptable to provide multiple URIs based on some of these things, and to serve the correct file dynamically. But the canonical URI should remain constant. This canonical URI can be used strictly as an identifier, so that id will never change. Public human-friendly access locations should instead be published as URLs, and thus url can be displayed publicly.
Per #51 there was some early discussion of URL schema, and I must admit that my earlier arguments were not the best; I previously advocated for having authorship included in URLs, and as a result Pixelfed now formats post urls as /p/username/id rather than simply /p/id. This was a mistake in part because I should have clarified that preserving authorship is only perhaps desirable in the url field specifically, and that the existing /p/id schema should have remained as the id. Of course, at that time Pixelfed's ActivityPub implementation wasn't quite implemented yet, so these details were still in flux -- there was no real distinction between id as a URI vs. url as a human-friendly location for sharing. But there is still a way forward...
Often in the search for a solution, people can pass over a decent step forward in favor of the theoretically perfect solution. Instead, I think there are some simple things we can do that lean into the assumptions already made by ActivityPub, without locking ourselves purely into the one-server or one-domain model.
Recall that the reason ActivityPub mandates HTTPS URI as the id field is because each domain name is globally unique due to the DNS system; therefore, all URIs on that domain are also globally unique because they are part of the domain namespace. By piggybacking onto DNS, global uniqueness is assured while local uniqueness is left up to the software powering that domain.
This gives us an opportunity to structure our URIs in a way that they are always locally unique, and thus the username can change freely. We do not need to worry about global uniqueness of account addressing because everything is already on the domain namespace; we can worry about eliminating the dependency on DNS later.
Mastodon doesn't really do a good job of this, because they treat the Webfinger user@domain as the authoritative way to address any account. This means that even the ActivityPub id is generally ignored when fetching user data, unless it is put directly into the search box. Again, though, generated id still relies on username for both users and posts:
id takes the form /users/username and user url takes the form /@username; the user id therefore prevents changing username for local Mastodon users.id takes the form /user/username/statuses/statusid and url as /@username/statusid; again, the reliance on username in URI generation means username cannot change for Mastodon users.Pleroma does a better job at this, and could theoretically implement Pleroma-Pleroma nomadic identity more easily due to the way it already replicates content across instances. Each Pleroma status is hosted at the URI /notice/postid, regardless of which domain it came from since these notices are replicated to other nodes. Consider the example of this notice https://pleroma.site/notice/9iNxfuTiSBFuFNv7ZY referring to a post authored by Mastodon, and this user https://pleroma.site/users/9hEDr9JSC3H7Z07lNA being a profile on a Mastodon instance. This essentially causes all internal IDs to be locally unique by default; global uniqueness can be assured if Pleroma changes its software to generate these IDs the exact same way across all instances of Pleroma software, in order to prevent collisions. Alternatively, Pleroma could convert all incoming objects to its local ID system.
For objects authored by Pleroma:
id takes the form /users/idid is /users/username, and /users/id is only used for remote users)id takes the form /notice/idid is /objects/uuid, and /notice/id is only used for the frontend and API)And in each case, the id is a base62 string converted from a 128-bit integer schema, known as flake id: https://git.pleroma.social/pleroma/pleroma/merge_requests/645
Zot sidesteps this issue entirely at time of specification by mandating that all implementations generate 256-bit ids for all users and posts. The Zot protocol can therefore expect these ids to remain reliably constant regardless of where they are being hosted, and authorship / addressing uses these ids as the primary mechanism while keeping track of the user/post current location (as well as all cloned/secondary locations).
In practical terms, this means assigning evergreen URIs for both posts and accounts, and changing all relevant code to take this into account.
A proper unchanging HTTPS id URI depends on a base schema and on id generation schema.
/p/username/, which can be reverted to /p/ to eliminate username and allow username mutability.The base schema will change according to each software implementation of an ActivityPub server, but frankly, it is irrelevant since id has to be known when interacting with an object.
As for the ID schema: again, we can rely on local uniqueness to remove the dependency on mutable data. This means each implementation can, at the most basic level, use whichever ID schema it wants to -- snowflake, integer, base62, base64, string, hash, whatever. Note that this is not optimal, of course -- coordinating a network-wide consistent ID schema would allow us to attain full nomadic identity and content addressing, but this is not required to assure username changes on the local level. And it's a discussion to be had on the spec level rather than the implementation level, because there needs to be agreement on the global uniqueness constraints. But at the very least, we can assign ids that are reasonably globally unique, i.e. sufficiently high in entropy so as to make collision unlikely.
Threaded toots about this: https://mastodon.social/@dansup/101990552492892782
IRC discussion:
[22:18:25] trwnh | food for thought: https://www.w3.org/Provider/Style/URI -- it was probably wrong of me to advocate for including username in url routes, /p/id is better and allows username changes without deprecating old urls
[22:19:18] dansup | trwnh: tbh usernames were to prevent GS like enumeration
[22:19:31] dansup | but now snowflake is around, thats not important
[22:19:56] dansup | usernames can't change due to AP. would require a lot of refactoring to support that
[22:20:13] trwnh | usernames can actually change in AP but there needs to be deduplication of identity first
[22:21:04] trwnh | that's part of what the w3c article i linked was saying -- a good uri should not include author info bc that can change
[22:21:22] trwnh | /users/1 is better than /users/trwnh
[22:21:41] trwnh | "a good uri never changes"
[22:22:15] trwnh | pixelfed could maybe set /u/id for `id` and then maintain /username for `url` on Actor objects
[22:22:54] dansup | Bob changes his username to Bobbie, he has 10k statuses and 5k followers. Alice on another instance follows Bob and her instance gets an update Person object, requiring every status of Bobs in Alices instance to update the url to support the new username. Thats pretty much a DoS vector
[22:22:55] trwnh | activitypub allows for separate id and url and in fact this is already used even in mastodon, where id = /users/username and url = /@username
[22:23:19] dansup | tbh, I'm not sure why mastodon uses @
[22:23:21] trwnh | if correctly designed, it should not require any Update on the child objects
[22:23:40] dansup | by using uuids yeah but not every implementation supports that
[22:23:44] trwnh | Bob -> Bobbie, but they should remain /u/2 and /u/2 should point to the same profile
[22:24:00] trwnh | but that's the thing, it doesn't even have to be uuid, it can just be locally unique!
[22:24:11] trwnh | you are already relying on the authority of dns
[22:24:35] trwnh | until dns is eliminated as a requirement for AP you can still use a consistent and unchanging local schema
[22:24:56] trwnh | id should never change, url can be the "pretty" version of a profile's location
[22:25:34] trwnh | the weird thing is that mastodon uses integers for ids in the api, but not in ActivityPub
[22:25:50] dansup | I'd say at this point its probably to late to change major routes without supporting a backwards compatible redirect
[22:26:23] trwnh | https://mastodon.social/api/v1/users/1 returns gargron's account but the activitypub id is https://mastodon.social/users/gargron (no 1 in there)
[22:26:54] dansup | yeah
[22:27:00] trwnh | dansup: i say add migrations, the earlier the better. we haven't released 1.0 yet so anything goes technically
[22:27:05] dansup | pixelfed does that too, http://pixelfed.social/users/trwnh -> https://pixelfed.social/trwnh
[22:27:31] dansup | that redirect means its technically possible to support a backwards compatible route change
[22:27:32] trwnh | ok so change /users/trwnh to /users/{id}
[22:27:53] dansup | users and profiles do not use snowflake ids yet
[22:28:26] trwnh | like if i wanna share my profile with friends i can still tell them "hey you can see my profile at pixelfed.social/trwnh" but that's a human-friendly name and machines aren't human
[22:29:01] trwnh | the server should not know or care if a name is human-friendly, there's no difference between "trwnh" and "34287492342" in server friendliness because they're all just strings
[22:29:21] trwnh | so the desired traits for machine-readable id is different than the traits we want for human-readable id
[22:29:39] trwnh | human names can change, machine names should never change
[22:30:31] trwnh | dansup: just something to consider -- if played right, pixelfed can support account migrations before mastodon ever does
[22:30:54] trwnh | and the earlier the better
[22:31:02] dansup | great point haha
[22:31:20] trwnh | at the very least i know the instagram users you're gunning for are gonna want to change their usernames
[22:31:29] trwnh | if they can't they'll never invest in using pixelfed
[22:31:56] dansup | you know actually if I update the way pixelfed handles remote statuses, a DoS vector would not occur since the object would be refetched every 24 hours if accessed
[22:31:57] trwnh | i don't know a single person that's never changed their username on twitter/instagram
[22:32:10] dansup | me
[22:32:12] dansup | lol
[22:32:31] dansup | i don't really post that much on twitter or IG
[22:32:49] trwnh | dansup: really the key is that the AP objects need to be owned by a consistent id -- that way they never change.
[22:33:05] trwnh | DoS vector applies only once, and only to convert all existing objects
[22:33:28] trwnh | like it would be prohibitive for mastodon.social to rewrite all their old statuses so i can imagine it being a breaking change over there
[22:34:07] trwnh | but still doable assuming they take the stance of "oh well we'll rewrite the json and it only matters for future fetches"
[22:34:18] trwnh | they'd probably have to write code to merge local profiles
[22:34:33] trwnh | which already happened when the krita/Krita thing occurred
[22:34:54] dansup | krita?
[22:35:10] trwnh | at the time they made usernames case-insensitive but that caused some problems because there were *already* accounts with similar names like claudia/Claudia (different people)
[22:35:30] dansup | lmao, thanks postgres
[22:35:33] trwnh | dansup: mastodon.art's admin manually changed the username in the database from krita to Krita
[22:35:46] trwnh | needless to say, it broke existing follows in masto
[22:36:08] dansup | hahaha
[22:36:12] trwnh | and no one could follow the "new" profile because it was the same as the old profile (basically state desync over the network)
[22:36:30] trwnh | garg's solution was to just delete whichever account was less recently updated
[22:36:55] dansup | that is crazy
[22:37:06] trwnh | it doesn't matter now but conceivable some people could've found their profiles deleted
[22:37:59] trwnh | dansup: the kicker is that literally none of that would've been an issue if garg had listened to me a year and a half ago and used api integer ids in the activitypub side too
[22:38:00] dansup | trwnh: when did that happen?
[22:38:34] trwnh | a year ago actually https://github.com/tootsuite/mastodon/issues/6837
[22:39:40] trwnh | two big issues with masto id is that 1) they use usernames in id uri, 2) they actually get ignored because webfinger is more authoritative than activitypub
[22:41:01] dansup | interesting
[22:41:15] trwnh | i guess i was misremembering, it was Krita -> krita
[22:41:38] trwnh | point still stands
[22:41:55] trwnh | it should be trivially easy to allow username changes on same domain
[22:42:10] trwnh | hard migration is only really necessary when mapping a redirect from one domain to another
[22:43:09] trwnh | if /users/14715 on one domain moves to /users/1 on another domain, that should be relatively easy to express (and might require a one-time rewrite of all old content *if* that content was imported)
[22:43:38] dansup | even so, the migration wouldn't take long
[22:43:50] trwnh | the only way to avoid mass-Update when domain changes is to use globally unique id (that everyone agrees on)
[22:44:23] dansup | actually local user urls are generated in the model, not stored in database
[22:44:24] trwnh | hopefully it should be clear now that we can at least make the first half easy by using locally-unique id
[22:44:58] trwnh | locally-unique id = domain constant, but location can change on that domain
[22:45:21] trwnh | globally-unique id = network constant, location can change across domains
[22:45:50] trwnh | doesn't have to be snowflake id, it can be any schema as long as it's consistent
[22:45:58] dansup | trwnh: a local user has a user_id and profile_id. Would be wise to use the profile id
[22:46:27] trwnh | pleroma uses their base62 whatever, mastodon generates incrementing integers (but only for api), pixelfed can do etc etc
[22:47:15] dansup | trwnh: i dont want to get rid of pixelfed.dev/{username} its easy to share
[22:47:38] trwnh | dansup: no need to get rid of example.com/username, that can remain as a pretty-url (masked)
[22:48:05] trwnh | the server should take "GET /username" and translate it on the backend, lookup the id
[22:48:29] trwnh | what you share with humans is not necessarily the same as what you share with API and servers
[22:50:04] trwnh | at the http layer you can have multiple routes lead to the same data
[22:50:35] dansup | the problem with that is how Profile are stored in the database
[22:50:40] trwnh | "GET /api/1/users/id" over http should redirect to /username anyway
[22:50:52] trwnh | unless you specify accepts:json
[22:50:55] dansup | we would need to keep an alias of the old usernames
[22:51:29] trwnh | dansup: only temporarily, really -- but no problem with leaving it up as long as you can
[22:51:58] dansup | trwnh: the issue is that would take 2 database queries, one to check if alias and another to check the Profile table
[22:52:19] dansup | or store the alias in Profile table with a new column called new_profile_id or something
[22:52:29] trwnh | dansup: i don't see why database is necessary if you can just redirect 301 in http
[22:52:30] dansup | that would be a perf hit though
[22:53:08] dansup | trwnh: manually edit nginx.conf & restart nginx every time a username is changed
[22:53:10] dansup | ?
[22:54:13] trwnh | dansup: you should be able to resolve urls via php so no need to do it in nginx unless you felt like it. if i were doing it in python i'd set up a flask route for text-based usernames and use a lookup table for their ids, then serve a 301
[22:55:37] trwnh | pixelfed has relatively no tech debt here, it's easy for us to change url routes at this stage in development without a single stable release
[22:55:53] dansup | trwnh: pixelfed uses a fancy router https://github.com/nikic/FastRoute but still requires a username lookup in the database
[22:55:54] trwnh | i realllllly want at least one impl to roll out username changes just to prove a point lol
[22:56:10] trwnh | dansup: sounds like a database migration waiting to be written
[22:56:36] dansup | it would not be faster
[22:56:54] dansup | actually using a redis list would be faster than database
[22:57:02] dansup | not important yet, imo
[22:57:04] trwnh | i do have to plead ignorance to sql performance issues though, i've not played enough with sql to learn how it works under the hood
[22:57:25] trwnh | my understanding of schemas is limited to the http level right now
[22:57:36] trwnh | in terms of what i have experience with and can actually talk reasonably about
We are not interested in nomadic identity.
Most helpful comment
Background
To implement a proper nomadic identity system, a given software would have to generate a globally unique ID for every single profile. Zot (the protocol Hubzilla uses) generates a 256-bit GUID, for which the expected collision time is around a million longer than the existence of the entire universe.
Zot links this GUID to a public key, and nomadic identity is accomplished by cloning data across each server, so that each URI is dereferenced to the GUID. By comparison, ActivityPub is also supposed to be dereferencable (so each URI should point to an object).
The problem comes in implementing the
idparameter. Whereas Zot has a GUID that is authoritative over the URI (meaning GUID is used internally and URI is only for lookup), ActivityPub uses the URI as the ID (meaning each AP implementation must implement its own UID system internally).Examples
Let's take an example. Webfinger is commonly used for lookup, and relies on each server having a
/.well-knowndirectory used to probe for public keys and such. Identities are usually federated out as pointers in the format[email protected]or@[email protected], similarly to email.A sample zot response for
[email protected]might look like this:zot response
{ { "success": true, "signed_token": "KBJrKTq1qrctNuxF3GwVh3GAGRqmgkirlXANPcJZAeWlvSt_9TMV097slR4AYnYCBEushbVqHEJ9Rb5wHTa0HzMbfRo8cRdl2yAirvvv5d98dtwHddQgX1jB0xEypXtmIYMdPGDLvhI1RNdIBhHkkrRcNreRzoy4xD--HM6m1W0-A8PJJJ9BcNxmGPcBtLzW08wzoP9trJ3M7DQ6Gkk6j7iwVsyApw1ZBaDvabGTdc_SFV-Iegtqw3rjzT_xXWsfzMlKBy-019MYn_KS-gu23YzjvGu5tS_zDfkQb8DMUlPLz5yyxM0yOMlUDtG2qQgIJAU2O0X6T5xDdJ6mtolNyhepg845PvFDEqBQGMIH1nc47CNumeudDi8IWymEALhjG_U8KAK7JVlQTJj2EKUb0au1g6fpiBFab5mmxCMtZEX3Jreyak5GOcFFz-WpxuXJD9TdSoIvaBfBFOoJnXkg2zE4RHXeQzZ2FotmrbBG5dm8B-_6byYGoHBc08ZsWze1K96JIeRnLpBaj6ifUDcVHxZMPcGHHT27dvU2PNbgLiBjlAsxhYqkhN5qOHN8XBcg2KRjcMBaI3V0YMxlzXz5MztmZq3fcB1p-ccIoIyMPMzSj3yMB7J9CEU2LYPSTHMdPkIeDE6GaCkQKviaQQJQde346tK_YjA2k7_SOBmvPYE", "guid": "sebQ-IC4rmFn9d9iu17m4BXO-kHuNutWo2ySjeV2SIW1LzksUkss12xVo3m3fykYxN5HMcc7gUZVYv26asx-Pg", "guid_sig": "Llenlbl4zHo6-g4sa63MlQmTP5dRCrsPmXHHFmoCHG63BLq5CUZJRLS1vRrrr_MNxr7zob_Ykt_m5xPKe5H0_i4pDj-UdP8dPZqH2fqhhx00kuYL4YUMJ8gRr5eO17vsZQ3XxTcyKewtgeW0j7ytwMp6-hFVUx_Cq08MrXas429ZrjzaEwgTfxGnbgeQYQ0R5EXpHpEmoERnZx77VaEahftmdjAUx9R4YKAp13pGYadJOX5xnLfqofHQD8DyRHWeMJ4G1OfWPSOlXfRayrV_jhnFlZjMU7vOdQwHoCMoR5TFsRsHuzd-qepbvo3pzvQZRWnTNu6oPucgbf94p13QbalYRpBXKOxdTXJrGdESNhGvhtaZnpT9c1QVqC46jdfP0LOX2xrVdbvvG2JMWFv7XJUVjLSk_yjzY6or2VD4V6ztYcjpCi9d_WoNHruoxro_br1YO3KatySxJs-LQ7SOkQI60FpysfbphNyvYMkotwUFI59G08IGKTMu3-GPnV1wp7NOQD1yzJbGGEGSEEysmEP0SO9vnN45kp3MiqbffBGc1r4_YM4e7DPmqOGM94qksOcLOJk1HNESw2dQYWxWQTBXPfOJT6jW9_crGLMEOsZ3Jcss0XS9KzBUA2p_9osvvhUKuKXbNztqH0oZIWlg37FEVsDs_hUwUJpv2Ar09k4", "key": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA7QCwvuEIwCHjhjbpz3Oc\ntyei/Pz9nDksNbsc44Cm8jxYGMXsTPFXDZYCcCB5rcAhPPdZSlzaPkv4vPVcMIrw\n5cdX0tvbwa3rNTng6uFE7qkt15D3YCTkwF0Y9FVZiZ2Ko+G23QeBt9wqb9dlDN1d\nuPmu9BLYXIT/JXoBwf0vjIPFM9WBi5W/EHGaiuqw7lt0qI7zDGw77yO5yehKE4cu\n7dt3SakrXphL70LGiZh2XGoLg9Gmpz98t+gvPAUEotAJxIUqnoiTA8jlxoiQjeRK\nHlJkwMOGmRNPS33awPos0kcSxAywuBbh2X3aSqUMjcbE4cGJ++/13zoa6RUZRObC\nZnaLYJxqYBh13/N8SfH7d005hecDxWnoYXeYuuMeT3a2hV0J84ztkJX5OoxIwk7S\nWmvBq4+m66usn6LNL+p5IAcs93KbvOxxrjtQrzohBXc6+elfLVSQ1Rr9g5xbgpub\npSc+hvzbB6p0tleDRzwAy9X16NI4DYiTj4nkmVjigNo9v2VPnAle5zSam86eiYLO\nt2u9YRqysMLPKevNdj3CIvst+BaGGQONlQalRdIcq8Lin+BhuX+1TBgqyav4XD9K\nd+JHMb1aBk/rFLI9/f2S3BJ1XqpbjXz7AbYlaCwKiJ836+HS8PmLKxwVOnpLMbfH\nPYM8k83Lip4bEKIyAuf02qkCAwEAAQ==\n-----END PUBLIC KEY-----\n", "name": "Mike Macgirvin", "name_updated": "2012-12-06 04:59:13", "address": "[email protected]", "photo_mimetype": "image/jpeg", "photo": "https://zothub.com/photo/profile/l/1", "photo_updated": "2012-12-06 05:06:11", "url": "https://zothub.com/channel/mike", "connections_url": "https://zothub.com/poco/mike", "target": "", "target_sig": "", "searchable": false, "permissions": { "view_stream": true, "view_profile": true, "view_photos": true, "view_contacts": true, "view_storage": true, "view_pages": true, "send_stream": false, "post_wall": false, "post_comments": false, "post_mail": false, "post_photos": false, "tag_deliver": false, "chat": false, "write_storage": false, "write_pages": false, "delegate": false }, "profile": { "description": "Freedom Fighter", "birthday": "0000-05-14", "next_birthday": "2013-05-14 00:00:00", "gender": "Male", "marital": "It's complicated", "sexual": "Females", "locale": "", "region": "", "postcode": "", "country": "Australia" }, "locations": [ { "host": "zothub.com", "address": "[email protected]", "primary": true, "url": "https://zothub.com", "url_sig": "eqkB_9Z8nduBYyyhaSQPPDN1AhSm5I4R0yfcFxPeFpuu17SYk7jKD7QzvmsyahM5Kq7vDW6VE8nx8kdFYpcNaurqw0_IKI2SWg15pGrhkZfrCnM-g6A6qbCv_gKCYqXvwpSMO8SMIO2mjQItbBrramSbWClUd2yO0ZAceq3Z_zhirCK1gNm6mGRJaDOCuuTQNb6D66TF80G8kGLklv0o8gBfxQTE12Gd0ThpUb5g6_1L3eDHcsArW_RWM2XnNPi_atGNyl9bS_eLI2TYq0fuxkEdcjjYx9Ka0-Ws-lXMGpTnynQNCaSFqy-Fe1aYF7X1JJVJIO01LX6cCs-kfSoz29ywnntj1I8ueYldLB6bUtu4t7eeo__4t2CUWd2PCZkY3PKcoOrrnm3TJP5_yVFV_VpjkcBCRj3skjoCwISPcGYrXDufJxfp6bayGKwgaCO6QoLPtqqjPGLFm-fbn8sVv3fYUDGilaR3sFNxdo9mQ3utxM291XE2Pd0jGgeUtpxZSRzBuhYeOybu9DPusID320QbgNcbEbEImO8DuGIxuVRartzEXQF4WSYRdraZzbOqCzmU0O55P836JAfrWjgxTQkXlYCic-DBk-iE75JeT72smCtZ4AOtoFWCjZAABCw42J7JELY9APixZXWriKtjy6JI0G9d3fs6r7SrXr1JMy0", "callback": "https://zothub.com/post", "sitekey": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1IWXwd/BZuevq8jzNFoR\n3VkenduQH2RpR3Wy9n4+ZDpbrUKGJddUGm/zUeWEdKMVkgyllVA/xHdB7jdyKs1X\nuIet9mIdnzvhdLO/JFD5hgbNG2wpSBIUY6aSNeCFTzszqXmuSXMW5U0Ef5pCbzEA\nnhoCoGL1KAgPqyxnGKUlj7q2aDwC9IRNtAqNyFQL67oT91vOQxuMThjlDhbR/29Q\ncYR4i1RzyahgEPCnHCPkT2GbRrkAPjNZAdlnk9UesgP16o8QB3tE2j50TVrbVc/d\nYRbzC56QMPP9UgUsapNeSJBHji75Ip/E5Eg/kfJC/HEQgyCqjCGfb7XeUaeQ7lLO\nqc7CGuMP+Jqr/cE54/aSHg8boTwxkMp11Ykb+ng17fl57MHTM2RJ99qZ1KBkXezR\nuH1lyvjzeJPxEFr9rkUqc4GH74/AgfbgaFvQc8TS7ovEa5I/7Pg04m7vLSEYc6UF\nYJYxXKrzmZT2TDoKeJzaBBx5MFLhW19l68h9dQ8hJXIpTP0hJrpI+Sr6VUAEfFQC\ndIDRiFcgjz6j7T/x8anqh63/hpsyf2PMYph1+4/fxtSCWJdvf+9jCRM8F1IDfluX\n87gm+88KBNaklYpchmGIohbjivJyru41CsSLe0uinQFvA741W00w6JrcrOAX+hkS\nRQuK1dDVwGKoIY85KtTUiMcCAwEAAQ==\n-----END PUBLIC KEY-----\n" } ], "site": { "url": "https://zothub.com", "directory_mode": "primary", "directory_url": "https://zothub.com/dirsearch" } }Whereas a sample ActivityPub response for
[email protected]might look like this:ActivityPub response
{"@context": "https://www.w3.org/ns/activitystreams", "type": "Person", "id": "https://social.example/alyssa/", "name": "Alyssa P. Hacker", "preferredUsername": "alyssa", "summary": "Lisp enthusiast hailing from MIT", "inbox": "https://social.example/alyssa/inbox/", "outbox": "https://social.example/alyssa/outbox/", "followers": "https://social.example/alyssa/followers/", "following": "https://social.example/alyssa/following/", "liked": "https://social.example/alyssa/liked/"}Analysis
The relevant bits are:
"guid": "sebQ-IC4rmFn9d9iu17m4BXO-kHuNutWo2ySjeV2SIW1LzksUkss12xVo3m3fykYxN5HMcc7gUZVYv26asx-Pg""id": "https://social.example/alyssa/".If the DNS goes down or changes, then every single AP implementation needs to do its own deduplication, in its own database. On the other hand, zot doesn't need to dedupe anything because the GUID is globally unique.
Note that this doesn't prevent exporting a profile's posts and importing them somewhere else; however, it does mean that this will break profiles for any software that is unaware of the concept of accounts moving, e.g. Mastodon. At the moment, Mastodon converts all ActivityPub Objects into its own database format. Webfinger is used not only for lookup, but also to create a username/domain pair that identifies every single user the server is aware of. So what might federate out as
mastodon.art/users/55816is irrelevant, because Mastodon then stores that response as@[email protected]and refers to it internally as such. Whenmastodon.art's admin changed the username fromkritatoKrita, a lot of Mastodon servers stored "user 55816" as a separate profile, leading to duplicatekrita/Kritaprofiles on those servers.The main takeaway here is that the ActivityPub spec doesn't cover what to do with an object after lookup; that's left entirely to the software. The only constraint is that ActivityPub ID should be dereferencable and globally unique; as a consequence, most implementations (i.e. Mastodon/Pleroma) simply tie this to the DNS, because each domain has its own unique namespace. Moving from one server to another would therefore mean re-federating out your profile as a different URI, with the burden of deduplication falling on the server software that you federate with. If the remote server doesn't dedupe the Object it receives after URI lookup, it will simply store it as a separate Object.
Summary
Extra context:
Takeaways and tldr:
idis a URI for lookupidis just a URI, each AP implementation must implement its own ID system internally, at software-level rather than spec-levelTo prevent future headaches, PixelFed should at least offer some alternative way to uniquely identify an account that isn't tied to DNS, so that moved profiles can easily be deduplicated.